summaryrefslogtreecommitdiffstats
path: root/image
diff options
context:
space:
mode:
Diffstat (limited to 'image')
-rw-r--r--image/AnimationParams.h47
-rw-r--r--image/Decoder.cpp49
-rw-r--r--image/Decoder.h22
-rw-r--r--image/DecoderFactory.cpp7
-rw-r--r--image/DecoderFactory.h1
-rw-r--r--image/DownscalingFilter.h4
-rw-r--r--image/FrameTimeout.h119
-rw-r--r--image/IDecodingTask.cpp15
-rw-r--r--image/IDecodingTask.h8
-rw-r--r--image/Image.h2
-rw-r--r--image/ImageOps.cpp2
-rw-r--r--image/RasterImage.cpp31
-rw-r--r--image/SourceBuffer.cpp14
-rw-r--r--image/SourceBuffer.h7
-rw-r--r--image/SurfaceFilters.h6
-rw-r--r--image/SurfacePipe.cpp13
-rw-r--r--image/SurfacePipe.h6
-rw-r--r--image/SurfacePipeFactory.h22
-rw-r--r--image/VectorImage.cpp9
-rw-r--r--image/VectorImage.h2
-rw-r--r--image/decoders/nsBMPDecoder.cpp43
-rw-r--r--image/decoders/nsBMPDecoder.h9
-rw-r--r--image/decoders/nsGIFDecoder2.cpp31
-rw-r--r--image/decoders/nsIconDecoder.cpp3
-rw-r--r--image/decoders/nsJPEGDecoder.cpp4
-rw-r--r--image/decoders/nsPNGDecoder.cpp41
-rw-r--r--image/decoders/nsWebPDecoder.cpp633
-rw-r--r--image/decoders/nsWebPDecoder.h56
-rw-r--r--image/encoders/png/nsPNGEncoder.cpp52
-rw-r--r--image/encoders/png/nsPNGEncoder.h1
-rw-r--r--image/imgFrame.cpp34
-rw-r--r--image/imgFrame.h172
-rw-r--r--image/test/gtest/Common.h4
-rw-r--r--image/test/gtest/TestADAM7InterpolatingFilter.cpp6
-rw-r--r--image/test/gtest/TestDeinterlacingFilter.cpp6
-rw-r--r--image/test/gtest/TestDownscalingFilter.cpp6
-rw-r--r--image/test/gtest/TestDownscalingFilterNoSkia.cpp2
-rw-r--r--image/test/gtest/TestRemoveFrameRectFilter.cpp6
-rw-r--r--image/test/gtest/TestSurfacePipeIntegration.cpp20
-rw-r--r--image/test/gtest/TestSurfaceSink.cpp4
40 files changed, 884 insertions, 635 deletions
diff --git a/image/AnimationParams.h b/image/AnimationParams.h
new file mode 100644
index 000000000..dc403a4e8
--- /dev/null
+++ b/image/AnimationParams.h
@@ -0,0 +1,47 @@
+/* -*- 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_AnimationParams_h
+#define mozilla_image_AnimationParams_h
+
+#include <stdint.h>
+#include "mozilla/gfx/Rect.h"
+#include "FrameTimeout.h"
+
+namespace mozilla {
+namespace image {
+
+enum class BlendMethod : int8_t {
+ // All color components of the frame, including alpha, overwrite the current
+ // contents of the frame's output buffer region.
+ SOURCE,
+
+ // The frame should be composited onto the output buffer based on its alpha,
+ // using a simple OVER operation.
+ OVER
+};
+
+enum class DisposalMethod : int8_t {
+ CLEAR_ALL = -1, // Clear the whole image, revealing what's underneath.
+ NOT_SPECIFIED, // Leave the frame and let the new frame draw on top.
+ KEEP, // Leave the frame and let the new frame draw on top.
+ CLEAR, // Clear the frame's area, revealing what's underneath.
+ RESTORE_PREVIOUS // Restore the previous (composited) frame.
+};
+
+struct AnimationParams
+{
+ gfx::IntRect mBlendRect;
+ FrameTimeout mTimeout;
+ uint32_t mFrameNum;
+ BlendMethod mBlendMethod;
+ DisposalMethod mDisposalMethod;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_AnimationParams_h
diff --git a/image/Decoder.cpp b/image/Decoder.cpp
index f9b1034cf..5ff8c72b5 100644
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -278,14 +278,14 @@ Decoder::Telemetry() const
}
nsresult
-Decoder::AllocateFrame(uint32_t aFrameNum,
- const gfx::IntSize& aOutputSize,
+Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
- uint8_t aPaletteDepth)
+ uint8_t aPaletteDepth,
+ const Maybe<AnimationParams>& aAnimParams)
{
- mCurrentFrame = AllocateFrameInternal(aFrameNum, aOutputSize, aFrameRect,
- aFormat, aPaletteDepth,
+ mCurrentFrame = AllocateFrameInternal(aOutputSize, aFrameRect, aFormat,
+ aPaletteDepth, aAnimParams,
mCurrentFrame.get());
if (mCurrentFrame) {
@@ -295,7 +295,7 @@ Decoder::AllocateFrame(uint32_t aFrameNum,
// We should now be on |aFrameNum|. (Note that we're comparing the frame
// number, which is zero-based, with the frame count, which is one-based.)
- MOZ_ASSERT(aFrameNum + 1 == mFrameCount);
+ MOZ_ASSERT_IF(aAnimParams, aAnimParams->mFrameNum + 1 == mFrameCount);
// If we're past the first frame, PostIsAnimated() should've been called.
MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
@@ -309,18 +309,19 @@ Decoder::AllocateFrame(uint32_t aFrameNum,
}
RawAccessFrameRef
-Decoder::AllocateFrameInternal(uint32_t aFrameNum,
- const gfx::IntSize& aOutputSize,
+Decoder::AllocateFrameInternal(const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
SurfaceFormat aFormat,
uint8_t aPaletteDepth,
+ const Maybe<AnimationParams>& aAnimParams,
imgFrame* aPreviousFrame)
{
if (HasError()) {
return RawAccessFrameRef();
}
- if (aFrameNum != mFrameCount) {
+ uint32_t frameNum = aAnimParams ? aAnimParams->mFrameNum : 0;
+ if (frameNum != mFrameCount) {
MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
return RawAccessFrameRef();
}
@@ -334,7 +335,8 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame());
bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat,
- aPaletteDepth, nonPremult))) {
+ aPaletteDepth, nonPremult,
+ aAnimParams))) {
NS_WARNING("imgFrame::Init should succeed");
return RawAccessFrameRef();
}
@@ -345,22 +347,22 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
return RawAccessFrameRef();
}
- if (aFrameNum == 1) {
+ if (frameNum == 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) {
- mFirstFrameRefreshArea = previousFrameData.mRect;
+ DisposalMethod prevDisposal = aPreviousFrame->GetDisposalMethod();
+ if (prevDisposal == DisposalMethod::CLEAR ||
+ prevDisposal == DisposalMethod::CLEAR_ALL ||
+ prevDisposal == DisposalMethod::RESTORE_PREVIOUS) {
+ mFirstFrameRefreshArea = aPreviousFrame->GetRect();
}
}
- if (aFrameNum > 0) {
+ if (frameNum > 0) {
ref->SetRawAccessOnly();
// Some GIFs are huge but only have a small area that they animate. We only
@@ -432,13 +434,7 @@ Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout)
}
void
-Decoder::PostFrameStop(Opacity aFrameOpacity
- /* = Opacity::SOME_TRANSPARENCY */,
- DisposalMethod aDisposalMethod
- /* = DisposalMethod::KEEP */,
- FrameTimeout aTimeout /* = FrameTimeout::Forever() */,
- BlendMethod aBlendMethod /* = BlendMethod::OVER */,
- const Maybe<nsIntRect>& aBlendRect /* = Nothing() */)
+Decoder::PostFrameStop(Opacity aFrameOpacity)
{
// We should be mid-frame
MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
@@ -449,12 +445,11 @@ Decoder::PostFrameStop(Opacity aFrameOpacity
mInFrame = false;
mFinishedNewFrame = true;
- mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
- aBlendMethod, aBlendRect);
+ mCurrentFrame->Finish(aFrameOpacity);
mProgress |= FLAG_FRAME_COMPLETE;
- mLoopLength += aTimeout;
+ mLoopLength += mCurrentFrame->GetTimeout();
// If we're not sending partial invalidations, then we send an invalidation
// here when the first frame is complete.
diff --git a/image/Decoder.h b/image/Decoder.h
index 065a3c213..ed0c19687 100644
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -11,6 +11,7 @@
#include "mozilla/Maybe.h"
#include "mozilla/NotNull.h"
#include "mozilla/RefPtr.h"
+#include "AnimationParams.h"
#include "DecodePool.h"
#include "DecoderFlags.h"
#include "Downscaler.h"
@@ -28,6 +29,8 @@ namespace Telemetry {
namespace image {
+class imgFrame;
+
struct DecoderFinalStatus final
{
DecoderFinalStatus(bool aWasMetadataDecode,
@@ -443,11 +446,7 @@ protected:
// Specify whether this frame is opaque as an optimization.
// For animated images, specify the disposal, blend method and timeout for
// this frame.
- void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
- DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
- FrameTimeout aTimeout = FrameTimeout::Forever(),
- BlendMethod aBlendMethod = BlendMethod::OVER,
- const Maybe<nsIntRect>& aBlendRect = Nothing());
+ void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY);
/**
* Called by the decoders when they have a region to invalidate. We may not
@@ -476,16 +475,13 @@ protected:
/**
* Allocates a new frame, making it our current frame if successful.
*
- * The @aFrameNum parameter only exists as a sanity check; it's illegal to
- * create a new frame anywhere but immediately after the existing frames.
- *
* If a non-paletted frame is desired, pass 0 for aPaletteDepth.
*/
- nsresult AllocateFrame(uint32_t aFrameNum,
- const gfx::IntSize& aOutputSize,
+ nsresult AllocateFrame(const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
- uint8_t aPaletteDepth = 0);
+ uint8_t aPaletteDepth = 0,
+ const Maybe<AnimationParams>& aAnimParams = Nothing());
private:
/// Report that an error was encountered while decoding.
@@ -509,11 +505,11 @@ private:
return mInFrame ? mFrameCount - 1 : mFrameCount;
}
- RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
- const gfx::IntSize& aOutputSize,
+ RawAccessFrameRef AllocateFrameInternal(const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth,
+ const Maybe<AnimationParams>& aAnimParams,
imgFrame* aPreviousFrame);
protected:
diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp
index 2085fb7c4..dffe4dc21 100644
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -58,6 +58,10 @@ DecoderFactory::GetDecoderType(const char* aMimeType)
} else if (!strcmp(aMimeType, IMAGE_BMP_MS)) {
type = DecoderType::BMP;
+ // BMP_CLIPBOARD
+ } else if (!strcmp(aMimeType, IMAGE_BMP_MS_CLIPBOARD)) {
+ type = DecoderType::BMP_CLIPBOARD;
+
// ICO
} else if (!strcmp(aMimeType, IMAGE_ICO)) {
type = DecoderType::ICO;
@@ -100,6 +104,9 @@ DecoderFactory::GetDecoder(DecoderType aType,
case DecoderType::BMP:
decoder = new nsBMPDecoder(aImage);
break;
+ case DecoderType::BMP_CLIPBOARD:
+ decoder = new nsBMPDecoder(aImage, /* aForClipboard */ true);
+ break;
case DecoderType::ICO:
decoder = new nsICODecoder(aImage);
break;
diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h
index f8cf64cc6..5638789ff 100644
--- a/image/DecoderFactory.h
+++ b/image/DecoderFactory.h
@@ -34,6 +34,7 @@ enum class DecoderType
GIF,
JPEG,
BMP,
+ BMP_CLIPBOARD,
ICO,
ICON,
WEBP,
diff --git a/image/DownscalingFilter.h b/image/DownscalingFilter.h
index 1485b85c2..936abdcd5 100644
--- a/image/DownscalingFilter.h
+++ b/image/DownscalingFilter.h
@@ -74,7 +74,7 @@ public:
Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
template <typename... Rest>
- nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
+ nsresult Configure(const DownscalingConfig& aConfig, const Rest&... aRest)
{
return NS_ERROR_FAILURE;
}
@@ -115,7 +115,7 @@ public:
}
template <typename... Rest>
- nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
+ nsresult Configure(const DownscalingConfig& aConfig, const Rest&... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
diff --git a/image/FrameTimeout.h b/image/FrameTimeout.h
new file mode 100644
index 000000000..4070bba65
--- /dev/null
+++ b/image/FrameTimeout.h
@@ -0,0 +1,119 @@
+/* -*- 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_FrameTimeout_h
+#define mozilla_image_FrameTimeout_h
+
+#include <stdint.h>
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * FrameTimeout wraps a frame timeout value (measured in milliseconds) after
+ * first normalizing it. This normalization is necessary because some tools
+ * generate incorrect frame timeout values which we nevertheless have to
+ * support. For this reason, code that deals with frame timeouts should always
+ * use a FrameTimeout value rather than the raw value from the image header.
+ */
+struct FrameTimeout
+{
+ /**
+ * @return a FrameTimeout of zero. This should be used only for math
+ * involving FrameTimeout values. You can't obtain a zero FrameTimeout from
+ * FromRawMilliseconds().
+ */
+ static FrameTimeout Zero() { return FrameTimeout(0); }
+
+ /// @return an infinite FrameTimeout.
+ static FrameTimeout Forever() { return FrameTimeout(-1); }
+
+ /// @return a FrameTimeout obtained by normalizing a raw timeout value.
+ static FrameTimeout FromRawMilliseconds(int32_t aRawMilliseconds)
+ {
+ // Normalize all infinite timeouts to the same value.
+ if (aRawMilliseconds < 0) {
+ return FrameTimeout::Forever();
+ }
+
+ // Very small timeout values are problematic for two reasons: we don't want
+ // to burn energy redrawing animated images extremely fast, and broken tools
+ // generate these values when they actually want a "default" value, so such
+ // images won't play back right without normalization. For some context,
+ // see bug 890743, bug 125137, bug 139677, and bug 207059. The historical
+ // behavior of IE and Opera was:
+ // IE 6/Win:
+ // 10 - 50ms is normalized to 100ms.
+ // >50ms is used unnormalized.
+ // Opera 7 final/Win:
+ // 10ms is normalized to 100ms.
+ // >10ms is used unnormalized.
+ if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10 ) {
+ return FrameTimeout(100);
+ }
+
+ // The provided timeout value is OK as-is.
+ return FrameTimeout(aRawMilliseconds);
+ }
+
+ bool operator==(const FrameTimeout& aOther) const
+ {
+ return mTimeout == aOther.mTimeout;
+ }
+
+ bool operator!=(const FrameTimeout& aOther) const { return !(*this == aOther); }
+
+ FrameTimeout operator+(const FrameTimeout& aOther)
+ {
+ if (*this == Forever() || aOther == Forever()) {
+ return Forever();
+ }
+
+ return FrameTimeout(mTimeout + aOther.mTimeout);
+ }
+
+ FrameTimeout& operator+=(const FrameTimeout& aOther)
+ {
+ *this = *this + aOther;
+ return *this;
+ }
+
+ /**
+ * @return this FrameTimeout's value in milliseconds. Illegal to call on a
+ * an infinite FrameTimeout value.
+ */
+ uint32_t AsMilliseconds() const
+ {
+ if (*this == Forever()) {
+ MOZ_ASSERT_UNREACHABLE("Calling AsMilliseconds() on an infinite FrameTimeout");
+ return 100; // Fail to something sane.
+ }
+
+ return uint32_t(mTimeout);
+ }
+
+ /**
+ * @return this FrameTimeout value encoded so that non-negative values
+ * represent a timeout in milliseconds, and -1 represents an infinite
+ * timeout.
+ *
+ * XXX(seth): This is a backwards compatibility hack that should be removed.
+ */
+ int32_t AsEncodedValueDeprecated() const { return mTimeout; }
+
+private:
+ explicit FrameTimeout(int32_t aTimeout)
+ : mTimeout(aTimeout)
+ { }
+
+ int32_t mTimeout;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_FrameTimeout_h
diff --git a/image/IDecodingTask.cpp b/image/IDecodingTask.cpp
index a067e7a7d..6e2f039be 100644
--- a/image/IDecodingTask.cpp
+++ b/image/IDecodingTask.cpp
@@ -142,8 +142,10 @@ MetadataDecodingTask::Run()
// AnonymousDecodingTask implementation.
///////////////////////////////////////////////////////////////////////////////
-AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder)
+AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder,
+ bool aResumable)
: mDecoder(aDecoder)
+ , mResumable(aResumable)
{ }
void
@@ -168,5 +170,16 @@ AnonymousDecodingTask::Run()
}
}
+void
+AnonymousDecodingTask::Resume()
+{
+ // Anonymous decoders normally get all their data at once. We have some situations
+ // where they don't. If explicitly requested, resuming should be supported.
+ if (mResumable) {
+ RefPtr<AnonymousDecodingTask> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction([self]() -> void { self->Run(); }));
+ }
+}
+
} // namespace image
} // namespace mozilla
diff --git a/image/IDecodingTask.h b/image/IDecodingTask.h
index 196ce5fdc..3cbab5006 100644
--- a/image/IDecodingTask.h
+++ b/image/IDecodingTask.h
@@ -102,22 +102,20 @@ class AnonymousDecodingTask final : public IDecodingTask
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override)
- explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder);
+ explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder, bool aResumable);
void Run() override;
bool ShouldPreferSyncRun() const override { return true; }
TaskPriority Priority() const override { return TaskPriority::eLow; }
- // Anonymous decoders normally get all their data at once. We have tests where
- // they don't; in these situations, the test re-runs them manually. So no
- // matter what, we don't want to resume by posting a task to the DecodePool.
- void Resume() override { }
+ void Resume() override;
private:
virtual ~AnonymousDecodingTask() { }
NotNull<RefPtr<Decoder>> mDecoder;
+ bool mResumable;
};
} // namespace image
diff --git a/image/Image.h b/image/Image.h
index bcabd1cc7..98c5e8ca5 100644
--- a/image/Image.h
+++ b/image/Image.h
@@ -220,8 +220,6 @@ public:
virtual void SetHasError() = 0;
virtual ImageURL* GetURI() = 0;
-
- virtual void ReportUseCounters() { }
};
class ImageResource : public Image
diff --git a/image/ImageOps.cpp b/image/ImageOps.cpp
index addee7f15..ddd2f200e 100644
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -126,7 +126,7 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
}
// Run the decoder synchronously.
- RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
+ RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
task->Run();
if (!decoder->GetDecodeDone() || decoder->HasError()) {
return nullptr;
diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp
index aad705473..b67ec3bef 100644
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -39,8 +39,8 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/Services.h"
#include <stdint.h>
-#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
+#include "mozilla/Telemetry.h"
#include "mozilla/Tuple.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/gfx/Scale.h"
@@ -104,9 +104,6 @@ RasterImage::~RasterImage()
// Release all frames from the surface cache.
SurfaceCache::RemoveImage(ImageKey(this));
-
- // Record Telemetry.
- Telemetry::Accumulate(Telemetry::IMAGE_DECODE_COUNT, mDecodeCount);
}
nsresult
@@ -1365,19 +1362,9 @@ RasterImage::Draw(gfxContext* aContext,
return DrawResult::NOT_READY;
}
- bool shouldRecordTelemetry = !mDrawStartTime.IsNull() &&
- surface->IsFinished();
-
auto result = DrawInternal(Move(surface), aContext, aSize,
aRegion, aSamplingFilter, flags);
- if (shouldRecordTelemetry) {
- TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
- Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY,
- int32_t(drawLatency.ToMicroseconds()));
- mDrawStartTime = TimeStamp();
- }
-
return result;
}
@@ -1614,22 +1601,6 @@ RasterImage::NotifyDecodeComplete(const DecoderFinalStatus& aStatus,
mAnimationState->SetDoneDecoding(true);
}
- // Do some telemetry if this isn't a metadata decode.
- if (!aStatus.mWasMetadataDecode) {
- if (aTelemetry.mChunkCount) {
- Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, aTelemetry.mChunkCount);
- }
-
- if (aStatus.mFinished) {
- Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
- int32_t(aTelemetry.mDecodeTime.ToMicroseconds()));
-
- if (aTelemetry.mSpeedHistogram) {
- Telemetry::Accumulate(*aTelemetry.mSpeedHistogram, aTelemetry.Speed());
- }
- }
- }
-
// Only act on errors if we have no usable frames from the decoder.
if (aStatus.mHadError &&
(!mAnimationState || mAnimationState->KnownFrameCount() == 0)) {
diff --git a/image/SourceBuffer.cpp b/image/SourceBuffer.cpp
index de0719d45..de066e29f 100644
--- a/image/SourceBuffer.cpp
+++ b/image/SourceBuffer.cpp
@@ -451,10 +451,18 @@ SourceBuffer::AppendFromInputStream(nsIInputStream* aInputStream,
uint32_t bytesRead;
nsresult rv = aInputStream->ReadSegments(AppendToSourceBuffer, this,
aCount, &bytesRead);
- if (!NS_WARN_IF(NS_FAILED(rv))) {
- MOZ_ASSERT(bytesRead == aCount,
- "AppendToSourceBuffer should consume everything");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (bytesRead == 0) {
+ // The loading of the image has been canceled.
+ return NS_ERROR_FAILURE;
}
+
+ MOZ_ASSERT(bytesRead == aCount,
+ "AppendToSourceBuffer should consume everything");
+
return rv;
}
diff --git a/image/SourceBuffer.h b/image/SourceBuffer.h
index 64727e65e..6f2c74d33 100644
--- a/image/SourceBuffer.h
+++ b/image/SourceBuffer.h
@@ -174,6 +174,13 @@ public:
return mState == READY ? mData.mIterating.mNextReadLength : 0;
}
+ /// If we're ready to read, returns whether or not everything available thus
+ /// far has been in the same contiguous buffer.
+ bool IsContiguous() const {
+ MOZ_ASSERT(mState == READY, "Calling IsContiguous() in the wrong state");
+ return mState == READY ? mData.mIterating.mChunk == 0 : false;
+ }
+
/// @return a count of the chunks we've advanced through.
uint32_t ChunkCount() const { return mChunkCount; }
diff --git a/image/SurfaceFilters.h b/image/SurfaceFilters.h
index 1885661b9..70c8d4087 100644
--- a/image/SurfaceFilters.h
+++ b/image/SurfaceFilters.h
@@ -70,7 +70,7 @@ public:
{ }
template <typename... Rest>
- nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig, Rest... aRest)
+ nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig, const Rest&... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
@@ -360,7 +360,7 @@ public:
{ }
template <typename... Rest>
- nsresult Configure(const RemoveFrameRectConfig& aConfig, Rest... aRest)
+ nsresult Configure(const RemoveFrameRectConfig& aConfig, const Rest&... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
@@ -590,7 +590,7 @@ public:
{ }
template <typename... Rest>
- nsresult Configure(const ADAM7InterpolatingConfig& aConfig, Rest... aRest)
+ nsresult Configure(const ADAM7InterpolatingConfig& aConfig, const Rest&... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
diff --git a/image/SurfacePipe.cpp b/image/SurfacePipe.cpp
index 5c306144a..882315112 100644
--- a/image/SurfacePipe.cpp
+++ b/image/SurfacePipe.cpp
@@ -104,10 +104,11 @@ SurfaceSink::Configure(const SurfaceConfig& aConfig)
// XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
// to allocate the frame directly here and get rid of Decoder::AllocateFrame
// altogether.
- nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
- surfaceSize,
+ nsresult rv = aConfig.mDecoder->AllocateFrame(surfaceSize,
frameRect,
- aConfig.mFormat);
+ aConfig.mFormat,
+ /* aPaletteDepth */ 0,
+ aConfig.mAnimParams);
if (NS_FAILED(rv)) {
return rv;
}
@@ -154,11 +155,11 @@ PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig)
// XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
// to allocate the frame directly here and get rid of Decoder::AllocateFrame
// altogether.
- nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
- aConfig.mOutputSize,
+ nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mOutputSize,
aConfig.mFrameRect,
aConfig.mFormat,
- aConfig.mPaletteDepth);
+ aConfig.mPaletteDepth,
+ aConfig.mAnimParams);
if (NS_FAILED(rv)) {
return rv;
}
diff --git a/image/SurfacePipe.h b/image/SurfacePipe.h
index f046afa56..61c8d30df 100644
--- a/image/SurfacePipe.h
+++ b/image/SurfacePipe.h
@@ -34,6 +34,8 @@
#include "mozilla/Variant.h"
#include "mozilla/gfx/2D.h"
+#include "AnimationParams.h"
+
namespace mozilla {
namespace image {
@@ -722,10 +724,10 @@ struct SurfaceConfig
{
using Filter = SurfaceSink;
Decoder* mDecoder; /// Which Decoder to use to allocate the surface.
- uint32_t mFrameNum; /// Which frame of animation this surface is for.
gfx::IntSize mOutputSize; /// The size of the surface.
gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX).
bool mFlipVertically; /// If true, write the rows from bottom to top.
+ Maybe<AnimationParams> mAnimParams; /// Given for animated images.
};
/**
@@ -750,12 +752,12 @@ struct PalettedSurfaceConfig
{
using Filter = PalettedSurfaceSink;
Decoder* mDecoder; /// Which Decoder to use to allocate the surface.
- uint32_t mFrameNum; /// Which frame of animation this surface is for.
gfx::IntSize mOutputSize; /// The logical size of the surface.
gfx::IntRect mFrameRect; /// The surface subrect which contains data.
gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX).
uint8_t mPaletteDepth; /// The palette depth of this surface.
bool mFlipVertically; /// If true, write the rows from bottom to top.
+ Maybe<AnimationParams> mAnimParams; /// Given for animated images.
};
/**
diff --git a/image/SurfacePipeFactory.h b/image/SurfacePipeFactory.h
index ff53fec5c..4571f2e13 100644
--- a/image/SurfacePipeFactory.h
+++ b/image/SurfacePipeFactory.h
@@ -70,8 +70,6 @@ public:
*
* @param aDecoder The decoder whose current frame the SurfacePipe will write
* to.
- * @param aFrameNum Which frame the SurfacePipe will write to. This will be 0
- * for non-animated images.
* @param aInputSize The original size of the image.
* @param aOutputSize The size the SurfacePipe should output. Must be the same
* as @aInputSize or smaller. If smaller, the image will be
@@ -79,6 +77,7 @@ public:
* @param aFrameRect The portion of the image that actually contains data.
* @param aFormat The surface format of the image; generally B8G8R8A8 or
* B8G8R8X8.
+ * @param aAnimParams Extra parameters used by animated images.
* @param aFlags Flags enabling or disabling various functionality for the
* SurfacePipe; see the SurfacePipeFlags documentation for more
* information.
@@ -89,11 +88,11 @@ public:
*/
static Maybe<SurfacePipe>
CreateSurfacePipe(Decoder* aDecoder,
- uint32_t aFrameNum,
const nsIntSize& aInputSize,
const nsIntSize& aOutputSize,
const nsIntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
+ const Maybe<AnimationParams>& aAnimParams,
SurfacePipeFlags aFlags)
{
const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
@@ -125,8 +124,8 @@ public:
ADAM7InterpolatingConfig interpolatingConfig;
RemoveFrameRectConfig removeFrameRectConfig { aFrameRect };
DownscalingConfig downscalingConfig { aInputSize, aFormat };
- SurfaceConfig surfaceConfig { aDecoder, aFrameNum, aOutputSize,
- aFormat, flipVertically };
+ SurfaceConfig surfaceConfig { aDecoder, aOutputSize, aFormat,
+ flipVertically, aAnimParams };
Maybe<SurfacePipe> pipe;
@@ -181,13 +180,12 @@ public:
*
* @param aDecoder The decoder whose current frame the SurfacePipe will write
* to.
- * @param aFrameNum Which frame the SurfacePipe will write to. This will be 0
- * for non-animated images.
* @param aInputSize The original size of the image.
* @param aFrameRect The portion of the image that actually contains data.
* @param aFormat The surface format of the image; generally B8G8R8A8 or
* B8G8R8X8.
* @param aPaletteDepth The palette depth of the image.
+ * @param aAnimParams Extra parameters used by animated images.
* @param aFlags Flags enabling or disabling various functionality for the
* SurfacePipe; see the SurfacePipeFlags documentation for more
* information.
@@ -198,11 +196,11 @@ public:
*/
static Maybe<SurfacePipe>
CreatePalettedSurfacePipe(Decoder* aDecoder,
- uint32_t aFrameNum,
const nsIntSize& aInputSize,
const nsIntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth,
+ const Maybe<AnimationParams>& aAnimParams,
SurfacePipeFlags aFlags)
{
const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
@@ -211,9 +209,9 @@ public:
// Construct configurations for the SurfaceFilters.
DeinterlacingConfig<uint8_t> deinterlacingConfig { progressiveDisplay };
- PalettedSurfaceConfig palettedSurfaceConfig { aDecoder, aFrameNum, aInputSize,
- aFrameRect, aFormat, aPaletteDepth,
- flipVertically };
+ PalettedSurfaceConfig palettedSurfaceConfig { aDecoder, aInputSize, aFrameRect,
+ aFormat, aPaletteDepth,
+ flipVertically, aAnimParams };
Maybe<SurfacePipe> pipe;
@@ -229,7 +227,7 @@ public:
private:
template <typename... Configs>
static Maybe<SurfacePipe>
- MakePipe(Configs... aConfigs)
+ MakePipe(const Configs&... aConfigs)
{
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
nsresult rv = pipe->Configure(aConfigs...);
diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp
index 1e59b13fa..3028c1c2e 100644
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -1337,15 +1337,6 @@ VectorImage::PropagateUseCounters(nsIDocument* aParentDocument)
}
}
-void
-VectorImage::ReportUseCounters()
-{
- nsIDocument* doc = mSVGDocumentWrapper->GetDocument();
- if (doc) {
- static_cast<nsDocument*>(doc)->ReportUseCounters();
- }
-}
-
nsIntSize
VectorImage::OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
diff --git a/image/VectorImage.h b/image/VectorImage.h
index e19aa939f..bd4d393ed 100644
--- a/image/VectorImage.h
+++ b/image/VectorImage.h
@@ -68,8 +68,6 @@ public:
void OnSVGDocumentLoaded();
void OnSVGDocumentError();
- virtual void ReportUseCounters() override;
-
protected:
explicit VectorImage(ImageURL* aURI = nullptr);
virtual ~VectorImage();
diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp
index 1f0449e4e..b93eb42b6 100644
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -66,6 +66,17 @@
// compression, then instead of treating the pixel data as 0RGB it is treated
// as ARGB, but only if one or more of the A values are non-zero.
//
+// Clipboard variants.
+// - It's the BMP format used for BMP images captured from the clipboard.
+// - It is missing the file header, containing the BM signature and the data
+// offset. Instead the data begins after the header.
+// - If it uses BITFIELDS compression, then there is always an additional 12
+// bytes of data after the header that must be read. In WinBMPv4+, the masks
+// are supposed to be included in the header size, which are the values we use
+// for decoding purposes, but there is additional three masks following the
+// header which must be skipped to get to the pixel data.
+//
+//
// OS/2 VERSIONS OF THE BMP FORMAT
// -------------------------------
// OS2-BMPv1.
@@ -168,10 +179,12 @@ static mozilla::LazyLogModule sBMPLog("BMPDecoder");
// The length of the mBIHSize field in the info header.
static const uint32_t BIHSIZE_FIELD_LENGTH = 4;
-nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength)
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength,
+ bool aForClipboard)
: Decoder(aImage)
, mLexer(Transition::To(aState, aLength), Transition::TerminateSuccess())
, mIsWithinICO(false)
+ , mIsForClipboard(aForClipboard)
, mMayHaveTransparency(false)
, mDoesHaveTransparency(false)
, mNumColors(0)
@@ -185,15 +198,19 @@ nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength)
{
}
-// Constructor for normal BMP files.
-nsBMPDecoder::nsBMPDecoder(RasterImage* aImage)
- : nsBMPDecoder(aImage, State::FILE_HEADER, FILE_HEADER_LENGTH)
+// Constructor for normal BMP files or from the clipboard.
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, bool aForClipboard)
+ : nsBMPDecoder(aImage,
+ aForClipboard ? State::INFO_HEADER_SIZE : State::FILE_HEADER,
+ aForClipboard ? BIHSIZE_FIELD_LENGTH : FILE_HEADER_LENGTH,
+ aForClipboard)
{
}
// Constructor used for WinBMPv3-ICO files, which lack a file header.
nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset)
- : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH)
+ : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH,
+ /* aForClipboard */ false)
{
SetIsWithinICO();
@@ -598,6 +615,13 @@ nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength)
// Bitfields are present in the info header, so we can read them
// immediately.
mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true);
+
+ // If this came from the clipboard, then we know that even if the header
+ // explicitly includes the bitfield masks, we need to add an additional
+ // offset for the start of the RGB data.
+ if (mIsForClipboard) {
+ mH.mDataOffset += BitFields::LENGTH;
+ }
} else {
// Bitfields are present after the info header, so we will read them in
// ReadBitfields().
@@ -674,8 +698,7 @@ nsBMPDecoder::ReadBitfields(const char* aData, size_t aLength)
}
MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
- nsresult rv = AllocateFrame(/* aFrameNum = */ 0, OutputSize(),
- FullOutputFrame(),
+ nsresult rv = AllocateFrame(OutputSize(), FullOutputFrame(),
mMayHaveTransparency ? SurfaceFormat::B8G8R8A8
: SurfaceFormat::B8G8R8X8);
if (NS_FAILED(rv)) {
@@ -713,6 +736,12 @@ nsBMPDecoder::ReadColorTable(const char* aData, size_t aLength)
aData += mBytesPerColor;
}
+ // If we are decoding a BMP from the clipboard, we did not know the data
+ // offset in advance. It is defined as just after the header and color table.
+ if (mIsForClipboard) {
+ mH.mDataOffset += mPreGapLength;
+ }
+
// We know how many bytes we've read so far (mPreGapLength) and we know the
// offset of the pixel data (mH.mDataOffset), so we can determine the length
// of the gap (possibly zero) between the color table and the pixel data.
diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h
index 0cf2af689..4b9568487 100644
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -164,14 +164,14 @@ private:
RLE_ABSOLUTE
};
- // This is the constructor used for normal BMP images.
- explicit nsBMPDecoder(RasterImage* aImage);
+ // This is the constructor used for normal and clipboard BMP images.
+ explicit nsBMPDecoder(RasterImage* aImage, bool aForClipboard = false);
// This is the constructor used for BMP resources in ICO images.
nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset);
// Helper constructor called by the other two.
- nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength);
+ nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength, bool aForClipboard);
int32_t AbsoluteHeight() const { return abs(mH.mHeight); }
@@ -198,6 +198,9 @@ private:
// If the BMP is within an ICO file our treatment of it differs slightly.
bool mIsWithinICO;
+ // If the BMP is decoded from the clipboard, we start with a data offset.
+ bool mIsForClipboard;
+
bmp::BitFields mBitFields;
// Might the image have transparency? Determined from the headers during
diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp
index 7955438e4..6f2be1fa1 100644
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -187,6 +187,14 @@ nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect,
// Make sure there's no animation if we're downscaling.
MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
+ AnimationParams animParams {
+ aFrameRect,
+ FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time),
+ uint32_t(mGIFStruct.images_decoded),
+ BlendMethod::OVER,
+ DisposalMethod(mGIFStruct.disposal_method)
+ };
+
SurfacePipeFlags pipeFlags = aIsInterlaced
? SurfacePipeFlags::DEINTERLACE
: SurfacePipeFlags();
@@ -198,17 +206,24 @@ nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect,
// The first frame is always decoded into an RGB surface.
pipe =
- SurfacePipeFactory::CreateSurfacePipe(this, mGIFStruct.images_decoded,
- Size(), OutputSize(),
- aFrameRect, format, pipeFlags);
+ SurfacePipeFactory::CreateSurfacePipe(this, Size(), OutputSize(),
+ aFrameRect, format,
+ Some(animParams), pipeFlags);
} else {
// This is an animation frame (and not the first). To minimize the memory
// usage of animations, the image data is stored in paletted form.
+ //
+ // We should never use paletted surfaces with a draw target directly, so
+ // the only practical difference between B8G8R8A8 and B8G8R8X8 is the
+ // cleared pixel value if we get truncated. We want 0 in that case to
+ // ensure it is an acceptable value for the color map as was the case
+ // historically.
MOZ_ASSERT(Size() == OutputSize());
pipe =
- SurfacePipeFactory::CreatePalettedSurfacePipe(this, mGIFStruct.images_decoded,
- Size(), aFrameRect, format,
- aDepth, pipeFlags);
+ SurfacePipeFactory::CreatePalettedSurfacePipe(this, Size(), aFrameRect,
+ SurfaceFormat::B8G8R8A8,
+ aDepth, Some(animParams),
+ pipeFlags);
}
mCurrentFrameIndex = mGIFStruct.images_decoded;
@@ -249,9 +264,7 @@ nsGIFDecoder2::EndImageFrame()
mGIFStruct.images_decoded++;
// Tell the superclass we finished a frame
- PostFrameStop(opacity,
- DisposalMethod(mGIFStruct.disposal_method),
- FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time));
+ PostFrameStop(opacity);
// Reset the transparent pixel
if (mOldColor) {
diff --git a/image/decoders/nsIconDecoder.cpp b/image/decoders/nsIconDecoder.cpp
index 9ca63f5ad..b186874c6 100644
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -70,8 +70,9 @@ nsIconDecoder::ReadHeader(const char* aData)
MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
Maybe<SurfacePipe> pipe =
- SurfacePipeFactory::CreateSurfacePipe(this, 0, Size(), OutputSize(),
+ SurfacePipeFactory::CreateSurfacePipe(this, Size(), OutputSize(),
FullFrame(), SurfaceFormat::B8G8R8A8,
+ /* aAnimParams */ Nothing(),
SurfacePipeFlags());
if (!pipe) {
return Transition::TerminateFailure();
diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp
index e76ffcbaf..7dac18e27 100644
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -388,8 +388,8 @@ nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength)
jpeg_has_multiple_scans(&mInfo);
MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
- nsresult rv = AllocateFrame(/* aFrameNum = */ 0, OutputSize(),
- FullOutputFrame(), SurfaceFormat::B8G8R8X8);
+ nsresult rv = AllocateFrame(OutputSize(), FullOutputFrame(),
+ SurfaceFormat::B8G8R8X8);
if (NS_FAILED(rv)) {
mState = JPEG_ERROR;
MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp
index 9596ae7d6..1f19c41bc 100644
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -208,6 +208,25 @@ nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo)
MOZ_ASSERT_IF(Size() != OutputSize(),
transparency != TransparencyType::eFrameRect);
+ Maybe<AnimationParams> animParams;
+#ifdef PNG_APNG_SUPPORTED
+ if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
+ mAnimInfo = AnimFrameInfo(mPNG, mInfo);
+
+ if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
+ // We may have to display the background under this image during
+ // animation playback, so we regard it as transparent.
+ PostHasTransparency();
+ }
+
+ animParams.emplace(AnimationParams {
+ aFrameInfo.mFrameRect,
+ FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout),
+ mNumFrames, mAnimInfo.mBlend, mAnimInfo.mDispose
+ });
+ }
+#endif
+
// If this image is interlaced, we can display better quality intermediate
// results to the user by post processing them with ADAM7InterpolatingFilter.
SurfacePipeFlags pipeFlags = aFrameInfo.mIsInterlaced
@@ -220,9 +239,9 @@ nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo)
}
Maybe<SurfacePipe> pipe =
- SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, Size(),
- OutputSize(), aFrameInfo.mFrameRect,
- format, pipeFlags);
+ SurfacePipeFactory::CreateSurfacePipe(this, Size(), OutputSize(),
+ aFrameInfo.mFrameRect, format,
+ animParams, pipeFlags);
if (!pipe) {
mPipe = SurfacePipe();
@@ -239,18 +258,6 @@ nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo)
"image frame with %dx%d pixels for decoder %p",
mFrameRect.width, mFrameRect.height, this));
-#ifdef PNG_APNG_SUPPORTED
- if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
- mAnimInfo = AnimFrameInfo(mPNG, mInfo);
-
- if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
- // We may have to display the background under this image during
- // animation playback, so we regard it as transparent.
- PostHasTransparency();
- }
- }
-#endif
-
return NS_OK;
}
@@ -269,9 +276,7 @@ nsPNGDecoder::EndImageFrame()
opacity = Opacity::FULLY_OPAQUE;
}
- PostFrameStop(opacity, mAnimInfo.mDispose,
- FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout),
- mAnimInfo.mBlend, Some(mFrameRect));
+ PostFrameStop(opacity);
}
nsresult
diff --git a/image/decoders/nsWebPDecoder.cpp b/image/decoders/nsWebPDecoder.cpp
index 6ed2c3e9c..3181e3a3a 100644
--- a/image/decoders/nsWebPDecoder.cpp
+++ b/image/decoders/nsWebPDecoder.cpp
@@ -19,10 +19,6 @@ 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)
@@ -30,6 +26,13 @@ nsWebPDecoder::nsWebPDecoder(RasterImage* aImage)
, mFormat(SurfaceFormat::B8G8R8X8)
, mLastRow(0)
, mCurrentFrame(0)
+ , mData(nullptr)
+ , mLength(0)
+ , mIteratorComplete(false)
+ , mNeedDemuxer(true)
+ , mGotColorProfile(false)
+ , mInProfile(nullptr)
+ , mTransform(nullptr)
{
MOZ_LOG(sWebPLog, LogLevel::Debug,
("[this=%p] nsWebPDecoder::nsWebPDecoder", this));
@@ -39,27 +42,161 @@ nsWebPDecoder::~nsWebPDecoder()
{
MOZ_LOG(sWebPLog, LogLevel::Debug,
("[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
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!");
- 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();
+ 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<const uint8_t*>(aIterator.Data());
+ }
+ 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
@@ -69,13 +206,22 @@ nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
MOZ_ASSERT(!mDecoder);
MOZ_LOG(sWebPLog, LogLevel::Debug,
- ("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, %d x %d\n",
- this, mCurrentFrame, aFrameRect.width, aFrameRect.height));
+ ("[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::B8G8R8A8;
PostHasTransparency();
}
@@ -90,16 +236,22 @@ nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
return NS_ERROR_FAILURE;
}
+ SurfacePipeFlags pipeFlags = SurfacePipeFlags();
+
+ AnimationParams animParams {
+ aFrameRect, mTimeout, mCurrentFrame, mBlend, mDisposal
+ };
+
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(this,
- mCurrentFrame, Size(), OutputSize(), aFrameRect,
- mFormat, SurfacePipeFlags());
+ Size(), OutputSize(), aFrameRect, mFormat, Some(animParams), pipeFlags);
if (!pipe) {
MOZ_LOG(sWebPLog, LogLevel::Error,
("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
return NS_ERROR_FAILURE;
}
- mPipe = Move(*pipe);
+ mFrameRect = aFrameRect;
+ mPipe = std::move(*pipe);
return NS_OK;
}
@@ -118,154 +270,149 @@ nsWebPDecoder::EndFrame()
this, mCurrentFrame, (int)opacity, (int)mDisposal,
mTimeout.AsEncodedValueDeprecated(), (int)mBlend));
- PostFrameStop(opacity, mDisposal, mTimeout, mBlend);
- WebPFreeDecBuffer(&mBuffer);
+ PostFrameStop(opacity);
WebPIDelete(mDecoder);
+ WebPFreeDecBuffer(&mBuffer);
mDecoder = nullptr;
mLastRow = 0;
++mCurrentFrame;
}
-nsresult
-nsWebPDecoder::GetDataBuffer(const uint8_t*& aData, size_t& aLength)
+void
+nsWebPDecoder::ApplyColorProfile(const char* aProfile, 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();
+ MOZ_ASSERT(!mGotColorProfile);
+ mGotColorProfile = true;
+
+ if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
+ return;
}
- return NS_OK;
-}
-nsresult
-nsWebPDecoder::SaveDataBuffer(const uint8_t* aData, size_t aLength)
-{
- 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,
- ("[this=%p] nsWebPDecoder::SaveDataBuffer -- oom, append %zu on %zu\n",
- this, aLength, mData.length()));
- return NS_ERROR_OUT_OF_MEMORY;
+ ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n",
+ this));
+ 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>
-nsWebPDecoder::ReadHeader(const char* aData, size_t aLength)
+LexerResult
+nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer,
+ bool aIsComplete)
{
+ MOZ_ASSERT(aDemuxer);
+
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();
- }
+ ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength));
+
+ uint32_t flags = WebPDemuxGetI(aDemuxer, WEBP_FF_FORMAT_FLAGS);
- 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();
+ if (!IsMetadataDecode() && !mGotColorProfile) {
+ if (flags & WebPFeatureFlags::ICCP_FLAG) {
+ WebPChunkIterator iter;
+ if (!WebPDemuxGetChunk(aDemuxer, "ICCP", 1, &iter)) {
+ return aIsComplete ? LexerResult(TerminalState::FAILURE)
+ : LexerResult(Yield::NEED_MORE_DATA);
}
- 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();
+
+ ApplyColorProfile(reinterpret_cast<const char*>(iter.chunk.bytes),
+ iter.chunk.size);
+ WebPDemuxReleaseChunkIterator(&iter);
+ } else {
+ ApplyColorProfile(nullptr, 0);
+ }
}
- if (features.has_animation) {
+ 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.
- 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);
+ if (!WebPDemuxGetFrame(aDemuxer, 1, &iter)) {
+ return aIsComplete ? LexerResult(TerminalState::FAILURE)
+ : LexerResult(Yield::NEED_MORE_DATA);
}
PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration));
WebPDemuxReleaseIterator(&iter);
- WebPDemuxDelete(demuxer);
+ } else {
+ // Single frames don't need a demuxer to be created.
+ mNeedDemuxer = false;
}
- 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) {
+ 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::B8G8R8A8;
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 Transition::TerminateSuccess();
+ return LexerResult(TerminalState::SUCCESS);
}
- auto transition = ReadPayload((const char*)data, length);
- if (!features.has_animation) {
- mData.clearAndFree();
- }
- return transition;
+ return ReadPayload(aDemuxer, aIsComplete);
}
-LexerTransition<nsWebPDecoder::State>
-nsWebPDecoder::ReadPayload(const char* aData, size_t aLength)
+LexerResult
+nsWebPDecoder::ReadPayload(WebPDemuxer* aDemuxer,
+ bool aIsComplete)
{
- auto data = (const uint8_t*)aData;
if (!HasAnimation()) {
- auto rv = ReadSingle(data, aLength, true, FullFrame());
- if (rv.NextStateIsTerminal() &&
- rv.NextStateAsTerminal() == TerminalState::SUCCESS) {
+ auto rv = ReadSingle(mData, mLength, FullFrame());
+ if (rv.is<TerminalState>() &&
+ rv.as<TerminalState>() == TerminalState::SUCCESS) {
PostDecodeDone();
}
return rv;
}
- return ReadMultiple(data, aLength);
+ return ReadMultiple(aDemuxer, aIsComplete);
}
-LexerTransition<nsWebPDecoder::State>
-nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, bool aAppend, const IntRect& aFrameRect)
+LexerResult
+nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, const IntRect& aFrameRect)
{
MOZ_ASSERT(!IsMetadataDecode());
MOZ_ASSERT(aData);
@@ -275,125 +422,120 @@ nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, bool aAppend, co
("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength));
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;
- 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();
- }
+ 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;
- const uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
- if (!rowStart || lastRow == -1) {
- return Transition::ContinueUnbuffered(State::WEBP_DATA);
- }
+ int lastRow = -1;
+ int width = 0;
+ int height = 0;
+ int stride = 0;
+ uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
- 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();
- }
+ MOZ_LOG(sWebPLog, LogLevel::Debug,
+ ("[this=%p] nsWebPDecoder::ReadSingle -- complete %d, read %d rows, "
+ "has %d rows available\n", this, complete, mLastRow, lastRow));
- 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) {
+ 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 -- write pixels error\n",
- this));
- return Transition::TerminateFailure();
+ ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, %d)\n",
+ this, width, height, stride));
+ return LexerResult(TerminalState::FAILURE);
}
- }
- if (mLastRow != lastRow) {
- mLastRow = lastRow;
+ const bool noPremultiply =
+ bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
+
+ for (int row = mLastRow; row < lastRow; row++) {
+ uint8_t* src = rowStart + row * stride;
+ if (mTransform) {
+ qcms_transform_data(mTransform, src, src, width);
+ }
- Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
- if (invalidRect) {
- PostInvalidation(invalidRect->mInputSpaceRect,
- Some(invalidRect->mOutputSpaceRect));
+ WriteState result;
+ if (noPremultiply) {
+ result = mPipe.WritePixelsToRow<uint32_t>([&]() -> NextPixel<uint32_t> {
+ 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;
+ }
}
- }
+
+ mLastRow = lastRow;
+ } while (!complete);
if (!complete) {
- return Transition::ContinueUnbuffered(State::WEBP_DATA);
+ return LexerResult(Yield::NEED_MORE_DATA);
}
EndFrame();
- return Transition::TerminateSuccess();
+ return LexerResult(TerminalState::SUCCESS);
}
-LexerTransition<nsWebPDecoder::State>
-nsWebPDecoder::ReadMultiple(const uint8_t* aData, size_t aLength)
+LexerResult
+nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer, bool aIsComplete)
{
MOZ_ASSERT(!IsMetadataDecode());
- MOZ_ASSERT(aData);
+ MOZ_ASSERT(aDemuxer);
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();
- }
+ ("[this=%p] nsWebPDecoder::ReadMultiple\n", this));
- 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;
+ bool complete = aIsComplete;
WebPIterator iter;
- auto rv = Transition::ContinueUnbuffered(State::WEBP_DATA);
- if (WebPDemuxGetFrame(demuxer, mCurrentFrame + 1, &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;
@@ -418,51 +560,34 @@ nsWebPDecoder::ReadMultiple(const uint8_t* aData, size_t aLength)
break;
}
- mFormat = iter.has_alpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
+ mFormat = iter.has_alpha || mCurrentFrame > 0 ? 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);
+ rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, frameRect);
+ complete = complete && !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);
- }
+ if (rv.is<TerminalState>() &&
+ rv.as<TerminalState>() == 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);
}
- } 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
diff --git a/image/decoders/nsWebPDecoder.h b/image/decoders/nsWebPDecoder.h
index 5b3951cfc..cdd2849f3 100644
--- a/image/decoders/nsWebPDecoder.h
+++ b/image/decoders/nsWebPDecoder.h
@@ -16,7 +16,7 @@ namespace mozilla {
namespace image {
class RasterImage;
-class nsWebPDecoder : public Decoder
+class nsWebPDecoder final : public Decoder
{
public:
virtual ~nsWebPDecoder();
@@ -31,34 +31,28 @@ private:
// Decoders should only be instantiated via DecoderFactory.
explicit nsWebPDecoder(RasterImage* aImage);
- enum class State
- {
- WEBP_DATA,
- FINISHED_WEBP_DATA
- };
+ void ApplyColorProfile(const char* aProfile, size_t aLength);
- LexerTransition<State> ReadHeader(const char* aData, size_t aLength);
- LexerTransition<State> ReadPayload(const char* aData, size_t aLength);
- LexerTransition<State> FinishedData();
+ LexerResult UpdateBuffer(SourceBufferIterator& aIterator,
+ SourceBufferIterator::State aState);
+ LexerResult ReadData();
+ LexerResult ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete);
+ LexerResult ReadPayload(WebPDemuxer* aDemuxer, bool aIsComplete);
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);
+ LexerResult ReadSingle(const uint8_t* aData, size_t aLength,
+ const IntRect& aFrameRect);
- 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;
+ LexerResult ReadMultiple(WebPDemuxer* aDemuxer, bool aIsComplete);
/// 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 buffer used to accumulate data until the complete WebP header is
+ /// received, if and only if the iterator is discontiguous.
+ Vector<uint8_t> mBufferedData;
/// The libwebp output buffer descriptor pointing to the decoded data.
WebPDecBuffer mBuffer;
@@ -78,11 +72,35 @@ private:
/// Surface format for the current frame.
gfx::SurfaceFormat mFormat;
+ /// Frame rect for the current frame.
+ IntRect mFrameRect;
+
/// The last row of decoded pixels written to mPipe.
int mLastRow;
/// Number of decoded frames.
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
diff --git a/image/encoders/png/nsPNGEncoder.cpp b/image/encoders/png/nsPNGEncoder.cpp
index 66294146d..abe6f35b4 100644
--- a/image/encoders/png/nsPNGEncoder.cpp
+++ b/image/encoders/png/nsPNGEncoder.cpp
@@ -9,6 +9,7 @@
#include "nsStreamUtils.h"
#include "nsString.h"
#include "prprf.h"
+#include "mozilla/CheckedInt.h"
using namespace mozilla;
@@ -703,30 +704,55 @@ nsPNGEncoder::WriteCallback(png_structp png, png_bytep data,
return;
}
- if (that->mImageBufferUsed + size > that->mImageBufferSize) {
+ CheckedUint32 sizeNeeded = CheckedUint32(that->mImageBufferUsed) + size;
+ if (!sizeNeeded.isValid()) {
+ // Take the lock to ensure that nobody is trying to read from the buffer
+ // we are destroying
+ ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
+
+ that->NullOutImageBuffer();
+ return;
+ }
+
+ if (sizeNeeded.value() > that->mImageBufferSize) {
// When we're reallocing the buffer we need to take the lock to ensure
// that nobody is trying to read from the buffer we are destroying
ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
- // expand buffer, just double each time
- that->mImageBufferSize *= 2;
- uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer,
- that->mImageBufferSize);
- if (!newBuf) {
- // can't resize, just zero (this will keep us from writing more)
- free(that->mImageBuffer);
- that->mImageBuffer = nullptr;
- that->mImageBufferSize = 0;
- that->mImageBufferUsed = 0;
- return;
+ while (sizeNeeded.value() > that->mImageBufferSize) {
+ // expand buffer, just double each time
+ CheckedUint32 bufferSize = CheckedUint32(that->mImageBufferSize) * 2;
+ if (!bufferSize.isValid()) {
+ that->NullOutImageBuffer();
+ return;
+ }
+ that->mImageBufferSize *= 2;
+ uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer,
+ that->mImageBufferSize);
+ if (!newBuf) {
+ // can't resize, just zero (this will keep us from writing more)
+ that->NullOutImageBuffer();
+ return;
+ }
+ that->mImageBuffer = newBuf;
}
- that->mImageBuffer = newBuf;
}
+
memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
that->mImageBufferUsed += size;
that->NotifyListener();
}
+void nsPNGEncoder::NullOutImageBuffer()
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+
+ free(mImageBuffer);
+ mImageBuffer = nullptr;
+ mImageBufferSize = 0;
+ mImageBufferUsed = 0;
+}
+
void
nsPNGEncoder::NotifyListener()
{
diff --git a/image/encoders/png/nsPNGEncoder.h b/image/encoders/png/nsPNGEncoder.h
index 95e7d5c19..8c2239c11 100644
--- a/image/encoders/png/nsPNGEncoder.h
+++ b/image/encoders/png/nsPNGEncoder.h
@@ -54,6 +54,7 @@ protected:
static void WarningCallback(png_structp png_ptr, png_const_charp warning_msg);
static void ErrorCallback(png_structp png_ptr, png_const_charp error_msg);
static void WriteCallback(png_structp png, png_bytep data, png_size_t size);
+ void NullOutImageBuffer();
void NotifyListener();
png_struct* mPNG;
diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp
index 9c5bf9b99..d982c17c4 100644
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -22,7 +22,6 @@
#include "mozilla/gfx/Tools.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
-#include "mozilla/Telemetry.h"
#include "nsMargin.h"
#include "nsThreadUtils.h"
@@ -162,13 +161,13 @@ imgFrame::imgFrame()
: mMonitor("imgFrame")
, mDecoded(0, 0, 0, 0)
, mLockCount(0)
- , mTimeout(FrameTimeout::FromRawMilliseconds(100))
- , mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
- , mBlendMethod(BlendMethod::OVER)
, mHasNoAlpha(false)
, mAborted(false)
, mFinished(false)
, mOptimizable(false)
+ , mTimeout(FrameTimeout::FromRawMilliseconds(100))
+ , mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
+ , mBlendMethod(BlendMethod::OVER)
, mPalettedImageData(nullptr)
, mPaletteDepth(0)
, mNonPremult(false)
@@ -193,7 +192,8 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
const nsIntRect& aRect,
SurfaceFormat aFormat,
uint8_t aPaletteDepth /* = 0 */,
- bool aNonPremult /* = false */)
+ bool aNonPremult /* = false */,
+ const Maybe<AnimationParams>& aAnimParams /* = Nothing() */)
{
// Assert for properties that should be verified by decoders,
// warn for properties related to bad content.
@@ -206,6 +206,15 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
mImageSize = aImageSize;
mFrameRect = aRect;
+ if (aAnimParams) {
+ mBlendRect = aAnimParams->mBlendRect;
+ mTimeout = aAnimParams->mTimeout;
+ mBlendMethod = aAnimParams->mBlendMethod;
+ mDisposalMethod = aAnimParams->mDisposalMethod;
+ } else {
+ mBlendRect = aRect;
+ }
+
// We only allow a non-trivial frame rect (i.e., a frame rect that doesn't
// cover the entire image) for paletted animation frames. We never draw those
// frames directly; we just use FrameAnimator to composite them and produce a
@@ -608,26 +617,15 @@ imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
}
void
-imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
- DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
- FrameTimeout aTimeout
- /* = FrameTimeout::FromRawMilliseconds(0) */,
- BlendMethod aBlendMethod /* = BlendMethod::OVER */,
- const Maybe<IntRect>& aBlendRect /* = Nothing() */)
+imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */)
{
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
if (aFrameOpacity == Opacity::FULLY_OPAQUE) {
mHasNoAlpha = true;
- Telemetry::Accumulate(Telemetry::IMAGE_DECODE_OPAQUE_BGRA,
- mFormat == SurfaceFormat::B8G8R8A8);
}
- mDisposalMethod = aDisposalMethod;
- mTimeout = aTimeout;
- mBlendMethod = aBlendMethod;
- mBlendRect = aBlendRect;
ImageUpdatedInternal(GetRect());
mFinished = true;
@@ -847,7 +845,7 @@ imgFrame::GetAnimationData() const
bool hasAlpha = mFormat == SurfaceFormat::B8G8R8A8;
return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(),
- mBlendMethod, mBlendRect, mDisposalMethod, hasAlpha);
+ mBlendMethod, Some(mBlendRect), mDisposalMethod, hasAlpha);
}
void
diff --git a/image/imgFrame.h b/image/imgFrame.h
index e864aca7f..928f6ad86 100644
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -12,6 +12,7 @@
#include "mozilla/Monitor.h"
#include "mozilla/Move.h"
#include "mozilla/VolatileBuffer.h"
+#include "AnimationParams.h"
#include "gfxDrawable.h"
#include "imgIContainer.h"
#include "MainThreadUtils.h"
@@ -23,130 +24,12 @@ class ImageRegion;
class DrawableFrameRef;
class RawAccessFrameRef;
-enum class BlendMethod : int8_t {
- // All color components of the frame, including alpha, overwrite the current
- // contents of the frame's output buffer region.
- SOURCE,
-
- // The frame should be composited onto the output buffer based on its alpha,
- // using a simple OVER operation.
- OVER
-};
-
-enum class DisposalMethod : int8_t {
- CLEAR_ALL = -1, // Clear the whole image, revealing what's underneath.
- NOT_SPECIFIED, // Leave the frame and let the new frame draw on top.
- KEEP, // Leave the frame and let the new frame draw on top.
- CLEAR, // Clear the frame's area, revealing what's underneath.
- RESTORE_PREVIOUS // Restore the previous (composited) frame.
-};
-
enum class Opacity : uint8_t {
FULLY_OPAQUE,
SOME_TRANSPARENCY
};
/**
- * FrameTimeout wraps a frame timeout value (measured in milliseconds) after
- * first normalizing it. This normalization is necessary because some tools
- * generate incorrect frame timeout values which we nevertheless have to
- * support. For this reason, code that deals with frame timeouts should always
- * use a FrameTimeout value rather than the raw value from the image header.
- */
-struct FrameTimeout
-{
- /**
- * @return a FrameTimeout of zero. This should be used only for math
- * involving FrameTimeout values. You can't obtain a zero FrameTimeout from
- * FromRawMilliseconds().
- */
- static FrameTimeout Zero() { return FrameTimeout(0); }
-
- /// @return an infinite FrameTimeout.
- static FrameTimeout Forever() { return FrameTimeout(-1); }
-
- /// @return a FrameTimeout obtained by normalizing a raw timeout value.
- static FrameTimeout FromRawMilliseconds(int32_t aRawMilliseconds)
- {
- // Normalize all infinite timeouts to the same value.
- if (aRawMilliseconds < 0) {
- return FrameTimeout::Forever();
- }
-
- // Very small timeout values are problematic for two reasons: we don't want
- // to burn energy redrawing animated images extremely fast, and broken tools
- // generate these values when they actually want a "default" value, so such
- // images won't play back right without normalization. For some context,
- // see bug 890743, bug 125137, bug 139677, and bug 207059. The historical
- // behavior of IE and Opera was:
- // IE 6/Win:
- // 10 - 50ms is normalized to 100ms.
- // >50ms is used unnormalized.
- // Opera 7 final/Win:
- // 10ms is normalized to 100ms.
- // >10ms is used unnormalized.
- if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10 ) {
- return FrameTimeout(100);
- }
-
- // The provided timeout value is OK as-is.
- return FrameTimeout(aRawMilliseconds);
- }
-
- bool operator==(const FrameTimeout& aOther) const
- {
- return mTimeout == aOther.mTimeout;
- }
-
- bool operator!=(const FrameTimeout& aOther) const { return !(*this == aOther); }
-
- FrameTimeout operator+(const FrameTimeout& aOther)
- {
- if (*this == Forever() || aOther == Forever()) {
- return Forever();
- }
-
- return FrameTimeout(mTimeout + aOther.mTimeout);
- }
-
- FrameTimeout& operator+=(const FrameTimeout& aOther)
- {
- *this = *this + aOther;
- return *this;
- }
-
- /**
- * @return this FrameTimeout's value in milliseconds. Illegal to call on a
- * an infinite FrameTimeout value.
- */
- uint32_t AsMilliseconds() const
- {
- if (*this == Forever()) {
- MOZ_ASSERT_UNREACHABLE("Calling AsMilliseconds() on an infinite FrameTimeout");
- return 100; // Fail to something sane.
- }
-
- return uint32_t(mTimeout);
- }
-
- /**
- * @return this FrameTimeout value encoded so that non-negative values
- * represent a timeout in milliseconds, and -1 represents an infinite
- * timeout.
- *
- * XXX(seth): This is a backwards compatibility hack that should be removed.
- */
- int32_t AsEncodedValueDeprecated() const { return mTimeout; }
-
-private:
- explicit FrameTimeout(int32_t aTimeout)
- : mTimeout(aTimeout)
- { }
-
- int32_t mTimeout;
-};
-
-/**
* AnimationData contains all of the information necessary for using an imgFrame
* as part of an animation.
*
@@ -210,14 +93,19 @@ public:
const nsIntRect& aRect,
SurfaceFormat aFormat,
uint8_t aPaletteDepth = 0,
- bool aNonPremult = false);
+ bool aNonPremult = false,
+ const Maybe<AnimationParams>& aAnimParams = Nothing());
nsresult InitForDecoder(const nsIntSize& aSize,
SurfaceFormat aFormat,
uint8_t aPaletteDepth = 0)
{
- return InitForDecoder(aSize, nsIntRect(0, 0, aSize.width, aSize.height),
- aFormat, aPaletteDepth);
+ nsIntRect frameRect(0, 0, aSize.width, aSize.height);
+ AnimationParams animParams { frameRect, FrameTimeout::Forever(),
+ /* aFrameNum */ 1, BlendMethod::OVER,
+ DisposalMethod::NOT_SPECIFIED };
+ return InitForDecoder(aSize, frameRect,
+ aFormat, aPaletteDepth, false, Some(animParams));
}
@@ -268,22 +156,8 @@ public:
* RawAccessFrameRef pointing to an imgFrame.
*
* @param aFrameOpacity Whether this imgFrame is opaque.
- * @param aDisposalMethod For animation frames, how this imgFrame is cleared
- * from the compositing frame before the next frame is
- * displayed.
- * @param aTimeout For animation frames, the timeout before the next
- * frame is displayed.
- * @param aBlendMethod For animation frames, a blending method to be used
- * when compositing this frame.
- * @param aBlendRect For animation frames, if present, the subrect in
- * which @aBlendMethod applies. Outside of this
- * subrect, BlendMethod::OVER is always used.
*/
- void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
- DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
- FrameTimeout aTimeout = FrameTimeout::FromRawMilliseconds(0),
- BlendMethod aBlendMethod = BlendMethod::OVER,
- const Maybe<IntRect>& aBlendRect = Nothing());
+ void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY);
/**
* Mark this imgFrame as aborted. This informs the imgFrame that if it isn't
@@ -322,9 +196,15 @@ public:
*/
uint32_t GetBytesPerPixel() const { return GetIsPaletted() ? 1 : 4; }
- IntSize GetImageSize() const { return mImageSize; }
- IntRect GetRect() const { return mFrameRect; }
+ const IntSize& GetImageSize() const { return mImageSize; }
+ const IntRect& GetRect() const { return mFrameRect; }
IntSize GetSize() const { return mFrameRect.Size(); }
+ const IntRect& GetBlendRect() const { return mBlendRect; }
+ IntRect GetBoundedBlendRect() const { return mBlendRect.Intersect(mFrameRect); }
+ FrameTimeout GetTimeout() const { return mTimeout; }
+ BlendMethod GetBlendMethod() const { return mBlendMethod; }
+ DisposalMethod GetDisposalMethod() const { return mDisposalMethod; }
+ bool FormatHasAlpha() const { return mFormat == SurfaceFormat::B8G8R8A8; }
void GetImageData(uint8_t** aData, uint32_t* length) const;
uint8_t* GetImageData() const;
@@ -406,14 +286,6 @@ private: // data
//! Number of RawAccessFrameRefs currently alive for this imgFrame.
int32_t mLockCount;
- //! The timeout for this frame.
- FrameTimeout mTimeout;
-
- DisposalMethod mDisposalMethod;
- BlendMethod mBlendMethod;
- Maybe<IntRect> mBlendRect;
- SurfaceFormat mFormat;
-
bool mHasNoAlpha;
bool mAborted;
bool mFinished;
@@ -426,6 +298,14 @@ private: // data
IntSize mImageSize;
IntRect mFrameRect;
+ IntRect mBlendRect;
+
+ //! The timeout for this frame.
+ FrameTimeout mTimeout;
+
+ DisposalMethod mDisposalMethod;
+ BlendMethod mBlendMethod;
+ SurfaceFormat mFormat;
// The palette and image data for images that are paletted, since Cairo
// doesn't support these images.
diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h
index 79bed9fc1..0c288cddc 100644
--- a/image/test/gtest/Common.h
+++ b/image/test/gtest/Common.h
@@ -245,7 +245,7 @@ already_AddRefed<Decoder> CreateTrivialDecoder();
* @param aConfigs The configuration for the pipeline.
*/
template <typename Func, typename... Configs>
-void WithFilterPipeline(Decoder* aDecoder, Func aFunc, Configs... aConfigs)
+void WithFilterPipeline(Decoder* aDecoder, Func aFunc, const Configs&... aConfigs)
{
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
nsresult rv = pipe->Configure(aConfigs...);
@@ -268,7 +268,7 @@ void WithFilterPipeline(Decoder* aDecoder, Func aFunc, Configs... aConfigs)
* @param aConfigs The configuration for the pipeline.
*/
template <typename... Configs>
-void AssertConfiguringPipelineFails(Decoder* aDecoder, Configs... aConfigs)
+void AssertConfiguringPipelineFails(Decoder* aDecoder, const Configs&... aConfigs)
{
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
nsresult rv = pipe->Configure(aConfigs...);
diff --git a/image/test/gtest/TestADAM7InterpolatingFilter.cpp b/image/test/gtest/TestADAM7InterpolatingFilter.cpp
index d9dab4346..d11224251 100644
--- a/image/test/gtest/TestADAM7InterpolatingFilter.cpp
+++ b/image/test/gtest/TestADAM7InterpolatingFilter.cpp
@@ -33,7 +33,7 @@ WithADAM7InterpolatingFilter(const IntSize& aSize, Func aFunc)
WithFilterPipeline(decoder, Forward<Func>(aFunc),
ADAM7InterpolatingConfig { },
- SurfaceConfig { decoder, 0, aSize,
+ SurfaceConfig { decoder, aSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -45,7 +45,7 @@ AssertConfiguringADAM7InterpolatingFilterFails(const IntSize& aSize)
AssertConfiguringPipelineFails(decoder,
ADAM7InterpolatingConfig { },
- SurfaceConfig { decoder, 0, aSize,
+ SurfaceConfig { decoder, aSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -664,7 +664,7 @@ TEST(ImageADAM7InterpolatingFilter, ConfiguringPalettedADAM7InterpolatingFilterF
// should fail.
AssertConfiguringPipelineFails(decoder,
ADAM7InterpolatingConfig { },
- PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
+ PalettedSurfaceConfig { decoder, IntSize(100, 100),
IntRect(0, 0, 50, 50),
SurfaceFormat::B8G8R8A8, 8,
false });
diff --git a/image/test/gtest/TestDeinterlacingFilter.cpp b/image/test/gtest/TestDeinterlacingFilter.cpp
index 30cad7993..82637bbf7 100644
--- a/image/test/gtest/TestDeinterlacingFilter.cpp
+++ b/image/test/gtest/TestDeinterlacingFilter.cpp
@@ -28,7 +28,7 @@ WithDeinterlacingFilter(const IntSize& aSize,
WithFilterPipeline(decoder, Forward<Func>(aFunc),
DeinterlacingConfig<uint32_t> { aProgressiveDisplay },
- SurfaceConfig { decoder, 0, aSize,
+ SurfaceConfig { decoder, aSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -41,7 +41,7 @@ WithPalettedDeinterlacingFilter(const IntSize& aSize,
WithFilterPipeline(decoder, Forward<Func>(aFunc),
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true },
- PalettedSurfaceConfig { decoder, 0, aSize,
+ PalettedSurfaceConfig { decoder, aSize,
IntRect(0, 0, 100, 100),
SurfaceFormat::B8G8R8A8, 8,
false });
@@ -55,7 +55,7 @@ AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize)
AssertConfiguringPipelineFails(decoder,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
- SurfaceConfig { decoder, 0, aSize,
+ SurfaceConfig { decoder, aSize,
SurfaceFormat::B8G8R8A8, false });
}
diff --git a/image/test/gtest/TestDownscalingFilter.cpp b/image/test/gtest/TestDownscalingFilter.cpp
index 596becab0..d7aa0ead2 100644
--- a/image/test/gtest/TestDownscalingFilter.cpp
+++ b/image/test/gtest/TestDownscalingFilter.cpp
@@ -29,7 +29,7 @@ WithDownscalingFilter(const IntSize& aInputSize,
WithFilterPipeline(decoder, Forward<Func>(aFunc),
DownscalingConfig { aInputSize,
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, aOutputSize,
+ SurfaceConfig { decoder, aOutputSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -43,7 +43,7 @@ AssertConfiguringDownscalingFilterFails(const IntSize& aInputSize,
AssertConfiguringPipelineFails(decoder,
DownscalingConfig { aInputSize,
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, aOutputSize,
+ SurfaceConfig { decoder, aOutputSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -224,7 +224,7 @@ TEST(ImageDownscalingFilter, ConfiguringPalettedDownscaleFails)
AssertConfiguringPipelineFails(decoder,
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- PalettedSurfaceConfig { decoder, 0, IntSize(20, 20),
+ PalettedSurfaceConfig { decoder, IntSize(20, 20),
IntRect(0, 0, 20, 20),
SurfaceFormat::B8G8R8A8, 8,
false });
diff --git a/image/test/gtest/TestDownscalingFilterNoSkia.cpp b/image/test/gtest/TestDownscalingFilterNoSkia.cpp
index c62ca018d..80928a880 100644
--- a/image/test/gtest/TestDownscalingFilterNoSkia.cpp
+++ b/image/test/gtest/TestDownscalingFilterNoSkia.cpp
@@ -52,6 +52,6 @@ TEST(ImageDownscalingFilter, NoSkia)
AssertConfiguringPipelineFails(decoder,
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, IntSize(50, 50),
+ SurfaceConfig { decoder, IntSize(50, 50),
SurfaceFormat::B8G8R8A8, false });
}
diff --git a/image/test/gtest/TestRemoveFrameRectFilter.cpp b/image/test/gtest/TestRemoveFrameRectFilter.cpp
index e1def590e..ad1f944fc 100644
--- a/image/test/gtest/TestRemoveFrameRectFilter.cpp
+++ b/image/test/gtest/TestRemoveFrameRectFilter.cpp
@@ -28,7 +28,7 @@ WithRemoveFrameRectFilter(const IntSize& aSize,
WithFilterPipeline(decoder, Forward<Func>(aFunc),
RemoveFrameRectConfig { aFrameRect },
- SurfaceConfig { decoder, 0, aSize,
+ SurfaceConfig { decoder, aSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -41,7 +41,7 @@ AssertConfiguringRemoveFrameRectFilterFails(const IntSize& aSize,
AssertConfiguringPipelineFails(decoder,
RemoveFrameRectConfig { aFrameRect },
- SurfaceConfig { decoder, 0, aSize,
+ SurfaceConfig { decoder, aSize,
SurfaceFormat::B8G8R8A8, false });
}
@@ -320,7 +320,7 @@ TEST(ImageRemoveFrameRectFilter, ConfiguringPalettedRemoveFrameRectFails)
// should fail.
AssertConfiguringPipelineFails(decoder,
RemoveFrameRectConfig { IntRect(0, 0, 50, 50) },
- PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
+ PalettedSurfaceConfig { decoder, IntSize(100, 100),
IntRect(0, 0, 50, 50),
SurfaceFormat::B8G8R8A8, 8,
false });
diff --git a/image/test/gtest/TestSurfacePipeIntegration.cpp b/image/test/gtest/TestSurfacePipeIntegration.cpp
index 5e8c19fc2..27138a3ee 100644
--- a/image/test/gtest/TestSurfacePipeIntegration.cpp
+++ b/image/test/gtest/TestSurfacePipeIntegration.cpp
@@ -149,7 +149,7 @@ TEST_F(ImageSurfacePipeIntegration, SurfacePipe)
auto sink = MakeUnique<SurfaceSink>();
nsresult rv =
- sink->Configure(SurfaceConfig { decoder, 0, IntSize(100, 100),
+ sink->Configure(SurfaceConfig { decoder, IntSize(100, 100),
SurfaceFormat::B8G8R8A8, false });
ASSERT_TRUE(NS_SUCCEEDED(rv));
@@ -227,7 +227,7 @@ TEST_F(ImageSurfacePipeIntegration, PalettedSurfacePipe)
auto sink = MakeUnique<PalettedSurfaceSink>();
nsresult rv =
- sink->Configure(PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
+ sink->Configure(PalettedSurfaceConfig { decoder, IntSize(100, 100),
IntRect(0, 0, 100, 100),
SurfaceFormat::B8G8R8A8,
8, false });
@@ -313,7 +313,7 @@ TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels)
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, IntSize(25, 25),
+ SurfaceConfig { decoder, IntSize(25, 25),
SurfaceFormat::B8G8R8A8, false });
}
@@ -369,7 +369,7 @@ TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectBottomRightDownscaleWritePixe
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, IntSize(20, 20),
+ SurfaceConfig { decoder, IntSize(20, 20),
SurfaceFormat::B8G8R8A8, false });
}
@@ -403,7 +403,7 @@ TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectTopLeftDownscaleWritePixels)
RemoveFrameRectConfig { IntRect(-50, -50, 100, 100) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, IntSize(20, 20),
+ SurfaceConfig { decoder, IntSize(20, 20),
SurfaceFormat::B8G8R8A8, false });
}
@@ -427,7 +427,7 @@ TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels)
WithFilterPipeline(decoder, test,
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
- SurfaceConfig { decoder, 0, IntSize(100, 100),
+ SurfaceConfig { decoder, IntSize(100, 100),
SurfaceFormat::B8G8R8A8, false });
}
@@ -450,7 +450,7 @@ TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixe
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, IntSize(20, 20),
+ SurfaceConfig { decoder, IntSize(20, 20),
SurfaceFormat::B8G8R8A8, false });
}
@@ -465,7 +465,7 @@ TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedRemoveFrameRectDownscaleF
RemoveFrameRectConfig { IntRect(0, 0, 50, 50) },
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
+ PalettedSurfaceConfig { decoder, IntSize(100, 100),
IntRect(0, 0, 50, 50),
SurfaceFormat::B8G8R8A8, 8,
false });
@@ -482,7 +482,7 @@ TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedDeinterlaceDownscaleFails
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true},
DownscalingConfig { IntSize(100, 100),
SurfaceFormat::B8G8R8A8 },
- PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
+ PalettedSurfaceConfig { decoder, IntSize(100, 100),
IntRect(0, 0, 20, 20),
SurfaceFormat::B8G8R8A8, 8,
false });
@@ -503,6 +503,6 @@ TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails)
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
DownscalingConfig { IntSize(60000, 60000),
SurfaceFormat::B8G8R8A8 },
- SurfaceConfig { decoder, 0, IntSize(600, 600),
+ SurfaceConfig { decoder, IntSize(600, 600),
SurfaceFormat::B8G8R8A8, false });
}
diff --git a/image/test/gtest/TestSurfaceSink.cpp b/image/test/gtest/TestSurfaceSink.cpp
index ccf9be3ec..3a1c74d12 100644
--- a/image/test/gtest/TestSurfaceSink.cpp
+++ b/image/test/gtest/TestSurfaceSink.cpp
@@ -32,7 +32,7 @@ WithSurfaceSink(Func aFunc)
const bool flipVertically = Orientation == Orient::FLIP_VERTICALLY;
WithFilterPipeline(decoder, Forward<Func>(aFunc),
- SurfaceConfig { decoder, 0, IntSize(100, 100),
+ SurfaceConfig { decoder, IntSize(100, 100),
SurfaceFormat::B8G8R8A8, flipVertically });
}
@@ -43,7 +43,7 @@ WithPalettedSurfaceSink(const IntRect& aFrameRect, Func aFunc)
ASSERT_TRUE(decoder != nullptr);
WithFilterPipeline(decoder, Forward<Func>(aFunc),
- PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
+ PalettedSurfaceConfig { decoder, IntSize(100, 100),
aFrameRect, SurfaceFormat::B8G8R8A8,
8, false });
}