mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-02 05:17:31 +09:00
Implement WebP image decoder.
This commit is contained in:
parent
85b4e45fa7
commit
9121db73a2
10 changed files with 579 additions and 2 deletions
|
|
@ -444,6 +444,7 @@ private:
|
||||||
DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor", ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
|
DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor", ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
|
||||||
DECL_GFX_PREF(Live, "image.mozsamplesize.enabled", ImageMozSampleSizeEnabled, bool, false);
|
DECL_GFX_PREF(Live, "image.mozsamplesize.enabled", ImageMozSampleSizeEnabled, bool, false);
|
||||||
DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1);
|
DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1);
|
||||||
|
DECL_GFX_PREF(Live, "image.webp.enabled", ImageWebPEnabled, bool, true);
|
||||||
|
|
||||||
DECL_GFX_PREF(Once, "layers.acceleration.disabled", LayersAccelerationDisabledDoNotUseDirectly, bool, false);
|
DECL_GFX_PREF(Once, "layers.acceleration.disabled", LayersAccelerationDisabledDoNotUseDirectly, bool, false);
|
||||||
DECL_GFX_PREF(Live, "layers.acceleration.draw-fps", LayersDrawFPS, bool, false);
|
DECL_GFX_PREF(Live, "layers.acceleration.draw-fps", LayersDrawFPS, bool, false);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "DecoderFactory.h"
|
#include "DecoderFactory.h"
|
||||||
|
|
||||||
|
#include "gfxPrefs.h"
|
||||||
#include "nsMimeTypes.h"
|
#include "nsMimeTypes.h"
|
||||||
#include "mozilla/RefPtr.h"
|
#include "mozilla/RefPtr.h"
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@
|
||||||
#include "nsBMPDecoder.h"
|
#include "nsBMPDecoder.h"
|
||||||
#include "nsICODecoder.h"
|
#include "nsICODecoder.h"
|
||||||
#include "nsIconDecoder.h"
|
#include "nsIconDecoder.h"
|
||||||
|
#include "nsWebPDecoder.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
|
|
@ -65,8 +67,12 @@ DecoderFactory::GetDecoderType(const char* aMimeType)
|
||||||
// Icon
|
// Icon
|
||||||
} else if (!strcmp(aMimeType, IMAGE_ICON_MS)) {
|
} else if (!strcmp(aMimeType, IMAGE_ICON_MS)) {
|
||||||
type = DecoderType::ICON;
|
type = DecoderType::ICON;
|
||||||
}
|
|
||||||
|
|
||||||
|
// WebP
|
||||||
|
} else if (!strcmp(aMimeType, IMAGE_WEBP) &&
|
||||||
|
gfxPrefs::ImageWebPEnabled()) {
|
||||||
|
type = DecoderType::WEBP;
|
||||||
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +106,9 @@ DecoderFactory::GetDecoder(DecoderType aType,
|
||||||
case DecoderType::ICON:
|
case DecoderType::ICON:
|
||||||
decoder = new nsIconDecoder(aImage);
|
decoder = new nsIconDecoder(aImage);
|
||||||
break;
|
break;
|
||||||
|
case DecoderType::WEBP:
|
||||||
|
decoder = new nsWebPDecoder(aImage);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
|
MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +180,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
|
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG ||
|
||||||
|
aType == DecoderType::WEBP,
|
||||||
"Calling CreateAnimationDecoder for non-animating DecoderType");
|
"Calling CreateAnimationDecoder for non-animating DecoderType");
|
||||||
|
|
||||||
// Create an anonymous decoder. Interaction with the SurfaceCache and the
|
// Create an anonymous decoder. Interaction with the SurfaceCache and the
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ enum class DecoderType
|
||||||
BMP,
|
BMP,
|
||||||
ICO,
|
ICO,
|
||||||
ICON,
|
ICON,
|
||||||
|
WEBP,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ static const mozilla::Module::CategoryEntry kImageCategories[] = {
|
||||||
{ "Gecko-Content-Viewers", IMAGE_PNG, "@mozilla.org/content/document-loader-factory;1" },
|
{ "Gecko-Content-Viewers", IMAGE_PNG, "@mozilla.org/content/document-loader-factory;1" },
|
||||||
{ "Gecko-Content-Viewers", IMAGE_APNG, "@mozilla.org/content/document-loader-factory;1" },
|
{ "Gecko-Content-Viewers", IMAGE_APNG, "@mozilla.org/content/document-loader-factory;1" },
|
||||||
{ "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" },
|
{ "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" },
|
||||||
|
{ "Gecko-Content-Viewers", IMAGE_WEBP, "@mozilla.org/content/document-loader-factory;1" },
|
||||||
{ "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
|
{ "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
|
||||||
{ nullptr }
|
{ nullptr }
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ UNIFIED_SOURCES += [
|
||||||
'nsIconDecoder.cpp',
|
'nsIconDecoder.cpp',
|
||||||
'nsJPEGDecoder.cpp',
|
'nsJPEGDecoder.cpp',
|
||||||
'nsPNGDecoder.cpp',
|
'nsPNGDecoder.cpp',
|
||||||
|
'nsWebPDecoder.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
include('/ipc/chromium/chromium-config.mozbuild')
|
include('/ipc/chromium/chromium-config.mozbuild')
|
||||||
|
|
|
||||||
467
image/decoders/nsWebPDecoder.cpp
Normal file
467
image/decoders/nsWebPDecoder.cpp
Normal file
|
|
@ -0,0 +1,467 @@
|
||||||
|
/* -*- 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 "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)
|
||||||
|
, mLexer(Transition::ToUnbuffered(State::FINISHED_WEBP_DATA,
|
||||||
|
State::WEBP_DATA,
|
||||||
|
SIZE_MAX),
|
||||||
|
Transition::TerminateSuccess())
|
||||||
|
, mDecoder(nullptr)
|
||||||
|
, mBlend(BlendMethod::OVER)
|
||||||
|
, mDisposal(DisposalMethod::KEEP)
|
||||||
|
, mTimeout(FrameTimeout::Forever())
|
||||||
|
, mFormat(SurfaceFormat::B8G8R8X8)
|
||||||
|
, mLastRow(0)
|
||||||
|
, mCurrentFrame(0)
|
||||||
|
{
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||||
|
("[this=%p] nsWebPDecoder::nsWebPDecoder", this));
|
||||||
|
}
|
||||||
|
|
||||||
|
nsWebPDecoder::~nsWebPDecoder()
|
||||||
|
{
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||||
|
("[this=%p] nsWebPDecoder::~nsWebPDecoder", this));
|
||||||
|
WebPIDelete(mDecoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
LexerResult
|
||||||
|
nsWebPDecoder::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::WEBP_DATA:
|
||||||
|
if (!HasSize()) {
|
||||||
|
return ReadHeader(aData, aLength);
|
||||||
|
}
|
||||||
|
return ReadPayload(aData, aLength);
|
||||||
|
case State::FINISHED_WEBP_DATA:
|
||||||
|
return FinishedData();
|
||||||
|
}
|
||||||
|
MOZ_CRASH("Unknown State");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(HasSize());
|
||||||
|
MOZ_ASSERT(!mDecoder);
|
||||||
|
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||||
|
("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, %d x %d\n",
|
||||||
|
this, mCurrentFrame, aFrameRect.width, aFrameRect.height));
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
PostHasTransparency();
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPInitDecBuffer(&mBuffer);
|
||||||
|
mBuffer.colorspace = MODE_RGBA;
|
||||||
|
|
||||||
|
mDecoder = WebPINewDecoder(&mBuffer);
|
||||||
|
if (!mDecoder) {
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||||
|
("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n",
|
||||||
|
this));
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(this,
|
||||||
|
mCurrentFrame, Size(), OutputSize(), aFrameRect,
|
||||||
|
mFormat, SurfacePipeFlags());
|
||||||
|
if (!pipe) {
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||||
|
("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPipe = Move(*pipe);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsWebPDecoder::EndFrame()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(HasSize());
|
||||||
|
MOZ_ASSERT(mDecoder);
|
||||||
|
|
||||||
|
auto opacity = mFormat == SurfaceFormat::B8G8R8A8
|
||||||
|
? 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, mDisposal, mTimeout, mBlend);
|
||||||
|
WebPIDelete(mDecoder);
|
||||||
|
mDecoder = nullptr;
|
||||||
|
mLastRow = 0;
|
||||||
|
++mCurrentFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
nsWebPDecoder::GetDataBuffer(const uint8_t*& aData, size_t& aLength)
|
||||||
|
{
|
||||||
|
if (!mData.empty() && mData.begin() != aData) {
|
||||||
|
if (!mData.append(aData, aLength)) {
|
||||||
|
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
|
||||||
|
nsWebPDecoder::SaveDataBuffer(const uint8_t* aData, size_t aLength)
|
||||||
|
{
|
||||||
|
if (mData.empty() && !mData.append(aData, aLength)) {
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||||
|
("[this=%p] nsWebPDecoder::SaveDataBuffer -- oom, append %zu on %zu\n",
|
||||||
|
this, aLength, mData.length()));
|
||||||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
LexerTransition<nsWebPDecoder::State>
|
||||||
|
nsWebPDecoder::ReadHeader(const char* aData, size_t aLength)
|
||||||
|
{
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, aLength));
|
||||||
|
|
||||||
|
// XXX(aosmond): In an ideal world, we could request the lexer to do this
|
||||||
|
// 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;
|
||||||
|
VP8StatusCode status = WebPGetFeatures(data, length, &features);
|
||||||
|
switch (status) {
|
||||||
|
case VP8_STATUS_OK:
|
||||||
|
break;
|
||||||
|
case VP8_STATUS_NOT_ENOUGH_DATA:
|
||||||
|
if (NS_FAILED(SaveDataBuffer(data, length))) {
|
||||||
|
return Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
return Transition::ContinueUnbuffered(State::WEBP_DATA);
|
||||||
|
default:
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadHeader -- parse error %d\n",
|
||||||
|
this, status));
|
||||||
|
return Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (features.has_animation) {
|
||||||
|
// A metadata decode expects to get the correct first frame timeout which
|
||||||
|
// 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;
|
||||||
|
if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
|
||||||
|
WebPDemuxDelete(demuxer);
|
||||||
|
if (state == WEBP_DEMUX_DONE) {
|
||||||
|
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));
|
||||||
|
WebPDemuxReleaseIterator(&iter);
|
||||||
|
WebPDemuxDelete(demuxer);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadHeader -- %d x %d, alpha %d, "
|
||||||
|
"animation %d, format %d, metadata decode %d, first frame decode %d\n",
|
||||||
|
this, features.width, features.height, features.has_alpha,
|
||||||
|
features.has_animation, features.format, IsMetadataDecode(),
|
||||||
|
IsFirstFrameDecode()));
|
||||||
|
|
||||||
|
PostSize(features.width, features.height);
|
||||||
|
if (features.has_alpha) {
|
||||||
|
mFormat = SurfaceFormat::B8G8R8A8;
|
||||||
|
PostHasTransparency();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsMetadataDecode()) {
|
||||||
|
return Transition::TerminateSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transition = ReadPayload((const char*)data, length);
|
||||||
|
if (!features.has_animation) {
|
||||||
|
mData.clearAndFree();
|
||||||
|
}
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
LexerTransition<nsWebPDecoder::State>
|
||||||
|
nsWebPDecoder::ReadPayload(const char* aData, size_t aLength)
|
||||||
|
{
|
||||||
|
auto data = (const uint8_t*)aData;
|
||||||
|
if (!HasAnimation()) {
|
||||||
|
auto rv = ReadSingle(data, aLength, true, FullFrame());
|
||||||
|
if (rv.NextStateIsTerminal() &&
|
||||||
|
rv.NextStateAsTerminal() == TerminalState::SUCCESS) {
|
||||||
|
PostDecodeDone();
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
return ReadMultiple(data, aLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
LexerTransition<nsWebPDecoder::State>
|
||||||
|
nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, bool aAppend, const IntRect& 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 Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
VP8StatusCode status;
|
||||||
|
if (aAppend) {
|
||||||
|
status = WebPIAppend(mDecoder, aData, aLength);
|
||||||
|
} else {
|
||||||
|
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 Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastRow = -1;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int stride = 0;
|
||||||
|
const uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
|
||||||
|
if (!rowStart || lastRow == -1) {
|
||||||
|
return Transition::ContinueUnbuffered(State::WEBP_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0 || stride <= 0) {
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, %d)\n",
|
||||||
|
this, width, height, stride));
|
||||||
|
return Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = mLastRow; row < lastRow; row++) {
|
||||||
|
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 uint32_t pixel = gfxPackedPixel(src[3], src[0], src[1], src[2]);
|
||||||
|
src += 4;
|
||||||
|
return AsVariant(pixel);
|
||||||
|
});
|
||||||
|
MOZ_ASSERT(result != WriteState::FAILURE);
|
||||||
|
MOZ_ASSERT_IF(result == WriteState::FINISHED, complete && row == lastRow - 1);
|
||||||
|
|
||||||
|
if (result == WriteState::FAILURE) {
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n",
|
||||||
|
this));
|
||||||
|
return Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mLastRow != lastRow) {
|
||||||
|
mLastRow = lastRow;
|
||||||
|
|
||||||
|
Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
|
||||||
|
if (invalidRect) {
|
||||||
|
PostInvalidation(invalidRect->mInputSpaceRect,
|
||||||
|
Some(invalidRect->mOutputSpaceRect));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!complete) {
|
||||||
|
return Transition::ContinueUnbuffered(State::WEBP_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndFrame();
|
||||||
|
return Transition::TerminateSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
LexerTransition<nsWebPDecoder::State>
|
||||||
|
nsWebPDecoder::ReadMultiple(const uint8_t* aData, size_t aLength)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(!IsMetadataDecode());
|
||||||
|
MOZ_ASSERT(aData);
|
||||||
|
|
||||||
|
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadMultiple -- %zu bytes\n", this, aLength));
|
||||||
|
|
||||||
|
auto data = aData;
|
||||||
|
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;
|
||||||
|
auto rv = Transition::ContinueUnbuffered(State::WEBP_DATA);
|
||||||
|
if (WebPDemuxGetFrame(demuxer, 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 ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
|
||||||
|
mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration);
|
||||||
|
nsIntRect frameRect(iter.x_offset, iter.y_offset, iter.width, iter.height);
|
||||||
|
|
||||||
|
rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, false, frameRect);
|
||||||
|
complete = state == WEBP_DEMUX_DONE && !WebPDemuxNextFrame(&iter);
|
||||||
|
WebPDemuxReleaseIterator(&iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv.NextStateIsTerminal()) {
|
||||||
|
if (rv.NextStateAsTerminal() == 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()) {
|
||||||
|
// The resume point is determined by whether or not we had to buffer.
|
||||||
|
// If we have yet to buffer, we want to resume at the same point,
|
||||||
|
// otherwise our internal buffer has everything we need and we want
|
||||||
|
// 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,
|
||||||
|
("[this=%p] nsWebPDecoder::ReadMultiple -- loop count %u\n",
|
||||||
|
this, loopCount));
|
||||||
|
PostDecodeDone(loopCount - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (NS_FAILED(SaveDataBuffer(data, length))) {
|
||||||
|
rv = Transition::TerminateFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPDemuxDelete(demuxer);
|
||||||
|
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 mozilla
|
||||||
91
image/decoders/nsWebPDecoder.h
Normal file
91
image/decoders/nsWebPDecoder.h
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* -*- 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/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_image_decoders_nsWebPDecoder_h
|
||||||
|
#define mozilla_image_decoders_nsWebPDecoder_h
|
||||||
|
|
||||||
|
#include "Decoder.h"
|
||||||
|
#include "webp/demux.h"
|
||||||
|
#include "StreamingLexer.h"
|
||||||
|
#include "SurfacePipe.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace image {
|
||||||
|
class RasterImage;
|
||||||
|
|
||||||
|
class nsWebPDecoder : public Decoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~nsWebPDecoder();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||||
|
IResumable* aOnResume) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class DecoderFactory;
|
||||||
|
|
||||||
|
// Decoders should only be instantiated via DecoderFactory.
|
||||||
|
explicit nsWebPDecoder(RasterImage* aImage);
|
||||||
|
|
||||||
|
enum class State
|
||||||
|
{
|
||||||
|
WEBP_DATA,
|
||||||
|
FINISHED_WEBP_DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
LexerTransition<State> ReadHeader(const char* aData, size_t aLength);
|
||||||
|
LexerTransition<State> ReadPayload(const char* aData, size_t aLength);
|
||||||
|
LexerTransition<State> FinishedData();
|
||||||
|
|
||||||
|
nsresult CreateFrame(const nsIntRect& aFrameRect);
|
||||||
|
void EndFrame();
|
||||||
|
|
||||||
|
nsresult GetDataBuffer(const uint8_t*& aData, size_t& aLength);
|
||||||
|
nsresult SaveDataBuffer(const uint8_t* aData, size_t aLength);
|
||||||
|
|
||||||
|
LexerTransition<State> ReadSingle(const uint8_t* aData, size_t aLength,
|
||||||
|
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.
|
||||||
|
SurfacePipe mPipe;
|
||||||
|
|
||||||
|
/// The buffer used to accumulate data until the complete WebP header is received.
|
||||||
|
Vector<uint8_t> mData;
|
||||||
|
|
||||||
|
/// The libwebp output buffer descriptor pointing to the decoded data.
|
||||||
|
WebPDecBuffer mBuffer;
|
||||||
|
|
||||||
|
/// The libwebp incremental decoder descriptor, wraps mBuffer.
|
||||||
|
WebPIDecoder* mDecoder;
|
||||||
|
|
||||||
|
/// Blend method for the current frame.
|
||||||
|
BlendMethod mBlend;
|
||||||
|
|
||||||
|
/// Disposal method for the current frame.
|
||||||
|
DisposalMethod mDisposal;
|
||||||
|
|
||||||
|
/// Frame timeout for the current frame;
|
||||||
|
FrameTimeout mTimeout;
|
||||||
|
|
||||||
|
/// Surface format for the current frame.
|
||||||
|
gfx::SurfaceFormat mFormat;
|
||||||
|
|
||||||
|
/// The last row of decoded pixels written to mPipe.
|
||||||
|
int mLastRow;
|
||||||
|
|
||||||
|
/// Number of decoded frames.
|
||||||
|
uint32_t mCurrentFrame;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace image
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_image_decoders_nsWebPDecoder_h
|
||||||
|
|
@ -4445,6 +4445,9 @@ pref("image.mem.surfacecache.discard_factor", 1);
|
||||||
// automatically determined based on the system's number of cores.
|
// automatically determined based on the system's number of cores.
|
||||||
pref("image.multithreaded_decoding.limit", -1);
|
pref("image.multithreaded_decoding.limit", -1);
|
||||||
|
|
||||||
|
// Whether we attempt to decode WebP images or not.
|
||||||
|
pref("image.webp.enabled", true);
|
||||||
|
|
||||||
// Limit for the canvas image cache. 0 means we don't limit the size of the
|
// Limit for the canvas image cache. 0 means we don't limit the size of the
|
||||||
// cache.
|
// cache.
|
||||||
pref("canvas.image.cache.limit", 0);
|
pref("canvas.image.cache.limit", 0);
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@
|
||||||
#define IMAGE_MNG "video/x-mng"
|
#define IMAGE_MNG "video/x-mng"
|
||||||
#define IMAGE_JNG "image/x-jng"
|
#define IMAGE_JNG "image/x-jng"
|
||||||
#define IMAGE_SVG_XML "image/svg+xml"
|
#define IMAGE_SVG_XML "image/svg+xml"
|
||||||
|
#define IMAGE_WEBP "image/webp"
|
||||||
|
|
||||||
#define MESSAGE_EXTERNAL_BODY "message/external-body"
|
#define MESSAGE_EXTERNAL_BODY "message/external-body"
|
||||||
#define MESSAGE_NEWS "message/news"
|
#define MESSAGE_NEWS "message/news"
|
||||||
|
|
|
||||||
|
|
@ -536,6 +536,7 @@ static const nsExtraMimeTypeEntry extraMimeEntries[] =
|
||||||
{ IMAGE_TIFF, "tiff,tif", "TIFF Image" },
|
{ IMAGE_TIFF, "tiff,tif", "TIFF Image" },
|
||||||
{ IMAGE_XBM, "xbm", "XBM Image" },
|
{ IMAGE_XBM, "xbm", "XBM Image" },
|
||||||
{ IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
|
{ IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
|
||||||
|
{ IMAGE_WEBP, "webp", "WebP Image" },
|
||||||
{ MESSAGE_RFC822, "eml", "RFC-822 data" },
|
{ MESSAGE_RFC822, "eml", "RFC-822 data" },
|
||||||
{ TEXT_PLAIN, "txt,text", "Text File" },
|
{ TEXT_PLAIN, "txt,text", "Text File" },
|
||||||
{ TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
|
{ TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue