Port WebP decoder changes.

This breaks animated WebP for the moment, but adds QCMS color management and lexer changes.
Tag #831
This commit is contained in:
wolfbeast 2018-11-20 19:59:25 +01:00 • committed by Roy Tam
commit dfe6efae2f
2 changed files with 408 additions and 273 deletions

View file

@ -19,10 +19,6 @@ static LazyLogModule sWebPLog("WebPDecoder");
nsWebPDecoder::nsWebPDecoder(RasterImage* aImage) nsWebPDecoder::nsWebPDecoder(RasterImage* aImage)
: Decoder(aImage) : Decoder(aImage)
, mLexer(Transition::ToUnbuffered(State::FINISHED_WEBP_DATA,
State::WEBP_DATA,
SIZE_MAX),
Transition::TerminateSuccess())
, mDecoder(nullptr) , mDecoder(nullptr)
, mBlend(BlendMethod::OVER) , mBlend(BlendMethod::OVER)
, mDisposal(DisposalMethod::KEEP) , mDisposal(DisposalMethod::KEEP)
@ -30,6 +26,13 @@ nsWebPDecoder::nsWebPDecoder(RasterImage* aImage)
, mFormat(SurfaceFormat::B8G8R8X8) , mFormat(SurfaceFormat::B8G8R8X8)
, mLastRow(0) , mLastRow(0)
, mCurrentFrame(0) , mCurrentFrame(0)
, mData(nullptr)
, mLength(0)
, mIteratorComplete(false)
, mNeedDemuxer(true)
, mGotColorProfile(false)
, mInProfile(nullptr)
, mTransform(nullptr)
{ {
MOZ_LOG(sWebPLog, LogLevel::Debug, MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::nsWebPDecoder", this)); ("[this=%p] nsWebPDecoder::nsWebPDecoder", this));
@ -39,27 +42,157 @@ nsWebPDecoder::~nsWebPDecoder()
{ {
MOZ_LOG(sWebPLog, LogLevel::Debug, MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::~nsWebPDecoder", this)); ("[this=%p] nsWebPDecoder::~nsWebPDecoder", this));
WebPIDelete(mDecoder); if (mDecoder) {
WebPIDelete(mDecoder);
WebPFreeDecBuffer(&mBuffer);
}
if (mInProfile) {
// mTransform belongs to us only if mInProfile is non-null
if (mTransform) {
qcms_transform_release(mTransform);
}
qcms_profile_release(mInProfile);
}
}
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 LexerResult
nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) 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<Yield>() && rv.as<Yield>() == 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!"); MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
return mLexer.Lex(aIterator, aOnResume, switch (aState) {
[=](State aState, const char* aData, size_t aLength) { case SourceBufferIterator::READY:
switch (aState) { if (!mData) {
case State::WEBP_DATA: // For as long as we hold onto an iterator, we know the data pointers
if (!HasSize()) { // to the chunks cannot change underneath us, so save the pointer to
return ReadHeader(aData, aLength); // the first block.
} MOZ_ASSERT(mLength == 0);
return ReadPayload(aData, aLength); mData = reinterpret_cast<const uint8_t*>(aIterator.Data());
case State::FINISHED_WEBP_DATA: }
return FinishedData(); mLength += aIterator.Length();
return ReadData();
case SourceBufferIterator::COMPLETE:
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_CRASH("Unknown State");
}); 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 nsresult
@ -69,13 +202,22 @@ nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
MOZ_ASSERT(!mDecoder); MOZ_ASSERT(!mDecoder);
MOZ_LOG(sWebPLog, LogLevel::Debug, MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, %d x %d\n", ("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, (%d, %d) %d x %d\n",
this, mCurrentFrame, aFrameRect.width, aFrameRect.height)); 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 // 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 // full frame, then we are transparent even if there is no alpha
if (mCurrentFrame == 0 && !aFrameRect.IsEqualEdges(FullFrame())) { if (mCurrentFrame == 0 && !aFrameRect.IsEqualEdges(FullFrame())) {
MOZ_ASSERT(HasAnimation()); MOZ_ASSERT(HasAnimation());
mFormat = SurfaceFormat::B8G8R8A8;
PostHasTransparency(); PostHasTransparency();
} }
@ -90,16 +232,18 @@ nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
SurfacePipeFlags pipeFlags = SurfacePipeFlags();
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(this, Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(this,
mCurrentFrame, Size(), OutputSize(), aFrameRect, mCurrentFrame, Size(), OutputSize(), aFrameRect, mFormat, pipeFlags);
mFormat, SurfacePipeFlags());
if (!pipe) { if (!pipe) {
MOZ_LOG(sWebPLog, LogLevel::Error, MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this)); ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
mPipe = Move(*pipe); mFrameRect = aFrameRect;
mPipe = std::move(*pipe);
return NS_OK; return NS_OK;
} }
@ -118,154 +262,149 @@ nsWebPDecoder::EndFrame()
this, mCurrentFrame, (int)opacity, (int)mDisposal, this, mCurrentFrame, (int)opacity, (int)mDisposal,
mTimeout.AsEncodedValueDeprecated(), (int)mBlend)); mTimeout.AsEncodedValueDeprecated(), (int)mBlend));
PostFrameStop(opacity, mDisposal, mTimeout, mBlend); PostFrameStop(opacity);
WebPFreeDecBuffer(&mBuffer);
WebPIDelete(mDecoder); WebPIDelete(mDecoder);
WebPFreeDecBuffer(&mBuffer);
mDecoder = nullptr; mDecoder = nullptr;
mLastRow = 0; mLastRow = 0;
++mCurrentFrame; ++mCurrentFrame;
} }
nsresult void
nsWebPDecoder::GetDataBuffer(const uint8_t*& aData, size_t& aLength) nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength)
{ {
if (!mData.empty() && mData.begin() != aData) { MOZ_ASSERT(!mGotColorProfile);
if (!mData.append(aData, aLength)) { mGotColorProfile = true;
MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::GetDataBuffer -- oom, append %zu on %zu\n",
this, aLength, mData.length()));
return NS_ERROR_OUT_OF_MEMORY;
}
aData = mData.begin();
aLength = mData.length();
}
return NS_OK;
}
nsresult if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
nsWebPDecoder::SaveDataBuffer(const uint8_t* aData, size_t aLength) return;
{ }
if (mData.empty() && !mData.append(aData, aLength)) {
auto mode = gfxPlatform::GetCMSMode();
if (mode == eCMSMode_Off || (mode == eCMSMode_TaggedOnly && !aProfile)) {
return;
}
if (!aProfile || !gfxPlatform::GetCMSOutputProfile()) {
MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged or no output "
"profile , use sRGB transform\n", this));
mTransform = gfxPlatform::GetCMSRGBATransform();
return;
}
mInProfile = qcms_profile_from_memory(aProfile, aLength);
if (!mInProfile) {
MOZ_LOG(sWebPLog, LogLevel::Error, MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::SaveDataBuffer -- oom, append %zu on %zu\n", ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n",
this, aLength, mData.length())); this));
return NS_ERROR_OUT_OF_MEMORY; return;
} }
return NS_OK;
// 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,
QCMS_DATA_RGBA_8,
gfxPlatform::GetCMSOutputProfile(),
QCMS_DATA_RGBA_8,
(qcms_intent)intent);
MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged "
"transform\n", this));
} }
LexerTransition<nsWebPDecoder::State> LexerResult
nsWebPDecoder::ReadHeader(const char* aData, size_t aLength) nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer,
bool aIsComplete)
{ {
MOZ_ASSERT(aDemuxer);
MOZ_LOG(sWebPLog, LogLevel::Debug, MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, aLength)); ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength));
// XXX(aosmond): In an ideal world, we could request the lexer to do this uint32_t flags = WebPDemuxGetI(aDemuxer, WEBP_FF_FORMAT_FLAGS);
// buffering for us (and in turn the underlying SourceBuffer). That way we
// could avoid extra copies during the decode and just do
// SourceBuffer::Compact on each iteration. For a typical WebP image we
// can hope that we will get the full header in the first packet, but
// for animated images we will end up buffering the whole stream if it
// not already fully received and contiguous.
auto data = (const uint8_t*)aData;
size_t length = aLength;
if (NS_FAILED(GetDataBuffer(data, length))) {
return Transition::TerminateFailure();
}
WebPBitstreamFeatures features; if (!IsMetadataDecode() && !mGotColorProfile) {
VP8StatusCode status = WebPGetFeatures(data, length, &features); if (flags & WebPFeatureFlags::ICCP_FLAG) {
switch (status) { WebPChunkIterator iter;
case VP8_STATUS_OK: if (!WebPDemuxGetChunk(aDemuxer, "ICCP", 1, &iter)) {
break; return aIsComplete ? LexerResult(TerminalState::FAILURE)
case VP8_STATUS_NOT_ENOUGH_DATA: : LexerResult(Yield::NEED_MORE_DATA);
if (NS_FAILED(SaveDataBuffer(data, length))) {
return Transition::TerminateFailure();
} }
return Transition::ContinueUnbuffered(State::WEBP_DATA);
default: ApplyColorProfile(reinterpret_cast<const char*>(iter.chunk.bytes),
MOZ_LOG(sWebPLog, LogLevel::Error, iter.chunk.size);
("[this=%p] nsWebPDecoder::ReadHeader -- parse error %d\n", WebPDemuxReleaseChunkIterator(&iter);
this, status)); } else {
return Transition::TerminateFailure(); ApplyColorProfile(nullptr, 0);
}
} }
if (features.has_animation) { if (flags & WebPFeatureFlags::ANIMATION_FLAG) {
// A metadata decode expects to get the correct first frame timeout which // A metadata decode expects to get the correct first frame timeout which
// sadly is not provided by the normal WebP header parsing. // sadly is not provided by the normal WebP header parsing.
WebPDemuxState state;
WebPData fragment;
fragment.bytes = data;
fragment.size = length;
WebPDemuxer* demuxer = WebPDemuxPartial(&fragment, &state);
if (!demuxer || state == WEBP_DEMUX_PARSE_ERROR) {
MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::ReadHeader -- demux parse error\n", this));
WebPDemuxDelete(demuxer);
return Transition::TerminateFailure();
}
WebPIterator iter; WebPIterator iter;
if (!WebPDemuxGetFrame(demuxer, 1, &iter)) { if (!WebPDemuxGetFrame(aDemuxer, 1, &iter)) {
WebPDemuxDelete(demuxer); return aIsComplete ? LexerResult(TerminalState::FAILURE)
if (state == WEBP_DEMUX_DONE) { : LexerResult(Yield::NEED_MORE_DATA);
MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::ReadHeader -- demux parse error\n",
this));
return Transition::TerminateFailure();
}
if (NS_FAILED(SaveDataBuffer(data, length))) {
return Transition::TerminateFailure();
}
return Transition::ContinueUnbuffered(State::WEBP_DATA);
} }
PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration)); PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration));
WebPDemuxReleaseIterator(&iter); WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demuxer); } else {
// Single frames don't need a demuxer to be created.
mNeedDemuxer = false;
} }
MOZ_LOG(sWebPLog, LogLevel::Debug, uint32_t width = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_WIDTH);
("[this=%p] nsWebPDecoder::ReadHeader -- %d x %d, alpha %d, " uint32_t height = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_HEIGHT);
"animation %d, format %d, metadata decode %d, first frame decode %d\n", if (width > INT32_MAX || height > INT32_MAX) {
this, features.width, features.height, features.has_alpha, return LexerResult(TerminalState::FAILURE);
features.has_animation, features.format, IsMetadataDecode(), }
IsFirstFrameDecode()));
PostSize(features.width, features.height); PostSize(width, height);
if (features.has_alpha) {
bool alpha = flags & WebPFeatureFlags::ALPHA_FLAG;
if (alpha) {
mFormat = SurfaceFormat::B8G8R8A8; mFormat = SurfaceFormat::B8G8R8A8;
PostHasTransparency(); 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()) { if (IsMetadataDecode()) {
return Transition::TerminateSuccess(); return LexerResult(TerminalState::SUCCESS);
} }
auto transition = ReadPayload((const char*)data, length); return ReadPayload(aDemuxer, aIsComplete);
if (!features.has_animation) {
mData.clearAndFree();
}
return transition;
} }
LexerTransition<nsWebPDecoder::State> LexerResult
nsWebPDecoder::ReadPayload(const char* aData, size_t aLength) nsWebPDecoder::ReadPayload(WebPDemuxer* aDemuxer,
bool aIsComplete)
{ {
auto data = (const uint8_t*)aData;
if (!HasAnimation()) { if (!HasAnimation()) {
auto rv = ReadSingle(data, aLength, true, FullFrame()); auto rv = ReadSingle(mData, mLength, FullFrame());
if (rv.NextStateIsTerminal() && if (rv.is<TerminalState>() &&
rv.NextStateAsTerminal() == TerminalState::SUCCESS) { rv.as<TerminalState>() == TerminalState::SUCCESS) {
PostDecodeDone(); PostDecodeDone();
} }
return rv; return rv;
} }
return ReadMultiple(data, aLength); return ReadMultiple(aDemuxer, aIsComplete);
} }
LexerTransition<nsWebPDecoder::State> LexerResult
nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, bool aAppend, const IntRect& aFrameRect) nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, const IntRect& aFrameRect)
{ {
MOZ_ASSERT(!IsMetadataDecode()); MOZ_ASSERT(!IsMetadataDecode());
MOZ_ASSERT(aData); MOZ_ASSERT(aData);
@ -275,125 +414,120 @@ nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, bool aAppend, co
("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength)); ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength));
if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) { if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) {
return Transition::TerminateFailure(); return LexerResult(TerminalState::FAILURE);
} }
// XXX(aosmond): The demux API can be used for single images according to the
// documentation. If WebPIAppend is not any more efficient in its buffering
// than what we do for animated images, we should just combine the use cases.
bool complete; bool complete;
VP8StatusCode status; do {
if (aAppend) { VP8StatusCode status = WebPIUpdate(mDecoder, aData, aLength);
status = WebPIAppend(mDecoder, aData, aLength); switch (status) {
} else { case VP8_STATUS_OK:
status = WebPIUpdate(mDecoder, aData, aLength); complete = true;
} break;
switch (status) { case VP8_STATUS_SUSPENDED:
case VP8_STATUS_OK: complete = false;
complete = true; break;
break; default:
case VP8_STATUS_SUSPENDED: MOZ_LOG(sWebPLog, LogLevel::Error,
complete = false; ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n",
break; this, status));
default: return LexerResult(TerminalState::FAILURE);
MOZ_LOG(sWebPLog, LogLevel::Error, }
("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n",
this, status)); int lastRow = -1;
return Transition::TerminateFailure(); int width = 0;
} int height = 0;
int stride = 0;
int lastRow = -1; uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
int width = 0;
int height = 0; MOZ_LOG(sWebPLog, LogLevel::Debug,
int stride = 0; ("[this=%p] nsWebPDecoder::ReadSingle -- complete %d, read %d rows, "
const uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride); "has %d rows available\n", this, complete, mLastRow, lastRow));
if (!rowStart || lastRow == -1) {
return Transition::ContinueUnbuffered(State::WEBP_DATA); if (!rowStart || lastRow == -1 || lastRow == mLastRow) {
} return LexerResult(Yield::NEED_MORE_DATA);
}
if (width <= 0 || height <= 0 || stride <= 0) {
MOZ_LOG(sWebPLog, LogLevel::Error, if (width != mFrameRect.width || height != mFrameRect.height ||
("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, %d)\n", stride < mFrameRect.width * 4 ||
this, width, height, stride)); lastRow > mFrameRect.height) {
return Transition::TerminateFailure(); MOZ_LOG(sWebPLog, LogLevel::Error,
} ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, %d)\n",
this, width, height, stride));
for (int row = mLastRow; row < lastRow; row++) { return LexerResult(TerminalState::FAILURE);
const uint8_t* src = rowStart + row * stride; }
auto result = mPipe.WritePixelsToRow<uint32_t>([&]() -> NextPixel<uint32_t> {
MOZ_ASSERT(mFormat == SurfaceFormat::B8G8R8A8 || src[3] == 0xFF); const bool noPremultiply =
const uint32_t pixel = gfxPackedPixel(src[3], src[0], src[1], src[2]); bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
src += 4;
return AsVariant(pixel); for (int row = mLastRow; row < lastRow; row++) {
}); uint8_t* src = rowStart + row * stride;
MOZ_ASSERT(result != WriteState::FAILURE); if (mTransform) {
MOZ_ASSERT_IF(result == WriteState::FINISHED, complete && row == lastRow - 1); qcms_transform_data(mTransform, src, src, width);
}
if (result == WriteState::FAILURE) {
MOZ_LOG(sWebPLog, LogLevel::Error, WriteState result;
("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n", if (noPremultiply) {
this)); result = mPipe.WritePixelsToRow<uint32_t>([&]() -> NextPixel<uint32_t> {
return Transition::TerminateFailure(); MOZ_ASSERT(mFormat == SurfaceFormat::B8G8R8A8 || src[3] == 0xFF);
const uint32_t pixel =
gfxPackedPixelNoPreMultiply(src[3], src[0], src[1], src[2]);
src += 4;
return AsVariant(pixel);
});
} else {
result = mPipe.WritePixelsToRow<uint32_t>([&]() -> NextPixel<uint32_t> {
MOZ_ASSERT(mFormat == SurfaceFormat::B8G8R8A8 || src[3] == 0xFF);
const uint32_t pixel = gfxPackedPixel(src[3], src[0], src[1], src[2]);
src += 4;
return AsVariant(pixel);
});
}
Maybe<SurfaceInvalidRect> 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;
}
} }
}
if (mLastRow != lastRow) {
mLastRow = lastRow; mLastRow = lastRow;
} while (!complete);
Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
if (invalidRect) {
PostInvalidation(invalidRect->mInputSpaceRect,
Some(invalidRect->mOutputSpaceRect));
}
}
if (!complete) { if (!complete) {
return Transition::ContinueUnbuffered(State::WEBP_DATA); return LexerResult(Yield::NEED_MORE_DATA);
} }
EndFrame(); EndFrame();
return Transition::TerminateSuccess(); return LexerResult(TerminalState::SUCCESS);
} }
LexerTransition<nsWebPDecoder::State> LexerResult
nsWebPDecoder::ReadMultiple(const uint8_t* aData, size_t aLength) nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer, bool aIsComplete)
{ {
MOZ_ASSERT(!IsMetadataDecode()); MOZ_ASSERT(!IsMetadataDecode());
MOZ_ASSERT(aData); MOZ_ASSERT(aDemuxer);
MOZ_LOG(sWebPLog, LogLevel::Debug, MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::ReadMultiple -- %zu bytes\n", this, aLength)); ("[this=%p] nsWebPDecoder::ReadMultiple\n", this));
auto data = aData; bool complete = aIsComplete;
size_t length = aLength;
if (NS_FAILED(GetDataBuffer(data, length))) {
return Transition::TerminateFailure();
}
WebPDemuxState state;
WebPData fragment;
fragment.bytes = data;
fragment.size = length;
WebPDemuxer* demuxer = WebPDemuxPartial(&fragment, &state);
if (!demuxer) {
MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::ReadMultiple -- create demuxer error\n",
this));
return Transition::TerminateFailure();
}
if (state == WEBP_DEMUX_PARSE_ERROR) {
MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::ReadMultiple -- demuxer parse error\n",
this));
WebPDemuxDelete(demuxer);
return Transition::TerminateFailure();
}
bool complete = false;
WebPIterator iter; WebPIterator iter;
auto rv = Transition::ContinueUnbuffered(State::WEBP_DATA); auto rv = LexerResult(Yield::NEED_MORE_DATA);
if (WebPDemuxGetFrame(demuxer, mCurrentFrame + 1, &iter)) { if (WebPDemuxGetFrame(aDemuxer, mCurrentFrame + 1, &iter)) {
switch (iter.blend_method) { switch (iter.blend_method) {
case WEBP_MUX_BLEND: case WEBP_MUX_BLEND:
mBlend = BlendMethod::OVER; mBlend = BlendMethod::OVER;
@ -418,51 +552,34 @@ nsWebPDecoder::ReadMultiple(const uint8_t* aData, size_t aLength)
break; break;
} }
mFormat = iter.has_alpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8; mFormat = iter.has_alpha || mCurrentFrame > 0 ? SurfaceFormat::B8G8R8A8
: SurfaceFormat::B8G8R8X8;
mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration); mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration);
nsIntRect frameRect(iter.x_offset, iter.y_offset, iter.width, iter.height); nsIntRect frameRect(iter.x_offset, iter.y_offset, iter.width, iter.height);
rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, false, frameRect); rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, frameRect);
complete = state == WEBP_DEMUX_DONE && !WebPDemuxNextFrame(&iter); complete = complete && !WebPDemuxNextFrame(&iter);
WebPDemuxReleaseIterator(&iter); WebPDemuxReleaseIterator(&iter);
} }
if (rv.NextStateIsTerminal()) { if (rv.is<TerminalState>() &&
if (rv.NextStateAsTerminal() == TerminalState::SUCCESS) { rv.as<TerminalState>() == TerminalState::SUCCESS) {
// If we extracted one frame, and it is not the last, we need to yield to // 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. // the lexer to allow the upper layers to acknowledge the frame.
if (!complete && !IsFirstFrameDecode()) { if (!complete && !IsFirstFrameDecode()) {
// The resume point is determined by whether or not we had to buffer. rv = LexerResult(Yield::OUTPUT_AVAILABLE);
// If we have yet to buffer, we want to resume at the same point, } else {
// otherwise our internal buffer has everything we need and we want uint32_t loopCount = WebPDemuxGetI(aDemuxer, WEBP_FF_LOOP_COUNT);
// to resume having consumed all of the current fragment.
rv = Transition::ContinueUnbufferedAfterYield(State::WEBP_DATA,
mData.empty() ? 0 : aLength);
} else {
uint32_t loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
MOZ_LOG(sWebPLog, LogLevel::Debug, MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::ReadMultiple -- loop count %u\n", ("[this=%p] nsWebPDecoder::ReadMultiple -- loop count %u\n",
this, loopCount)); this, loopCount));
PostDecodeDone(loopCount - 1); PostDecodeDone(loopCount - 1);
}
} }
} else if (NS_FAILED(SaveDataBuffer(data, length))) {
rv = Transition::TerminateFailure();
} }
WebPDemuxDelete(demuxer);
return rv; return rv;
} }
LexerTransition<nsWebPDecoder::State>
nsWebPDecoder::FinishedData()
{
// 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();
}
} // namespace image } // namespace image
} // namespace mozilla } // namespace mozilla

