michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ImageLogging.h" michael@0: #include "nsJPEGDecoder.h" michael@0: #include "Orientation.h" michael@0: #include "EXIF.h" michael@0: michael@0: #include "nsIInputStream.h" michael@0: michael@0: #include "nspr.h" michael@0: #include "nsCRT.h" michael@0: #include "gfxColor.h" michael@0: michael@0: #include "jerror.h" michael@0: michael@0: #include "gfxPlatform.h" michael@0: michael@0: extern "C" { michael@0: #include "iccjpeg.h" michael@0: } michael@0: michael@0: #if defined(IS_BIG_ENDIAN) michael@0: #define MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB JCS_EXT_XRGB michael@0: #else michael@0: #define MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB JCS_EXT_BGRX michael@0: #endif michael@0: michael@0: static void cmyk_convert_rgb(JSAMPROW row, JDIMENSION width); michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: #if defined(PR_LOGGING) michael@0: static PRLogModuleInfo * michael@0: GetJPEGLog() michael@0: { michael@0: static PRLogModuleInfo *sJPEGLog; michael@0: if (!sJPEGLog) michael@0: sJPEGLog = PR_NewLogModule("JPEGDecoder"); michael@0: return sJPEGLog; michael@0: } michael@0: michael@0: static PRLogModuleInfo * michael@0: GetJPEGDecoderAccountingLog() michael@0: { michael@0: static PRLogModuleInfo *sJPEGDecoderAccountingLog; michael@0: if (!sJPEGDecoderAccountingLog) michael@0: sJPEGDecoderAccountingLog = PR_NewLogModule("JPEGDecoderAccounting"); michael@0: return sJPEGDecoderAccountingLog; michael@0: } michael@0: #else michael@0: #define GetJPEGLog() michael@0: #define GetJPEGDecoderAccountingLog() michael@0: #endif michael@0: michael@0: static qcms_profile* michael@0: GetICCProfile(struct jpeg_decompress_struct &info) michael@0: { michael@0: JOCTET* profilebuf; michael@0: uint32_t profileLength; michael@0: qcms_profile* profile = nullptr; michael@0: michael@0: if (read_icc_profile(&info, &profilebuf, &profileLength)) { michael@0: profile = qcms_profile_from_memory(profilebuf, profileLength); michael@0: free(profilebuf); michael@0: } michael@0: michael@0: return profile; michael@0: } michael@0: michael@0: METHODDEF(void) init_source (j_decompress_ptr jd); michael@0: METHODDEF(boolean) fill_input_buffer (j_decompress_ptr jd); michael@0: METHODDEF(void) skip_input_data (j_decompress_ptr jd, long num_bytes); michael@0: METHODDEF(void) term_source (j_decompress_ptr jd); michael@0: METHODDEF(void) my_error_exit (j_common_ptr cinfo); michael@0: michael@0: /* Normal JFIF markers can't have more bytes than this. */ michael@0: #define MAX_JPEG_MARKER_LENGTH (((uint32_t)1 << 16) - 1) michael@0: michael@0: michael@0: nsJPEGDecoder::nsJPEGDecoder(RasterImage& aImage, Decoder::DecodeStyle aDecodeStyle) michael@0: : Decoder(aImage) michael@0: , mDecodeStyle(aDecodeStyle) michael@0: { michael@0: mState = JPEG_HEADER; michael@0: mReading = true; michael@0: mImageData = nullptr; michael@0: michael@0: mBytesToSkip = 0; michael@0: memset(&mInfo, 0, sizeof(jpeg_decompress_struct)); michael@0: memset(&mSourceMgr, 0, sizeof(mSourceMgr)); michael@0: mInfo.client_data = (void*)this; michael@0: michael@0: mSegment = nullptr; michael@0: mSegmentLen = 0; michael@0: michael@0: mBackBuffer = nullptr; michael@0: mBackBufferLen = mBackBufferSize = mBackBufferUnreadLen = 0; michael@0: michael@0: mInProfile = nullptr; michael@0: mTransform = nullptr; michael@0: michael@0: mCMSMode = 0; michael@0: michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("nsJPEGDecoder::nsJPEGDecoder: Creating JPEG decoder %p", michael@0: this)); michael@0: } michael@0: michael@0: nsJPEGDecoder::~nsJPEGDecoder() michael@0: { michael@0: // Step 8: Release JPEG decompression object michael@0: mInfo.src = nullptr; michael@0: jpeg_destroy_decompress(&mInfo); michael@0: michael@0: PR_FREEIF(mBackBuffer); michael@0: if (mTransform) michael@0: qcms_transform_release(mTransform); michael@0: if (mInProfile) michael@0: qcms_profile_release(mInProfile); michael@0: michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("nsJPEGDecoder::~nsJPEGDecoder: Destroying JPEG decoder %p", michael@0: this)); michael@0: } michael@0: michael@0: Telemetry::ID michael@0: nsJPEGDecoder::SpeedHistogram() michael@0: { michael@0: return Telemetry::IMAGE_DECODE_SPEED_JPEG; michael@0: } michael@0: michael@0: void michael@0: nsJPEGDecoder::InitInternal() michael@0: { michael@0: mCMSMode = gfxPlatform::GetCMSMode(); michael@0: if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0) michael@0: mCMSMode = eCMSMode_Off; michael@0: michael@0: /* We set up the normal JPEG error routines, then override error_exit. */ michael@0: mInfo.err = jpeg_std_error(&mErr.pub); michael@0: /* mInfo.err = jpeg_std_error(&mErr.pub); */ michael@0: mErr.pub.error_exit = my_error_exit; michael@0: /* Establish the setjmp return context for my_error_exit to use. */ michael@0: if (setjmp(mErr.setjmp_buffer)) { michael@0: /* If we get here, the JPEG code has signaled an error. michael@0: * We need to clean up the JPEG object, close the input file, and return. michael@0: */ michael@0: PostDecoderError(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: /* Step 1: allocate and initialize JPEG decompression object */ michael@0: jpeg_create_decompress(&mInfo); michael@0: /* Set the source manager */ michael@0: mInfo.src = &mSourceMgr; michael@0: michael@0: /* Step 2: specify data source (eg, a file) */ michael@0: michael@0: /* Setup callback functions. */ michael@0: mSourceMgr.init_source = init_source; michael@0: mSourceMgr.fill_input_buffer = fill_input_buffer; michael@0: mSourceMgr.skip_input_data = skip_input_data; michael@0: mSourceMgr.resync_to_restart = jpeg_resync_to_restart; michael@0: mSourceMgr.term_source = term_source; michael@0: michael@0: /* Record app markers for ICC data */ michael@0: for (uint32_t m = 0; m < 16; m++) michael@0: jpeg_save_markers(&mInfo, JPEG_APP0 + m, 0xFFFF); michael@0: } michael@0: michael@0: void michael@0: nsJPEGDecoder::FinishInternal() michael@0: { michael@0: /* If we're not in any sort of error case, flush the decoder. michael@0: * michael@0: * XXXbholley - It seems wrong that this should be necessary, but at the michael@0: * moment I'm just folding the contents of Flush() into Close() so that michael@0: * we can get rid of it. michael@0: * michael@0: * XXX(seth): It'd be great to get rid of this. For now, we treat this as a michael@0: * write to a synchronous decoder, which means that this must be called only michael@0: * on the main thread. (That's asserted in Decoder::Finish and michael@0: * Decoder::FinishSharedDecoder.) michael@0: */ michael@0: if ((mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) && michael@0: (mState != JPEG_ERROR) && michael@0: !IsSizeDecode()) michael@0: this->Write(nullptr, 0, DECODE_SYNC); michael@0: } michael@0: michael@0: void michael@0: nsJPEGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy) michael@0: { michael@0: mSegment = (const JOCTET *)aBuffer; michael@0: mSegmentLen = aCount; michael@0: michael@0: NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); michael@0: michael@0: /* Return here if there is a fatal error within libjpeg. */ michael@0: nsresult error_code; michael@0: // This cast to nsresult makes sense because setjmp() returns whatever we michael@0: // passed to longjmp(), which was actually an nsresult. michael@0: if ((error_code = (nsresult)setjmp(mErr.setjmp_buffer)) != NS_OK) { michael@0: if (error_code == NS_ERROR_FAILURE) { michael@0: PostDataError(); michael@0: /* Error due to corrupt stream - return NS_OK and consume silently michael@0: so that libpr0n doesn't throw away a partial image load */ michael@0: mState = JPEG_SINK_NON_JPEG_TRAILER; michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (setjmp returned NS_ERROR_FAILURE)")); michael@0: return; michael@0: } else { michael@0: /* Error due to reasons external to the stream (probably out of michael@0: memory) - let libpr0n attempt to clean up, even though michael@0: mozilla is seconds away from falling flat on its face. */ michael@0: PostDecoderError(error_code); michael@0: mState = JPEG_ERROR; michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (setjmp returned an error)")); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: PR_LOG(GetJPEGLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] nsJPEGDecoder::Write -- processing JPEG data\n", this)); michael@0: michael@0: switch (mState) { michael@0: case JPEG_HEADER: michael@0: { michael@0: LOG_SCOPE(GetJPEGLog(), "nsJPEGDecoder::Write -- entering JPEG_HEADER case"); michael@0: michael@0: /* Step 3: read file parameters with jpeg_read_header() */ michael@0: if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) { michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (JPEG_SUSPENDED)")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: michael@0: int sampleSize = mImage.GetRequestedSampleSize(); michael@0: if (sampleSize > 0) { michael@0: mInfo.scale_num = 1; michael@0: mInfo.scale_denom = sampleSize; michael@0: } michael@0: michael@0: /* Used to set up image size so arrays can be allocated */ michael@0: jpeg_calc_output_dimensions(&mInfo); michael@0: michael@0: // Post our size to the superclass michael@0: PostSize(mInfo.output_width, mInfo.output_height, ReadOrientationFromEXIF()); michael@0: if (HasError()) { michael@0: // Setting the size led to an error. michael@0: mState = JPEG_ERROR; michael@0: return; michael@0: } michael@0: michael@0: /* If we're doing a size decode, we're done. */ michael@0: if (IsSizeDecode()) michael@0: return; michael@0: michael@0: /* We're doing a full decode. */ michael@0: if (mCMSMode != eCMSMode_Off && michael@0: (mInProfile = GetICCProfile(mInfo)) != nullptr) { michael@0: uint32_t profileSpace = qcms_profile_get_color_space(mInProfile); michael@0: bool mismatch = false; michael@0: michael@0: #ifdef DEBUG_tor michael@0: fprintf(stderr, "JPEG profileSpace: 0x%08X\n", profileSpace); michael@0: #endif michael@0: switch (mInfo.jpeg_color_space) { michael@0: case JCS_GRAYSCALE: michael@0: if (profileSpace == icSigRgbData) michael@0: mInfo.out_color_space = JCS_RGB; michael@0: else if (profileSpace != icSigGrayData) michael@0: mismatch = true; michael@0: break; michael@0: case JCS_RGB: michael@0: if (profileSpace != icSigRgbData) michael@0: mismatch = true; michael@0: break; michael@0: case JCS_YCbCr: michael@0: if (profileSpace == icSigRgbData) michael@0: mInfo.out_color_space = JCS_RGB; michael@0: else michael@0: // qcms doesn't support ycbcr michael@0: mismatch = true; michael@0: break; michael@0: case JCS_CMYK: michael@0: case JCS_YCCK: michael@0: // qcms doesn't support cmyk michael@0: mismatch = true; michael@0: break; michael@0: default: michael@0: mState = JPEG_ERROR; michael@0: PostDataError(); michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (unknown colorpsace (1))")); michael@0: return; michael@0: } michael@0: michael@0: if (!mismatch) { michael@0: qcms_data_type type; michael@0: switch (mInfo.out_color_space) { michael@0: case JCS_GRAYSCALE: michael@0: type = QCMS_DATA_GRAY_8; michael@0: break; michael@0: case JCS_RGB: michael@0: type = QCMS_DATA_RGB_8; michael@0: break; michael@0: default: michael@0: mState = JPEG_ERROR; michael@0: PostDataError(); michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (unknown colorpsace (2))")); michael@0: return; michael@0: } michael@0: #if 0 michael@0: /* We don't currently support CMYK profiles. The following michael@0: * code dealt with lcms types. Add something like this michael@0: * back when we gain support for CMYK. michael@0: */ michael@0: /* Adobe Photoshop writes YCCK/CMYK files with inverted data */ michael@0: if (mInfo.out_color_space == JCS_CMYK) michael@0: type |= FLAVOR_SH(mInfo.saw_Adobe_marker ? 1 : 0); michael@0: #endif michael@0: michael@0: if (gfxPlatform::GetCMSOutputProfile()) { michael@0: michael@0: /* Calculate rendering intent. */ michael@0: int intent = gfxPlatform::GetRenderingIntent(); michael@0: if (intent == -1) michael@0: intent = qcms_profile_get_rendering_intent(mInProfile); michael@0: michael@0: /* Create the color management transform. */ michael@0: mTransform = qcms_transform_create(mInProfile, michael@0: type, michael@0: gfxPlatform::GetCMSOutputProfile(), michael@0: QCMS_DATA_RGB_8, michael@0: (qcms_intent)intent); michael@0: } michael@0: } else { michael@0: #ifdef DEBUG_tor michael@0: fprintf(stderr, "ICM profile colorspace mismatch\n"); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: if (!mTransform) { michael@0: switch (mInfo.jpeg_color_space) { michael@0: case JCS_GRAYSCALE: michael@0: case JCS_RGB: michael@0: case JCS_YCbCr: michael@0: // if we're not color managing we can decode directly to michael@0: // MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB michael@0: if (mCMSMode != eCMSMode_All) { michael@0: mInfo.out_color_space = MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB; michael@0: mInfo.out_color_components = 4; michael@0: } else { michael@0: mInfo.out_color_space = JCS_RGB; michael@0: } michael@0: break; michael@0: case JCS_CMYK: michael@0: case JCS_YCCK: michael@0: /* libjpeg can convert from YCCK to CMYK, but not to RGB */ michael@0: mInfo.out_color_space = JCS_CMYK; michael@0: break; michael@0: default: michael@0: mState = JPEG_ERROR; michael@0: PostDataError(); michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (unknown colorpsace (3))")); michael@0: return; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Don't allocate a giant and superfluous memory buffer michael@0: * when not doing a progressive decode. michael@0: */ michael@0: mInfo.buffered_image = mDecodeStyle == PROGRESSIVE && jpeg_has_multiple_scans(&mInfo); michael@0: michael@0: if (!mImageData) { michael@0: mState = JPEG_ERROR; michael@0: PostDecoderError(NS_ERROR_OUT_OF_MEMORY); michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (could not initialize image frame)")); michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: (" JPEGDecoderAccounting: nsJPEGDecoder::Write -- created image frame with %ux%u pixels", michael@0: mInfo.output_width, mInfo.output_height)); michael@0: michael@0: mState = JPEG_START_DECOMPRESS; michael@0: } michael@0: michael@0: case JPEG_START_DECOMPRESS: michael@0: { michael@0: LOG_SCOPE(GetJPEGLog(), "nsJPEGDecoder::Write -- entering JPEG_START_DECOMPRESS case"); michael@0: /* Step 4: set parameters for decompression */ michael@0: michael@0: /* FIXME -- Should reset dct_method and dither mode michael@0: * for final pass of progressive JPEG michael@0: */ michael@0: mInfo.dct_method = JDCT_ISLOW; michael@0: mInfo.dither_mode = JDITHER_FS; michael@0: mInfo.do_fancy_upsampling = TRUE; michael@0: mInfo.enable_2pass_quant = FALSE; michael@0: mInfo.do_block_smoothing = TRUE; michael@0: michael@0: /* Step 5: Start decompressor */ michael@0: if (jpeg_start_decompress(&mInfo) == FALSE) { michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (I/O suspension after jpeg_start_decompress())")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: michael@0: michael@0: /* If this is a progressive JPEG ... */ michael@0: mState = mInfo.buffered_image ? JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL; michael@0: } michael@0: michael@0: case JPEG_DECOMPRESS_SEQUENTIAL: michael@0: { michael@0: if (mState == JPEG_DECOMPRESS_SEQUENTIAL) michael@0: { michael@0: LOG_SCOPE(GetJPEGLog(), "nsJPEGDecoder::Write -- JPEG_DECOMPRESS_SEQUENTIAL case"); michael@0: michael@0: bool suspend; michael@0: OutputScanlines(&suspend); michael@0: michael@0: if (suspend) { michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (I/O suspension after OutputScanlines() - SEQUENTIAL)")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: michael@0: /* If we've completed image output ... */ michael@0: NS_ASSERTION(mInfo.output_scanline == mInfo.output_height, "We didn't process all of the data!"); michael@0: mState = JPEG_DONE; michael@0: } michael@0: } michael@0: michael@0: case JPEG_DECOMPRESS_PROGRESSIVE: michael@0: { michael@0: if (mState == JPEG_DECOMPRESS_PROGRESSIVE) michael@0: { michael@0: LOG_SCOPE(GetJPEGLog(), "nsJPEGDecoder::Write -- JPEG_DECOMPRESS_PROGRESSIVE case"); michael@0: michael@0: int status; michael@0: do { michael@0: status = jpeg_consume_input(&mInfo); michael@0: } while ((status != JPEG_SUSPENDED) && michael@0: (status != JPEG_REACHED_EOI)); michael@0: michael@0: for (;;) { michael@0: if (mInfo.output_scanline == 0) { michael@0: int scan = mInfo.input_scan_number; michael@0: michael@0: /* if we haven't displayed anything yet (output_scan_number==0) michael@0: and we have enough data for a complete scan, force output michael@0: of the last full scan */ michael@0: if ((mInfo.output_scan_number == 0) && michael@0: (scan > 1) && michael@0: (status != JPEG_REACHED_EOI)) michael@0: scan--; michael@0: michael@0: if (!jpeg_start_output(&mInfo, scan)) { michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (I/O suspension after jpeg_start_output() - PROGRESSIVE)")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: } michael@0: michael@0: if (mInfo.output_scanline == 0xffffff) michael@0: mInfo.output_scanline = 0; michael@0: michael@0: bool suspend; michael@0: OutputScanlines(&suspend); michael@0: michael@0: if (suspend) { michael@0: if (mInfo.output_scanline == 0) { michael@0: /* didn't manage to read any lines - flag so we don't call michael@0: jpeg_start_output() multiple times for the same scan */ michael@0: mInfo.output_scanline = 0xffffff; michael@0: } michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (I/O suspension after OutputScanlines() - PROGRESSIVE)")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: michael@0: if (mInfo.output_scanline == mInfo.output_height) michael@0: { michael@0: if (!jpeg_finish_output(&mInfo)) { michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (I/O suspension after jpeg_finish_output() - PROGRESSIVE)")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: michael@0: if (jpeg_input_complete(&mInfo) && michael@0: (mInfo.input_scan_number == mInfo.output_scan_number)) michael@0: break; michael@0: michael@0: mInfo.output_scanline = 0; michael@0: } michael@0: } michael@0: michael@0: mState = JPEG_DONE; michael@0: } michael@0: } michael@0: michael@0: case JPEG_DONE: michael@0: { michael@0: LOG_SCOPE(GetJPEGLog(), "nsJPEGDecoder::ProcessData -- entering JPEG_DONE case"); michael@0: michael@0: /* Step 7: Finish decompression */ michael@0: michael@0: if (jpeg_finish_decompress(&mInfo) == FALSE) { michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (I/O suspension after jpeg_finish_decompress() - DONE)")); michael@0: return; /* I/O suspension */ michael@0: } michael@0: michael@0: mState = JPEG_SINK_NON_JPEG_TRAILER; michael@0: michael@0: /* we're done dude */ michael@0: break; michael@0: } michael@0: case JPEG_SINK_NON_JPEG_TRAILER: michael@0: PR_LOG(GetJPEGLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] nsJPEGDecoder::ProcessData -- entering JPEG_SINK_NON_JPEG_TRAILER case\n", this)); michael@0: michael@0: break; michael@0: michael@0: case JPEG_ERROR: michael@0: NS_ABORT_IF_FALSE(0, "Should always return immediately after error and not re-enter decoder"); michael@0: } michael@0: michael@0: PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("} (end of function)")); michael@0: return; michael@0: } michael@0: michael@0: Orientation michael@0: nsJPEGDecoder::ReadOrientationFromEXIF() michael@0: { michael@0: jpeg_saved_marker_ptr marker; michael@0: michael@0: // Locate the APP1 marker, where EXIF data is stored, in the marker list. michael@0: for (marker = mInfo.marker_list ; marker != nullptr ; marker = marker->next) { michael@0: if (marker->marker == JPEG_APP0 + 1) michael@0: break; michael@0: } michael@0: michael@0: // If we're at the end of the list, there's no EXIF data. michael@0: if (!marker) michael@0: return Orientation(); michael@0: michael@0: // Extract the orientation information. michael@0: EXIFData exif = EXIFParser::Parse(marker->data, michael@0: static_cast(marker->data_length)); michael@0: return exif.orientation; michael@0: } michael@0: michael@0: void michael@0: nsJPEGDecoder::NotifyDone() michael@0: { michael@0: PostFrameStop(FrameBlender::kFrameOpaque); michael@0: PostDecodeDone(); michael@0: } michael@0: michael@0: void michael@0: nsJPEGDecoder::OutputScanlines(bool* suspend) michael@0: { michael@0: *suspend = false; michael@0: michael@0: const uint32_t top = mInfo.output_scanline; michael@0: michael@0: while ((mInfo.output_scanline < mInfo.output_height)) { michael@0: /* Use the Cairo image buffer as scanline buffer */ michael@0: uint32_t *imageRow = ((uint32_t*)mImageData) + michael@0: (mInfo.output_scanline * mInfo.output_width); michael@0: michael@0: if (mInfo.out_color_space == MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB) { michael@0: /* Special case: scanline will be directly converted into packed ARGB */ michael@0: if (jpeg_read_scanlines(&mInfo, (JSAMPARRAY)&imageRow, 1) != 1) { michael@0: *suspend = true; /* suspend */ michael@0: break; michael@0: } michael@0: continue; /* all done for this row! */ michael@0: } michael@0: michael@0: JSAMPROW sampleRow = (JSAMPROW)imageRow; michael@0: if (mInfo.output_components == 3) { michael@0: /* Put the pixels at end of row to enable in-place expansion */ michael@0: sampleRow += mInfo.output_width; michael@0: } michael@0: michael@0: /* Request one scanline. Returns 0 or 1 scanlines. */ michael@0: if (jpeg_read_scanlines(&mInfo, &sampleRow, 1) != 1) { michael@0: *suspend = true; /* suspend */ michael@0: break; michael@0: } michael@0: michael@0: if (mTransform) { michael@0: JSAMPROW source = sampleRow; michael@0: if (mInfo.out_color_space == JCS_GRAYSCALE) { michael@0: /* Convert from the 1byte grey pixels at begin of row michael@0: to the 3byte RGB byte pixels at 'end' of row */ michael@0: sampleRow += mInfo.output_width; michael@0: } michael@0: qcms_transform_data(mTransform, source, sampleRow, mInfo.output_width); michael@0: /* Move 3byte RGB data to end of row */ michael@0: if (mInfo.out_color_space == JCS_CMYK) { michael@0: memmove(sampleRow + mInfo.output_width, michael@0: sampleRow, michael@0: 3 * mInfo.output_width); michael@0: sampleRow += mInfo.output_width; michael@0: } michael@0: } else { michael@0: if (mInfo.out_color_space == JCS_CMYK) { michael@0: /* Convert from CMYK to RGB */ michael@0: /* We cannot convert directly to Cairo, as the CMSRGBTransform may wants to do a RGB transform... */ michael@0: /* Would be better to have platform CMSenabled transformation from CMYK to (A)RGB... */ michael@0: cmyk_convert_rgb((JSAMPROW)imageRow, mInfo.output_width); michael@0: sampleRow += mInfo.output_width; michael@0: } michael@0: if (mCMSMode == eCMSMode_All) { michael@0: /* No embedded ICC profile - treat as sRGB */ michael@0: qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); michael@0: if (transform) { michael@0: qcms_transform_data(transform, sampleRow, sampleRow, mInfo.output_width); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // counter for while() loops below michael@0: uint32_t idx = mInfo.output_width; michael@0: michael@0: // copy as bytes until source pointer is 32-bit-aligned michael@0: for (; (NS_PTR_TO_UINT32(sampleRow) & 0x3) && idx; --idx) { michael@0: *imageRow++ = gfxPackedPixel(0xFF, sampleRow[0], sampleRow[1], sampleRow[2]); michael@0: sampleRow += 3; michael@0: } michael@0: michael@0: // copy pixels in blocks of 4 michael@0: while (idx >= 4) { michael@0: GFX_BLOCK_RGB_TO_FRGB(sampleRow, imageRow); michael@0: idx -= 4; michael@0: sampleRow += 12; michael@0: imageRow += 4; michael@0: } michael@0: michael@0: // copy remaining pixel(s) michael@0: while (idx--) { michael@0: // 32-bit read of final pixel will exceed buffer, so read bytes michael@0: *imageRow++ = gfxPackedPixel(0xFF, sampleRow[0], sampleRow[1], sampleRow[2]); michael@0: sampleRow += 3; michael@0: } michael@0: } michael@0: michael@0: if (top != mInfo.output_scanline) { michael@0: nsIntRect r(0, top, mInfo.output_width, mInfo.output_scanline-top); michael@0: PostInvalidation(r); michael@0: } michael@0: michael@0: } michael@0: michael@0: michael@0: /* Override the standard error method in the IJG JPEG decoder code. */ michael@0: METHODDEF(void) michael@0: my_error_exit (j_common_ptr cinfo) michael@0: { michael@0: decoder_error_mgr *err = (decoder_error_mgr *) cinfo->err; michael@0: michael@0: /* Convert error to a browser error code */ michael@0: nsresult error_code = err->pub.msg_code == JERR_OUT_OF_MEMORY michael@0: ? NS_ERROR_OUT_OF_MEMORY michael@0: : NS_ERROR_FAILURE; michael@0: michael@0: #ifdef DEBUG michael@0: char buffer[JMSG_LENGTH_MAX]; michael@0: michael@0: /* Create the message */ michael@0: (*err->pub.format_message) (cinfo, buffer); michael@0: michael@0: fprintf(stderr, "JPEG decoding error:\n%s\n", buffer); michael@0: #endif michael@0: michael@0: /* Return control to the setjmp point. We pass an nsresult masquerading as michael@0: * an int, which works because the setjmp() caller casts it back. */ michael@0: longjmp(err->setjmp_buffer, static_cast(error_code)); michael@0: } michael@0: michael@0: /******************************************************************************/ michael@0: /*----------------------------------------------------------------------------- michael@0: * This is the callback routine from the IJG JPEG library used to supply new michael@0: * data to the decompressor when its input buffer is exhausted. It juggles michael@0: * multiple buffers in an attempt to avoid unnecessary copying of input data. michael@0: * michael@0: * (A simpler scheme is possible: It's much easier to use only a single michael@0: * buffer; when fill_input_buffer() is called, move any unconsumed data michael@0: * (beyond the current pointer/count) down to the beginning of this buffer and michael@0: * then load new data into the remaining buffer space. This approach requires michael@0: * a little more data copying but is far easier to get right.) michael@0: * michael@0: * At any one time, the JPEG decompressor is either reading from the necko michael@0: * input buffer, which is volatile across top-level calls to the IJG library, michael@0: * or the "backtrack" buffer. The backtrack buffer contains the remaining michael@0: * unconsumed data from the necko buffer after parsing was suspended due michael@0: * to insufficient data in some previous call to the IJG library. michael@0: * michael@0: * When suspending, the decompressor will back up to a convenient restart michael@0: * point (typically the start of the current MCU). The variables michael@0: * next_input_byte & bytes_in_buffer indicate where the restart point will be michael@0: * if the current call returns FALSE. Data beyond this point must be michael@0: * rescanned after resumption, so it must be preserved in case the decompressor michael@0: * decides to backtrack. michael@0: * michael@0: * Returns: michael@0: * TRUE if additional data is available, FALSE if no data present and michael@0: * the JPEG library should therefore suspend processing of input stream michael@0: *---------------------------------------------------------------------------*/ michael@0: michael@0: /******************************************************************************/ michael@0: /* data source manager method */ michael@0: /******************************************************************************/ michael@0: michael@0: michael@0: /******************************************************************************/ michael@0: /* data source manager method michael@0: Initialize source. This is called by jpeg_read_header() before any michael@0: data is actually read. May leave michael@0: bytes_in_buffer set to 0 (in which case a fill_input_buffer() call michael@0: will occur immediately). michael@0: */ michael@0: METHODDEF(void) michael@0: init_source (j_decompress_ptr jd) michael@0: { michael@0: } michael@0: michael@0: /******************************************************************************/ michael@0: /* data source manager method michael@0: Skip num_bytes worth of data. The buffer pointer and count should michael@0: be advanced over num_bytes input bytes, refilling the buffer as michael@0: needed. This is used to skip over a potentially large amount of michael@0: uninteresting data (such as an APPn marker). In some applications michael@0: it may be possible to optimize away the reading of the skipped data, michael@0: but it's not clear that being smart is worth much trouble; large michael@0: skips are uncommon. bytes_in_buffer may be zero on return. michael@0: A zero or negative skip count should be treated as a no-op. michael@0: */ michael@0: METHODDEF(void) michael@0: skip_input_data (j_decompress_ptr jd, long num_bytes) michael@0: { michael@0: struct jpeg_source_mgr *src = jd->src; michael@0: nsJPEGDecoder *decoder = (nsJPEGDecoder *)(jd->client_data); michael@0: michael@0: if (num_bytes > (long)src->bytes_in_buffer) { michael@0: /* michael@0: * Can't skip it all right now until we get more data from michael@0: * network stream. Set things up so that fill_input_buffer michael@0: * will skip remaining amount. michael@0: */ michael@0: decoder->mBytesToSkip = (size_t)num_bytes - src->bytes_in_buffer; michael@0: src->next_input_byte += src->bytes_in_buffer; michael@0: src->bytes_in_buffer = 0; michael@0: michael@0: } else { michael@0: /* Simple case. Just advance buffer pointer */ michael@0: michael@0: src->bytes_in_buffer -= (size_t)num_bytes; michael@0: src->next_input_byte += num_bytes; michael@0: } michael@0: } michael@0: michael@0: michael@0: /******************************************************************************/ michael@0: /* data source manager method michael@0: This is called whenever bytes_in_buffer has reached zero and more michael@0: data is wanted. In typical applications, it should read fresh data michael@0: into the buffer (ignoring the current state of next_input_byte and michael@0: bytes_in_buffer), reset the pointer & count to the start of the michael@0: buffer, and return TRUE indicating that the buffer has been reloaded. michael@0: It is not necessary to fill the buffer entirely, only to obtain at michael@0: least one more byte. bytes_in_buffer MUST be set to a positive value michael@0: if TRUE is returned. A FALSE return should only be used when I/O michael@0: suspension is desired. michael@0: */ michael@0: METHODDEF(boolean) michael@0: fill_input_buffer (j_decompress_ptr jd) michael@0: { michael@0: struct jpeg_source_mgr *src = jd->src; michael@0: nsJPEGDecoder *decoder = (nsJPEGDecoder *)(jd->client_data); michael@0: michael@0: if (decoder->mReading) { michael@0: const JOCTET *new_buffer = decoder->mSegment; michael@0: uint32_t new_buflen = decoder->mSegmentLen; michael@0: michael@0: if (!new_buffer || new_buflen == 0) michael@0: return false; /* suspend */ michael@0: michael@0: decoder->mSegmentLen = 0; michael@0: michael@0: if (decoder->mBytesToSkip) { michael@0: if (decoder->mBytesToSkip < new_buflen) { michael@0: /* All done skipping bytes; Return what's left. */ michael@0: new_buffer += decoder->mBytesToSkip; michael@0: new_buflen -= decoder->mBytesToSkip; michael@0: decoder->mBytesToSkip = 0; michael@0: } else { michael@0: /* Still need to skip some more data in the future */ michael@0: decoder->mBytesToSkip -= (size_t)new_buflen; michael@0: return false; /* suspend */ michael@0: } michael@0: } michael@0: michael@0: decoder->mBackBufferUnreadLen = src->bytes_in_buffer; michael@0: michael@0: src->next_input_byte = new_buffer; michael@0: src->bytes_in_buffer = (size_t)new_buflen; michael@0: decoder->mReading = false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: if (src->next_input_byte != decoder->mSegment) { michael@0: /* Backtrack data has been permanently consumed. */ michael@0: decoder->mBackBufferUnreadLen = 0; michael@0: decoder->mBackBufferLen = 0; michael@0: } michael@0: michael@0: /* Save remainder of netlib buffer in backtrack buffer */ michael@0: const uint32_t new_backtrack_buflen = src->bytes_in_buffer + decoder->mBackBufferLen; michael@0: michael@0: /* Make sure backtrack buffer is big enough to hold new data. */ michael@0: if (decoder->mBackBufferSize < new_backtrack_buflen) { michael@0: /* Check for malformed MARKER segment lengths, before allocating space for it */ michael@0: if (new_backtrack_buflen > MAX_JPEG_MARKER_LENGTH) { michael@0: my_error_exit((j_common_ptr)(&decoder->mInfo)); michael@0: } michael@0: michael@0: /* Round up to multiple of 256 bytes. */ michael@0: const size_t roundup_buflen = ((new_backtrack_buflen + 255) >> 8) << 8; michael@0: JOCTET *buf = (JOCTET *)PR_REALLOC(decoder->mBackBuffer, roundup_buflen); michael@0: /* Check for OOM */ michael@0: if (!buf) { michael@0: decoder->mInfo.err->msg_code = JERR_OUT_OF_MEMORY; michael@0: my_error_exit((j_common_ptr)(&decoder->mInfo)); michael@0: } michael@0: decoder->mBackBuffer = buf; michael@0: decoder->mBackBufferSize = roundup_buflen; michael@0: } michael@0: michael@0: /* Copy remainder of netlib segment into backtrack buffer. */ michael@0: memmove(decoder->mBackBuffer + decoder->mBackBufferLen, michael@0: src->next_input_byte, michael@0: src->bytes_in_buffer); michael@0: michael@0: /* Point to start of data to be rescanned. */ michael@0: src->next_input_byte = decoder->mBackBuffer + decoder->mBackBufferLen - decoder->mBackBufferUnreadLen; michael@0: src->bytes_in_buffer += decoder->mBackBufferUnreadLen; michael@0: decoder->mBackBufferLen = (size_t)new_backtrack_buflen; michael@0: decoder->mReading = true; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /******************************************************************************/ michael@0: /* data source manager method */ michael@0: /* michael@0: * Terminate source --- called by jpeg_finish_decompress() after all michael@0: * data has been read to clean up JPEG source manager. NOT called by michael@0: * jpeg_abort() or jpeg_destroy(). michael@0: */ michael@0: METHODDEF(void) michael@0: term_source (j_decompress_ptr jd) michael@0: { michael@0: nsJPEGDecoder *decoder = (nsJPEGDecoder *)(jd->client_data); michael@0: michael@0: // This function shouldn't be called if we ran into an error we didn't michael@0: // recover from. michael@0: NS_ABORT_IF_FALSE(decoder->mState != JPEG_ERROR, michael@0: "Calling term_source on a JPEG with mState == JPEG_ERROR!"); michael@0: michael@0: // Notify using a helper method to get around protectedness issues. michael@0: decoder->NotifyDone(); michael@0: } michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla michael@0: michael@0: michael@0: /**************** Inverted CMYK -> RGB conversion **************/ michael@0: /* michael@0: * Input is (Inverted) CMYK stored as 4 bytes per pixel. michael@0: * Output is RGB stored as 3 bytes per pixel. michael@0: * @param row Points to row buffer containing the CMYK bytes for each pixel in the row. michael@0: * @param width Number of pixels in the row. michael@0: */ michael@0: static void cmyk_convert_rgb(JSAMPROW row, JDIMENSION width) michael@0: { michael@0: /* Work from end to front to shrink from 4 bytes per pixel to 3 */ michael@0: JSAMPROW in = row + width*4; michael@0: JSAMPROW out = in; michael@0: michael@0: for (uint32_t i = width; i > 0; i--) { michael@0: in -= 4; michael@0: out -= 3; michael@0: michael@0: // Source is 'Inverted CMYK', output is RGB. michael@0: // See: http://www.easyrgb.com/math.php?MATH=M12#text12 michael@0: // Or: http://www.ilkeratalay.com/colorspacesfaq.php#rgb michael@0: michael@0: // From CMYK to CMY michael@0: // C = ( C * ( 1 - K ) + K ) michael@0: // M = ( M * ( 1 - K ) + K ) michael@0: // Y = ( Y * ( 1 - K ) + K ) michael@0: michael@0: // From Inverted CMYK to CMY is thus: michael@0: // C = ( (1-iC) * (1 - (1-iK)) + (1-iK) ) => 1 - iC*iK michael@0: // Same for M and Y michael@0: michael@0: // Convert from CMY (0..1) to RGB (0..1) michael@0: // R = 1 - C => 1 - (1 - iC*iK) => iC*iK michael@0: // G = 1 - M => 1 - (1 - iM*iK) => iM*iK michael@0: // B = 1 - Y => 1 - (1 - iY*iK) => iY*iK michael@0: michael@0: // Convert from Inverted CMYK (0..255) to RGB (0..255) michael@0: const uint32_t iC = in[0]; michael@0: const uint32_t iM = in[1]; michael@0: const uint32_t iY = in[2]; michael@0: const uint32_t iK = in[3]; michael@0: out[0] = iC*iK/255; // Red michael@0: out[1] = iM*iK/255; // Green michael@0: out[2] = iY*iK/255; // Blue michael@0: } michael@0: }