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; }
static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { if (handle) { FIBITMAP *dib = NULL; try { // set up the jpeglib structures struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; // step 1: allocate and initialize JPEG decompression object cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpeg_error_exit; jerr.output_message = jpeg_output_message; jpeg_create_decompress(&cinfo); // step 2a: specify data source (eg, a handle) jpeg_freeimage_src(&cinfo, handle, io); // step 2b: save special markers for later reading jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); for(int m = 0; m < 16; m++) { jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); } // step 3: read handle parameters with jpeg_read_header() jpeg_read_header(&cinfo, TRUE); // step 4: set parameters for decompression unsigned int scale_denom = 1; // fraction by which to scale image int requested_size = flags >> 16; // requested user size in pixels if(requested_size > 0) { // the JPEG codec can perform x2, x4 or x8 scaling on loading // try to find the more appropriate scaling according to user's need double scale = MAX((double)cinfo.image_width, (double)cinfo.image_height) / (double)requested_size; if(scale >= 8) { scale_denom = 8; } else if(scale >= 4) { scale_denom = 4; } else if(scale >= 2) { scale_denom = 2; } } cinfo.scale_num = 1; cinfo.scale_denom = scale_denom; if ((flags & JPEG_ACCURATE) != JPEG_ACCURATE) { cinfo.dct_method = JDCT_IFAST; cinfo.do_fancy_upsampling = FALSE; } // step 5a: start decompressor and calculate output width and height jpeg_start_decompress(&cinfo); // step 5b: allocate dib and init header if((cinfo.num_components == 4) && (cinfo.out_color_space == JCS_CMYK)) { // CMYK image if((flags & JPEG_CMYK) == JPEG_CMYK) { // load as CMYK dib = FreeImage_Allocate(cinfo.output_width, cinfo.output_height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); if(!dib) return NULL; FreeImage_GetICCProfile(dib)->flags |= FIICC_COLOR_IS_CMYK; } else { // load as CMYK and convert to RGB dib = FreeImage_Allocate(cinfo.output_width, cinfo.output_height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); if(!dib) return NULL; } } else { // RGB or greyscale image dib = FreeImage_Allocate(cinfo.output_width, cinfo.output_height, 8 * cinfo.num_components, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); if(!dib) return NULL; if (cinfo.num_components == 1) { // build a greyscale palette RGBQUAD *colors = FreeImage_GetPalette(dib); for (int i = 0; i < 256; i++) { colors[i].rgbRed = (BYTE)i; colors[i].rgbGreen = (BYTE)i; colors[i].rgbBlue = (BYTE)i; } } } if(scale_denom != 1) { // store original size info if a scaling was requested store_size_info(dib, cinfo.image_width, cinfo.image_height); } // step 5c: handle metrices if (cinfo.density_unit == 1) { // dots/inch FreeImage_SetDotsPerMeterX(dib, (unsigned) (((float)cinfo.X_density) / 0.0254000 + 0.5)); FreeImage_SetDotsPerMeterY(dib, (unsigned) (((float)cinfo.Y_density) / 0.0254000 + 0.5)); } else if (cinfo.density_unit == 2) { // dots/cm FreeImage_SetDotsPerMeterX(dib, (unsigned) (cinfo.X_density * 100)); FreeImage_SetDotsPerMeterY(dib, (unsigned) (cinfo.Y_density * 100)); } // step 6a: while (scan lines remain to be read) jpeg_read_scanlines(...); if((cinfo.out_color_space == JCS_CMYK) && ((flags & JPEG_CMYK) != JPEG_CMYK)) { // convert from CMYK to RGB JSAMPARRAY buffer; // output row buffer unsigned row_stride; // physical row width in output buffer // JSAMPLEs per row in output buffer row_stride = cinfo.output_width * cinfo.output_components; // make a one-row-high sample array that will go away when done with image buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); while (cinfo.output_scanline < cinfo.output_height) { JSAMPROW src = buffer[0]; JSAMPROW dst = FreeImage_GetScanLine(dib, cinfo.output_height - cinfo.output_scanline - 1); jpeg_read_scanlines(&cinfo, buffer, 1); for(unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { WORD K = (WORD)src[3]; dst[FI_RGBA_RED] = (BYTE)((K * src[0]) / 255); dst[FI_RGBA_GREEN] = (BYTE)((K * src[1]) / 255); dst[FI_RGBA_BLUE] = (BYTE)((K * src[2]) / 255); src += 4; dst += 3; } } } else { // normal case (RGB or greyscale image) while (cinfo.output_scanline < cinfo.output_height) { JSAMPROW dst = FreeImage_GetScanLine(dib, cinfo.output_height - cinfo.output_scanline - 1); jpeg_read_scanlines(&cinfo, &dst, 1); } // step 6b: swap red and blue components (see LibJPEG/jmorecfg.h: #define RGB_RED, ...) // The default behavior of the JPEG library is kept "as is" because LibTIFF uses // LibJPEG "as is". #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR if(cinfo.num_components == 3) { for(unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { BYTE *target = FreeImage_GetScanLine(dib, y); for(unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { INPLACESWAP(target[0], target[2]); target += 3; } } } #endif } // step 7: read special markers read_markers(&cinfo, dib); // step 8: finish decompression jpeg_finish_decompress(&cinfo); // step 9: release JPEG decompression object jpeg_destroy_decompress(&cinfo); // check for automatic Exif rotation if((flags & JPEG_EXIFROTATE) == JPEG_EXIFROTATE) { rotate_exif(&dib); } // everything went well. return the loaded dib return (FIBITMAP *)dib; } catch (...) { if(NULL != dib) { FreeImage_Unload(dib); } } } return NULL; }