/* -*- 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 "gfxPlatform.h" #include "mozilla/TelemetryHistogramEnums.h" #include "nsWebPDecoder.h" #include "RasterImage.h" #include "SurfacePipeFactory.h" using namespace mozilla::gfx; namespace mozilla { namespace image { static LazyLogModule sWebPLog("WebPDecoder"); nsWebPDecoder::nsWebPDecoder(RasterImage* aImage) : Decoder(aImage), mDecoder(nullptr), mBlend(BlendMethod::OVER), mDisposal(DisposalMethod::KEEP), mTimeout(FrameTimeout::Forever()), mFormat(SurfaceFormat::OS_RGBX), mLastRow(0), mCurrentFrame(0), mData(nullptr), mLength(0), mIteratorComplete(false), mNeedDemuxer(true), mGotColorProfile(false) { MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::nsWebPDecoder", this)); } nsWebPDecoder::~nsWebPDecoder() { MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::~nsWebPDecoder", this)); if (mDecoder) { WebPIDelete(mDecoder); WebPFreeDecBuffer(&mBuffer); } } LexerResult nsWebPDecoder::ReadData() { MOZ_ASSERT(mData); MOZ_ASSERT(mLength > 0); WebPDemuxer* demuxer = nullptr; bool complete = mIteratorComplete; if (mNeedDemuxer) { WebPDemuxState state; WebPData fragment; fragment.bytes = mData; fragment.size = mLength; demuxer = WebPDemuxPartial(&fragment, &state); if (state == WEBP_DEMUX_PARSE_ERROR) { MOZ_LOG( sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ReadData -- demux parse error\n", this)); WebPDemuxDelete(demuxer); return LexerResult(TerminalState::FAILURE); } if (state == WEBP_DEMUX_PARSING_HEADER) { WebPDemuxDelete(demuxer); return LexerResult(Yield::NEED_MORE_DATA); } if (!demuxer) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ReadData -- no demuxer\n", this)); return LexerResult(TerminalState::FAILURE); } complete = complete || state == WEBP_DEMUX_DONE; } LexerResult rv(TerminalState::FAILURE); if (!HasSize()) { rv = ReadHeader(demuxer, complete); } else { rv = ReadPayload(demuxer, complete); } WebPDemuxDelete(demuxer); return rv; } LexerResult nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) { while (true) { SourceBufferIterator::State state = SourceBufferIterator::COMPLETE; if (!mIteratorComplete) { state = aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume); // We need to remember since we can't advance a complete iterator. mIteratorComplete = state == SourceBufferIterator::COMPLETE; } if (state == SourceBufferIterator::WAITING) { return LexerResult(Yield::NEED_MORE_DATA); } LexerResult rv = UpdateBuffer(aIterator, state); if (rv.is() && rv.as() == Yield::NEED_MORE_DATA) { // We need to check the iterator to see if more is available before // giving up unless we are already complete. if (mIteratorComplete) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::DoDecode -- read all data, " "but needs more\n", this)); return LexerResult(TerminalState::FAILURE); } continue; } return rv; } } LexerResult nsWebPDecoder::UpdateBuffer(SourceBufferIterator& aIterator, SourceBufferIterator::State aState) { MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); switch (aState) { case SourceBufferIterator::READY: if (!aIterator.IsContiguous()) { // We need to buffer. This should be rare, but expensive. break; } if (!mData) { // For as long as we hold onto an iterator, we know the data pointers // to the chunks cannot change underneath us, so save the pointer to // the first block. MOZ_ASSERT(mLength == 0); mData = reinterpret_cast(aIterator.Data()); } mLength += aIterator.Length(); return ReadData(); case SourceBufferIterator::COMPLETE: if (!mData) { // We must have hit an error, such as an OOM, when buffering the // first set of encoded data. MOZ_LOG( sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::DoDecode -- complete no data\n", this)); return LexerResult(TerminalState::FAILURE); } return ReadData(); default: MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::DoDecode -- bad state\n", this)); return LexerResult(TerminalState::FAILURE); } // We need to buffer. If we have no data buffered, we need to get everything // from the first chunk of the source buffer before appending the new data. if (mBufferedData.empty()) { MOZ_ASSERT(mData); MOZ_ASSERT(mLength > 0); if (!mBufferedData.append(mData, mLength)) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::DoDecode -- oom, initialize %zu\n", this, mLength)); return LexerResult(TerminalState::FAILURE); } MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu bytes\n", this, mLength)); } // Append the incremental data from the iterator. if (!mBufferedData.append(aIterator.Data(), aIterator.Length())) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::DoDecode -- oom, append %zu on %zu\n", this, aIterator.Length(), mBufferedData.length())); return LexerResult(TerminalState::FAILURE); } MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu -> %zu bytes\n", this, aIterator.Length(), mBufferedData.length())); mData = mBufferedData.begin(); mLength = mBufferedData.length(); return ReadData(); } nsresult nsWebPDecoder::CreateFrame(const OrientedIntRect& aFrameRect) { MOZ_ASSERT(HasSize()); MOZ_ASSERT(!mDecoder); MOZ_LOG( sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, (%d, %d) %d x %d\n", this, mCurrentFrame, aFrameRect.x, aFrameRect.y, aFrameRect.width, aFrameRect.height)); if (aFrameRect.width <= 0 || aFrameRect.height <= 0) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::CreateFrame -- bad frame rect\n", this)); return NS_ERROR_FAILURE; } // If this is our first frame in an animation and it doesn't cover the // full frame, then we are transparent even if there is no alpha if (mCurrentFrame == 0 && !aFrameRect.IsEqualEdges(FullFrame())) { MOZ_ASSERT(HasAnimation()); mFormat = SurfaceFormat::OS_RGBA; PostHasTransparency(); } WebPInitDecBuffer(&mBuffer); switch (SurfaceFormat::OS_RGBA) { case SurfaceFormat::B8G8R8A8: mBuffer.colorspace = MODE_BGRA; break; case SurfaceFormat::A8R8G8B8: mBuffer.colorspace = MODE_ARGB; break; case SurfaceFormat::R8G8B8A8: mBuffer.colorspace = MODE_RGBA; break; default: MOZ_ASSERT_UNREACHABLE("Unknown OS_RGBA"); return NS_ERROR_FAILURE; } mDecoder = WebPINewDecoder(&mBuffer); if (!mDecoder) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n", this)); return NS_ERROR_FAILURE; } // WebP doesn't guarantee that the alpha generated matches the hint in the // header, so we always need to claim the input is BGRA. If the output is // BGRX, swizzling will mask off the alpha channel. SurfaceFormat inFormat = SurfaceFormat::OS_RGBA; SurfacePipeFlags pipeFlags = SurfacePipeFlags(); if (mFormat == SurfaceFormat::OS_RGBA && !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) { pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA; } Maybe animParams; if (!IsFirstFrameDecode()) { animParams.emplace(aFrameRect.ToUnknownRect(), mTimeout, mCurrentFrame, mBlend, mDisposal); } Maybe pipe = SurfacePipeFactory::CreateSurfacePipe( this, Size(), OutputSize(), aFrameRect, inFormat, mFormat, animParams, mTransform, pipeFlags); if (!pipe) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this)); return NS_ERROR_FAILURE; } mFrameRect = aFrameRect; mPipe = std::move(*pipe); return NS_OK; } void nsWebPDecoder::EndFrame() { MOZ_ASSERT(HasSize()); MOZ_ASSERT(mDecoder); auto opacity = mFormat == SurfaceFormat::OS_RGBA ? Opacity::SOME_TRANSPARENCY : Opacity::FULLY_OPAQUE; MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::EndFrame -- frame %u, opacity %d, " "disposal %d, timeout %d, blend %d\n", this, mCurrentFrame, (int)opacity, (int)mDisposal, mTimeout.AsEncodedValueDeprecated(), (int)mBlend)); PostFrameStop(opacity); WebPIDelete(mDecoder); WebPFreeDecBuffer(&mBuffer); mDecoder = nullptr; mLastRow = 0; ++mCurrentFrame; } void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) { MOZ_ASSERT(!mGotColorProfile); mGotColorProfile = true; if (mCMSMode == CMSMode::Off || !GetCMSOutputProfile() || (mCMSMode == CMSMode::TaggedOnly && !aProfile)) { return; } if (!aProfile) { MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use " "sRGB transform\n", this)); mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA); return; } mInProfile = qcms_profile_from_memory(aProfile, aLength); if (!mInProfile) { MOZ_LOG( sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n", this)); return; } uint32_t profileSpace = qcms_profile_get_color_space(mInProfile); if (profileSpace != icSigRgbData) { // WebP doesn't produce grayscale data, this must be corrupt. MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ApplyColorProfile -- ignoring non-rgb " "color profile\n", this)); return; } // Calculate rendering intent. int intent = gfxPlatform::GetRenderingIntent(); if (intent == -1) { intent = qcms_profile_get_rendering_intent(mInProfile); } // Create the color management transform. qcms_data_type type = gfxPlatform::GetCMSOSRGBAType(); mTransform = qcms_transform_create(mInProfile, type, GetCMSOutputProfile(), type, (qcms_intent)intent); MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged " "transform\n", this)); } LexerResult nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete) { MOZ_ASSERT(aDemuxer); MOZ_LOG( sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength)); uint32_t flags = WebPDemuxGetI(aDemuxer, WEBP_FF_FORMAT_FLAGS); if (!IsMetadataDecode() && !mGotColorProfile) { if (flags & WebPFeatureFlags::ICCP_FLAG) { WebPChunkIterator iter; if (WebPDemuxGetChunk(aDemuxer, "ICCP", 1, &iter)) { ApplyColorProfile(reinterpret_cast(iter.chunk.bytes), iter.chunk.size); WebPDemuxReleaseChunkIterator(&iter); } else { if (!aIsComplete) { return LexerResult(Yield::NEED_MORE_DATA); } MOZ_LOG(sWebPLog, LogLevel::Warning, ("[this=%p] nsWebPDecoder::ReadHeader header specified ICCP " "but no ICCP chunk found, ignoring\n", this)); ApplyColorProfile(nullptr, 0); } } else { ApplyColorProfile(nullptr, 0); } } if (flags & WebPFeatureFlags::ANIMATION_FLAG) { // A metadata decode expects to get the correct first frame timeout which // sadly is not provided by the normal WebP header parsing. WebPIterator iter; if (!WebPDemuxGetFrame(aDemuxer, 1, &iter)) { return aIsComplete ? LexerResult(TerminalState::FAILURE) : LexerResult(Yield::NEED_MORE_DATA); } PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration)); WebPDemuxReleaseIterator(&iter); } else { // Single frames don't need a demuxer to be created. mNeedDemuxer = false; } uint32_t width = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_WIDTH); uint32_t height = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_HEIGHT); if (width > INT32_MAX || height > INT32_MAX) { return LexerResult(TerminalState::FAILURE); } PostSize(width, height); bool alpha = flags & WebPFeatureFlags::ALPHA_FLAG; if (alpha) { mFormat = SurfaceFormat::OS_RGBA; PostHasTransparency(); } MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ReadHeader -- %u x %u, alpha %d, " "animation %d, metadata decode %d, first frame decode %d\n", this, width, height, alpha, HasAnimation(), IsMetadataDecode(), IsFirstFrameDecode())); if (IsMetadataDecode()) { return LexerResult(TerminalState::SUCCESS); } return ReadPayload(aDemuxer, aIsComplete); } LexerResult nsWebPDecoder::ReadPayload(WebPDemuxer* aDemuxer, bool aIsComplete) { if (!HasAnimation()) { auto rv = ReadSingle(mData, mLength, FullFrame()); if (rv.is() && rv.as() == TerminalState::SUCCESS) { PostDecodeDone(); } return rv; } return ReadMultiple(aDemuxer, aIsComplete); } LexerResult nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, const OrientedIntRect& aFrameRect) { MOZ_ASSERT(!IsMetadataDecode()); MOZ_ASSERT(aData); MOZ_ASSERT(aLength > 0); MOZ_LOG( sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength)); if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) { return LexerResult(TerminalState::FAILURE); } bool complete; do { VP8StatusCode status = WebPIUpdate(mDecoder, aData, aLength); switch (status) { case VP8_STATUS_OK: complete = true; break; case VP8_STATUS_SUSPENDED: complete = false; break; default: MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n", this, status)); return LexerResult(TerminalState::FAILURE); } int lastRow = -1; int width = 0; int height = 0; int stride = 0; uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride); MOZ_LOG( sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ReadSingle -- complete %d, read %d rows, " "has %d rows available\n", this, complete, mLastRow, lastRow)); if (!rowStart || lastRow == -1 || lastRow == mLastRow) { return LexerResult(Yield::NEED_MORE_DATA); } if (width != mFrameRect.width || height != mFrameRect.height || stride < mFrameRect.width * 4 || lastRow > mFrameRect.height) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, " "%d)\n", this, width, height, stride)); return LexerResult(TerminalState::FAILURE); } for (int row = mLastRow; row < lastRow; row++) { uint32_t* src = reinterpret_cast(rowStart + row * stride); WriteState result = mPipe.WriteBuffer(src); Maybe invalidRect = mPipe.TakeInvalidRect(); if (invalidRect) { PostInvalidation(invalidRect->mInputSpaceRect, Some(invalidRect->mOutputSpaceRect)); } if (result == WriteState::FAILURE) { MOZ_LOG(sWebPLog, LogLevel::Error, ("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n", this)); return LexerResult(TerminalState::FAILURE); } if (result == WriteState::FINISHED) { MOZ_ASSERT(row == lastRow - 1, "There was more data to read?"); complete = true; break; } } mLastRow = lastRow; } while (!complete); if (!complete) { return LexerResult(Yield::NEED_MORE_DATA); } EndFrame(); return LexerResult(TerminalState::SUCCESS); } LexerResult nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer, bool aIsComplete) { MOZ_ASSERT(!IsMetadataDecode()); MOZ_ASSERT(aDemuxer); MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ReadMultiple\n", this)); bool complete = aIsComplete; WebPIterator iter; auto rv = LexerResult(Yield::NEED_MORE_DATA); if (WebPDemuxGetFrame(aDemuxer, mCurrentFrame + 1, &iter)) { switch (iter.blend_method) { case WEBP_MUX_BLEND: mBlend = BlendMethod::OVER; break; case WEBP_MUX_NO_BLEND: mBlend = BlendMethod::SOURCE; break; default: MOZ_ASSERT_UNREACHABLE("Unhandled blend method"); break; } switch (iter.dispose_method) { case WEBP_MUX_DISPOSE_NONE: mDisposal = DisposalMethod::KEEP; break; case WEBP_MUX_DISPOSE_BACKGROUND: mDisposal = DisposalMethod::CLEAR; break; default: MOZ_ASSERT_UNREACHABLE("Unhandled dispose method"); break; } mFormat = iter.has_alpha || mCurrentFrame > 0 ? SurfaceFormat::OS_RGBA : SurfaceFormat::OS_RGBX; mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration); OrientedIntRect frameRect(iter.x_offset, iter.y_offset, iter.width, iter.height); rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, frameRect); complete = complete && !WebPDemuxNextFrame(&iter); WebPDemuxReleaseIterator(&iter); } if (rv.is() && rv.as() == TerminalState::SUCCESS) { // If we extracted one frame, and it is not the last, we need to yield to // the lexer to allow the upper layers to acknowledge the frame. if (!complete && !IsFirstFrameDecode()) { rv = LexerResult(Yield::OUTPUT_AVAILABLE); } else { uint32_t loopCount = WebPDemuxGetI(aDemuxer, WEBP_FF_LOOP_COUNT); MOZ_LOG(sWebPLog, LogLevel::Debug, ("[this=%p] nsWebPDecoder::ReadMultiple -- loop count %u\n", this, loopCount)); PostDecodeDone(loopCount - 1); } } return rv; } Maybe nsWebPDecoder::SpeedHistogram() const { return Some(Telemetry::IMAGE_DECODE_SPEED_WEBP); } } // namespace image } // namespace mozilla