View file

@ -16,7 +16,7 @@ namespace mozilla {
namespace image { namespace image {
class RasterImage; class RasterImage;
class nsWebPDecoder : public Decoder class nsWebPDecoder final : public Decoder
{ {
public: public:
virtual ~nsWebPDecoder(); virtual ~nsWebPDecoder();
@ -31,34 +31,28 @@ private:
// Decoders should only be instantiated via DecoderFactory. // Decoders should only be instantiated via DecoderFactory.
explicit nsWebPDecoder(RasterImage* aImage); explicit nsWebPDecoder(RasterImage* aImage);
enum class State void ApplyColorProfile(const char* aProfile, size_t aLength);
{
WEBP_DATA,
FINISHED_WEBP_DATA
};
LexerTransition<State> ReadHeader(const char* aData, size_t aLength); LexerResult UpdateBuffer(SourceBufferIterator& aIterator,
LexerTransition<State> ReadPayload(const char* aData, size_t aLength); SourceBufferIterator::State aState);
LexerTransition<State> FinishedData(); LexerResult ReadData();
LexerResult ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete);
LexerResult ReadPayload(WebPDemuxer* aDemuxer, bool aIsComplete);
nsresult CreateFrame(const nsIntRect& aFrameRect); nsresult CreateFrame(const nsIntRect& aFrameRect);
void EndFrame(); void EndFrame();
nsresult GetDataBuffer(const uint8_t*& aData, size_t& aLength); LexerResult ReadSingle(const uint8_t* aData, size_t aLength,
nsresult SaveDataBuffer(const uint8_t* aData, size_t aLength); const IntRect& aFrameRect);
LexerTransition<State> ReadSingle(const uint8_t* aData, size_t aLength, LexerResult ReadMultiple(WebPDemuxer* aDemuxer, bool aIsComplete);
bool aAppend, const IntRect& aFrameRect);
LexerTransition<State> ReadMultiple(const uint8_t* aData, size_t aLength);
StreamingLexer<State> mLexer;
/// The SurfacePipe used to write to the output surface. /// The SurfacePipe used to write to the output surface.
SurfacePipe mPipe; SurfacePipe mPipe;
/// The buffer used to accumulate data until the complete WebP header is received. /// The buffer used to accumulate data until the complete WebP header is
Vector<uint8_t> mData; /// received, if and only if the iterator is discontiguous.
Vector<uint8_t> mBufferedData;
/// The libwebp output buffer descriptor pointing to the decoded data. /// The libwebp output buffer descriptor pointing to the decoded data.
WebPDecBuffer mBuffer; WebPDecBuffer mBuffer;
@ -78,11 +72,35 @@ private:
/// Surface format for the current frame. /// Surface format for the current frame.
gfx::SurfaceFormat mFormat; gfx::SurfaceFormat mFormat;
/// Frame rect for the current frame.
IntRect mFrameRect;
/// The last row of decoded pixels written to mPipe. /// The last row of decoded pixels written to mPipe.
int mLastRow; int mLastRow;
/// Number of decoded frames. /// Number of decoded frames.
uint32_t mCurrentFrame; uint32_t mCurrentFrame;
/// Pointer to the start of the contiguous encoded image data.
const uint8_t* mData;
/// Length of data pointed to by mData.
size_t mLength;
/// True if the iterator has reached its end.
bool mIteratorComplete;
/// True if this decoding pass requires a WebPDemuxer.
bool mNeedDemuxer;
/// True if we have setup the color profile for the image.
bool mGotColorProfile;
/// Color management profile from the ICCP chunk in the image.
qcms_profile* mInProfile;
/// Color management transform to apply to image data.
qcms_transform* mTransform;
}; };
} // namespace image } // namespace image