| // ========================================================== | |
| // JPEG lossless transformations | |
| // | |
| // Design and implementation by | |
| // - Petr Pytelka (pyta@lightcomp.com) | |
| // - Hervé Drolon (drolon@infonie.fr) | |
| // - Mihail Naydenov (mnaydenov@users.sourceforge.net) | |
| // | |
| // 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! | |
| // ========================================================== | |
| extern "C" { | |
| #define XMD_H | |
| #undef FAR | |
| #include <setjmp.h> | |
| #include "third_party/jpeg/jinclude.h" | |
| #include "third_party/jpeg/jpeglib.h" | |
| #include "third_party/jpeg/jerror.h" | |
| #include "third_party/jpeg/transupp.h" | |
| } | |
| #include "FreeImage.h" | |
| #include "Utilities.h" | |
| #include "FreeImageIO.h" | |
| // ---------------------------------------------------------- | |
| // Source manager & Destination manager setup | |
| // (see PluginJPEG.cpp) | |
| // ---------------------------------------------------------- | |
| void jpeg_freeimage_src(j_decompress_ptr cinfo, fi_handle infile, FreeImageIO *io); | |
| void jpeg_freeimage_dst(j_compress_ptr cinfo, fi_handle outfile, FreeImageIO *io); | |
| // ---------------------------------------------------------- | |
| // Error handling | |
| // (see also PluginJPEG.cpp) | |
| // ---------------------------------------------------------- | |
| /** | |
| Receives control for a fatal error. Information sufficient to | |
| generate the error message has been stored in cinfo->err; call | |
| output_message to display it. Control must NOT return to the caller; | |
| generally this routine will exit() or longjmp() somewhere. | |
| */ | |
| METHODDEF(void) | |
| ls_jpeg_error_exit (j_common_ptr cinfo) { | |
| // always display the message | |
| (*cinfo->err->output_message)(cinfo); | |
| // allow JPEG with a premature end of file | |
| if((cinfo)->err->msg_parm.i[0] != 13) { | |
| // let the memory manager delete any temp files before we die | |
| jpeg_destroy(cinfo); | |
| throw FIF_JPEG; | |
| } | |
| } | |
| /** | |
| Actual output of any JPEG message. Note that this method does not know | |
| how to generate a message, only where to send it. | |
| */ | |
| METHODDEF(void) | |
| ls_jpeg_output_message (j_common_ptr cinfo) { | |
| char buffer[JMSG_LENGTH_MAX]; | |
| // create the message | |
| (*cinfo->err->format_message)(cinfo, buffer); | |
| // send it to user's message proc | |
| FreeImage_OutputMessageProc(FIF_JPEG, buffer); | |
| } | |
| // ---------------------------------------------------------- | |
| // Main program | |
| // ---------------------------------------------------------- | |
| /** | |
| Build a crop string. | |
| @param crop Output crop string | |
| @param left Specifies the left position of the cropped rectangle | |
| @param top Specifies the top position of the cropped rectangle | |
| @param right Specifies the right position of the cropped rectangle | |
| @param bottom Specifies the bottom position of the cropped rectangle | |
| @param width Image width | |
| @param height Image height | |
| @return Returns TRUE if successful, returns FALSE otherwise | |
| */ | |
| static BOOL | |
| getCropString(char* crop, int* left, int* top, int* right, int* bottom, int width, int height) { | |
| if(!left || !top || !right || !bottom) { | |
| return FALSE; | |
| } | |
| *left = CLAMP(*left, 0, width); | |
| *top = CLAMP(*top, 0, height); | |
| // negative/zero right and bottom count from the edges inwards | |
| if(*right <= 0) { | |
| *right = width + *right; | |
| } | |
| if(*bottom <= 0) { | |
| *bottom = height + *bottom; | |
| } | |
| *right = CLAMP(*right, 0, width); | |
| *bottom = CLAMP(*bottom, 0, height); | |
| // test for empty rect | |
| if(((*left - *right) == 0) || ((*top - *bottom) == 0)) { | |
| return FALSE; | |
| } | |
| // normalize the rectangle | |
| if(*right < *left) { | |
| INPLACESWAP(*left, *right); | |
| } | |
| if(*bottom < *top) { | |
| INPLACESWAP(*top, *bottom); | |
| } | |
| // test for "noop" rect | |
| if(*left == 0 && *right == width && *top == 0 && *bottom == height) { | |
| return FALSE; | |
| } | |
| // build the crop option | |
| sprintf(crop, "%dx%d+%d+%d", *right - *left, *bottom - *top, *left, *top); | |
| return TRUE; | |
| } | |
| static BOOL | |
| JPEGTransformFromHandle(FreeImageIO* src_io, fi_handle src_handle, FreeImageIO* dst_io, fi_handle dst_handle, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) { | |
| const BOOL onlyReturnCropRect = (dst_io == NULL) || (dst_handle == NULL); | |
| const long stream_start = onlyReturnCropRect ? 0 : dst_io->tell_proc(dst_handle); | |
| BOOL swappedDim = FALSE; | |
| BOOL trimH = FALSE; | |
| BOOL trimV = FALSE; | |
| // Set up the jpeglib structures | |
| jpeg_decompress_struct srcinfo; | |
| jpeg_compress_struct dstinfo; | |
| jpeg_error_mgr jsrcerr, jdsterr; | |
| jvirt_barray_ptr *src_coef_arrays = NULL; | |
| jvirt_barray_ptr *dst_coef_arrays = NULL; | |
| // Support for copying optional markers from source to destination file | |
| JCOPY_OPTION copyoption; | |
| // Image transformation options | |
| jpeg_transform_info transfoptions; | |
| // Initialize structures | |
| memset(&srcinfo, 0, sizeof(srcinfo)); | |
| memset(&jsrcerr, 0, sizeof(jsrcerr)); | |
| memset(&jdsterr, 0, sizeof(jdsterr)); | |
| memset(&dstinfo, 0, sizeof(dstinfo)); | |
| memset(&transfoptions, 0, sizeof(transfoptions)); | |
| // Copy all extra markers from source file | |
| copyoption = JCOPYOPT_ALL; | |
| // Set up default JPEG parameters | |
| transfoptions.force_grayscale = FALSE; | |
| transfoptions.crop = FALSE; | |
| // Select the transform option | |
| switch(operation) { | |
| case FIJPEG_OP_FLIP_H: // horizontal flip | |
| transfoptions.transform = JXFORM_FLIP_H; | |
| trimH = TRUE; | |
| break; | |
| case FIJPEG_OP_FLIP_V: // vertical flip | |
| transfoptions.transform = JXFORM_FLIP_V; | |
| trimV = TRUE; | |
| break; | |
| case FIJPEG_OP_TRANSPOSE: // transpose across UL-to-LR axis | |
| transfoptions.transform = JXFORM_TRANSPOSE; | |
| swappedDim = TRUE; | |
| break; | |
| case FIJPEG_OP_TRANSVERSE: // transpose across UR-to-LL axis | |
| transfoptions.transform = JXFORM_TRANSVERSE; | |
| trimH = TRUE; | |
| trimV = TRUE; | |
| swappedDim = TRUE; | |
| break; | |
| case FIJPEG_OP_ROTATE_90: // 90-degree clockwise rotation | |
| transfoptions.transform = JXFORM_ROT_90; | |
| trimH = TRUE; | |
| swappedDim = TRUE; | |
| break; | |
| case FIJPEG_OP_ROTATE_180: // 180-degree rotation | |
| trimH = TRUE; | |
| trimV = TRUE; | |
| transfoptions.transform = JXFORM_ROT_180; | |
| break; | |
| case FIJPEG_OP_ROTATE_270: // 270-degree clockwise (or 90 ccw) | |
| transfoptions.transform = JXFORM_ROT_270; | |
| trimV = TRUE; | |
| swappedDim = TRUE; | |
| break; | |
| default: | |
| case FIJPEG_OP_NONE: // no transformation | |
| transfoptions.transform = JXFORM_NONE; | |
| break; | |
| } | |
| // (perfect == TRUE) ==> fail if there is non-transformable edge blocks | |
| transfoptions.perfect = (perfect == TRUE) ? TRUE : FALSE; | |
| // Drop non-transformable edge blocks: trim off any partial edge MCUs that the transform can't handle. | |
| transfoptions.trim = TRUE; | |
| try { | |
| // Initialize the JPEG decompression object with default error handling | |
| srcinfo.err = jpeg_std_error(&jsrcerr); | |
| srcinfo.err->error_exit = ls_jpeg_error_exit; | |
| srcinfo.err->output_message = ls_jpeg_output_message; | |
| jpeg_create_decompress(&srcinfo); | |
| // Initialize the JPEG compression object with default error handling | |
| dstinfo.err = jpeg_std_error(&jdsterr); | |
| dstinfo.err->error_exit = ls_jpeg_error_exit; | |
| dstinfo.err->output_message = ls_jpeg_output_message; | |
| jpeg_create_compress(&dstinfo); | |
| // Specify data source for decompression | |
| jpeg_freeimage_src(&srcinfo, src_handle, src_io); | |
| // Enable saving of extra markers that we want to copy | |
| jcopy_markers_setup(&srcinfo, copyoption); | |
| // Read the file header | |
| jpeg_read_header(&srcinfo, TRUE); | |
| // crop option | |
| char crop[64]; | |
| const BOOL hasCrop = getCropString(crop, left, top, right, bottom, swappedDim ? srcinfo.image_height : srcinfo.image_width, swappedDim ? srcinfo.image_width : srcinfo.image_height); | |
| if(hasCrop) { | |
| if(!jtransform_parse_crop_spec(&transfoptions, crop)) { | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Bogus crop argument %s", crop); | |
| throw(1); | |
| } | |
| } | |
| // Any space needed by a transform option must be requested before | |
| // jpeg_read_coefficients so that memory allocation will be done right | |
| // Prepare transformation workspace | |
| // Fails right away if perfect flag is TRUE and transformation is not perfect | |
| if( !jtransform_request_workspace(&srcinfo, &transfoptions) ) { | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Transformation is not perfect"); | |
| throw(1); | |
| } | |
| if(left || top) { | |
| // compute left and top offsets, it's a bit tricky, taking into account both | |
| // transform, which might have trimed the image, | |
| // and crop itself, which is adjusted to lie on a iMCU boundary | |
| const int fullWidth = swappedDim ? srcinfo.image_height : srcinfo.image_width; | |
| const int fullHeight = swappedDim ? srcinfo.image_width : srcinfo.image_height; | |
| int transformedFullWidth = fullWidth; | |
| int transformedFullHeight = fullHeight; | |
| if(trimH && transformedFullWidth/transfoptions.iMCU_sample_width > 0) { | |
| transformedFullWidth = (transformedFullWidth/transfoptions.iMCU_sample_width) * transfoptions.iMCU_sample_width; | |
| } | |
| if(trimV && transformedFullHeight/transfoptions.iMCU_sample_height > 0) { | |
| transformedFullHeight = (transformedFullHeight/transfoptions.iMCU_sample_height) * transfoptions.iMCU_sample_height; | |
| } | |
| const int trimmedWidth = fullWidth - transformedFullWidth; | |
| const int trimmedHeight = fullHeight - transformedFullHeight; | |
| if(left) { | |
| *left = trimmedWidth + transfoptions.x_crop_offset * transfoptions.iMCU_sample_width; | |
| } | |
| if(top) { | |
| *top = trimmedHeight + transfoptions.y_crop_offset * transfoptions.iMCU_sample_height; | |
| } | |
| } | |
| if(right) { | |
| *right = (left ? *left : 0) + transfoptions.output_width; | |
| } | |
| if(bottom) { | |
| *bottom = (top ? *top : 0) + transfoptions.output_height; | |
| } | |
| // if only the crop rect is requested, we are done | |
| if(onlyReturnCropRect) { | |
| jpeg_destroy_compress(&dstinfo); | |
| jpeg_destroy_decompress(&srcinfo); | |
| return TRUE; | |
| } | |
| // Read source file as DCT coefficients | |
| src_coef_arrays = jpeg_read_coefficients(&srcinfo); | |
| // Initialize destination compression parameters from source values | |
| jpeg_copy_critical_parameters(&srcinfo, &dstinfo); | |
| // Adjust destination parameters if required by transform options; | |
| // also find out which set of coefficient arrays will hold the output | |
| dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, src_coef_arrays, &transfoptions); | |
| // Note: we assume that jpeg_read_coefficients consumed all input | |
| // until JPEG_REACHED_EOI, and that jpeg_finish_decompress will | |
| // only consume more while (! cinfo->inputctl->eoi_reached). | |
| // We cannot call jpeg_finish_decompress here since we still need the | |
| // virtual arrays allocated from the source object for processing. | |
| if(src_handle == dst_handle) { | |
| dst_io->seek_proc(dst_handle, stream_start, SEEK_SET); | |
| } | |
| // Specify data destination for compression | |
| jpeg_freeimage_dst(&dstinfo, dst_handle, dst_io); | |
| // Start compressor (note no image data is actually written here) | |
| jpeg_write_coefficients(&dstinfo, dst_coef_arrays); | |
| // Copy to the output file any extra markers that we want to preserve | |
| jcopy_markers_execute(&srcinfo, &dstinfo, copyoption); | |
| // Execute image transformation, if any | |
| jtransform_execute_transformation(&srcinfo, &dstinfo, src_coef_arrays, &transfoptions); | |
| // Finish compression and release memory | |
| jpeg_finish_compress(&dstinfo); | |
| jpeg_destroy_compress(&dstinfo); | |
| jpeg_finish_decompress(&srcinfo); | |
| jpeg_destroy_decompress(&srcinfo); | |
| } | |
| catch(...) { | |
| jpeg_destroy_compress(&dstinfo); | |
| jpeg_destroy_decompress(&srcinfo); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| // ---------------------------------------------------------- | |
| // FreeImage interface | |
| // ---------------------------------------------------------- | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGTransformFromHandle(FreeImageIO* src_io, fi_handle src_handle, FreeImageIO* dst_io, fi_handle dst_handle, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) { | |
| return JPEGTransformFromHandle(src_io, src_handle, dst_io, dst_handle, operation, left, top, right, bottom, perfect); | |
| } | |
| static void | |
| closeStdIO(fi_handle src_handle, fi_handle dst_handle) { | |
| if(src_handle) { | |
| fclose((FILE*)src_handle); | |
| } | |
| if(dst_handle && (dst_handle != src_handle)) { | |
| fclose((FILE*)dst_handle); | |
| } | |
| } | |
| static BOOL | |
| openStdIO(const char* src_file, const char* dst_file, FreeImageIO* dst_io, fi_handle* src_handle, fi_handle* dst_handle) { | |
| *src_handle = NULL; | |
| *dst_handle = NULL; | |
| FreeImageIO io; | |
| SetDefaultIO (&io); | |
| const BOOL isSameFile = (dst_file && (strcmp(src_file, dst_file) == 0)) ? TRUE : FALSE; | |
| FILE* srcp = NULL; | |
| FILE* dstp = NULL; | |
| if(isSameFile) { | |
| srcp = fopen(src_file, "r+b"); | |
| dstp = srcp; | |
| } | |
| else { | |
| srcp = fopen(src_file, "rb"); | |
| if(dst_file) { | |
| dstp = fopen(dst_file, "wb"); | |
| } | |
| } | |
| if(!srcp || (dst_file && !dstp)) { | |
| if(!srcp) { | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open \"%s\" for reading", src_file); | |
| } else { | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open \"%s\" for writing", dst_file); | |
| } | |
| closeStdIO(srcp, dstp); | |
| return FALSE; | |
| } | |
| if(FreeImage_GetFileTypeFromHandle(&io, srcp) != FIF_JPEG) { | |
| FreeImage_OutputMessageProc(FIF_JPEG, " Source file \"%s\" is not jpeg", src_file); | |
| closeStdIO(srcp, dstp); | |
| return FALSE; | |
| } | |
| *dst_io = io; | |
| *src_handle = srcp; | |
| *dst_handle = dstp; | |
| return TRUE; | |
| } | |
| static BOOL | |
| openStdIOU(const wchar_t* src_file, const wchar_t* dst_file, FreeImageIO* dst_io, fi_handle* src_handle, fi_handle* dst_handle) { | |
| #ifdef _WIN32 | |
| *src_handle = NULL; | |
| *dst_handle = NULL; | |
| FreeImageIO io; | |
| SetDefaultIO (&io); | |
| const BOOL isSameFile = (dst_file && (wcscmp(src_file, dst_file) == 0)) ? TRUE : FALSE; | |
| FILE* srcp = NULL; | |
| FILE* dstp = NULL; | |
| if(isSameFile) { | |
| srcp = _wfopen(src_file, L"r+b"); | |
| dstp = srcp; | |
| } else { | |
| srcp = _wfopen(src_file, L"rb"); | |
| if(dst_file) { | |
| dstp = _wfopen(dst_file, L"wb"); | |
| } | |
| } | |
| if(!srcp || (dst_file && !dstp)) { | |
| if(!srcp) { | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open source file for reading"); | |
| } else { | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open destination file for writing"); | |
| } | |
| closeStdIO(srcp, dstp); | |
| return FALSE; | |
| } | |
| if(FreeImage_GetFileTypeFromHandle(&io, srcp) != FIF_JPEG) { | |
| FreeImage_OutputMessageProc(FIF_JPEG, " Source file is not jpeg"); | |
| closeStdIO(srcp, dstp); | |
| return FALSE; | |
| } | |
| *dst_io = io; | |
| *src_handle = srcp; | |
| *dst_handle = dstp; | |
| return TRUE; | |
| #else | |
| return FALSE; | |
| #endif // _WIN32 | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGTransform(const char *src_file, const char *dst_file, FREE_IMAGE_JPEG_OPERATION operation, BOOL perfect) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!openStdIO(src_file, dst_file, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| BOOL ret = JPEGTransformFromHandle(&io, src, &io, dst, operation, NULL, NULL, NULL, NULL, perfect); | |
| closeStdIO(src, dst); | |
| return ret; | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGCrop(const char *src_file, const char *dst_file, int left, int top, int right, int bottom) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!openStdIO(src_file, dst_file, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, FIJPEG_OP_NONE, &left, &top, &right, &bottom, FALSE); | |
| closeStdIO(src, dst); | |
| return ret; | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGTransformU(const wchar_t *src_file, const wchar_t *dst_file, FREE_IMAGE_JPEG_OPERATION operation, BOOL perfect) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!openStdIOU(src_file, dst_file, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| BOOL ret = JPEGTransformFromHandle(&io, src, &io, dst, operation, NULL, NULL, NULL, NULL, perfect); | |
| closeStdIO(src, dst); | |
| return ret; | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGCropU(const wchar_t *src_file, const wchar_t *dst_file, int left, int top, int right, int bottom) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!openStdIOU(src_file, dst_file, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, FIJPEG_OP_NONE, &left, &top, &right, &bottom, FALSE); | |
| closeStdIO(src, dst); | |
| return ret; | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGTransformCombined(const char *src_file, const char *dst_file, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!openStdIO(src_file, dst_file, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, operation, left, top, right, bottom, perfect); | |
| closeStdIO(src, dst); | |
| return ret; | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGTransformCombinedU(const wchar_t *src_file, const wchar_t *dst_file, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!openStdIOU(src_file, dst_file, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, operation, left, top, right, bottom, perfect); | |
| closeStdIO(src, dst); | |
| return ret; | |
| } | |
| // -------------------------------------------------------------------------- | |
| static BOOL | |
| getMemIO(FIMEMORY* src_stream, FIMEMORY* dst_stream, FreeImageIO* dst_io, fi_handle* src_handle, fi_handle* dst_handle) { | |
| *src_handle = NULL; | |
| *dst_handle = NULL; | |
| FreeImageIO io; | |
| SetMemoryIO (&io); | |
| if(dst_stream) { | |
| FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(dst_stream->data); | |
| if(mem_header->delete_me != TRUE) { | |
| // do not save in a user buffer | |
| FreeImage_OutputMessageProc(FIF_JPEG, "Destination memory buffer is read only"); | |
| return FALSE; | |
| } | |
| } | |
| *dst_io = io; | |
| *src_handle = src_stream; | |
| *dst_handle = dst_stream; | |
| return TRUE; | |
| } | |
| BOOL DLL_CALLCONV | |
| FreeImage_JPEGTransformCombinedFromMemory(FIMEMORY* src_stream, FIMEMORY* dst_stream, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) { | |
| FreeImageIO io; | |
| fi_handle src; | |
| fi_handle dst; | |
| if(!getMemIO(src_stream, dst_stream, &io, &src, &dst)) { | |
| return FALSE; | |
| } | |
| return FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, operation, left, top, right, bottom, perfect); | |
| } | |