12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007 |
- /* -*- 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(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
- }
- }
|