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