func NewImageTile(filename string, r image.Rectangle, width, height int) (err error, tile *ImageTile) { l_stream := C.opj_stream_create_default_file_stream_v3(C.CString(filename), 1) if l_stream == nil { return errors.New("failed to create stream"), nil } l_codec := C.opj_create_decompress(C.OPJ_CODEC_JP2) var parameters C.opj_dparameters_t C.opj_set_default_decoder_parameters(¶meters) level := desired_progression_level(r, width, height) log.Println("desired level:", level) //(parameters).cp_reduce = C.OPJ_UINT32(level) C.set_handlers(l_codec) if err == nil && C.opj_setup_decoder(l_codec, ¶meters) == C.OPJ_FALSE { err = errors.New("failed to setup decoder") } if err == nil && C.opj_set_decoded_resolution_factor(l_codec, C.OPJ_UINT32(level)) == C.OPJ_FALSE { err = errors.New("failed to set decode resolution factor") } var img *C.opj_image_t if err == nil && C.opj_read_header(l_stream, l_codec, &img) == C.OPJ_FALSE { err = errors.New("failed to read the header") } if err == nil { log.Println("num comps:", img.numcomps) log.Println("x0:", img.x0, "x1:", img.x1, "y0:", img.y0, "y1:", img.y1) } if err == nil && C.opj_set_decode_area(l_codec, img, C.OPJ_INT32(r.Min.X), C.OPJ_INT32(r.Min.Y), C.OPJ_INT32(r.Max.X), C.OPJ_INT32(r.Max.Y)) == C.OPJ_FALSE { err = errors.New("failed to set the decoded area") } if err == nil && C.opj_decode(l_codec, l_stream, img) == C.OPJ_FALSE { err = errors.New("failed to decode image") } if err == nil && C.opj_end_decompress(l_codec, l_stream) == C.OPJ_FALSE { err = errors.New("failed to decode image") } C.opj_stream_destroy_v3(l_stream) if l_codec != nil { C.opj_destroy_codec(l_codec) } if err == nil { var comps []C.opj_image_comp_t compsSlice := (*reflect.SliceHeader)((unsafe.Pointer(&comps))) compsSlice.Cap = int(img.numcomps) compsSlice.Len = int(img.numcomps) compsSlice.Data = uintptr(unsafe.Pointer(img.comps)) bounds := image.Rect(0, 0, int(comps[0].w), int(comps[0].h)) var data []int32 dataSlice := (*reflect.SliceHeader)((unsafe.Pointer(&data))) dataSlice.Cap = bounds.Dx() * bounds.Dy() dataSlice.Len = bounds.Dx() * bounds.Dy() dataSlice.Data = uintptr(unsafe.Pointer(comps[0].data)) tile = &ImageTile{data, bounds, bounds.Dx(), img} runtime.SetFinalizer(tile, func(it *ImageTile) { C.opj_image_destroy(it.img) }) } else { C.opj_image_destroy(img) } return }
// DecodeImage returns an image.Image that holds the decoded image data, // resized and cropped if resizing or cropping was requested. Both cropping // and resizing happen here due to the nature of openjpeg, so SetScale, // SetResizeWH, and SetCrop must be called before this function. func (i *JP2Image) DecodeImage() (image.Image, error) { // We need the codec to be ready for all operations below if err := i.initializeCodec(); err != nil { goLog(3, "Error initializing codec - aborting") return nil, err } // If we want to resize, but not crop, we have to set the decode area to the // full image - which means reading in the image header and then cleaning up // all previously-initialized data if (i.resizeByPixels || i.resizeByPercent) && !i.crop { if err := i.ReadHeader(); err != nil { goLog(3, "Error getting dimensions - aborting") return nil, err } i.decodeArea = i.Dimensions() i.CleanupResources() } // If resize is by percent, we now have the decode area, and can use that to // get pixel dimensions if i.resizeByPercent { i.decodeWidth = int(float64(i.decodeArea.Dx()) * i.scaleFactor) i.decodeHeight = int(float64(i.decodeArea.Dy()) * i.scaleFactor) i.resizeByPixels = true } // Get progression level if we're resizing to specific dimensions (it's zero // if there isn't any scaling of the output) if i.resizeByPixels { level := desiredProgressionLevel(i.decodeArea, i.decodeWidth, i.decodeHeight) if err := i.SetDynamicProgressionLevel(level); err != nil { goLog(3, "Unable to set dynamic progression level - aborting") return nil, err } } if err := i.ReadHeader(); err != nil { goLog(3, "Error reading header before decode - aborting") return nil, err } goLog(6, fmt.Sprintf("num comps: %d", i.image.numcomps)) goLog(6, fmt.Sprintf("x0: %d, x1: %d, y0: %d, y1: %d", i.image.x0, i.image.x1, i.image.y0, i.image.y1)) // Setting decode area has to happen *after* reading the header / image data if i.crop { r := i.decodeArea if C.opj_set_decode_area(i.codec, i.image, C.OPJ_INT32(r.Min.X), C.OPJ_INT32(r.Min.Y), C.OPJ_INT32(r.Max.X), C.OPJ_INT32(r.Max.Y)) == C.OPJ_FALSE { return nil, errors.New("failed to set the decoded area") } } // Decode the JP2 into the image stream if C.opj_decode(i.codec, i.stream, i.image) == C.OPJ_FALSE { return nil, errors.New("failed to decode image") } if C.opj_end_decompress(i.codec, i.stream) == C.OPJ_FALSE { return nil, errors.New("failed to close decompression") } var comps []C.opj_image_comp_t compsSlice := (*reflect.SliceHeader)((unsafe.Pointer(&comps))) compsSlice.Cap = int(i.image.numcomps) compsSlice.Len = int(i.image.numcomps) compsSlice.Data = uintptr(unsafe.Pointer(i.image.comps)) bounds := image.Rect(0, 0, int(comps[0].w), int(comps[0].h)) var img image.Image // We assume grayscale if we don't have at least 3 components, because it's // probably the safest default if len(comps) < 3 { img = &image.Gray{Pix: JP2ComponentData(comps[0]), Stride: bounds.Dx(), Rect: bounds} } else { // If we have 3+ components, we only care about the first three - I have no // idea what else we might have other than alpha, and as a tile server, we // don't care about alpha. It's worth noting that this will almost certainly // blow up on any JP2 that isn't using RGB. realData := make([]uint8, (bounds.Dx()*bounds.Dy())<<2) for x, comp := range comps[0:3] { compData := JP2ComponentData(comp) for y, point := range compData { realData[y*4+x] = point } } img = &image.RGBA{Pix: realData, Stride: bounds.Dx() << 2, Rect: bounds} } if i.resizeByPixels { img = resize.Resize(uint(i.decodeWidth), uint(i.decodeHeight), img, resize.Bilinear) } return img, nil }