imagecore/formats/internal/jpeg.cpp (1,116 lines of code) (raw):

/* * MIT License * * Copyright (c) 2017 Twitter * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "jpeg.h" #include "imagecore/utils/mathutils.h" #include "imagecore/utils/securemath.h" #include "imagecore/utils/memorystream.h" #include "imagecore/image/rgba.h" #include "imagecore/image/yuv.h" #include "imagecore/formats/exif/exifwriter.h" #include "jerror.h" extern "C" { #include "iccjpeg.h" #if IMAGECORE_WITH_JPEG_TRANSFORMS #include "transupp.h" #endif } #if JPEG_LIB_VERSION >= 70 #define _DCT_h_scaled_size DCT_h_scaled_size #define _DCT_v_scaled_size DCT_v_scaled_size #else #define _DCT_h_scaled_size DCT_scaled_size #define _DCT_v_scaled_size DCT_scaled_size #endif #ifdef JCS_EXTENSIONS #define HAVE_RGBX 1 #else #warning libjpeg-turbo is strongly recommended, but not detected. performance will suffer. #define HAVE_RGBX 0 #define JCS_EXT_RGBX (J_COLOR_SPACE)0 #endif namespace imagecore { REGISTER_IMAGE_READER(ImageReaderJPEG); REGISTER_IMAGE_WRITER(ImageWriterJPEG); #if IMAGECORE_WITH_LCMS unsigned int getLCMSInputFormat(J_COLOR_SPACE colorSpace, bool sawAdobeMarker) { if( colorSpace == JCS_GRAYSCALE ) { return TYPE_GRAY_8; } if( colorSpace == JCS_YCCK || colorSpace == JCS_CMYK ) { if( sawAdobeMarker ) { return TYPE_YUVK_8; } else { return TYPE_CMYK_8; } } return TYPE_RGBA_8; } #endif static char s_JPEGLastError[JMSG_LENGTH_MAX] = ""; static void jpegError(j_common_ptr jinfo) { // The address of JPEGErrorMgr::pub and JPEGErrorMgr are the same, so we can just cast // the pointer to the standard error mgr (pub) to ours. JPEGErrorMgr* err = (JPEGErrorMgr*)jinfo->err; longjmp(err->jmp, 1); } static void jpegMessage(j_common_ptr jinfo) { (*jinfo->err->format_message)(jinfo, s_JPEGLastError); } bool ImageReaderJPEG::Factory::matchesSignature(const uint8_t* sig, unsigned int sigLen) { if( sigLen >= 2 && sig[0] == 0xFF && sig[1] == 0xD8 ) { return true; } return false; } ImageReaderJPEG::ImageReaderJPEG() : m_Source(NULL) , m_SourceManager(NULL) , m_Width(0) , m_Height(0) , m_Orientation(kImageOrientation_Up) , m_TotalRowsRead(0) , m_EXIFData(NULL) , m_EXIFDataSize(0) , m_RawColorProfile(NULL) , m_ReadOptions(0) , m_MarkerReadError(false) , m_NativeColorModel(kColorModel_RGBX) #if IMAGECORE_WITH_LCMS , m_ColorProfile(NULL) , m_sRGBProfile(NULL) , m_ColorTransform(NULL) #endif { } ImageReaderJPEG::~ImageReaderJPEG() { free(m_EXIFData); m_EXIFData = NULL; m_EXIFDataSize = 0; if ( m_JPEGDecompress.global_state ) { m_JPEGDecompress.client_data = NULL; jpeg_abort_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); } free(m_RawColorProfile); m_RawColorProfile = NULL; delete m_SourceManager; m_SourceManager = NULL; #if IMAGECORE_WITH_LCMS if( m_ColorProfile != NULL ) { cmsCloseProfile(m_ColorProfile); m_ColorProfile = NULL; } if( m_sRGBProfile != NULL ) { cmsCloseProfile(m_sRGBProfile); m_sRGBProfile = NULL; } if( m_ColorTransform != NULL ) { cmsDeleteTransform(m_ColorTransform); m_ColorTransform = NULL; } #endif } bool ImageReaderJPEG::initWithStorage(ImageReader::Storage* source) { if( source == NULL ) { return false; } m_Source = source; // Set up custom error handler. m_JPEGDecompress.client_data = this; m_JPEGDecompress.err = jpeg_std_error(&m_JPEGError.pub); m_JPEGError.pub.error_exit = jpegError; m_JPEGError.pub.output_message = jpegMessage; if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg init: %s\n", s_JPEGLastError); return false; } jpeg_create_decompress(&m_JPEGDecompress); FILE* sourceFile; uint8_t* buffer; uint64_t length; if( source->asBuffer(buffer, length) ) { jpeg_mem_src(&m_JPEGDecompress, buffer, (unsigned long)length); } else if( source->asFile(sourceFile) ) { jpeg_stdio_src(&m_JPEGDecompress, sourceFile); } else { m_SourceManager = new SourceManager(source, this); m_JPEGDecompress.src = m_SourceManager; } // EXIF jpeg_set_marker_processor(&m_JPEGDecompress, EXIF_MARKER, &handleJPEGMarker); // ICC Color Profile setup_read_icc_profile(&m_JPEGDecompress); return true; } unsigned int jpegRead(j_decompress_ptr dinfo, uint8_t* destBuffer, unsigned int numBytes) { unsigned int remainingBytes = numBytes; while( remainingBytes > 0 ) { if( dinfo->src->bytes_in_buffer == 0 && !dinfo->src->fill_input_buffer(dinfo) ) { break; } unsigned int bytesToRead = min(remainingBytes, (unsigned int)dinfo->src->bytes_in_buffer); memcpy(destBuffer + (numBytes - remainingBytes), dinfo->src->next_input_byte, bytesToRead); dinfo->src->next_input_byte += bytesToRead; dinfo->src->bytes_in_buffer -= bytesToRead; remainingBytes -= bytesToRead; } return numBytes - remainingBytes; } boolean ImageReaderJPEG::handleJPEGMarker(j_decompress_ptr dinfo) { ImageReaderJPEG* reader = (ImageReaderJPEG*)dinfo->client_data; uint8_t rawLength[2]; jpegRead(dinfo, rawLength, 2); uint16_t segmentLength = (((uint16_t)rawLength[0] << 8) + (uint16_t)rawLength[1]) - 2; uint8_t* segmentData = (uint8_t*)malloc(segmentLength); if( segmentData == NULL ) { reader->m_MarkerReadError = true; return FALSE; } jpegRead(dinfo, segmentData, segmentLength); reader->processJPEGSegment(dinfo->unread_marker, segmentData, segmentLength); return TRUE; } void ImageReaderJPEG::processJPEGSegment(unsigned int marker, uint8_t* segmentData, unsigned int segmentLength) { // Do the first few bytes equal "Exif"? Sometimes APP1 markers don't contain EXIF metadata. if( segmentLength >= 4 && marker == EXIF_MARKER && m_EXIFData == NULL && segmentData[0] == 0x45 && segmentData[1] == 0x78 && segmentData[2] == 0x69 && segmentData[3] == 0x66 ) { m_EXIFData = segmentData; m_EXIFDataSize = segmentLength; return; } free(segmentData); } bool ImageReaderJPEG::readHeader() { if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error reading JPEG header: %s\n", s_JPEGLastError); return false; } jpeg_read_header(&m_JPEGDecompress, TRUE); if( m_MarkerReadError ) { return false; } if( m_JPEGDecompress.num_components == 3 ) { bool scaleY2 = m_JPEGDecompress.comp_info[0].h_samp_factor == 2 && m_JPEGDecompress.comp_info[0].v_samp_factor == 2; bool scaleU1 = m_JPEGDecompress.comp_info[1].h_samp_factor == 1 && m_JPEGDecompress.comp_info[1].v_samp_factor == 1; bool scaleV1 = m_JPEGDecompress.comp_info[2].h_samp_factor == 1 && m_JPEGDecompress.comp_info[2].v_samp_factor == 1; if( m_JPEGDecompress.jpeg_color_space == JCS_YCbCr && scaleY2 && scaleU1 && scaleV1 ) { m_NativeColorModel = kColorModel_YUV_420; } } m_Width = m_JPEGDecompress.image_width; m_Height = m_JPEGDecompress.image_height; if( m_EXIFData != NULL ) { ExifCommon::ExifString emptyStr; ExifCommon::ExifU64Rational3 defaultRational3; Rational<uint32_t> defaultRational; int8_t defaultAltitudeRef = 0; m_ExifReader.initialize(m_EXIFData + 6, m_EXIFDataSize - 6); // skip some JPEG data m_Orientation = (EImageOrientation)m_ExifReader.getValue((uint16_t)kImageOrientation_Up, ExifCommon::kTagId::kOrientation); m_GPSLatitudeRef = m_ExifReader.getValue(emptyStr, ExifCommon::kTagId::kGPSLatitudeRef); m_GPSLatitude = m_ExifReader.getValue(defaultRational3, ExifCommon::kTagId::kGPSLatitude); m_GPSLongitudeRef = m_ExifReader.getValue(emptyStr, ExifCommon::kTagId::kGPSLongitudeRef); m_GPSLongitude = m_ExifReader.getValue(defaultRational3, ExifCommon::kTagId::kGPSLongitude); m_AltitudeRef = (EAltitudeRef)m_ExifReader.getValue(defaultAltitudeRef, ExifCommon::kTagId::kGPSAltitudeRef); m_GPSAltitude = m_ExifReader.getValue(defaultRational, ExifCommon::kTagId::kGPSAltitude); m_GPSTimeStamp = m_ExifReader.getValue(defaultRational3, ExifCommon::kTagId::kGPSTimeStamp); m_GPSSpeedRef = m_ExifReader.getValue(emptyStr, ExifCommon::kTagId::kGPSSpeedRef); m_GPSSpeed = m_ExifReader.getValue(defaultRational, ExifCommon::kTagId::kGPSSpeed); m_GPSImgDirectionRef = m_ExifReader.getValue(emptyStr, ExifCommon::kTagId::kGPSImgDirectionRef); m_GPSImgDirection = m_ExifReader.getValue(defaultRational, ExifCommon::kTagId::kGPSImgDirection); m_GPSDestBearingRef = m_ExifReader.getValue(emptyStr, ExifCommon::kTagId::kGPSDestBearingRef); m_GPSDestBearing = m_ExifReader.getValue(defaultRational, ExifCommon::kTagId::kGPSDestBearing); } read_icc_profile(&m_JPEGDecompress, &m_RawColorProfile, &m_RawColorProfileSize); return true; } bool ImageReaderJPEG::beginReadInternal(unsigned int destWidth, unsigned int destHeight, EImageColorModel destColorModel) { J_COLOR_SPACE outputColorSpace = destColorModel == kColorModel_YUV_420 ? JCS_YCbCr : (HAVE_RGBX ? JCS_EXT_RGBX : JCS_RGB); #if IMAGECORE_WITH_LCMS if( m_RawColorProfileSize > 0 && (m_ReadOptions & kReadOption_ApplyColorProfile) != 0 && Image::colorModelIsRGBA(destColorModel) ) { m_ColorProfile = cmsOpenProfileFromMem(m_RawColorProfile, m_RawColorProfileSize); m_IgnoreColorProfile = true; if( m_ColorProfile != NULL ) { // Ignore mismatched color profiles. if( m_JPEGDecompress.jpeg_color_space != JCS_GRAYSCALE || cmsGetColorSpace(m_ColorProfile) == cmsSigGrayData ) { m_sRGBProfile = cmsCreate_sRGBProfile(); if( m_sRGBProfile == NULL) { fprintf(stderr, "error: unable to allocate sRGB profile\n"); return false; } unsigned int inputFormat = getLCMSInputFormat(m_JPEGDecompress.jpeg_color_space, m_JPEGDecompress.saw_Adobe_marker); m_ColorTransform = cmsCreateTransform(m_ColorProfile, inputFormat, m_sRGBProfile, HAVE_RGBX ? TYPE_RGBA_8 : TYPE_RGB_8, INTENT_PERCEPTUAL, 0); if( m_ColorTransform == NULL ) { fprintf(stderr, "error: unable to create ICC transform\n"); return false; } if( m_JPEGDecompress.jpeg_color_space == JCS_GRAYSCALE ) { outputColorSpace = JCS_GRAYSCALE; } else if( m_JPEGDecompress.jpeg_color_space == JCS_YCCK || m_JPEGDecompress.jpeg_color_space == JCS_CMYK ) { outputColorSpace = JCS_CMYK; } m_IgnoreColorProfile = false; } } } #endif m_JPEGDecompress.out_color_space = outputColorSpace; m_JPEGDecompress.scale_num = 1; m_JPEGDecompress.scale_denom = (unsigned int)roundf((float)m_Width / (float)destWidth); // This is the default. m_JPEGDecompress.dct_method = JDCT_ISLOW; if( (m_ReadOptions & kReadOption_DecompressQualityFast) != 0) { m_JPEGDecompress.do_fancy_upsampling = FALSE; m_JPEGDecompress.dct_method = JDCT_FASTEST; m_JPEGDecompress.do_block_smoothing = FALSE; } if( destColorModel == kColorModel_YUV_420 ) { m_JPEGDecompress.raw_data_out = TRUE; } if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error reading JPEG data: %s\n", s_JPEGLastError); jpeg_abort_decompress(&m_JPEGDecompress); return false; } jpeg_start_decompress(&m_JPEGDecompress); if( destColorModel == kColorModel_YUV_420 ) { // Make sure libjpeg doesn't try to upsample the UV components, we need them at half the resolution of Y. // Recompute all of the scaling parameters jpeg_start_decompress configured. for (int i = 1; i < 3; i++) { m_JPEGDecompress.comp_info[i]._DCT_h_scaled_size = m_JPEGDecompress.comp_info[0]._DCT_h_scaled_size; m_JPEGDecompress.comp_info[i]._DCT_v_scaled_size = m_JPEGDecompress.comp_info[0]._DCT_v_scaled_size; m_JPEGDecompress.comp_info[i].MCU_sample_width = m_JPEGDecompress.comp_info[0].MCU_sample_width / 2; m_JPEGDecompress.comp_info[i].downsampled_width = m_JPEGDecompress.comp_info[i].downsampled_width / 2; m_JPEGDecompress.comp_info[i].downsampled_height = m_JPEGDecompress.comp_info[i].downsampled_height / 2; } // Re-trigger the dct method and table calculations. int oldState = m_JPEGDecompress.global_state; m_JPEGDecompress.global_state = 207; jpeg_start_output(&m_JPEGDecompress, 1); m_JPEGDecompress.global_state = oldState; } if( m_JPEGDecompress.output_width != destWidth || m_JPEGDecompress.output_height != destHeight ) { fprintf(stderr, "JPEG scaled size mismatch"); jpeg_abort_decompress(&m_JPEGDecompress); return false; } return true; } bool ImageReaderJPEG::beginRead(unsigned int outputWidth, unsigned int outputHeight, EImageColorModel outputColorModel) { return beginReadInternal(outputWidth, outputHeight, outputColorModel); } bool ImageReaderJPEG::postProcessScanlines(uint8_t* buf, unsigned int size) { #if IMAGECORE_WITH_LCMS // Grayscale images can't operate on the same buffer, because of the different component count uint8_t* dest; if ( m_JPEGDecompress.jpeg_color_space == JCS_GRAYSCALE ) { dest = (uint8_t*)malloc(size); if( dest == NULL ) { fprintf(stderr, "error: out of memory applying color profile"); return false; } } else { dest = buf; } cmsDoTransform(m_ColorTransform, buf, dest, size / 4); if( m_JPEGDecompress.jpeg_color_space == JCS_GRAYSCALE ) { memcpy(buf, dest, size); free(dest); } return true; #else return false; #endif } unsigned int ImageReaderJPEG::readRows(Image* dest, unsigned int destRow, unsigned int numRows) { bool failed = true; if( Image::colorModelIsRGBA(dest->getColorModel()) ) { ImageRGBA* destImage = dest->asRGBA(); unsigned int destHeight = destImage->getHeight(); unsigned int destPitch = 0; SECURE_ASSERT(destRow + numRows <= destHeight); uint8_t* destBuffer = destImage->lockRect(0, destRow, destImage->getWidth(), numRows, destPitch); // Prepare row pointers, not essential, but it makes the following read loop simpler. uint8_t** rows = (uint8_t**)malloc(numRows * sizeof(uint8_t*)); if( rows == NULL ) { fprintf(stderr, "error: allocation failed\n"); jpeg_abort_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); return 0; } if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error reading JPEG data: %s\n", s_JPEGLastError); free(rows); jpeg_abort_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); return 0; } #if !HAVE_RGBX unsigned int destWidth = destImage->getWidth(); uint8_t* jpegBuffer = (uint8_t*)malloc(destWidth * numRows * 3); if( jpegBuffer == NULL ) { free(rows); jpeg_abort_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); return 0; } unsigned int jpegPitch = destWidth * 3; #else uint8_t* jpegBuffer = destBuffer; unsigned int jpegPitch = destPitch; #endif for( unsigned int y = 0; y < numRows; y++ ) { rows[y] = jpegBuffer + jpegPitch * y; } unsigned int currentRow = 0; bool shouldProcessScanlines = false; #if IMAGECORE_WITH_LCMS shouldProcessScanlines = (m_ReadOptions & kReadOption_ApplyColorProfile) != 0 && !m_IgnoreColorProfile && m_ColorProfile != NULL && m_ColorTransform != NULL; #endif if( shouldProcessScanlines ) { while( currentRow < numRows ) { unsigned int numRowsRead = jpeg_read_scanlines(&m_JPEGDecompress, rows + currentRow, numRows - currentRow); if( !postProcessScanlines(destBuffer + (currentRow * destPitch), numRowsRead * destPitch) ) { break; } currentRow += numRowsRead; m_TotalRowsRead++; } } else { while( currentRow < numRows ) { unsigned int numRowsRead = jpeg_read_scanlines(&m_JPEGDecompress, rows + currentRow, numRows - currentRow); currentRow += numRowsRead; m_TotalRowsRead++; } } #if !HAVE_RGBX // Provided purely for compatibility for vanilla libjpeg, not recommended. for (unsigned int y = 0; y < numRows; y++) { for (unsigned int x = 0; x < destWidth; x++) { uint8_t* in = jpegBuffer + y * jpegPitch + x * 3; uint8_t* out = destBuffer + y * destPitch + x * 4; out[0] = in[0]; out[1] = in[1]; out[2] = in[2]; } } free(jpegBuffer); #endif failed = false; destImage->unlockRect(); free(rows); } else if ( Image::colorModelIsYUV(dest->getColorModel()) ) { // jpeg_read_raw_data requires you to handle the edge padding data inserted to align // the image to the DCT block sizes. Make sure the destination image has enough space in each // row beyond the normal image data. The bottom edge is handled by reading into junk buffers // and discarding the information. // TODO: Relax this by reading into temporary buffers, and copying. ImageYUV* destImage = dest->asYUV(); EYUVRange desiredRange = destImage->getRange(); unsigned int destPadding = destImage->getPadding(); unsigned int dctAlignedPitchY = align(destImage->getPlaneY()->getWidth(), DCTSIZE * 2); unsigned int dctAlignedPitchU = align(destImage->getPlaneU()->getWidth(), DCTSIZE * 2); unsigned int dctAlignedPitchV = align(destImage->getPlaneV()->getWidth(), DCTSIZE * 2); unsigned int heightY = destImage->getPlaneY()->getHeight(); unsigned int heightUV = destImage->getPlaneU()->getHeight(); SECURE_ASSERT(destImage->getPlaneY()->getPitch() >= dctAlignedPitchY); SECURE_ASSERT(destImage->getPlaneU()->getPitch() >= dctAlignedPitchU); SECURE_ASSERT(destImage->getPlaneV()->getPitch() >= dctAlignedPitchV); ImagePlane8* planeY = destImage->getPlaneY(); ImagePlane8* planeU = destImage->getPlaneU(); ImagePlane8* planeV = destImage->getPlaneV(); unsigned int pitchY = 0; uint8_t* bufferY = planeY->lockRect(0, destRow, planeY->getWidth(), numRows, pitchY); unsigned int pitchU = 0; uint8_t* bufferU = planeU->lockRect(0, destRow / 2, planeU->getWidth(), div2_round(numRows), pitchU); unsigned int pitchV = 0; uint8_t* bufferV = planeV->lockRect(0, destRow / 2, planeV->getWidth(), div2_round(numRows), pitchV); unsigned int currentRowY = 0; unsigned int currentRowUV = 0; constexpr unsigned int rowStepY = DCTSIZE * 2; constexpr unsigned int rowStepUV = DCTSIZE; if (destPadding < 16) { // If the image has insufficient padding to use to write the unused edge padding, allocate space for it. uint8_t* bottomEdgeJunkY = (uint8_t*)malloc(dctAlignedPitchY); uint8_t* bottomEdgeJunkU = (uint8_t*)malloc(dctAlignedPitchU); uint8_t* bottomEdgeJunkV = (uint8_t*)malloc(dctAlignedPitchV); if( bottomEdgeJunkY != NULL && bottomEdgeJunkU != NULL && bottomEdgeJunkV != NULL ) { while( currentRowY < numRows ) { JSAMPROW rowsY[rowStepY]; JSAMPROW rowsU[rowStepY]; JSAMPROW rowsV[rowStepY]; for( unsigned int y = 0; y < rowStepY; y++ ) { unsigned int destY = y + currentRowY; if( destY < heightY ) { rowsY[y] = bufferY + pitchY * destY; } else { rowsY[y] = bottomEdgeJunkY; } } for( unsigned int y = 0; y < rowStepUV; y++ ) { unsigned int destUV = y + currentRowUV; if( destUV < heightUV ) { rowsU[y] = bufferU + pitchU * destUV; rowsV[y] = bufferV + pitchV * destUV; } else { rowsU[y] = bottomEdgeJunkU; rowsV[y] = bottomEdgeJunkV; } } JSAMPARRAY rows[] = { rowsY, rowsU, rowsV }; unsigned int numRowsRead = jpeg_read_raw_data(&m_JPEGDecompress, rows, rowStepY); currentRowY += numRowsRead; currentRowUV += numRowsRead / 2; m_TotalRowsRead += numRowsRead; } failed = false; } free(bottomEdgeJunkY); free(bottomEdgeJunkU); free(bottomEdgeJunkV); } else { // Enough space to read the raw image. while( currentRowY < numRows ) { JSAMPROW rowsY[rowStepY]; JSAMPROW rowsU[rowStepY]; JSAMPROW rowsV[rowStepY]; for( unsigned int y = 0; y < rowStepY; y++ ) { rowsY[y] = bufferY + pitchY * (y + currentRowY); } for( unsigned int y = 0; y < rowStepUV; y++ ) { rowsU[y] = bufferU + pitchU * (y + currentRowUV); rowsV[y] = bufferV + pitchV * (y + currentRowUV); } JSAMPARRAY rows[] = { rowsY, rowsU, rowsV }; unsigned int numRowsRead = jpeg_read_raw_data(&m_JPEGDecompress, rows, rowStepY); currentRowY += numRowsRead; currentRowUV += numRowsRead / 2; m_TotalRowsRead += numRowsRead; } failed = false; } destImage->setRange(kYUVRange_Full); if( desiredRange == kYUVRange_Compressed ) { destImage->compressRange(destImage); } } if (failed) { jpeg_abort_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); return 0; } return numRows; } bool ImageReaderJPEG::endRead() { if( setjmp(m_JPEGError.jmp) ) { return false; } if( m_TotalRowsRead < m_JPEGDecompress.output_height ) { jpeg_abort_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); } else { jpeg_finish_decompress(&m_JPEGDecompress); jpeg_destroy_decompress(&m_JPEGDecompress); } return true; } bool ImageReaderJPEG::readImage(Image* destImage) { unsigned int destWidth = destImage->getWidth(); unsigned int destHeight = destImage->getHeight(); if( !beginReadInternal(destWidth, destHeight, destImage->getColorModel()) ) { return false; } if( destWidth != m_JPEGDecompress.output_width || destHeight != m_JPEGDecompress.output_height ) { fprintf(stderr, "error: unable to scale jpeg to desired dimensions\n"); return false; } if( readRows(destImage, 0, destHeight) != destHeight ) { return false; } endRead(); return true; } void ImageReaderJPEG::computeReadDimensions(unsigned int desiredWidth, unsigned int desiredHeight, unsigned int& readWidth, unsigned int& readHeight) { readWidth = m_Width; readHeight = m_Height; unsigned int reduceCount = 0; while( div2_round(readWidth) >= desiredWidth && div2_round(readHeight) >= desiredHeight && reduceCount < 3 ) { readWidth = div2_round(readWidth); readHeight = div2_round(readHeight); reduceCount++; } } EImageFormat ImageReaderJPEG::getFormat() { return kImageFormat_JPEG; } const char* ImageReaderJPEG::getFormatName() { return "JPEG"; } unsigned int ImageReaderJPEG::getWidth() { return m_Width; } unsigned int ImageReaderJPEG::getHeight() { return m_Height; } EImageOrientation ImageReaderJPEG::getOrientation() { return m_Orientation; } void ImageReaderJPEG::storeGeoTagData(ExifWriter& exifWriter) const { exifWriter.putValue(m_GPSLatitudeRef, ExifCommon::kTagId::kGPSLatitudeRef); exifWriter.putValue(m_GPSLatitude, ExifCommon::kTagId::kGPSLatitude); exifWriter.putValue(m_GPSLongitudeRef, ExifCommon::kTagId::kGPSLongitudeRef); exifWriter.putValue(m_GPSLongitude, ExifCommon::kTagId::kGPSLongitude); exifWriter.putValue((uint8_t)m_AltitudeRef, ExifCommon::kTagId::kGPSAltitudeRef); exifWriter.putValue(m_GPSAltitude, ExifCommon::kTagId::kGPSAltitude); exifWriter.putValue(m_GPSTimeStamp, ExifCommon::kTagId::kGPSTimeStamp); exifWriter.putValue(m_GPSSpeedRef, ExifCommon::kTagId::kGPSSpeedRef); exifWriter.putValue(m_GPSSpeed, ExifCommon::kTagId::kGPSSpeed); exifWriter.putValue(m_GPSImgDirectionRef, ExifCommon::kTagId::kGPSImgDirectionRef); exifWriter.putValue(m_GPSImgDirection, ExifCommon::kTagId::kGPSImgDirection); exifWriter.putValue(m_GPSDestBearingRef, ExifCommon::kTagId::kGPSDestBearingRef); exifWriter.putValue(m_GPSDestBearing, ExifCommon::kTagId::kGPSDestBearing); } bool ImageReaderJPEG::hasValidGeoTagData() const { return (m_GPSLatitude.m_value[0].getInt() != 0) || (m_GPSLatitude.m_value[1].getInt() != 0) || (m_GPSLatitude.m_value[2].getInt() != 0); } void ImageReaderJPEG::setReadOptions(unsigned int readOptions) { m_ReadOptions = readOptions; } uint8_t* ImageReaderJPEG::getEXIFData(unsigned int& size) { size = m_EXIFDataSize; return m_EXIFData; } EImageColorModel ImageReaderJPEG::getNativeColorModel() { return m_NativeColorModel; } bool ImageReaderJPEG::supportsOutputColorModel(EImageColorModel colorSpace) { return Image::colorModelIsRGBA(colorSpace) || colorSpace == m_NativeColorModel; } uint8_t* ImageReaderJPEG::getColorProfile(unsigned int& size) { size = m_RawColorProfileSize; return m_RawColorProfile; } ImageReaderJPEG::SourceManager::SourceManager(ImageReader::Storage* storage, ImageReaderJPEG* reader) : storage(storage) , reader(reader) , startOfFile(false) { // jpeg_source_mgr this->init_source = initSource; this->fill_input_buffer = fillInputBuffer; this->skip_input_data = skipInputData; this->resync_to_restart = jpeg_resync_to_restart; this->term_source = term_source; this->bytes_in_buffer = 0; this->next_input_byte = NULL; } void ImageReaderJPEG::SourceManager::initSource(j_decompress_ptr cinfo) { ImageReaderJPEG::SourceManager* self = (ImageReaderJPEG::SourceManager*)cinfo->src; self->startOfFile = true; } boolean ImageReaderJPEG::SourceManager::fillInputBuffer(j_decompress_ptr cinfo) { ImageReaderJPEG::SourceManager* self = (ImageReaderJPEG::SourceManager*)cinfo->src; size_t nbytes = (size_t)self->storage->read(self->buffer, kReadBufferSize); if (nbytes <= 0) { if (self->startOfFile) { ERREXIT(cinfo, JERR_INPUT_EMPTY); } WARNMS(cinfo, JWRN_JPEG_EOF); // Insert a fake EOI marker self->buffer[0] = (JOCTET)0xFF; self->buffer[1] = (JOCTET)JPEG_EOI; nbytes = 2; } self->next_input_byte = self->buffer; self->bytes_in_buffer = nbytes; self->startOfFile = false; return TRUE; } void ImageReaderJPEG::SourceManager::skipInputData(j_decompress_ptr cinfo, long numBytes) { ImageReaderJPEG::SourceManager* self = (ImageReaderJPEG::SourceManager*)cinfo->src; if (numBytes > 0) { while( numBytes > (long)self->bytes_in_buffer ) { numBytes -= (long)self->bytes_in_buffer; fillInputBuffer(cinfo); } self->next_input_byte += (size_t)numBytes; self->bytes_in_buffer -= (size_t)numBytes; } } void ImageReaderJPEG::SourceManager::termSource(j_decompress_ptr cinfo) { } ////// bool ImageWriterJPEG::Factory::matchesExtension(const char *extension) { return strcasecmp(extension, "jpg") == 0 || strcasecmp(extension, "jpeg") == 0; } bool ImageWriterJPEG::Factory::appropriateForInputFormat(EImageFormat inputFormat) { return inputFormat == kImageFormat_JPEG; } bool ImageWriterJPEG::Factory::supportsInputColorModel(EImageColorModel colorModel) { #if JCS_EXTENSIONS return Image::colorModelIsRGBA(colorModel) || Image::colorModelIsYUV(colorModel); #else return Image::colorModelIsRGBA(colorModel); #endif } EImageFormat ImageWriterJPEG::Factory::getFormat() { return kImageFormat_JPEG; } ImageWriterJPEG::ImageWriterJPEG() : m_WriteError(false) , m_WriteOptions(kWriteOption_CopyColorProfile) , m_Quality(75) , m_SourceReader(NULL) , m_DestinationManager(NULL) , m_QuantTables(NULL) { } ImageWriterJPEG::~ImageWriterJPEG() { delete m_DestinationManager; m_DestinationManager = NULL; delete m_QuantTables; m_QuantTables = NULL; } bool ImageWriterJPEG::initWithStorage(Storage* output) { // Set up custom error handler. m_JPEGCompress.client_data = this; m_JPEGCompress.err = jpeg_std_error(&m_JPEGError.pub); m_JPEGError.pub.error_exit = jpegError; m_JPEGError.pub.output_message = jpegMessage; if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg compress init: %s", s_JPEGLastError); jpeg_destroy_compress(&m_JPEGCompress); return false; } jpeg_create_compress(&m_JPEGCompress); m_DestinationManager = new DestinationManager(output, this); if( m_DestinationManager == NULL) { fprintf(stderr, "error: could not create destination manager\n"); jpeg_destroy_compress(&m_JPEGCompress); return false; } m_JPEGCompress.dest = m_DestinationManager; return true; } void ImageWriterJPEG::setSourceReader(ImageReader* sourceReader) { if( sourceReader != NULL && sourceReader->getFormat() == kImageFormat_JPEG ) { m_SourceReader = (ImageReaderJPEG*)sourceReader; } } void ImageWriterJPEG::setWriteOptions(unsigned int options) { m_WriteOptions = options; } void ImageWriterJPEG::setQuality(unsigned int quality) { m_Quality = quality; } void ImageWriterJPEG::setCopyMetaData(bool copyMetaData) { m_CopyMetaData = copyMetaData; } bool ImageWriterJPEG::beginWrite(unsigned int width, unsigned int height, EImageColorModel colorModel) { if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg compress init: %s", s_JPEGLastError); jpeg_destroy_compress(&m_JPEGCompress); return false; } m_JPEGCompress.image_width = width; m_JPEGCompress.image_height = height; if( Image::colorModelIsRGBA(colorModel) ) { m_JPEGCompress.input_components = HAVE_RGBX ? 4 : 3; m_JPEGCompress.in_color_space = HAVE_RGBX ? JCS_EXT_RGBX : JCS_RGB; } else if( Image::colorModelIsYUV(colorModel) ) { m_JPEGCompress.input_components = 3; m_JPEGCompress.in_color_space = JCS_YCbCr; } else { return false; } jpeg_set_defaults(&m_JPEGCompress); jpeg_set_quality(&m_JPEGCompress, m_Quality, TRUE); jpeg_set_colorspace(&m_JPEGCompress, JCS_YCbCr); if( m_QuantTables != NULL ) { jpeg_add_quant_table(&m_JPEGCompress, 0, m_QuantTables, 100, TRUE); jpeg_add_quant_table(&m_JPEGCompress, 1, m_QuantTables + DCTSIZE2, 100, TRUE); } if( (m_WriteOptions & kWriteOption_QualityFast) == 0 ) { // Compressing takes about 50% longer with this on, but produces files a few percent smaller. m_JPEGCompress.optimize_coding = TRUE; } // This makes more of a difference than the documentation suggests, and it's not really slower (faster in some tests). m_JPEGCompress.dct_method = JDCT_ISLOW; if( (m_WriteOptions & kWriteOption_Progressive) != 0 ) { jpeg_simple_progression(&m_JPEGCompress); } m_JPEGCompress.comp_info[1].h_samp_factor = 1; m_JPEGCompress.comp_info[1].h_samp_factor = 1; m_JPEGCompress.comp_info[2].v_samp_factor = 1; m_JPEGCompress.comp_info[2].v_samp_factor = 1; if( Image::colorModelIsRGBA(colorModel) ) { if( m_Quality == 100 ) { m_JPEGCompress.comp_info[0].h_samp_factor = 1; m_JPEGCompress.comp_info[0].v_samp_factor = 1; } else if( m_Quality > 95 ) { m_JPEGCompress.comp_info[0].h_samp_factor = 2; m_JPEGCompress.comp_info[0].v_samp_factor = 1; } else { m_JPEGCompress.comp_info[0].h_samp_factor = 2; m_JPEGCompress.comp_info[0].v_samp_factor = 2; } } else if( colorModel == kColorModel_YUV_420 ) { m_JPEGCompress.raw_data_in = TRUE; m_JPEGCompress.comp_info[0].h_samp_factor = 2; m_JPEGCompress.comp_info[0].v_samp_factor = 2; } else { SECURE_ASSERT(0); } jpeg_start_compress(&m_JPEGCompress, TRUE); if( !writeMarkers() ) { jpeg_destroy_compress(&m_JPEGCompress); return false; } return true; } bool ImageWriterJPEG::writeMarkers() { bool didWriteColorProfile = false; if( (m_WriteOptions & kWriteOption_CopyColorProfile) != 0 && m_SourceReader != NULL ) { unsigned int colorProfileSize = 0; uint8_t* colorProfile = m_SourceReader->getColorProfile(colorProfileSize); if( colorProfile != NULL && colorProfileSize > 0 ) { write_icc_profile(&m_JPEGCompress, colorProfile, colorProfileSize); didWriteColorProfile = true; } } if( (m_WriteOptions & kWriteOption_WriteDefaultColorProfile) != 0 && !didWriteColorProfile) { #if IMAGECORE_WITH_LCMS cmsHPROFILE sRGBProfile = cmsCreate_sRGBProfile(); if( !sRGBProfile ) { return false; } // First call with a null output buffer to determine output size unsigned int outputSize = 0; if( cmsSaveProfileToMem(sRGBProfile, NULL, &outputSize) ) { if( outputSize > 0 ) { // Next allocate the bytes for the profile and write into it cmsUInt8Number* buffer = (cmsUInt8Number*)malloc(outputSize + 1); if( buffer != NULL ) { if( cmsSaveProfileToMem(sRGBProfile, buffer, &outputSize) ) { write_icc_profile(&m_JPEGCompress, buffer, outputSize); didWriteColorProfile = true; } free(buffer); } } } cmsCloseProfile(sRGBProfile); #endif } if( (m_WriteOptions & kWriteOption_CopyMetaData) != 0 && m_SourceReader != NULL ) { unsigned int exifDataSize = 0; uint8_t* exifData = m_SourceReader->getEXIFData(exifDataSize); if( exifData != NULL && exifDataSize > 0 ) { jpeg_write_marker(&m_JPEGCompress, EXIF_MARKER, exifData, exifDataSize); } } else { ExifWriter exifWriter(true); if ((m_WriteOptions & kWriteOption_WriteExifOrientation) != 0 && m_SourceReader != NULL && m_SourceReader->getOrientation() != kImageOrientation_Up) { exifWriter.putValue((uint16_t)m_SourceReader->getOrientation(), ExifCommon::kTagId::kOrientation); } if ((m_WriteOptions & kWriteOption_GeoTagData) != 0 && m_SourceReader != NULL && m_SourceReader->hasValidGeoTagData()) { exifWriter.putValue((uint16_t)m_SourceReader->getOrientation(), ExifCommon::kTagId::kOrientation); m_SourceReader->storeGeoTagData(exifWriter); } if(!exifWriter.isEmpty()) { // if anything got written to EXIF section, flush it to the jpeg writer uint8_t* exifData = new uint8_t[64 * 1024]; MemoryStreamWriter exifStream(exifData, 64 * 1024, true); exifWriter.WriteToStream(exifStream); jpeg_write_marker(&m_JPEGCompress, EXIF_MARKER, exifStream.getData(), exifStream.getSize()); delete [] exifData; } } return true; } unsigned int ImageWriterJPEG::writeRows(Image* source, unsigned int sourceRow, unsigned int numRows) { if( Image::colorModelIsRGBA(source->getColorModel()) ) { ImageRGBA* sourceImage = source->asRGBA(); uint8_t** rows = (uint8_t**)malloc(numRows * sizeof(uint8_t*)); if( rows == NULL ) { return 0; } if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg compress: %s", s_JPEGLastError); jpeg_destroy_compress(&m_JPEGCompress); free(rows); return 0; } unsigned int sourceHeight = sourceImage->getHeight(); unsigned int sourcePitch = sourceImage->getPitch(); uint8_t* sourceBuffer = sourceImage->lockRect(0, sourceRow, sourceImage->getWidth(), numRows, sourcePitch); SECURE_ASSERT(sourceRow + numRows <= sourceHeight); #if !HAVE_RGBX unsigned int sourceWidth = sourceImage->getWidth(); // Provided purely for compatibility for vanilla libjpeg, not recommended. uint8_t* jpegBuffer = (uint8_t*)malloc(sourceWidth * numRows * 3); if( jpegBuffer == NULL ) { free(rows); jpeg_destroy_compress(&m_JPEGCompress); return 0; } unsigned int jpegPitch = sourceWidth * 3; for (unsigned int y = 0; y < numRows; y++) { for (unsigned int x = 0; x < sourceWidth; x++) { uint8_t* out = jpegBuffer + y * jpegPitch + x * 3; uint8_t* in = sourceBuffer + y * sourcePitch + x * 4; out[0] = in[0]; out[1] = in[1]; out[2] = in[2]; } } #else uint8_t* jpegBuffer = sourceBuffer; unsigned int jpegPitch = sourcePitch; #endif for( unsigned int i = 0; i < numRows; i++ ) { rows[i] = jpegBuffer + i * jpegPitch; } unsigned int currentRow = 0; while( currentRow < numRows ) { unsigned int numRowsWritten = jpeg_write_scanlines(&m_JPEGCompress, rows + currentRow, numRows - currentRow); if( numRowsWritten == 0 ) { break; } currentRow += numRowsWritten; } free(rows); #if !HAVE_RGBX free(jpegBuffer); #endif return numRows; } else if( Image::colorModelIsYUV(source->getColorModel()) ) { // TODO: Incremental writing ASSERT(sourceRow == 0); ASSERT(numRows == m_JPEGCompress.image_height); if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg compress: %s", s_JPEGLastError); jpeg_destroy_compress(&m_JPEGCompress); return 0; } ImageYUV* yuvImage = source->asYUV(); unsigned int minPitchY = align(DCTSIZE * 2, yuvImage->getPlaneY()->getWidth()); unsigned int minPitchUV = align(DCTSIZE, yuvImage->getPlaneU()->getWidth()); ImageYUV* sourceImage = yuvImage; ImageYUV* tempImage = NULL; // Image doesn't meet padding or range requirements, copy to a new buffer and correct. if( yuvImage->getRange() == kYUVRange_Compressed || (sourceImage->getPadding() < 16 && (m_WriteOptions & kWriteOption_AssumeMCUPaddingFilled) == 0) || yuvImage->getPlaneY()->getPitch() < minPitchY || yuvImage->getPlaneU()->getPitch() < minPitchUV ) { tempImage = ImageYUV::create(yuvImage->getWidth(), yuvImage->getHeight(), 16, 16); if( tempImage == NULL ) { return 0; } if( yuvImage->getRange() == kYUVRange_Compressed ) { yuvImage->expandRange(tempImage); } else { yuvImage->copy(tempImage); } sourceImage = tempImage; // Fill padding region of new image. sourceImage->getPlaneY()->fillPadding(kEdge_Right); sourceImage->getPlaneU()->fillPadding(kEdge_Right); sourceImage->getPlaneV()->fillPadding(kEdge_Right); } else if( (m_WriteOptions & kWriteOption_AssumeMCUPaddingFilled) == 0 ) { // Image was large enough and in the correct range, just fill the padding (unless it's been requested that we don't). // Use the existing padding region to satisfy jpeg_write_raw_data's requirement that we write // data aligned to the block size. Fill the edge padding data with data from the image (clamped) // to prevent any problems with the DCT. sourceImage->getPlaneY()->fillPadding(kEdge_Right); sourceImage->getPlaneU()->fillPadding(kEdge_Right); sourceImage->getPlaneV()->fillPadding(kEdge_Right); } SECURE_ASSERT(sourceImage->getPlaneY()->getPitch() >= minPitchY); SECURE_ASSERT(sourceImage->getPlaneU()->getPitch() >= minPitchUV); SECURE_ASSERT(sourceImage->getPlaneV()->getPitch() >= minPitchUV); ImagePlane8* planeY = sourceImage->getPlaneY(); ImagePlane8* planeU = sourceImage->getPlaneU(); ImagePlane8* planeV = sourceImage->getPlaneV(); uint8_t* bufferY = (uint8_t*)planeY->getBytes(); uint8_t* bufferU = (uint8_t*)planeU->getBytes(); uint8_t* bufferV = (uint8_t*)planeV->getBytes(); unsigned int pitchY = planeY->getPitch(); unsigned int pitchU = planeU->getPitch(); unsigned int pitchV = planeV->getPitch(); unsigned int currentRowY = 0; unsigned int currentRowUV = 0; constexpr unsigned int rowStepY = DCTSIZE * 2; constexpr unsigned int rowStepUV = DCTSIZE; while( currentRowY < numRows ) { JSAMPROW rowsY[rowStepY]; JSAMPROW rowsU[rowStepY]; JSAMPROW rowsV[rowStepY]; for( unsigned int y = 0; y < rowStepY; y++ ) { rowsY[y] = bufferY + pitchY * clamp(0, planeY->getHeight() - 1, y + currentRowY); } for( unsigned int y = 0; y < rowStepUV; y++ ) { rowsU[y] = bufferU + pitchU * clamp(0, planeU->getHeight() - 1, y + currentRowUV); rowsV[y] = bufferV + pitchV * clamp(0, planeV->getHeight() - 1, y + currentRowUV); } JSAMPARRAY rows[] = { rowsY, rowsU, rowsV }; unsigned int numRowsWritten = jpeg_write_raw_data(&m_JPEGCompress, rows, rowStepY); currentRowY += numRowsWritten; currentRowUV += numRowsWritten / 2; } delete tempImage; return numRows; } else { return 0; } } bool ImageWriterJPEG::endWrite() { if( setjmp(m_JPEGError.jmp) ) { return false; } jpeg_finish_compress(&m_JPEGCompress); jpeg_destroy_compress(&m_JPEGCompress); if( m_WriteError ) { return false; } return true; } void ImageWriterJPEG::setWriteError() { m_WriteError = true; } bool ImageWriterJPEG::writeImage(Image* sourceImage) { unsigned int sourceWidth = sourceImage->getWidth(); unsigned int sourceHeight = sourceImage->getHeight(); if( !beginWrite(sourceWidth, sourceHeight, sourceImage->getColorModel()) ) { return false; } if( writeRows(sourceImage, 0, sourceHeight) != sourceHeight ) { return false; } if( !endWrite() ) { return false; } return true; } bool ImageWriterJPEG::copyLossless(ImageReader* reader) { #if IMAGECORE_WITH_JPEG_TRANSFORMS if( setjmp(m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg lossless copy: %s", s_JPEGLastError); jpeg_destroy_compress(&m_JPEGCompress); return false; } if( reader->getFormat() != kImageFormat_JPEG ) { jpeg_destroy_compress(&m_JPEGCompress); return false; } ImageReaderJPEG* jpegReader = (ImageReaderJPEG*)reader; setSourceReader(reader); if( setjmp(jpegReader->m_JPEGError.jmp) ) { fprintf(stderr, "error during jpeg lossless copy: %s", s_JPEGLastError); jpeg_destroy_compress(&m_JPEGCompress); return false; } jpeg_transform_info transform; memset(&transform, 0, sizeof(jpeg_transform_info)); if( (m_WriteOptions & kWriteOption_LosslessPerfect) != 0 ) { transform.perfect = true; } else{ // Trim edge blocks, otherwise the garbage padding data will be visible. transform.trim = true; } bool haveTransform = false; EImageOrientation orientation = reader->getOrientation(); // If we're preserving the EXIF orientation tag, we can skip this. bool skipRotate = (m_WriteOptions & kWriteOption_WriteExifOrientation) != 0 || (m_WriteOptions & kWriteOption_CopyMetaData) != 0; if( !skipRotate && (orientation == kImageOrientation_Down || orientation == kImageOrientation_Left || orientation == kImageOrientation_Right) ) { switch( orientation ) { case kImageOrientation_Down: transform.transform = JXFORM_ROT_180; break; case kImageOrientation_Left: transform.transform = JXFORM_ROT_90; break; case kImageOrientation_Right: transform.transform = JXFORM_ROT_270; break; case kImageOrientation_Up: break; } haveTransform = true; } if( !jtransform_request_workspace(&jpegReader->m_JPEGDecompress, &transform) ) { // This will fail if perfect is specified, but the rotation results in lost edge blocks. jpeg_destroy_compress(&m_JPEGCompress); return false; } jpeg_copy_critical_parameters(&jpegReader->m_JPEGDecompress, &m_JPEGCompress); if( (m_WriteOptions & kWriteOption_Progressive) != 0 ) { jpeg_simple_progression(&m_JPEGCompress); } // Always re-optimize the huffman table for the new JPEG. m_JPEGCompress.optimize_coding = true; jvirt_barray_ptr* sourceCoeffs = jpeg_read_coefficients(&jpegReader->m_JPEGDecompress); jvirt_barray_ptr* destCoeffs = sourceCoeffs; if( haveTransform ) { destCoeffs = jtransform_adjust_parameters(&jpegReader->m_JPEGDecompress, &m_JPEGCompress, sourceCoeffs, &transform); } jpeg_write_coefficients(&m_JPEGCompress, destCoeffs); if( haveTransform ) { jtransform_execute_transformation(&jpegReader->m_JPEGDecompress, &m_JPEGCompress, sourceCoeffs, &transform); } if( !writeMarkers() ) { jpeg_destroy_compress(&m_JPEGCompress); return false; } if( !endWrite() ) { jpeg_destroy_compress(&m_JPEGCompress); return false; } return true; #else return false; #endif } void ImageWriterJPEG::setQuantizationTables(uint32_t* table) { if( m_QuantTables == NULL ) { m_QuantTables = (uint32_t*)malloc(sizeof(uint32_t) * DCTSIZE2 * 2); } memcpy(m_QuantTables, table, sizeof(uint32_t) * DCTSIZE2 * 2); } ImageWriterJPEG::DestinationManager::DestinationManager(ImageWriter::Storage* storage, ImageWriterJPEG* writer) : storage(storage) , writer(writer) { this->init_destination = initDestination; this->empty_output_buffer = emptyOutputBuffer; this->term_destination = termDestination; } void ImageWriterJPEG::DestinationManager::initDestination(j_compress_ptr cinfo) { ImageWriterJPEG::DestinationManager* dest = (ImageWriterJPEG::DestinationManager*)cinfo->dest; dest->next_output_byte = dest->m_Buffer; dest->free_in_buffer = kWriteBufferSize; } boolean ImageWriterJPEG::DestinationManager::emptyOutputBuffer(j_compress_ptr cinfo) { ImageWriterJPEG::DestinationManager* dest = (ImageWriterJPEG::DestinationManager*)cinfo->dest; uint64_t written = dest->storage->write(dest->m_Buffer, kWriteBufferSize); if( written < kWriteBufferSize ) { return FALSE; } dest->next_output_byte = dest->m_Buffer; dest->free_in_buffer = kWriteBufferSize; return TRUE; } void ImageWriterJPEG::DestinationManager::termDestination(j_compress_ptr cinfo) { ImageWriterJPEG::DestinationManager* dest = (ImageWriterJPEG::DestinationManager*)cinfo->dest; unsigned int size = (unsigned int)(kWriteBufferSize - dest->free_in_buffer); if( size > 0 && (dest->storage->write(dest->m_Buffer, size) < size) ) { dest->writer->setWriteError(); } dest->storage->flush(); } }