mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
422 lines
12 KiB
C++
422 lines
12 KiB
C++
|
|
/* -*- 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 "Decoder.h"
|
|
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "imgIContainer.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "nsIScriptError.h"
|
|
#include "GeckoProfiler.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
|
|
using mozilla::gfx::IntSize;
|
|
using mozilla::gfx::SurfaceFormat;
|
|
|
|
namespace mozilla {
|
|
namespace image {
|
|
|
|
Decoder::Decoder(RasterImage &aImage)
|
|
: mImage(aImage)
|
|
, mProgress(NoProgress)
|
|
, mImageData(nullptr)
|
|
, mColormap(nullptr)
|
|
, mChunkCount(0)
|
|
, mDecodeFlags(0)
|
|
, mBytesDecoded(0)
|
|
, mDecodeDone(false)
|
|
, mDataError(false)
|
|
, mFrameCount(0)
|
|
, mFailCode(NS_OK)
|
|
, mInitialized(false)
|
|
, mSizeDecode(false)
|
|
, mInFrame(false)
|
|
, mIsAnimated(false)
|
|
{ }
|
|
|
|
Decoder::~Decoder()
|
|
{
|
|
MOZ_ASSERT(mProgress == NoProgress,
|
|
"Destroying Decoder without taking all its progress changes");
|
|
MOZ_ASSERT(mInvalidRect.IsEmpty(),
|
|
"Destroying Decoder without taking all its invalidations");
|
|
mInitialized = false;
|
|
}
|
|
|
|
/*
|
|
* Common implementation of the decoder interface.
|
|
*/
|
|
|
|
void
|
|
Decoder::Init()
|
|
{
|
|
// No re-initializing
|
|
NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
|
|
|
|
// Fire OnStartDecode at init time to support bug 512435.
|
|
if (!IsSizeDecode()) {
|
|
mProgress |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED;
|
|
}
|
|
|
|
// Implementation-specific initialization
|
|
InitInternal();
|
|
|
|
mInitialized = true;
|
|
}
|
|
|
|
void
|
|
Decoder::Write(const char* aBuffer, uint32_t aCount)
|
|
{
|
|
PROFILER_LABEL("ImageDecoder", "Write",
|
|
js::ProfileEntry::Category::GRAPHICS);
|
|
|
|
MOZ_ASSERT(aBuffer, "Should have a buffer");
|
|
MOZ_ASSERT(aCount > 0, "Should have a non-zero count");
|
|
|
|
// We're strict about decoder errors
|
|
MOZ_ASSERT(!HasDecoderError(),
|
|
"Not allowed to make more decoder calls after error!");
|
|
|
|
// Begin recording telemetry data.
|
|
TimeStamp start = TimeStamp::Now();
|
|
mChunkCount++;
|
|
|
|
// Keep track of the total number of bytes written.
|
|
mBytesDecoded += aCount;
|
|
|
|
// If a data error occured, just ignore future data.
|
|
if (HasDataError())
|
|
return;
|
|
|
|
if (IsSizeDecode() && HasSize()) {
|
|
// More data came in since we found the size. We have nothing to do here.
|
|
return;
|
|
}
|
|
|
|
// Pass the data along to the implementation.
|
|
WriteInternal(aBuffer, aCount);
|
|
|
|
// Finish telemetry.
|
|
mDecodeTime += (TimeStamp::Now() - start);
|
|
}
|
|
|
|
void
|
|
Decoder::Finish(ShutdownReason aReason)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Implementation-specific finalization
|
|
if (!HasError())
|
|
FinishInternal();
|
|
|
|
// If the implementation left us mid-frame, finish that up.
|
|
if (mInFrame && !HasError())
|
|
PostFrameStop();
|
|
|
|
// If PostDecodeDone() has not been called, we need to sent teardown
|
|
// notifications.
|
|
if (!IsSizeDecode() && !mDecodeDone) {
|
|
|
|
// Log data errors to the error console
|
|
nsCOMPtr<nsIConsoleService> consoleService =
|
|
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
|
nsCOMPtr<nsIScriptError> errorObject =
|
|
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
|
|
|
if (consoleService && errorObject && !HasDecoderError()) {
|
|
nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated: ") +
|
|
NS_ConvertUTF8toUTF16(mImage.GetURIString()));
|
|
|
|
if (NS_SUCCEEDED(errorObject->InitWithWindowID(
|
|
msg,
|
|
NS_ConvertUTF8toUTF16(mImage.GetURIString()),
|
|
EmptyString(), 0, 0, nsIScriptError::errorFlag,
|
|
"Image", mImage.InnerWindowID()
|
|
))) {
|
|
consoleService->LogMessage(errorObject);
|
|
}
|
|
}
|
|
|
|
bool usable = !HasDecoderError();
|
|
if (aReason != ShutdownReason::NOT_NEEDED && !HasDecoderError()) {
|
|
// If we only have a data error, we're usable if we have at least one complete frame.
|
|
if (GetCompleteFrameCount() == 0) {
|
|
usable = false;
|
|
}
|
|
}
|
|
|
|
// If we're usable, do exactly what we should have when the decoder
|
|
// completed.
|
|
if (usable) {
|
|
if (mInFrame) {
|
|
PostFrameStop();
|
|
}
|
|
PostDecodeDone();
|
|
} else {
|
|
if (!IsSizeDecode()) {
|
|
mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
|
|
}
|
|
mProgress |= FLAG_HAS_ERROR;
|
|
}
|
|
}
|
|
|
|
// Set image metadata before calling DecodingComplete, because
|
|
// DecodingComplete calls Optimize().
|
|
mImageMetadata.SetOnImage(&mImage);
|
|
|
|
if (mDecodeDone) {
|
|
MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
|
|
mImage.DecodingComplete(mCurrentFrame.get(), mIsAnimated);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
Decoder::AllocateFrame(uint32_t aFrameNum,
|
|
const nsIntRect& aFrameRect,
|
|
gfx::SurfaceFormat aFormat,
|
|
uint8_t aPaletteDepth /* = 0 */)
|
|
{
|
|
mCurrentFrame = AllocateFrameInternal(aFrameNum, aFrameRect, mDecodeFlags,
|
|
aFormat, aPaletteDepth,
|
|
mCurrentFrame.get());
|
|
|
|
if (mCurrentFrame) {
|
|
// Gather the raw pointers the decoders will use.
|
|
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
|
|
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
|
|
|
|
if (aFrameNum + 1 == mFrameCount) {
|
|
PostFrameStart();
|
|
}
|
|
} else {
|
|
PostDataError();
|
|
}
|
|
|
|
return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RawAccessFrameRef
|
|
Decoder::AllocateFrameInternal(uint32_t aFrameNum,
|
|
const nsIntRect& aFrameRect,
|
|
uint32_t aDecodeFlags,
|
|
SurfaceFormat aFormat,
|
|
uint8_t aPaletteDepth,
|
|
imgFrame* aPreviousFrame)
|
|
{
|
|
if (mDataError || NS_FAILED(mFailCode)) {
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
if (aFrameNum != mFrameCount) {
|
|
MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
MOZ_ASSERT(mImageMetadata.HasSize());
|
|
nsIntSize imageSize(mImageMetadata.GetWidth(), mImageMetadata.GetHeight());
|
|
if (imageSize.width <= 0 || imageSize.height <= 0 ||
|
|
aFrameRect.width <= 0 || aFrameRect.height <= 0) {
|
|
NS_WARNING("Trying to add frame with zero or negative size");
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
if (!SurfaceCache::CanHold(imageSize.ToIntSize())) {
|
|
NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
nsRefPtr<imgFrame> frame = new imgFrame();
|
|
bool nonPremult =
|
|
aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
|
|
if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
|
|
aPaletteDepth, nonPremult))) {
|
|
NS_WARNING("imgFrame::Init should succeed");
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
RawAccessFrameRef ref = frame->RawAccessRef();
|
|
if (!ref) {
|
|
frame->Abort();
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
bool succeeded =
|
|
SurfaceCache::Insert(frame, ImageKey(&mImage),
|
|
RasterSurfaceKey(imageSize.ToIntSize(),
|
|
aDecodeFlags,
|
|
aFrameNum),
|
|
Lifetime::Persistent);
|
|
if (!succeeded) {
|
|
ref->Abort();
|
|
return RawAccessFrameRef();
|
|
}
|
|
|
|
nsIntRect refreshArea;
|
|
|
|
if (aFrameNum == 1) {
|
|
MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
|
|
aPreviousFrame->SetRawAccessOnly();
|
|
|
|
// If we dispose of the first frame by clearing it, then the first frame's
|
|
// refresh area is all of itself.
|
|
// RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
|
|
AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
|
|
if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
|
|
previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
|
|
previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
|
|
refreshArea = previousFrameData.mRect;
|
|
}
|
|
}
|
|
|
|
if (aFrameNum > 0) {
|
|
ref->SetRawAccessOnly();
|
|
|
|
// Some GIFs are huge but only have a small area that they animate. We only
|
|
// need to refresh that small area when frame 0 comes around again.
|
|
refreshArea.UnionRect(refreshArea, frame->GetRect());
|
|
}
|
|
|
|
mFrameCount++;
|
|
mImage.OnAddedFrame(mFrameCount, refreshArea);
|
|
|
|
return ref;
|
|
}
|
|
|
|
void
|
|
Decoder::SetSizeOnImage()
|
|
{
|
|
MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
|
|
MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
|
|
|
|
mImage.SetSize(mImageMetadata.GetWidth(),
|
|
mImageMetadata.GetHeight(),
|
|
mImageMetadata.GetOrientation());
|
|
}
|
|
|
|
/*
|
|
* Hook stubs. Override these as necessary in decoder implementations.
|
|
*/
|
|
|
|
void Decoder::InitInternal() { }
|
|
void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount) { }
|
|
void Decoder::FinishInternal() { }
|
|
|
|
/*
|
|
* Progress Notifications
|
|
*/
|
|
|
|
void
|
|
Decoder::PostSize(int32_t aWidth,
|
|
int32_t aHeight,
|
|
Orientation aOrientation /* = Orientation()*/)
|
|
{
|
|
// Validate
|
|
NS_ABORT_IF_FALSE(aWidth >= 0, "Width can't be negative!");
|
|
NS_ABORT_IF_FALSE(aHeight >= 0, "Height can't be negative!");
|
|
|
|
// Tell the image
|
|
mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
|
|
|
|
// Record this notification.
|
|
mProgress |= FLAG_SIZE_AVAILABLE;
|
|
}
|
|
|
|
void
|
|
Decoder::PostHasTransparency()
|
|
{
|
|
mProgress |= FLAG_HAS_TRANSPARENCY;
|
|
}
|
|
|
|
void
|
|
Decoder::PostFrameStart()
|
|
{
|
|
// We shouldn't already be mid-frame
|
|
NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
|
|
|
|
// Update our state to reflect the new frame
|
|
mInFrame = true;
|
|
|
|
// If we just became animated, record that fact.
|
|
if (mFrameCount > 1) {
|
|
mIsAnimated = true;
|
|
mProgress |= FLAG_IS_ANIMATED;
|
|
}
|
|
}
|
|
|
|
void
|
|
Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
|
|
DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
|
|
int32_t aTimeout /* = 0 */,
|
|
BlendMethod aBlendMethod /* = BlendMethod::OVER */)
|
|
{
|
|
// We should be mid-frame
|
|
MOZ_ASSERT(!IsSizeDecode(), "Stopping frame during a size decode");
|
|
MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
|
|
MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
|
|
|
|
// Update our state
|
|
mInFrame = false;
|
|
|
|
mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
|
|
|
|
mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
|
|
}
|
|
|
|
void
|
|
Decoder::PostInvalidation(nsIntRect& aRect)
|
|
{
|
|
// We should be mid-frame
|
|
NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!");
|
|
NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!");
|
|
|
|
// Account for the new region
|
|
mInvalidRect.UnionRect(mInvalidRect, aRect);
|
|
mCurrentFrame->ImageUpdated(aRect);
|
|
}
|
|
|
|
void
|
|
Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
|
|
{
|
|
NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
|
|
NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
|
|
NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
|
|
mDecodeDone = true;
|
|
|
|
mImageMetadata.SetLoopCount(aLoopCount);
|
|
|
|
mProgress |= FLAG_DECODE_COMPLETE;
|
|
}
|
|
|
|
void
|
|
Decoder::PostDataError()
|
|
{
|
|
mDataError = true;
|
|
|
|
if (mInFrame && mCurrentFrame) {
|
|
mCurrentFrame->Abort();
|
|
}
|
|
}
|
|
|
|
void
|
|
Decoder::PostDecoderError(nsresult aFailureCode)
|
|
{
|
|
NS_ABORT_IF_FALSE(NS_FAILED(aFailureCode), "Not a failure code!");
|
|
|
|
mFailCode = aFailureCode;
|
|
|
|
// XXXbholley - we should report the image URI here, but imgContainer
|
|
// needs to know its URI first
|
|
NS_WARNING("Image decoding error - This is probably a bug!");
|
|
|
|
if (mInFrame && mCurrentFrame) {
|
|
mCurrentFrame->Abort();
|
|
}
|
|
}
|
|
|
|
} // namespace image
|
|
} // namespace mozilla
|