| // ========================================================== |
| // JPEG2000 helpers |
| // |
| // Design and implementation by |
| // - Hervé Drolon (drolon@infonie.fr) |
| // |
| // This file is part of FreeImage 3 |
| // |
| // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY |
| // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES |
| // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE |
| // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED |
| // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT |
| // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY |
| // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL |
| // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER |
| // THIS DISCLAIMER. |
| // |
| // Use at your own risk! |
| // ========================================================== |
| |
| #include "FreeImage.h" |
| #include "Utilities.h" |
| #include "third_party/openjpeg2/src/lib/openjp2/openjpeg.h" |
| #include "J2KHelper.h" |
| |
| // -------------------------------------------------------------------------- |
| |
| static OPJ_UINT64 |
| _LengthProc(J2KFIO_t *fio) { |
| long start_pos = fio->io->tell_proc(fio->handle); |
| fio->io->seek_proc(fio->handle, 0, SEEK_END); |
| unsigned file_length = fio->io->tell_proc(fio->handle) - start_pos; |
| fio->io->seek_proc(fio->handle, start_pos, SEEK_SET); |
| return (OPJ_UINT64)file_length; |
| } |
| |
| static OPJ_SIZE_T |
| _ReadProc(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { |
| J2KFIO_t *fio = (J2KFIO_t*)p_user_data; |
| OPJ_SIZE_T l_nb_read = fio->io->read_proc(p_buffer, 1, (unsigned)p_nb_bytes, fio->handle); |
| return l_nb_read ? l_nb_read : (OPJ_SIZE_T)-1; |
| } |
| |
| static OPJ_SIZE_T |
| _WriteProc(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { |
| J2KFIO_t *fio = (J2KFIO_t*)p_user_data; |
| return fio->io->write_proc(p_buffer, 1, (unsigned)p_nb_bytes, fio->handle); |
| } |
| |
| static OPJ_OFF_T |
| _SkipProc(OPJ_OFF_T p_nb_bytes, void *p_user_data) { |
| J2KFIO_t *fio = (J2KFIO_t*)p_user_data; |
| if( fio->io->seek_proc(fio->handle, (long)p_nb_bytes, SEEK_CUR) ) { |
| return -1; |
| } |
| return p_nb_bytes; |
| } |
| |
| static OPJ_BOOL |
| _SeekProc(OPJ_OFF_T p_nb_bytes, FILE * p_user_data) { |
| J2KFIO_t *fio = (J2KFIO_t*)p_user_data; |
| if( fio->io->seek_proc(fio->handle, (long)p_nb_bytes, SEEK_SET) ) { |
| return OPJ_FALSE; |
| } |
| return OPJ_TRUE; |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| J2KFIO_t* |
| opj_freeimage_stream_create(FreeImageIO *io, fi_handle handle, BOOL bRead) { |
| if(!handle) { |
| return NULL; |
| } |
| J2KFIO_t *fio = (J2KFIO_t*)malloc(sizeof(J2KFIO_t)); |
| if(fio) { |
| fio->io = io; |
| fio->handle = handle; |
| |
| opj_stream_t *l_stream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, bRead ? OPJ_TRUE : OPJ_FALSE); |
| if (l_stream) { |
| opj_stream_set_user_data(l_stream, fio, NULL); |
| opj_stream_set_user_data_length(l_stream, _LengthProc(fio)); |
| opj_stream_set_read_function(l_stream, (opj_stream_read_fn)_ReadProc); |
| opj_stream_set_write_function(l_stream, (opj_stream_write_fn)_WriteProc); |
| opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)_SkipProc); |
| opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)_SeekProc); |
| fio->stream = l_stream; |
| return fio; |
| } else { |
| free(fio); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void |
| opj_freeimage_stream_destroy(J2KFIO_t* fio) { |
| if(fio) { |
| if(fio->stream) { |
| opj_stream_destroy(fio->stream); |
| } |
| free(fio); |
| } |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| /** |
| Divide an integer by a power of 2 and round upwards |
| @return Returns a divided by 2^b |
| */ |
| static int int_ceildivpow2(int a, int b) { |
| return (a + (1 << b) - 1) >> b; |
| } |
| |
| /** |
| Convert a OpenJPEG image to a FIBITMAP |
| @param format_id Plugin ID |
| @param image OpenJPEG image |
| @param header_only If TRUE, allocate a 'header only' FIBITMAP, otherwise allocate a full FIBITMAP |
| @return Returns the converted image if successful, returns NULL otherwise |
| */ |
| FIBITMAP* J2KImageToFIBITMAP(int format_id, const opj_image_t *image, BOOL header_only) { |
| FIBITMAP *dib = NULL; |
| |
| try { |
| // compute image width and height |
| |
| //int w = int_ceildiv(image->x1 - image->x0, image->comps[0].dx); |
| int wr = image->comps[0].w; |
| int wrr = int_ceildivpow2(image->comps[0].w, image->comps[0].factor); |
| |
| //int h = int_ceildiv(image->y1 - image->y0, image->comps[0].dy); |
| //int hr = image->comps[0].h; |
| int hrr = int_ceildivpow2(image->comps[0].h, image->comps[0].factor); |
| |
| // check the number of components |
| |
| int numcomps = image->numcomps; |
| |
| BOOL bIsValid = TRUE; |
| for(int c = 0; c < numcomps - 1; c++) { |
| if( (image->comps[c].dx == image->comps[c+1].dx) && |
| (image->comps[c].dy == image->comps[c+1].dy) && |
| (image->comps[c].prec == image->comps[c+1].prec) ) { |
| continue; |
| } else { |
| bIsValid = FALSE; |
| break; |
| } |
| } |
| bIsValid &= ((numcomps == 1) || (numcomps == 3) || (numcomps == 4)); |
| if(!bIsValid) { |
| if(numcomps) { |
| FreeImage_OutputMessageProc(format_id, "Warning: image contains %d greyscale components. Only the first will be loaded.\n", numcomps); |
| numcomps = 1; |
| } else { |
| // unknown type |
| throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; |
| } |
| } |
| |
| // create a new DIB |
| |
| if(image->comps[0].prec <= 8) { |
| switch(numcomps) { |
| case 1: |
| dib = FreeImage_AllocateHeader(header_only, wrr, hrr, 8); |
| break; |
| case 3: |
| dib = FreeImage_AllocateHeader(header_only, wrr, hrr, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
| break; |
| case 4: |
| dib = FreeImage_AllocateHeader(header_only, wrr, hrr, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
| break; |
| } |
| } else if(image->comps[0].prec <= 16) { |
| switch(numcomps) { |
| case 1: |
| dib = FreeImage_AllocateHeaderT(header_only, FIT_UINT16, wrr, hrr); |
| break; |
| case 3: |
| dib = FreeImage_AllocateHeaderT(header_only, FIT_RGB16, wrr, hrr); |
| break; |
| case 4: |
| dib = FreeImage_AllocateHeaderT(header_only, FIT_RGBA16, wrr, hrr); |
| break; |
| } |
| } else { |
| throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; |
| } |
| if(!dib) { |
| throw FI_MSG_ERROR_DIB_MEMORY; |
| } |
| |
| // "header only" FIBITMAP ? |
| if(header_only) { |
| return dib; |
| } |
| |
| if(image->comps[0].prec <= 8) { |
| if(numcomps == 1) { |
| // 8-bit greyscale |
| // ---------------------------------------------------------- |
| |
| // build a greyscale palette |
| |
| RGBQUAD *pal = FreeImage_GetPalette(dib); |
| for (int i = 0; i < 256; i++) { |
| pal[i].rgbRed = (BYTE)i; |
| pal[i].rgbGreen = (BYTE)i; |
| pal[i].rgbBlue = (BYTE)i; |
| } |
| |
| // load pixel data |
| |
| unsigned pixel_count = 0; |
| |
| for(int y = 0; y < hrr; y++) { |
| BYTE *bits = FreeImage_GetScanLine(dib, hrr - 1 - y); |
| |
| for(int x = 0; x < wrr; x++) { |
| const unsigned pixel_pos = pixel_count / wrr * wr + pixel_count % wrr; |
| |
| int index = image->comps[0].data[pixel_pos]; |
| index += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); |
| |
| bits[x] = (BYTE)index; |
| |
| pixel_count++; |
| } |
| } |
| } |
| else if(numcomps == 3) { |
| |
| // 24-bit RGB |
| // ---------------------------------------------------------- |
| |
| // load pixel data |
| |
| unsigned pixel_count = 0; |
| |
| for(int y = 0; y < hrr; y++) { |
| BYTE *bits = FreeImage_GetScanLine(dib, hrr - 1 - y); |
| |
| for(int x = 0; x < wrr; x++) { |
| const unsigned pixel_pos = pixel_count / wrr * wr + pixel_count % wrr; |
| |
| int r = image->comps[0].data[pixel_pos]; |
| r += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); |
| |
| int g = image->comps[1].data[pixel_pos]; |
| g += (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); |
| |
| int b = image->comps[2].data[pixel_pos]; |
| b += (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); |
| |
| bits[FI_RGBA_RED] = (BYTE)r; |
| bits[FI_RGBA_GREEN] = (BYTE)g; |
| bits[FI_RGBA_BLUE] = (BYTE)b; |
| bits += 3; |
| |
| pixel_count++; |
| } |
| } |
| } |
| else if(numcomps == 4) { |
| |
| // 32-bit RGBA |
| // ---------------------------------------------------------- |
| |
| // load pixel data |
| |
| unsigned pixel_count = 0; |
| |
| for(int y = 0; y < hrr; y++) { |
| BYTE *bits = FreeImage_GetScanLine(dib, hrr - 1 - y); |
| |
| for(int x = 0; x < wrr; x++) { |
| const unsigned pixel_pos = pixel_count / wrr * wr + pixel_count % wrr; |
| |
| int r = image->comps[0].data[pixel_pos]; |
| r += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); |
| |
| int g = image->comps[1].data[pixel_pos]; |
| g += (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); |
| |
| int b = image->comps[2].data[pixel_pos]; |
| b += (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); |
| |
| int a = image->comps[3].data[pixel_pos]; |
| a += (image->comps[3].sgnd ? 1 << (image->comps[3].prec - 1) : 0); |
| |
| bits[FI_RGBA_RED] = (BYTE)r; |
| bits[FI_RGBA_GREEN] = (BYTE)g; |
| bits[FI_RGBA_BLUE] = (BYTE)b; |
| bits[FI_RGBA_ALPHA] = (BYTE)a; |
| bits += 4; |
| |
| pixel_count++; |
| } |
| } |
| } |
| } |
| else if(image->comps[0].prec <= 16) { |
| if(numcomps == 1) { |
| // 16-bit greyscale |
| // ---------------------------------------------------------- |
| |
| // load pixel data |
| |
| unsigned pixel_count = 0; |
| |
| for(int y = 0; y < hrr; y++) { |
| WORD *bits = (WORD*)FreeImage_GetScanLine(dib, hrr - 1 - y); |
| |
| for(int x = 0; x < wrr; x++) { |
| const unsigned pixel_pos = pixel_count / wrr * wr + pixel_count % wrr; |
| |
| int index = image->comps[0].data[pixel_pos]; |
| index += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); |
| |
| bits[x] = (WORD)index; |
| |
| pixel_count++; |
| } |
| } |
| } |
| else if(numcomps == 3) { |
| |
| // 48-bit RGB |
| // ---------------------------------------------------------- |
| |
| // load pixel data |
| |
| unsigned pixel_count = 0; |
| |
| for(int y = 0; y < hrr; y++) { |
| FIRGB16 *bits = (FIRGB16*)FreeImage_GetScanLine(dib, hrr - 1 - y); |
| |
| for(int x = 0; x < wrr; x++) { |
| const unsigned pixel_pos = pixel_count / wrr * wr + pixel_count % wrr; |
| |
| int r = image->comps[0].data[pixel_pos]; |
| r += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); |
| |
| int g = image->comps[1].data[pixel_pos]; |
| g += (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); |
| |
| int b = image->comps[2].data[pixel_pos]; |
| b += (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); |
| |
| bits[x].red = (WORD)r; |
| bits[x].green = (WORD)g; |
| bits[x].blue = (WORD)b; |
| |
| pixel_count++; |
| } |
| } |
| } |
| else if(numcomps == 4) { |
| |
| // 64-bit RGBA |
| // ---------------------------------------------------------- |
| |
| // load pixel data |
| |
| unsigned pixel_count = 0; |
| |
| for(int y = 0; y < hrr; y++) { |
| FIRGBA16 *bits = (FIRGBA16*)FreeImage_GetScanLine(dib, hrr - 1 - y); |
| |
| for(int x = 0; x < wrr; x++) { |
| const unsigned pixel_pos = pixel_count / wrr * wr + pixel_count % wrr; |
| |
| int r = image->comps[0].data[pixel_pos]; |
| r += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); |
| |
| int g = image->comps[1].data[pixel_pos]; |
| g += (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); |
| |
| int b = image->comps[2].data[pixel_pos]; |
| b += (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); |
| |
| int a = image->comps[3].data[pixel_pos]; |
| a += (image->comps[3].sgnd ? 1 << (image->comps[3].prec - 1) : 0); |
| |
| bits[x].red = (WORD)r; |
| bits[x].green = (WORD)g; |
| bits[x].blue = (WORD)b; |
| bits[x].alpha = (WORD)a; |
| |
| pixel_count++; |
| } |
| } |
| } |
| } |
| |
| return dib; |
| |
| } catch(const char *text) { |
| if(dib) FreeImage_Unload(dib); |
| FreeImage_OutputMessageProc(format_id, text); |
| return NULL; |
| } |
| |
| } |
| |
| /** |
| Convert a FIBITMAP to a OpenJPEG image |
| @param format_id Plugin ID |
| @param dib FreeImage image |
| @param parameters Compression parameters |
| @return Returns the converted image if successful, returns NULL otherwise |
| */ |
| opj_image_t* FIBITMAPToJ2KImage(int format_id, FIBITMAP *dib, const opj_cparameters_t *parameters) { |
| int prec, numcomps, x, y, index; |
| OPJ_COLOR_SPACE color_space; |
| opj_image_cmptparm_t cmptparm[4]; // maximum of 4 components |
| opj_image_t *image = NULL; // image to encode |
| |
| try { |
| int w = FreeImage_GetWidth(dib); |
| int h = FreeImage_GetHeight(dib); |
| |
| // get image characteristics |
| FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); |
| |
| if(image_type == FIT_BITMAP) { |
| // standard image ... |
| prec = 8; |
| switch(FreeImage_GetColorType(dib)) { |
| case FIC_MINISBLACK: |
| numcomps = 1; |
| color_space = OPJ_CLRSPC_GRAY; |
| break; |
| case FIC_RGB: |
| if(FreeImage_GetBPP(dib) == 32) { |
| // 32-bit image with a fully opaque layer |
| numcomps = 4; |
| color_space = OPJ_CLRSPC_SRGB; |
| } else { |
| // 24-bit image |
| numcomps = 3; |
| color_space = OPJ_CLRSPC_SRGB; |
| } |
| break; |
| case FIC_RGBALPHA: |
| numcomps = 4; |
| color_space = OPJ_CLRSPC_SRGB; |
| break; |
| default: |
| return NULL; |
| } |
| } else { |
| // HDR image ... |
| prec = 16; |
| switch(image_type) { |
| case FIT_UINT16: |
| numcomps = 1; |
| color_space = OPJ_CLRSPC_GRAY; |
| break; |
| case FIT_RGB16: |
| numcomps = 3; |
| color_space = OPJ_CLRSPC_SRGB; |
| break; |
| case FIT_RGBA16: |
| numcomps = 4; |
| color_space = OPJ_CLRSPC_SRGB; |
| break; |
| default: |
| return NULL; |
| } |
| } |
| |
| // initialize image components |
| memset(&cmptparm[0], 0, 4 * sizeof(opj_image_cmptparm_t)); |
| for(int i = 0; i < numcomps; i++) { |
| cmptparm[i].dx = parameters->subsampling_dx; |
| cmptparm[i].dy = parameters->subsampling_dy; |
| cmptparm[i].w = w; |
| cmptparm[i].h = h; |
| cmptparm[i].prec = prec; |
| cmptparm[i].bpp = prec; |
| cmptparm[i].sgnd = 0; |
| } |
| // create the image |
| image = opj_image_create(numcomps, &cmptparm[0], color_space); |
| if(!image) { |
| throw FI_MSG_ERROR_DIB_MEMORY; |
| } |
| |
| // set image offset and reference grid |
| image->x0 = parameters->image_offset_x0; |
| image->y0 = parameters->image_offset_y0; |
| image->x1 = parameters->image_offset_x0 + (w - 1) * parameters->subsampling_dx + 1; |
| image->y1 = parameters->image_offset_y0 + (h - 1) * parameters->subsampling_dy + 1; |
| |
| // set image data |
| if(prec == 8) { |
| switch(numcomps) { |
| case 1: |
| index = 0; |
| for(y = 0; y < h; y++) { |
| BYTE *bits = FreeImage_GetScanLine(dib, h - 1 - y); |
| for(x = 0; x < w; x++) { |
| image->comps[0].data[index] = bits[x]; |
| index++; |
| } |
| } |
| break; |
| case 3: |
| index = 0; |
| for(y = 0; y < h; y++) { |
| BYTE *bits = FreeImage_GetScanLine(dib, h - 1 - y); |
| for(x = 0; x < w; x++) { |
| image->comps[0].data[index] = bits[FI_RGBA_RED]; |
| image->comps[1].data[index] = bits[FI_RGBA_GREEN]; |
| image->comps[2].data[index] = bits[FI_RGBA_BLUE]; |
| bits += 3; |
| index++; |
| } |
| } |
| break; |
| case 4: |
| index = 0; |
| for(y = 0; y < h; y++) { |
| BYTE *bits = FreeImage_GetScanLine(dib, h - 1 - y); |
| for(x = 0; x < w; x++) { |
| image->comps[0].data[index] = bits[FI_RGBA_RED]; |
| image->comps[1].data[index] = bits[FI_RGBA_GREEN]; |
| image->comps[2].data[index] = bits[FI_RGBA_BLUE]; |
| image->comps[3].data[index] = bits[FI_RGBA_ALPHA]; |
| bits += 4; |
| index++; |
| } |
| } |
| break; |
| } |
| } |
| else if(prec == 16) { |
| switch(numcomps) { |
| case 1: |
| index = 0; |
| for(y = 0; y < h; y++) { |
| WORD *bits = (WORD*)FreeImage_GetScanLine(dib, h - 1 - y); |
| for(x = 0; x < w; x++) { |
| image->comps[0].data[index] = bits[x]; |
| index++; |
| } |
| } |
| break; |
| case 3: |
| index = 0; |
| for(y = 0; y < h; y++) { |
| FIRGB16 *bits = (FIRGB16*)FreeImage_GetScanLine(dib, h - 1 - y); |
| for(x = 0; x < w; x++) { |
| image->comps[0].data[index] = bits[x].red; |
| image->comps[1].data[index] = bits[x].green; |
| image->comps[2].data[index] = bits[x].blue; |
| index++; |
| } |
| } |
| break; |
| case 4: |
| index = 0; |
| for(y = 0; y < h; y++) { |
| FIRGBA16 *bits = (FIRGBA16*)FreeImage_GetScanLine(dib, h - 1 - y); |
| for(x = 0; x < w; x++) { |
| image->comps[0].data[index] = bits[x].red; |
| image->comps[1].data[index] = bits[x].green; |
| image->comps[2].data[index] = bits[x].blue; |
| image->comps[3].data[index] = bits[x].alpha; |
| index++; |
| } |
| } |
| break; |
| } |
| } |
| |
| return image; |
| |
| } catch (const char *text) { |
| if(image) opj_image_destroy(image); |
| FreeImage_OutputMessageProc(format_id, text); |
| return NULL; |
| } |
| } |