import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1188569: Drop unneeded MOZ_WARN_UNUSED_RESULT from from LookupBestMatch in SurfaceCache.cpp. r=seth (5e74e0028c)
- Bug 1192356 (Part 1) - Take advantage of mozilla::Tie() in SurfaceCache.cpp. r=dholbert (e4908c725d)
- Bug 1192356 (Part 2) - Take advantage of mozilla::Tie() in RasterImage.cpp. r=tn (1204189b73)
- Bug 1185800 - Add DecoderFlags and SurfaceFlags enum classes and use them instead of imgIContainer flags in all decoder-related code. r=tn (3abdab11f6)
- Bug 1196066 (Part 3) - Rewrite nsICODecoder to use StreamingLexer. r=tn (e2ba590c9d)
- Bug 1196066 (Part 4) - Enable the ICOMultiChunk test, which now passes. r=tn (9e02611959)
- Bug 1124084 - Flip on downscale-during-decode everywhere. r=tn (bd9deff966)
- Bug 1160801 - Treat invalid GIF disposal methods as DisposalMethod::NOT_SPECIFIED. r=jrmuizel (e26feaf8fb)
- Bug 1201796 (Part 1) - Treat ICOs with wrong widths and heights as corrupt. r=tn (322ba20808)
- Bug 1201796 (Part 2) - Add GetFrameAtSize() to support downscale-during-decode for GetFrame() use cases. r=tn (92f5d3a0a7)
- Bug 1194906 - Replace 'NS_ENSURE_TRUE(BadImage(..))' warnings with more useful messages. r=tn (cc3b368673)
- Bug 1201796 (Part 3) - Enable downscale-during-decode for imgITools::EncodeScaledImage(). r=tn (e2cdb5b520)
- Bug 1194472 - Correctly fetch compositor backend in WebGLContext. r=jgilbert (0092052dfc)
- Bug 1161913 - Part 1 - Add invalidation state for CaptureStream to Canvas and Contexts. r=mt (0377d6bbe7)
- Bug 1168075 - Fix CanvasCaptureMediaStream build fail for bluetooth2. r=pehrsons (53c67c0056)
- Bug 1176363 - Part 1: Make a raw copy of each Canvas::CaptureStream frame. r=mattwoodrow (a5df5793d6)
- Bug 1194575 - Rename RecoverFromLossOfFrames() to RecoverFromInvalidFrames() to better reflect its role. r=tn (baa6455e79)
- Bug 1146663 (Part 1) - Remove HQ scaling, which is now dead code. r=tn (efaddadea0)
- Bug 1146663 (Part 2) - Remove the concept of lifetimes from the SurfaceCache. r=dholbert (ab9862d7ee)
- Bug 1146663 (Part 3) - Make it impossible to deoptimize imgFrames. r=tn (19e2f1b370)
- Bug 1201763 - Add downscale-during-decode support for the ICON decoder. r=tn (33a2b95e5c)
- Bug 1194058 (Part 1) - Add Deinterlacer to allow Downscaler to work with interlaced images. r=tn (f7c57b7a8e)
- Bug 1194058 (Part 2) - Add downscale-during-decode support for the GIF decoder. r=tn (85622f9d55)
- Bug 1201796 (Part 4) - Add downscale-during-decode support for the ICO decoder. r=tn (d09d18b0d9)
- Bug 1146663 (Part 4) - Make all RasterImages support downscale-during-decode. r=tn (264642a895)
- Bug 1146663 (Part 5) - Require that all image decoders support downscale-during-decode. r=tn (79ad99885d)
- Bug 1206836 - When downscaling ICOs, downscale the AND mask as well. r=tn a=KWierso (08ec3d092b)
- missing bit of Bug 1138293 - Use malloc/free/realloc/calloc (eb8e5e1b9c)
- missing bit of Bug 1146663 (Part 3) - Make it impossible to deoptimize imgFrames. (233befe48f)
- Bug 1208935 - Move Deinterlacer to a standalone file. r=seth (b50322abc286)
This commit is contained in:
2022-05-18 11:52:08 +08:00
parent 00b0a024a4
commit f1d1e16669
78 changed files with 1660 additions and 1424 deletions
+1 -1
View File
@@ -62,7 +62,6 @@ pref("browser.cache.memory_limit", 2048); // 2 MB
/* image cache prefs */
pref("image.cache.size", 1048576); // bytes
pref("image.high_quality_downscaling.enabled", false);
pref("canvas.image.cache.limit", 20971520); // 20 MB
/* offline cache prefs */
@@ -326,6 +325,7 @@ pref("media.gonk.enabled", true);
pref("media.video-queue.default-size", 3);
// optimize images' memory usage
pref("image.downscale-during-decode.enabled", true);
pref("image.decode-only-on-draw.enabled", true);
pref("image.mem.allow_locking_in_content_processes", false); /* don't allow image locking */
// Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
+21 -1
View File
@@ -949,7 +949,9 @@ CanvasRenderingContext2D::CanvasRenderingContext2D()
, mIPC(false)
, mDrawObserver(nullptr)
, mIsEntireFrameInvalid(false)
, mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
, mPredictManyRedrawCalls(false)
, mIsCapturedFrameInvalid(false)
, mPathTransformWillUpdate(false)
, mInvalidateCount(0)
{
sNumLivingContexts++;
@@ -1054,6 +1056,7 @@ CanvasRenderingContext2D::Reset()
// no longer be valid.
mIsEntireFrameInvalid = false;
mPredictManyRedrawCalls = false;
mIsCapturedFrameInvalid = false;
return NS_OK;
}
@@ -1112,6 +1115,8 @@ CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& a
nsresult
CanvasRenderingContext2D::Redraw()
{
mIsCapturedFrameInvalid = true;
if (mIsEntireFrameInvalid) {
return NS_OK;
}
@@ -1133,6 +1138,8 @@ CanvasRenderingContext2D::Redraw()
void
CanvasRenderingContext2D::Redraw(const mgfx::Rect &r)
{
mIsCapturedFrameInvalid = true;
++mInvalidateCount;
if (mIsEntireFrameInvalid) {
@@ -1170,6 +1177,8 @@ CanvasRenderingContext2D::DidRefresh()
void
CanvasRenderingContext2D::RedrawUser(const gfxRect& r)
{
mIsCapturedFrameInvalid = true;
if (mIsEntireFrameInvalid) {
++mInvalidateCount;
return;
@@ -5710,6 +5719,17 @@ CanvasRenderingContext2D::MarkContextClean()
mInvalidateCount = 0;
}
void
CanvasRenderingContext2D::MarkContextCleanForFrameCapture()
{
mIsCapturedFrameInvalid = false;
}
bool
CanvasRenderingContext2D::IsContextCleanForFrameCapture()
{
return !mIsCapturedFrameInvalid;
}
bool
CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager)
+9
View File
@@ -527,6 +527,8 @@ public:
LayerManager *aManager) override;
virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override;
void MarkContextClean() override;
void MarkContextCleanForFrameCapture() override;
bool IsContextCleanForFrameCapture() override;
NS_IMETHOD SetIsIPC(bool isIPC) override;
// this rect is in canvas device space
void Redraw(const mozilla::gfx::Rect &r);
@@ -819,6 +821,13 @@ protected:
*/
bool mPredictManyRedrawCalls;
/**
* Flag to avoid unnecessary surface copies to FrameCaptureListeners in the
* case when the canvas is not currently being drawn into and not rendered
* but canvas capturing is still ongoing.
*/
bool mIsCapturedFrameInvalid;
// This is stored after GetThebesSurface has been called once to avoid
// excessive ThebesSurface initialization overhead.
nsRefPtr<gfxASurface> mThebesSurface;
+20 -4
View File
@@ -220,6 +220,7 @@ WebGLContext::WebGLContext()
{
mGeneration = 0;
mInvalidated = false;
mCapturedFrameInvalidated = false;
mShouldPresent = true;
mResetLayer = true;
mOptionsFrozen = false;
@@ -413,10 +414,12 @@ WebGLContext::DestroyResourcesAndContext()
void
WebGLContext::Invalidate()
{
if (mInvalidated)
if (!mCanvasElement)
return;
if (!mCanvasElement)
mCapturedFrameInvalidated = true;
if (mInvalidated)
return;
nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
@@ -669,6 +672,7 @@ PopulateCapFallbackQueue(const SurfaceCaps& baseCaps,
static bool
CreateOffscreen(GLContext* gl, const WebGLContextOptions& options,
const nsCOMPtr<nsIGfxInfo>& gfxInfo, WebGLContext* webgl,
layers::LayersBackend layersBackend,
layers::ISurfaceAllocator* surfAllocator)
{
SurfaceCaps baseCaps;
@@ -686,7 +690,7 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options,
if (gl->IsANGLE() ||
(gl->GetContextType() == GLContextType::GLX &&
gfxPlatform::GetPlatform()->GetCompositorBackend() == LayersBackend::LAYERS_OPENGL))
layersBackend == LayersBackend::LAYERS_OPENGL))
{
// We can't use no-alpha formats on ANGLE yet because of:
// https://code.google.com/p/angleproject/issues/detail?id=764
@@ -760,7 +764,8 @@ WebGLContext::CreateOffscreenGL(bool forceEnabled)
if (!gl)
break;
if (!CreateOffscreen(gl, mOptions, gfxInfo, this, surfAllocator))
if (!CreateOffscreen(gl, mOptions, gfxInfo, this,
GetCompositorBackendType(), surfAllocator))
break;
if (!InitAndValidateGL())
@@ -1278,6 +1283,17 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
return canvasLayer.forget();
}
layers::LayersBackend
WebGLContext::GetCompositorBackendType() const
{
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
if (docWidget) {
layers::LayerManager* layerManager = docWidget->GetLayerManager();
return layerManager->GetCompositorBackendType();
}
return LayersBackend::LAYERS_NONE;
}
void
WebGLContext::GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval)
{
+7
View File
@@ -315,6 +315,10 @@ public:
// contents of the buffer.
void MarkContextClean() override { mInvalidated = false; }
void MarkContextCleanForFrameCapture() override { mCapturedFrameInvalidated = false; }
bool IsContextCleanForFrameCapture() override { return !mCapturedFrameInvalidated; }
gl::GLContext* GL() const { return gl; }
bool IsPremultAlpha() const { return mOptions.premultipliedAlpha; }
@@ -362,6 +366,8 @@ public:
return IsContextLost() ? 0 : mHeight;
}
layers::LayersBackend GetCompositorBackendType() const;
void
GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval);
@@ -1031,6 +1037,7 @@ protected:
WebGLContextOptions mOptions;
bool mInvalidated;
bool mCapturedFrameInvalidated;
bool mResetLayer;
bool mOptionsFrozen;
bool mMinCapability;
@@ -134,6 +134,13 @@ public:
virtual void MarkContextClean() = 0;
// Called when a frame is captured.
virtual void MarkContextCleanForFrameCapture() = 0;
// Whether the context is clean or has been invalidated since the last frame
// was captured.
virtual bool IsContextCleanForFrameCapture() = 0;
// Redraw the dirty rectangle of this canvas.
NS_IMETHOD Redraw(const gfxRect &dirty) = 0;
+15
View File
@@ -1033,6 +1033,21 @@ HTMLCanvasElement::MarkContextClean()
mCurrentContext->MarkContextClean();
}
void
HTMLCanvasElement::MarkContextCleanForFrameCapture()
{
if (!mCurrentContext)
return;
mCurrentContext->MarkContextCleanForFrameCapture();
}
bool
HTMLCanvasElement::IsContextCleanForFrameCapture()
{
return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
}
already_AddRefed<SourceSurface>
HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
{
+7
View File
@@ -214,6 +214,13 @@ public:
// take a snapshot of the canvas that needs to be "live" (e.g. -moz-element).
void MarkContextClean();
// Call this after capturing a frame, so we can avoid unnecessary surface
// copies for future frames when no drawing has occurred.
void MarkContextCleanForFrameCapture();
// Starts returning false when something is drawn.
bool IsContextCleanForFrameCapture();
nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
protected:
+34 -6
View File
@@ -8,9 +8,10 @@
#include "gfxPlatform.h"
#include "ImageContainer.h"
#include "MediaStreamGraph.h"
#include "mozilla/Mutex.h"
#include "mozilla/dom/CanvasCaptureMediaStreamBinding.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Mutex.h"
#include "nsContentUtils.h"
using namespace mozilla::layers;
@@ -204,15 +205,42 @@ public:
return NS_ERROR_FAILURE;
}
RefPtr<SourceSurface> opt = gfxPlatform::GetPlatform()
->ScreenReferenceDrawTarget()->OptimizeSourceSurface(snapshot);
if (!opt) {
RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
if (!data) {
return NS_ERROR_FAILURE;
}
RefPtr<DataSourceSurface> copy;
{
DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
if (!read.IsMapped()) {
return NS_ERROR_FAILURE;
}
copy = Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
data->GetFormat(),
read.GetStride());
if (!copy) {
return NS_ERROR_FAILURE;
}
DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
if (!write.IsMapped()) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(read.GetStride() == write.GetStride());
MOZ_ASSERT(data->GetSize() == copy->GetSize());
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
memcpy(write.GetData(), read.GetData(),
write.GetStride() * copy->GetSize().height);
}
CairoImage::Data imageData;
imageData.mSize = opt->GetSize();
imageData.mSourceSurface = opt;
imageData.mSize = copy->GetSize();
imageData.mSourceSurface = copy;
RefPtr<CairoImage> image = new layers::CairoImage();
image->SetData(imageData);
+4 -1
View File
@@ -6,6 +6,9 @@
#ifndef mozilla_dom_CanvasCaptureMediaStream_h_
#define mozilla_dom_CanvasCaptureMediaStream_h_
#include "DOMMediaStream.h"
#include "StreamBuffer.h"
namespace mozilla {
class DOMMediaStream;
class MediaStreamListener;
@@ -13,7 +16,7 @@ class SourceMediaStream;
namespace layers {
class Image;
}
} // namespace layers
namespace dom {
class CanvasCaptureMediaStream;
+1 -4
View File
@@ -272,10 +272,7 @@ private:
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);
DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false);
DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, false);
DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false);
DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000);
DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520);
DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000);
DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false);
+10
View File
@@ -220,6 +220,16 @@ ClippedImage::GetFrame(uint32_t aWhichFrame,
return GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
ClippedImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
// XXX(seth): It'd be nice to support downscale-during-decode for this case,
// but right now we just fall back to the intrinsic size.
return GetFrame(aWhichFrame, aFlags);
}
already_AddRefed<SourceSurface>
ClippedImage::GetFrameInternal(const nsIntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
+4
View File
@@ -37,6 +37,10 @@ public:
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrameAtSize(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags) override;
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
+13 -11
View File
@@ -47,35 +47,37 @@ public:
static void Dispatch(RasterImage* aImage,
Progress aProgress,
const nsIntRect& aInvalidRect,
uint32_t aFlags)
SurfaceFlags aSurfaceFlags)
{
MOZ_ASSERT(aImage);
nsCOMPtr<nsIRunnable> worker =
new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags);
new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aSurfaceFlags);
NS_DispatchToMainThread(worker);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
mImage->NotifyProgress(mProgress, mInvalidRect, mFlags);
mImage->NotifyProgress(mProgress, mInvalidRect, mSurfaceFlags);
return NS_OK;
}
private:
NotifyProgressWorker(RasterImage* aImage, Progress aProgress,
const nsIntRect& aInvalidRect, uint32_t aFlags)
NotifyProgressWorker(RasterImage* aImage,
Progress aProgress,
const nsIntRect& aInvalidRect,
SurfaceFlags aSurfaceFlags)
: mImage(aImage)
, mProgress(aProgress)
, mInvalidRect(aInvalidRect)
, mFlags(aFlags)
, mSurfaceFlags(aSurfaceFlags)
{ }
nsRefPtr<RasterImage> mImage;
const Progress mProgress;
const nsIntRect mInvalidRect;
const uint32_t mFlags;
const SurfaceFlags mSurfaceFlags;
};
class NotifyDecodeCompleteWorker : public nsRunnable
@@ -470,17 +472,17 @@ DecodePool::NotifyProgress(Decoder* aDecoder)
MOZ_ASSERT(aDecoder);
if (!NS_IsMainThread() ||
(aDecoder->GetFlags() & imgIContainer::FLAG_ASYNC_NOTIFY)) {
(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
aDecoder->TakeProgress(),
aDecoder->TakeInvalidRect(),
aDecoder->GetDecodeFlags());
aDecoder->GetSurfaceFlags());
return;
}
aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
aDecoder->TakeInvalidRect(),
aDecoder->GetDecodeFlags());
aDecoder->GetSurfaceFlags());
}
void
@@ -489,7 +491,7 @@ DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
MOZ_ASSERT(aDecoder);
if (!NS_IsMainThread() ||
(aDecoder->GetFlags() & imgIContainer::FLAG_ASYNC_NOTIFY)) {
(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
NotifyDecodeCompleteWorker::Dispatch(aDecoder);
return;
}
+26 -15
View File
@@ -31,13 +31,11 @@ Decoder::Decoder(RasterImage* aImage)
, mFrameCount(0)
, mFailCode(NS_OK)
, mChunkCount(0)
, mFlags(0)
, mDecoderFlags(DefaultDecoderFlags())
, mSurfaceFlags(DefaultSurfaceFlags())
, mBytesDecoded(0)
, mInitialized(false)
, mMetadataDecode(false)
, mSendPartialInvalidations(false)
, mImageIsTransient(false)
, mFirstFrameDecode(false)
, mInFrame(false)
, mDataDone(false)
, mDecodeDone(false)
@@ -238,12 +236,28 @@ Decoder::CompleteDecode()
// If this image wasn't animated and isn't a transient image, mark its frame
// as optimizable. We don't support optimizing animated images and
// optimizing transient images isn't worth it.
if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) {
if (!HasAnimation() &&
!(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
mCurrentFrame) {
mCurrentFrame->SetOptimizable();
}
}
}
nsresult
Decoder::SetTargetSize(const nsIntSize& aSize)
{
// Make sure the size is reasonable.
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
return NS_ERROR_FAILURE;
}
// Create a downscaler that we'll filter our output through.
mDownscaler.emplace(aSize);
return NS_OK;
}
nsresult
Decoder::AllocateFrame(uint32_t aFrameNum,
const nsIntSize& aTargetSize,
@@ -252,8 +266,8 @@ Decoder::AllocateFrame(uint32_t aFrameNum,
uint8_t aPaletteDepth)
{
mCurrentFrame = AllocateFrameInternal(aFrameNum, aTargetSize, aFrameRect,
GetDecodeFlags(), aFormat,
aPaletteDepth, mCurrentFrame.get());
aFormat, aPaletteDepth,
mCurrentFrame.get());
if (mCurrentFrame) {
// Gather the raw pointers the decoders will use.
@@ -279,7 +293,6 @@ RawAccessFrameRef
Decoder::AllocateFrameInternal(uint32_t aFrameNum,
const nsIntSize& aTargetSize,
const nsIntRect& aFrameRect,
uint32_t aDecodeFlags,
SurfaceFormat aFormat,
uint8_t aPaletteDepth,
imgFrame* aPreviousFrame)
@@ -307,8 +320,7 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
}
nsRefPtr<imgFrame> frame = new imgFrame();
bool nonPremult =
aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat,
aPaletteDepth, nonPremult))) {
NS_WARNING("imgFrame::Init should succeed");
@@ -325,9 +337,8 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
InsertOutcome outcome =
SurfaceCache::Insert(frame, ImageKey(mImage.get()),
RasterSurfaceKey(aTargetSize,
aDecodeFlags,
aFrameNum),
Lifetime::Persistent);
mSurfaceFlags,
aFrameNum));
if (outcome == InsertOutcome::FAILURE) {
// We couldn't insert the surface, almost certainly due to low memory. We
// treat this as a permanent error to help the system recover; otherwise,
@@ -440,7 +451,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
// If we're not sending partial invalidations, then we send an invalidation
// here when the first frame is complete.
if (!mSendPartialInvalidations && !HasAnimation()) {
if (!ShouldSendPartialInvalidations() && !HasAnimation()) {
mInvalidRect.UnionRect(mInvalidRect,
gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
}
@@ -457,7 +468,7 @@ Decoder::PostInvalidation(const nsIntRect& aRect,
// Record this invalidation, unless we're not sending partial invalidations
// or we're past the first frame.
if (mSendPartialInvalidations && !HasAnimation()) {
if (ShouldSendPartialInvalidations() && !HasAnimation()) {
mInvalidRect.UnionRect(mInvalidRect, aRect);
mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect));
}
+35 -45
View File
@@ -10,9 +10,12 @@
#include "RasterImage.h"
#include "mozilla/RefPtr.h"
#include "DecodePool.h"
#include "DecoderFlags.h"
#include "Downscaler.h"
#include "ImageMetadata.h"
#include "Orientation.h"
#include "SourceBuffer.h"
#include "SurfaceFlags.h"
namespace mozilla {
@@ -110,19 +113,14 @@ public:
* If this decoder supports downscale-during-decode, sets the target size that
* this image should be decoded to.
*
* If this decoder *doesn't* support downscale-during-decode, returns
* NS_ERROR_NOT_AVAILABLE. If the provided size is unacceptable, returns
* another error.
* If the provided size is unacceptable, an error is returned.
*
* Returning NS_OK from this method is a promise that the decoder will decode
* the image to the requested target size unless it encounters an error.
*
* This must be called before Init() is called.
*/
virtual nsresult SetTargetSize(const nsIntSize& aSize)
{
return NS_ERROR_NOT_AVAILABLE;
}
nsresult SetTargetSize(const nsIntSize& aSize);
/**
* Set the requested sample size for this decoder. Used to implement the
@@ -140,24 +138,6 @@ public:
*/
virtual void SetResolution(const gfx::IntSize& aResolution) { }
/**
* Set whether should send partial invalidations.
*
* If @aSend is true, we'll send partial invalidations when decoding the first
* frame of the image, so image notifications observers will be able to
* gradually draw in the image as it downloads.
*
* If @aSend is false (the default), we'll only send an invalidation when we
* complete the first frame.
*
* This must be called before Init() is called.
*/
void SetSendPartialInvalidations(bool aSend)
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mSendPartialInvalidations = aSend;
}
/**
* Set an iterator to the SourceBuffer which will feed data to this decoder.
*
@@ -174,27 +154,21 @@ public:
}
/**
* Set whether this decoder is associated with a transient image. The decoder
* may choose to avoid certain optimizations that don't pay off for
* short-lived images in this case.
* Should this decoder send partial invalidations?
*/
void SetImageIsTransient(bool aIsTransient)
bool ShouldSendPartialInvalidations() const
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mImageIsTransient = aIsTransient;
return !(mDecoderFlags & DecoderFlags::IS_REDECODE);
}
/**
* Set whether we should stop decoding after the first frame.
* Should we stop decoding after the first frame?
*/
void SetIsFirstFrameDecode()
bool IsFirstFrameDecode() const
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mFirstFrameDecode = true;
return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY);
}
bool IsFirstFrameDecode() const { return mFirstFrameDecode; }
size_t BytesDecoded() const { return mBytesDecoded; }
// The amount of time we've spent inside Write() so far for this decoder.
@@ -258,9 +232,26 @@ public:
SEQUENTIAL // decode to final image immediately
};
void SetFlags(uint32_t aFlags) { mFlags = aFlags; }
uint32_t GetFlags() const { return mFlags; }
uint32_t GetDecodeFlags() const { return DecodeFlags(mFlags); }
/**
* Get or set the DecoderFlags that influence the behavior of this decoder.
*/
void SetDecoderFlags(DecoderFlags aDecoderFlags)
{
MOZ_ASSERT(!mInitialized);
mDecoderFlags = aDecoderFlags;
}
DecoderFlags GetDecoderFlags() const { return mDecoderFlags; }
/**
* Get or set the SurfaceFlags that select the kind of output this decoder
* will produce.
*/
void SetSurfaceFlags(SurfaceFlags aSurfaceFlags)
{
MOZ_ASSERT(!mInitialized);
mSurfaceFlags = aSurfaceFlags;
}
SurfaceFlags GetSurfaceFlags() const { return mSurfaceFlags; }
bool HasSize() const { return mImageMetadata.HasSize(); }
@@ -408,12 +399,13 @@ protected:
RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
const nsIntSize& aTargetSize,
const nsIntRect& aFrameRect,
uint32_t aDecodeFlags,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth,
imgFrame* aPreviousFrame);
protected:
Maybe<Downscaler> mDownscaler;
uint8_t* mImageData; // Pointer to image data in either Cairo or 8bit format
uint32_t mImageDataLength;
uint32_t* mColormap; // Current colormap to be used in Cairo format
@@ -435,14 +427,12 @@ private:
TimeDuration mDecodeTime;
uint32_t mChunkCount;
uint32_t mFlags;
DecoderFlags mDecoderFlags;
SurfaceFlags mSurfaceFlags;
size_t mBytesDecoded;
bool mInitialized : 1;
bool mMetadataDecode : 1;
bool mSendPartialInvalidations : 1;
bool mImageIsTransient : 1;
bool mFirstFrameDecode : 1;
bool mInFrame : 1;
bool mDataDone : 1;
bool mDecodeDone : 1;
+21 -19
View File
@@ -134,34 +134,30 @@ DecoderFactory::CreateDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
const Maybe<IntSize>& aTargetSize,
uint32_t aFlags,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags,
int aSampleSize,
const IntSize& aResolution,
bool aIsRedecode,
bool aImageIsTransient)
const IntSize& aResolution)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
nsRefPtr<Decoder> decoder = GetDecoder(aType, aImage, aIsRedecode);
nsRefPtr<Decoder> decoder =
GetDecoder(aType, aImage, bool(aDecoderFlags & DecoderFlags::IS_REDECODE));
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetFlags(aFlags);
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY);
decoder->SetSurfaceFlags(aSurfaceFlags);
decoder->SetSampleSize(aSampleSize);
decoder->SetResolution(aResolution);
decoder->SetSendPartialInvalidations(!aIsRedecode);
decoder->SetImageIsTransient(aImageIsTransient);
decoder->SetIsFirstFrameDecode();
// Set a target size for downscale-during-decode if applicable.
if (aTargetSize) {
DebugOnly<nsresult> rv = decoder->SetTargetSize(*aTargetSize);
MOZ_ASSERT(nsresult(rv) != NS_ERROR_NOT_AVAILABLE,
"We're downscale-during-decode but decoder doesn't support it?");
MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
}
@@ -177,7 +173,8 @@ DecoderFactory::CreateDecoder(DecoderType aType,
DecoderFactory::CreateAnimationDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
uint32_t aFlags,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags,
const IntSize& aResolution)
{
if (aType == DecoderType::UNKNOWN) {
@@ -194,9 +191,9 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetFlags(aFlags);
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE);
decoder->SetSurfaceFlags(aSurfaceFlags);
decoder->SetResolution(aResolution);
decoder->SetSendPartialInvalidations(false);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
@@ -238,7 +235,7 @@ DecoderFactory::CreateMetadataDecoder(DecoderType aType,
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
SourceBuffer* aSourceBuffer,
uint32_t aFlags)
SurfaceFlags aSurfaceFlags)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
@@ -251,15 +248,20 @@ DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetFlags(aFlags);
decoder->SetImageIsTransient(true);
// Anonymous decoders are always transient; we don't want to optimize surfaces
// or do any other expensive work that might be wasted.
DecoderFlags decoderFlags = DecoderFlags::IMAGE_IS_TRANSIENT;
// Without an image, the decoder can't store anything in the SurfaceCache, so
// callers will only be able to retrieve the most recent frame via
// Decoder::GetCurrentFrame(). That means that anonymous decoders should
// always be first-frame-only decoders, because nobody ever wants the *last*
// frame.
decoder->SetIsFirstFrameDecode();
decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY;
decoder->SetDecoderFlags(decoderFlags);
decoder->SetSurfaceFlags(aSurfaceFlags);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
@@ -284,7 +286,7 @@ DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
// Initialize the decoder.
decoder->SetMetadataDecode(true);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetIsFirstFrameDecode();
decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
+21 -20
View File
@@ -7,9 +7,12 @@
#ifndef mozilla_image_DecoderFactory_h
#define mozilla_image_DecoderFactory_h
#include "DecoderFlags.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/gfx/2D.h"
#include "nsCOMPtr.h"
#include "SurfaceFlags.h"
class nsACString;
@@ -20,6 +23,10 @@ class Decoder;
class RasterImage;
class SourceBuffer;
/**
* The type of decoder; this is usually determined from a MIME type using
* DecoderFactory::GetDecoderType().
*/
enum class DecoderType
{
PNG,
@@ -44,10 +51,6 @@ public:
* (If the image *is* animated, only the first frame will be decoded.) The
* decoder will send notifications to @aImage.
*
* XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of
* @aFlags. This requires changes to the way that decoder flags work, though.
* See bug 1185800.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
* notifications as decoding progresses.
@@ -57,25 +60,23 @@ public:
* be scaled to during decoding. It's an error to specify
* a target size for a decoder type which doesn't support
* downscale-during-decode.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
* @param aDecoderFlags Flags specifying the behavior of this decoder.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
* @param aSampleSize The sample size requested using #-moz-samplesize (or 0
* if none).
* @param aResolution The resolution requested using #-moz-resolution (or an
* empty rect if none).
* @param aIsRedecode Specify 'true' if this image has been decoded before.
* @param aImageIsTransient Specify 'true' if this image is transient.
*/
static already_AddRefed<Decoder>
CreateDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
const Maybe<gfx::IntSize>& aTargetSize,
uint32_t aFlags,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags,
int aSampleSize,
const gfx::IntSize& aResolution,
bool aIsRedecode,
bool aImageIsTransient);
const gfx::IntSize& aResolution);
/**
* Creates and initializes a decoder for animated images of type @aType.
@@ -86,8 +87,9 @@ public:
* notifications as decoding progresses.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
* @param aDecoderFlags Flags specifying the behavior of this decoder.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
* @param aResolution The resolution requested using #-moz-resolution (or an
* empty rect if none).
*/
@@ -95,7 +97,8 @@ public:
CreateAnimationDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
uint32_t aFlags,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags,
const gfx::IntSize& aResolution);
/**
@@ -128,13 +131,13 @@ public:
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
*/
static already_AddRefed<Decoder>
CreateAnonymousDecoder(DecoderType aType,
SourceBuffer* aSourceBuffer,
uint32_t aFlags);
SurfaceFlags aSurfaceFlags);
/**
* Creates and initializes an anonymous metadata decoder (one which isn't
@@ -145,8 +148,6 @@ public:
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
*/
static already_AddRefed<Decoder>
CreateAnonymousMetadataDecoder(DecoderType aType,
+42
View File
@@ -0,0 +1,42 @@
/* -*- 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_DecoderFlags_h
#define mozilla_image_DecoderFlags_h
#include "mozilla/TypedEnumBits.h"
namespace mozilla {
namespace image {
/**
* Flags that influence decoder behavior. Note that these flags *don't*
* influence the logical content of the surfaces that the decoder generates, so
* they're not in a factor in SurfaceCache lookups and the like. These flags
* instead either influence which surfaces are generated at all or the tune the
* decoder's behavior for a particular scenario.
*/
enum class DecoderFlags : uint8_t
{
FIRST_FRAME_ONLY = 1 << 0,
IS_REDECODE = 1 << 1,
IMAGE_IS_TRANSIENT = 1 << 2,
ASYNC_NOTIFY = 1 << 3
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags)
/**
* @return the default set of decode flags.
*/
inline DecoderFlags
DefaultDecoderFlags()
{
return DecoderFlags();
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DecoderFlags_h
+45
View File
@@ -0,0 +1,45 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Downscaler.h"
namespace mozilla {
namespace image {
Deinterlacer::Deinterlacer(const nsIntSize& aImageSize)
: mImageSize(aImageSize)
, mBuffer(MakeUnique<uint8_t[]>(mImageSize.width *
mImageSize.height *
sizeof(uint32_t)))
{ }
uint32_t
Deinterlacer::RowSize() const
{
return mImageSize.width * sizeof(uint32_t);
}
uint8_t*
Deinterlacer::RowBuffer(uint32_t aRow)
{
uint32_t offset = aRow * RowSize();
MOZ_ASSERT(offset < mImageSize.width * mImageSize.height * sizeof(uint32_t),
"Row is outside of image");
return mBuffer.get() + offset;
}
void
Deinterlacer::PropagatePassToDownscaler(Downscaler& aDownscaler)
{
for (int32_t row = 0 ; row < mImageSize.height ; ++row) {
memcpy(aDownscaler.RowBuffer(), RowBuffer(row), RowSize());
aDownscaler.CommitRow();
}
}
} // namespace image
} // namespace mozilla
+50
View File
@@ -0,0 +1,50 @@
/* -*- 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/. */
/**
* Deinterlacer is a utility class to allow Downscaler to work with interlaced
* images.
* Since Downscaler needs to receive rows in top-to-bottom or
* bottom-to-top order, it can't natively handle interlaced images, in which the
* rows arrive in an interleaved order. Deinterlacer solves this problem by
* acting as an intermediate buffer that records decoded rows. Unlike
* Downscaler, it allows the rows to be written in arbitrary order. After each
* pass, calling PropagatePassToDownscaler() will downscale every buffered row
* in a single operation. The rows remain in the buffer, so rows that were
* written in one pass will be included in subsequent passes.
*/
#ifndef mozilla_image_Deinterlacer_h
#define mozilla_image_Deinterlacer_h
#include "Downscaler.h"
namespace mozilla {
namespace image {
class Deinterlacer
{
public:
explicit Deinterlacer(const nsIntSize& aImageSize);
uint8_t* RowBuffer(uint32_t aRow);
void PropagatePassToDownscaler(Downscaler& aDownscaler);
private:
uint32_t RowSize() const;
nsIntSize mImageSize;
UniquePtr<uint8_t[]> mBuffer;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_Deinterlacer_h
+2
View File
@@ -287,5 +287,7 @@ Downscaler::DownscaleInputLine()
}
}
} // namespace image
} // namespace mozilla
+1
View File
@@ -162,6 +162,7 @@ public:
#endif // MOZ_ENABLE_SKIA
} // namespace image
} // namespace mozilla
+11 -3
View File
@@ -168,10 +168,18 @@ DynamicImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
gfxIntSize size(mDrawable->Size());
return GetFrameAtSize(IntSize(size.width, size.height),
aWhichFrame,
aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
DynamicImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
CreateOffscreenContentDrawTarget(IntSize(size.width, size.height),
SurfaceFormat::B8G8R8A8);
CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
if (!dt) {
gfxWarning() <<
"DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget";
@@ -179,7 +187,7 @@ DynamicImage::GetFrame(uint32_t aWhichFrame,
}
nsRefPtr<gfxContext> context = new gfxContext(dt);
auto result = Draw(context, size, ImageRegion::Create(size),
auto result = Draw(context, aSize, ImageRegion::Create(aSize),
aWhichFrame, GraphicsFilter::FILTER_NEAREST,
Nothing(), aFlags);
+3 -3
View File
@@ -281,7 +281,7 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
0, // Default decode flags.
DefaultSurfaceFlags(),
aFrameNum));
MOZ_ASSERT(!result || !result.DrawableRef()->GetIsPaletted(),
"About to return a paletted frame");
@@ -332,7 +332,7 @@ DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
{
// Concoct a SurfaceKey for this surface.
SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
imgIContainer::DECODE_FLAGS_DEFAULT,
DefaultSurfaceFlags(),
/* aFrameNum = */ 0);
// Create a counter for this surface.
@@ -374,7 +374,7 @@ FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
0, // Default decode flags.
DefaultSurfaceFlags(),
aFrameNum));
return result ? result.DrawableRef()->RawAccessRef()
: RawAccessFrameRef();
+8
View File
@@ -44,6 +44,14 @@ FrozenImage::GetFrame(uint32_t aWhichFrame,
return InnerImage()->GetFrame(FRAME_FIRST, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
FrozenImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
return InnerImage()->GetFrameAtSize(aSize, FRAME_FIRST, aFlags);
}
NS_IMETHODIMP_(bool)
FrozenImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
+4
View File
@@ -37,6 +37,10 @@ public:
NS_IMETHOD GetAnimated(bool* aAnimated) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrameAtSize(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags) override;
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
+4 -7
View File
@@ -147,16 +147,14 @@ public:
* flag is set, INIT_FLAG_DISCARDABLE and INIT_FLAG_DECODE_ONLY_ON_DRAW must
* not be set.
*
* INIT_FLAG_DOWNSCALE_DURING_DECODE: The container should attempt to
* downscale images during decoding instead of decoding them to their
* intrinsic size.
* INIT_FLAG_SYNC_LOAD: The container is being loaded synchronously, so
* it should avoid relying on async workers to get the container ready.
*/
static const uint32_t INIT_FLAG_NONE = 0x0;
static const uint32_t INIT_FLAG_DISCARDABLE = 0x1;
static const uint32_t INIT_FLAG_DECODE_IMMEDIATELY = 0x2;
static const uint32_t INIT_FLAG_TRANSIENT = 0x4;
static const uint32_t INIT_FLAG_DOWNSCALE_DURING_DECODE = 0x8;
static const uint32_t INIT_FLAG_SYNC_LOAD = 0x10;
static const uint32_t INIT_FLAG_SYNC_LOAD = 0x8;
virtual already_AddRefed<ProgressTracker> GetProgressTracker() = 0;
virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
@@ -214,8 +212,7 @@ public:
bool aLastPart) = 0;
/**
* Called when the SurfaceCache discards a persistent surface belonging to
* this image.
* Called when the SurfaceCache discards a surface belonging to this image.
*/
virtual void OnSurfaceDiscarded() = 0;
+18 -29
View File
@@ -32,15 +32,6 @@ namespace image {
ImageFactory::Initialize()
{ }
static bool
ShouldDownscaleDuringDecode(const nsCString& aMimeType)
{
DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get());
return type == DecoderType::JPEG ||
type == DecoderType::PNG ||
type == DecoderType::BMP;
}
static uint32_t
ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
{
@@ -49,7 +40,6 @@ ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
// We default to the static globals.
bool isDiscardable = gfxPrefs::ImageMemDiscardable();
bool doDecodeImmediately = gfxPrefs::ImageDecodeImmediatelyEnabled();
bool doDownscaleDuringDecode = gfxPrefs::ImageDownscaleDuringDecodeEnabled();
// We want UI to be as snappy as possible and not to flicker. Disable
// discarding for chrome URLS.
@@ -66,15 +56,10 @@ ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
isDiscardable = false;
}
// Downscale-during-decode is only enabled for certain content types.
if (doDownscaleDuringDecode && !ShouldDownscaleDuringDecode(aMimeType)) {
doDownscaleDuringDecode = false;
}
// For multipart/x-mixed-replace, we basically want a direct channel to the
// decoder. Disable everything for this case.
if (isMultiPart) {
isDiscardable = doDownscaleDuringDecode = false;
isDiscardable = false;
}
// We have all the information we need.
@@ -88,9 +73,6 @@ ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
if (isMultiPart) {
imageFlags |= Image::INIT_FLAG_TRANSIENT;
}
if (doDownscaleDuringDecode) {
imageFlags |= Image::INIT_FLAG_DOWNSCALE_DURING_DECODE;
}
return imageFlags;
}
@@ -119,15 +101,14 @@ ImageFactory::CreateImage(nsIRequest* aRequest,
}
}
// Marks an image as having an error before returning it. Used with macros like
// NS_ENSURE_SUCCESS, since we guarantee to always return an image even if an
// error occurs, but callers need to be able to tell that this happened.
// Marks an image as having an error before returning it.
template <typename T>
static already_AddRefed<Image>
BadImage(nsRefPtr<T>& image)
BadImage(const char* aMessage, nsRefPtr<T>& aImage)
{
image->SetHasError();
return image.forget();
NS_WARNING(aMessage);
aImage->SetHasError();
return aImage.forget();
}
/* static */ already_AddRefed<Image>
@@ -142,7 +123,9 @@ ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
newImage->SetProgressTracker(newTracker);
rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_SYNC_LOAD);
NS_ENSURE_SUCCESS(rv, BadImage(newImage));
if (NS_FAILED(rv)) {
return BadImage("RasterImage::Init failed", newImage);
}
return newImage.forget();
}
@@ -246,7 +229,9 @@ ImageFactory::CreateRasterImage(nsIRequest* aRequest,
}
rv = newImage->Init(aMimeType.get(), aImageFlags);
NS_ENSURE_SUCCESS(rv, BadImage(newImage));
if (NS_FAILED(rv)) {
return BadImage("RasterImage::Init failed", newImage);
}
newImage->SetInnerWindowID(aInnerWindowId);
@@ -289,12 +274,16 @@ ImageFactory::CreateVectorImage(nsIRequest* aRequest,
newImage->SetProgressTracker(aProgressTracker);
rv = newImage->Init(aMimeType.get(), aImageFlags);
NS_ENSURE_SUCCESS(rv, BadImage(newImage));
if (NS_FAILED(rv)) {
return BadImage("VectorImage::Init failed", newImage);
}
newImage->SetInnerWindowID(aInnerWindowId);
rv = newImage->OnStartRequest(aRequest, nullptr);
NS_ENSURE_SUCCESS(rv, BadImage(newImage));
if (NS_FAILED(rv)) {
return BadImage("VectorImage::OnStartRequest failed", newImage);
}
return newImage.forget();
}
+3 -1
View File
@@ -116,7 +116,9 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
DecoderType decoderType =
DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
nsRefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aFlags);
DecoderFactory::CreateAnonymousDecoder(decoderType,
sourceBuffer,
ToSurfaceFlags(aFlags));
if (!decoder) {
return nullptr;
}
+8
View File
@@ -174,6 +174,14 @@ ImageWrapper::GetFrame(uint32_t aWhichFrame,
return mInnerImage->GetFrame(aWhichFrame, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
ImageWrapper::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags);
}
NS_IMETHODIMP_(bool)
ImageWrapper::IsOpaque()
{
+10
View File
@@ -122,6 +122,16 @@ OrientedImage::GetFrame(uint32_t aWhichFrame,
return target->Snapshot();
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
OrientedImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
// XXX(seth): It'd be nice to support downscale-during-decode for this case,
// but right now we just fall back to the intrinsic size.
return GetFrame(aWhichFrame, aFlags);
}
NS_IMETHODIMP_(bool)
OrientedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
+4
View File
@@ -34,6 +34,10 @@ public:
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrameAtSize(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags) override;
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
+115 -369
View File
@@ -52,6 +52,7 @@
#include "mozilla/Services.h"
#include <stdint.h>
#include "mozilla/TimeStamp.h"
#include "mozilla/Tuple.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/gfx/Scale.h"
@@ -74,139 +75,6 @@ using std::min;
// used for statistics.
static int32_t sMaxDecodeCount = 0;
class ScaleRunner : public nsRunnable
{
enum ScaleState
{
eNew,
eReady,
eFinish,
eFinishWithError
};
public:
ScaleRunner(RasterImage* aImage,
uint32_t aImageFlags,
const IntSize& aSize,
RawAccessFrameRef&& aSrcRef)
: mImage(aImage)
, mSrcRef(Move(aSrcRef))
, mDstSize(aSize)
, mImageFlags(aImageFlags)
, mState(eNew)
{
MOZ_ASSERT(!mSrcRef->GetIsPaletted());
MOZ_ASSERT(aSize.width > 0 && aSize.height > 0);
}
bool Init()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == eNew, "Calling Init() twice?");
// We'll need a destination frame. It's unconditionally ARGB32 because
// that's what the scaler outputs.
nsRefPtr<imgFrame> tentativeDstFrame = new imgFrame();
nsresult rv =
tentativeDstFrame->InitForDecoder(mDstSize, SurfaceFormat::B8G8R8A8);
if (NS_FAILED(rv)) {
return false;
}
// We need a strong reference to the raw data for the destination frame.
// (We already got one for the source frame in the constructor.)
RawAccessFrameRef tentativeDstRef = tentativeDstFrame->RawAccessRef();
if (!tentativeDstRef) {
return false;
}
// Everything worked, so commit to these objects and mark ourselves ready.
mDstRef = Move(tentativeDstRef);
mState = eReady;
// Insert the new surface into the cache immediately. We need to do this so
// that we won't start multiple scaling jobs for the same size.
SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()),
RasterSurfaceKey(mDstSize, mImageFlags, 0),
Lifetime::Transient);
return true;
}
NS_IMETHOD Run() override
{
if (mState == eReady) {
// Collect information from the frames that we need to scale.
ScalingData srcData = mSrcRef->GetScalingData();
ScalingData dstData = mDstRef->GetScalingData();
// Actually do the scaling.
bool succeeded =
gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height,
srcData.mBytesPerRow, dstData.mRawData, mDstSize.width,
mDstSize.height, dstData.mBytesPerRow, srcData.mFormat);
if (succeeded) {
// Mark the frame as complete and discardable.
mDstRef->ImageUpdated(mDstRef->GetRect());
MOZ_ASSERT(mDstRef->IsImageComplete(),
"Incomplete, but just updated the entire frame");
}
// We need to send notifications and release our references on the main
// thread, so finish up there.
mState = succeeded ? eFinish : eFinishWithError;
NS_DispatchToMainThread(this);
} else if (mState == eFinish) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mDstRef, "Should have a valid scaled frame");
// Notify, so observers can redraw.
nsRefPtr<RasterImage> image = mImage.get();
if (image) {
image->NotifyNewScaledFrame();
}
// We're done, so release everything.
mSrcRef.reset();
mDstRef.reset();
} else if (mState == eFinishWithError) {
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("HQ scaling failed");
// Remove the frame from the cache since we know we don't need it.
SurfaceCache::RemoveSurface(ImageKey(mImage.get()),
RasterSurfaceKey(mDstSize,
mImageFlags, 0));
// Release everything we're holding, too.
mSrcRef.reset();
mDstRef.reset();
} else {
// mState must be eNew, which is invalid in Run().
MOZ_ASSERT(false, "Need to call Init() before dispatching");
}
return NS_OK;
}
private:
virtual ~ScaleRunner()
{
MOZ_ASSERT(!mSrcRef && !mDstRef,
"Should have released strong refs in Run()");
}
WeakPtr<RasterImage> mImage;
RawAccessFrameRef mSrcRef;
RawAccessFrameRef mDstRef;
const IntSize mDstSize;
uint32_t mImageFlags;
ScaleState mState;
};
static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
#ifndef DEBUG
NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties)
#else
@@ -233,7 +101,6 @@ RasterImage::RasterImage(ImageURL* aURI /* = nullptr */) :
mDiscardable(false),
mHasSourceData(false),
mHasBeenDecoded(false),
mDownscaleDuringDecode(false),
mPendingAnimation(false),
mAnimationFinished(false),
mWantFullDecode(false)
@@ -269,23 +136,15 @@ RasterImage::Init(const char* aMimeType,
}
// We want to avoid redecodes for transient images.
MOZ_ASSERT(!(aFlags & INIT_FLAG_TRANSIENT) ||
(!(aFlags & INIT_FLAG_DISCARDABLE) &&
!(aFlags & INIT_FLAG_DOWNSCALE_DURING_DECODE)),
"Illegal init flags for transient image");
MOZ_ASSERT_IF(aFlags & INIT_FLAG_TRANSIENT,
!(aFlags & INIT_FLAG_DISCARDABLE));
// Store initialization data
mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE);
mWantFullDecode = !!(aFlags & INIT_FLAG_DECODE_IMMEDIATELY);
mTransient = !!(aFlags & INIT_FLAG_TRANSIENT);
mDownscaleDuringDecode = !!(aFlags & INIT_FLAG_DOWNSCALE_DURING_DECODE);
mSyncLoad = !!(aFlags & INIT_FLAG_SYNC_LOAD);
#ifndef MOZ_ENABLE_SKIA
// Downscale-during-decode requires Skia.
mDownscaleDuringDecode = false;
#endif
// Use the MIME type to select a decoder type, and make sure there *is* a
// decoder for this MIME type.
NS_ENSURE_ARG_POINTER(aMimeType);
@@ -434,17 +293,18 @@ RasterImage::LookupFrameInternal(uint32_t aFrameNum,
}
if (mAnim && aFrameNum > 0) {
MOZ_ASSERT(DecodeFlags(aFlags) == DECODE_FLAGS_DEFAULT,
"Can't composite frames with non-default decode flags");
MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(),
"Can't composite frames with non-default surface flags");
return mAnim->GetCompositedFrame(aFrameNum);
}
Maybe<uint32_t> alternateFlags;
Maybe<SurfaceFlags> alternateFlags;
if (IsOpaque()) {
// If we're opaque, we can always substitute a frame that was decoded with a
// different decode flag for premultiplied alpha, because that can only
// matter for frames with transparency.
alternateFlags = Some(aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
alternateFlags.emplace(ToSurfaceFlags(aFlags) ^
SurfaceFlags::NO_PREMULTIPLY_ALPHA);
}
// We don't want any substitution for sync decodes (except the premultiplied
@@ -452,7 +312,7 @@ RasterImage::LookupFrameInternal(uint32_t aFrameNum,
if (aFlags & FLAG_SYNC_DECODE) {
return SurfaceCache::Lookup(ImageKey(this),
RasterSurfaceKey(aSize,
DecodeFlags(aFlags),
ToSurfaceFlags(aFlags),
aFrameNum),
alternateFlags);
}
@@ -460,7 +320,7 @@ RasterImage::LookupFrameInternal(uint32_t aFrameNum,
// We'll return the best match we can find to the requested frame.
return SurfaceCache::LookupBestMatch(ImageKey(this),
RasterSurfaceKey(aSize,
DecodeFlags(aFlags),
ToSurfaceFlags(aFlags),
aFrameNum),
alternateFlags);
}
@@ -682,7 +542,7 @@ RasterImage::CopyFrame(uint32_t aWhichFrame, uint32_t aFlags)
} else {
RefPtr<SourceSurface> srcSurf = frameRef->GetSurface();
if (!srcSurf) {
RecoverFromLossOfFrames(mSize, aFlags);
RecoverFromInvalidFrames(mSize, aFlags);
return nullptr;
}
@@ -703,14 +563,28 @@ NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
RasterImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
return GetFrameInternal(aWhichFrame, aFlags).second().forget();
return GetFrameInternal(mSize, aWhichFrame, aFlags).second().forget();
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
RasterImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget();
}
Pair<DrawResult, RefPtr<SourceSurface>>
RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags)
RasterImage::GetFrameInternal(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
if (aSize.IsEmpty()) {
return MakePair(DrawResult::BAD_ARGS, RefPtr<SourceSurface>());
}
if (aWhichFrame > FRAME_MAX_VALUE) {
return MakePair(DrawResult::BAD_ARGS, RefPtr<SourceSurface>());
}
@@ -723,7 +597,7 @@ RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags)
// not waiting for the data to be loaded from the network or not passing
// FLAG_SYNC_DECODE
DrawableFrameRef frameRef =
LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags);
LookupFrame(GetRequestedFrameIndex(aWhichFrame), aSize, aFlags);
if (!frameRef) {
// The OS threw this frame away and we couldn't redecode it.
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
@@ -732,15 +606,15 @@ RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags)
// If this frame covers the entire image, we can just reuse its existing
// surface.
RefPtr<SourceSurface> frameSurf;
IntRect frameRect = frameRef->GetRect();
if (frameRect.x == 0 && frameRect.y == 0 &&
frameRect.width == mSize.width &&
frameRect.height == mSize.height) {
if (!frameRef->NeedsPadding() &&
frameRef->GetSize() == aSize) {
frameSurf = frameRef->GetSurface();
}
// The image doesn't have a usable surface because it's been optimized away or
// because it's a partial update frame from an animation. Create one.
// because it's a partial update frame from an animation. Create one. (In this
// case we fall back to returning a surface at our intrinsic size, even if a
// different size was originally specified.)
if (!frameSurf) {
frameSurf = CopyFrame(aWhichFrame, aFlags);
}
@@ -758,17 +632,20 @@ RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags)
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aContainer);
auto result = GetFrameInternal(FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY);
if (!result.second()) {
DrawResult drawResult;
RefPtr<SourceSurface> surface;
Tie(drawResult, surface) =
GetFrameInternal(mSize, FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY);
if (!surface) {
// The OS threw out some or all of our buffer. We'll need to wait for the
// redecode (which was automatically triggered by GetFrame) to complete.
return MakePair(result.first(), nsRefPtr<layers::Image>());
return MakePair(drawResult, nsRefPtr<layers::Image>());
}
CairoImage::Data cairoData;
GetWidth(&cairoData.mSize.width);
GetHeight(&cairoData.mSize.height);
cairoData.mSourceSurface = result.second();
cairoData.mSourceSurface = surface;
nsRefPtr<layers::Image> image =
aContainer->CreateImage(ImageFormat::CAIRO_SURFACE);
@@ -776,7 +653,7 @@ RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags)
static_cast<CairoImage*>(image.get())->SetData(cairoData);
return MakePair(result.first(), Move(image));
return MakePair(drawResult, Move(image));
}
NS_IMETHODIMP_(bool)
@@ -828,18 +705,19 @@ RasterImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
// We need a new ImageContainer, so create one.
container = LayerManager::CreateImageContainer();
auto result = GetCurrentImage(container, aFlags);
if (!result.second()) {
// We couldn't get an Image.
DrawResult drawResult;
nsRefPtr<layers::Image> image;
Tie(drawResult, image) = GetCurrentImage(container, aFlags);
if (!image) {
return nullptr;
}
// |result.second()| holds a reference to a SourceSurface which in turn holds
// a lock on the current frame's VolatileBuffer, ensuring that it doesn't get
// freed as long as the layer system keeps this ImageContainer alive.
container->SetCurrentImageInTransaction(result.second());
// |image| holds a reference to a SourceSurface which in turn holds a lock on
// the current frame's VolatileBuffer, ensuring that it doesn't get freed as
// long as the layer system keeps this ImageContainer alive.
container->SetCurrentImageInTransaction(image);
mLastImageContainerDrawResult = result.first();
mLastImageContainerDrawResult = drawResult;
mImageContainer = container;
return container.forget();
@@ -855,14 +733,15 @@ RasterImage::UpdateImageContainer()
return;
}
auto result = GetCurrentImage(container, FLAG_NONE);
if (!result.second()) {
// We couldn't get an Image.
DrawResult drawResult;
nsRefPtr<layers::Image> image;
Tie(drawResult, image) = GetCurrentImage(container, FLAG_NONE);
if (!image) {
return;
}
mLastImageContainerDrawResult = result.first();
container->SetCurrentImage(result.second());
mLastImageContainerDrawResult = drawResult;
container->SetCurrentImage(image);
}
size_t
@@ -986,7 +865,7 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata,
// discovered that it actually was during the full decode. This is a
// rare failure that only occurs for corrupt images. To recover, we need
// to discard all existing surfaces and redecode.
RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT);
RecoverFromInvalidFrames(mSize, DECODE_FLAGS_DEFAULT);
}
}
@@ -1327,10 +1206,6 @@ RasterImage::RequestDecodeForSize(const IntSize& aSize, uint32_t aFlags)
return NS_OK;
}
// Fall back to our intrinsic size if we don't support
// downscale-during-decode.
IntSize targetSize = mDownscaleDuringDecode ? aSize : mSize;
// Decide whether to sync decode images we can decode quickly. Here we are
// explicitly trading off flashing for responsiveness in the case that we're
// redecoding an image (see bug 845147).
@@ -1343,7 +1218,7 @@ RasterImage::RequestDecodeForSize(const IntSize& aSize, uint32_t aFlags)
// Look up the first frame of the image, which will implicitly start decoding
// if it's not available right now.
LookupFrame(0, targetSize, flags);
LookupFrame(0, aSize, flags);
return NS_OK;
}
@@ -1393,35 +1268,42 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags)
return NS_OK;
}
if (mDownscaleDuringDecode) {
// We're about to decode again, which may mean that some of the previous
// sizes we've decoded at aren't useful anymore. We can allow them to
// expire from the cache by unlocking them here. When the decode finishes,
// it will send an invalidation that will cause all instances of this image
// to redraw. If this image is locked, any surfaces that are still useful
// will become locked again when LookupFrame touches them, and the remainder
// will eventually expire.
SurfaceCache::UnlockSurfaces(ImageKey(this));
}
MOZ_ASSERT(mDownscaleDuringDecode || aSize == mSize,
"Can only decode to our intrinsic size if we're not allowed to "
"downscale-during-decode");
// We're about to decode again, which may mean that some of the previous sizes
// we've decoded at aren't useful anymore. We can allow them to expire from
// the cache by unlocking them here. When the decode finishes, it will send an
// invalidation that will cause all instances of this image to redraw. If this
// image is locked, any surfaces that are still useful will become locked
// again when LookupFrame touches them, and the remainder will eventually
// expire.
SurfaceCache::UnlockSurfaces(ImageKey(this));
Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing();
// Determine which flags we need to decode this image with.
DecoderFlags decoderFlags = DefaultDecoderFlags();
if (aFlags & FLAG_ASYNC_NOTIFY) {
decoderFlags |= DecoderFlags::ASYNC_NOTIFY;
}
if (mTransient) {
decoderFlags |= DecoderFlags::IMAGE_IS_TRANSIENT;
}
if (mHasBeenDecoded) {
decoderFlags |= DecoderFlags::IS_REDECODE;
}
// Create a decoder.
nsRefPtr<Decoder> decoder;
if (mAnim) {
decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this,
mSourceBuffer, aFlags,
mSourceBuffer, decoderFlags,
ToSurfaceFlags(aFlags),
mRequestedResolution);
} else {
decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer,
targetSize, aFlags,
targetSize, decoderFlags,
ToSurfaceFlags(aFlags),
mRequestedSampleSize,
mRequestedResolution,
mHasBeenDecoded, mTransient);
mRequestedResolution);
}
// Make sure DecoderFactory was able to create a decoder successfully.
@@ -1434,7 +1316,7 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags)
InsertOutcome outcome =
SurfaceCache::InsertPlaceholder(ImageKey(this),
RasterSurfaceKey(aSize,
decoder->GetDecodeFlags(),
decoder->GetSurfaceFlags(),
/* aFrameNum = */ 0));
if (outcome != InsertOutcome::SUCCESS) {
return NS_ERROR_FAILURE;
@@ -1481,13 +1363,13 @@ RasterImage::DecodeMetadata(uint32_t aFlags)
}
void
RasterImage::RecoverFromLossOfFrames(const IntSize& aSize, uint32_t aFlags)
RasterImage::RecoverFromInvalidFrames(const IntSize& aSize, uint32_t aFlags)
{
if (!mHasSize) {
return;
}
NS_WARNING("An imgFrame became invalid. Attempting to recover...");
NS_WARNING("A RasterImage's frames became invalid. Attempting to recover...");
// Discard all existing frames, since they're probably all now invalid.
SurfaceCache::RemoveImage(ImageKey(this));
@@ -1504,75 +1386,24 @@ RasterImage::RecoverFromLossOfFrames(const IntSize& aSize, uint32_t aFlags)
Decode(aSize, aFlags);
}
bool
RasterImage::CanScale(GraphicsFilter aFilter,
const IntSize& aSize,
uint32_t aFlags)
static bool
HaveSkia()
{
#ifndef MOZ_ENABLE_SKIA
// The high-quality scaler requires Skia.
return false;
#ifdef MOZ_ENABLE_SKIA
return true;
#else
// Check basic requirements: HQ downscaling is enabled, we have all the source
// data and know our size, the flags allow us to do it, and a 'good' filter is
// being used. The flags may ask us not to scale because the caller isn't
// drawing to the window. If we're drawing to something else (e.g. a canvas)
// we usually have no way of updating what we've drawn, so HQ scaling is
// useless.
if (!gfxPrefs::ImageHQDownscalingEnabled() || !mHasSize || !mHasSourceData ||
!(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) ||
aFilter != GraphicsFilter::FILTER_GOOD) {
return false;
}
// We don't HQ scale images that we can downscale during decode.
if (mDownscaleDuringDecode) {
return false;
}
// We don't use the scaler for animated or transient images to avoid doing a
// bunch of work on an image that just gets thrown away.
if (mAnim || mTransient) {
return false;
}
// If target size is 1:1 with original, don't scale.
if (aSize == mSize) {
return false;
}
// To save memory, don't quality upscale images bigger than the limit.
if (aSize.width > mSize.width || aSize.height > mSize.height) {
uint32_t scaledSize = static_cast<uint32_t>(aSize.width * aSize.height);
if (scaledSize > gfxPrefs::ImageHQUpscalingMaxSize()) {
return false;
}
}
// There's no point in scaling if we can't store the result.
if (!SurfaceCache::CanHold(aSize)) {
return false;
}
// XXX(seth): It's not clear what this check buys us over
// gfxPrefs::ImageHQUpscalingMaxSize().
// The default value of this pref is 1000, which means that we never upscale.
// If that's all it's getting us, I'd rather we just forbid that explicitly.
gfx::Size scale(double(aSize.width) / mSize.width,
double(aSize.height) / mSize.height);
gfxFloat minFactor = gfxPrefs::ImageHQDownscalingMinFactor() / 1000.0;
return (scale.width < minFactor || scale.height < minFactor);
return false;
#endif
}
bool
RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags)
{
// Check basic requirements: downscale-during-decode is enabled for this
// image, we have all the source data and know our size, the flags allow us to
// do it, and a 'good' filter is being used.
if (!mDownscaleDuringDecode || !mHasSize ||
!gfxPrefs::ImageHQDownscalingEnabled() ||
// Check basic requirements: downscale-during-decode is enabled, Skia is
// available, this image isn't transient, we have all the source data and know
// our size, and the flags allow us to do it.
if (!mHasSize || mTransient || !HaveSkia() ||
!gfxPrefs::ImageDownscaleDuringDecodeEnabled() ||
!(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) {
return false;
}
@@ -1600,87 +1431,21 @@ RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags)
return true;
}
void
RasterImage::NotifyNewScaledFrame()
{
// Send an invalidation so observers will repaint and can take advantage of
// the new scaled frame if possible.
NotifyProgress(NoProgress, IntRect(0, 0, mSize.width, mSize.height));
}
void
RasterImage::RequestScale(imgFrame* aFrame,
uint32_t aFlags,
const IntSize& aSize)
{
// We don't scale frames which aren't fully decoded.
if (!aFrame->IsImageComplete()) {
return;
}
// We can't scale frames that need padding or are single pixel.
if (aFrame->NeedsPadding() || aFrame->IsSinglePixel()) {
return;
}
// We also can't scale if we can't lock the image data for this frame.
RawAccessFrameRef frameRef = aFrame->RawAccessRef();
if (!frameRef) {
return;
}
nsRefPtr<ScaleRunner> runner =
new ScaleRunner(this, DecodeFlags(aFlags), aSize, Move(frameRef));
if (runner->Init()) {
if (!sScaleWorkerThread) {
NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread));
ClearOnShutdown(&sScaleWorkerThread);
}
sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL);
}
}
DrawResult
RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
gfxContext* aContext,
const IntSize& aSize,
const ImageRegion& aRegion,
GraphicsFilter aFilter,
uint32_t aFlags)
RasterImage::DrawInternal(DrawableFrameRef&& aFrameRef,
gfxContext* aContext,
const IntSize& aSize,
const ImageRegion& aRegion,
GraphicsFilter aFilter,
uint32_t aFlags)
{
DrawableFrameRef frameRef;
if (CanScale(aFilter, aSize, aFlags)) {
LookupResult result =
SurfaceCache::Lookup(ImageKey(this),
RasterSurfaceKey(aSize,
DecodeFlags(aFlags),
0));
if (!result) {
// We either didn't have a matching scaled frame or the OS threw it away.
// Request a new one so we'll be ready next time. For now, we'll fall back
// to aFrameRef below.
RequestScale(aFrameRef.get(), aFlags, aSize);
}
if (result && result.DrawableRef()->IsImageComplete()) {
frameRef = Move(result.DrawableRef()); // The scaled version is ready.
}
}
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
ImageRegion region(aRegion);
bool frameIsComplete = true; // We already checked HQ scaled frames.
if (!frameRef) {
// There's no HQ scaled frame available, so we'll have to use the frame
// provided by the caller.
frameRef = Move(aFrameRef);
frameIsComplete = frameRef->IsImageComplete();
}
bool frameIsComplete = aFrameRef->IsImageComplete();
// By now we may have a frame with the requested size. If not, we need to
// adjust the drawing parameters accordingly.
IntSize finalSize = frameRef->GetImageSize();
IntSize finalSize = aFrameRef->GetImageSize();
bool couldRedecodeForBetterFrame = false;
if (finalSize != aSize) {
gfx::Size scale(double(aSize.width) / finalSize.width,
@@ -1688,12 +1453,11 @@ RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
region.Scale(1.0 / scale.width, 1.0 / scale.height);
couldRedecodeForBetterFrame = mDownscaleDuringDecode &&
CanDownscaleDuringDecode(aSize, aFlags);
couldRedecodeForBetterFrame = CanDownscaleDuringDecode(aSize, aFlags);
}
if (!frameRef->Draw(aContext, region, aFilter, aFlags)) {
RecoverFromLossOfFrames(aSize, aFlags);
if (!aFrameRef->Draw(aContext, region, aFilter, aFlags)) {
RecoverFromInvalidFrames(aSize, aFlags);
return DrawResult::TEMPORARY_ERROR;
}
if (!frameIsComplete) {
@@ -1735,7 +1499,7 @@ RasterImage::Draw(gfxContext* aContext,
// Illegal -- you can't draw with non-default decode flags.
// (Disabling colorspace conversion might make sense to allow, but
// we don't currently.)
if (DecodeFlags(aFlags) != DECODE_FLAGS_DEFAULT) {
if (ToSurfaceFlags(aFlags) != DefaultSurfaceFlags()) {
return DrawResult::BAD_ARGS;
}
@@ -1763,8 +1527,8 @@ RasterImage::Draw(gfxContext* aContext,
return DrawResult::NOT_READY;
}
auto result = DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize,
aRegion, aFilter, flags);
auto result = DrawInternal(Move(ref), aContext, aSize,
aRegion, aFilter, flags);
return result;
}
@@ -1916,14 +1680,15 @@ RasterImage::GetFramesNotified(uint32_t* aFramesNotified)
void
RasterImage::NotifyProgress(Progress aProgress,
const IntRect& aInvalidRect /* = IntRect() */,
uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */)
SurfaceFlags aSurfaceFlags
/* = DefaultSurfaceFlags() */)
{
MOZ_ASSERT(NS_IsMainThread());
// Ensure that we stay alive long enough to finish notifying.
nsRefPtr<RasterImage> image(this);
bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT;
bool wasDefaultFlags = aSurfaceFlags == DefaultSurfaceFlags();
if (!aInvalidRect.IsEmpty() && wasDefaultFlags) {
// Update our image container since we're invalidating.
@@ -1968,7 +1733,7 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
// Send out any final notifications.
NotifyProgress(aDecoder->TakeProgress(),
aDecoder->TakeInvalidRect(),
aDecoder->GetDecodeFlags());
aDecoder->GetSurfaceFlags());
bool wasMetadata = aDecoder->IsMetadataDecode();
bool done = aDecoder->GetDecodeDone();
@@ -2060,28 +1825,9 @@ RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame,
if (aFilter == GraphicsFilter::FILTER_GOOD &&
CanDownscaleDuringDecode(destSize, aFlags)) {
return destSize;
} else if (CanScale(aFilter, destSize, aFlags)) {
LookupResult result =
SurfaceCache::Lookup(ImageKey(this),
RasterSurfaceKey(destSize,
DecodeFlags(aFlags),
0));
if (result && result.DrawableRef()->IsImageComplete()) {
return destSize; // We have an existing HQ scale for this size.
}
if (!result) {
// We could HQ scale to this size, but we haven't. Request a scale now.
DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
mSize, aFlags);
if (ref) {
RequestScale(ref.get(), aFlags, destSize);
}
}
}
// We either can't HQ scale to this size or the scaled version isn't ready
// yet. Use our intrinsic size for now.
// We can't scale to this size. Use our intrinsic size for now.
return mSize;
}
+22 -42
View File
@@ -135,17 +135,6 @@ class FrameAnimator;
class ImageMetadata;
class SourceBuffer;
/**
* Given a set of imgIContainer FLAG_* flags, returns those flags that can
* affect the output of decoders.
*/
inline MOZ_CONSTEXPR uint32_t
DecodeFlags(uint32_t aFlags)
{
return aFlags & (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA |
imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION);
}
class RasterImage final : public ImageResource
, public nsIProperties
, public SupportsWeakPtr<RasterImage>
@@ -196,13 +185,13 @@ public:
*
* @param aProgress The progress notifications to send.
* @param aInvalidRect An invalidation rect to send.
* @param aFlags The decode flags used by the decoder that generated
* these notifications, or DECODE_FLAGS_DEFAULT if the
* @param aFlags The surface flags used by the decoder that generated
* these notifications, or DefaultSurfaceFlags() if the
* notifications don't come from a decoder.
*/
void NotifyProgress(Progress aProgress,
const nsIntRect& aInvalidRect = nsIntRect(),
uint32_t aFlags = DECODE_FLAGS_DEFAULT);
SurfaceFlags aSurfaceFlags = DefaultSurfaceFlags());
/**
* Records telemetry and does final teardown of the provided decoder.
@@ -265,18 +254,20 @@ public:
private:
nsresult Init(const char* aMimeType, uint32_t aFlags);
DrawResult DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
GraphicsFilter aFilter,
uint32_t aFlags);
DrawResult DrawInternal(DrawableFrameRef&& aFrameRef,
gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
GraphicsFilter aFilter,
uint32_t aFlags);
already_AddRefed<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame,
uint32_t aFlags);
Pair<DrawResult, RefPtr<gfx::SourceSurface>>
GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags);
GetFrameInternal(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags);
LookupResult LookupFrameInternal(uint32_t aFrameNum,
const gfx::IntSize& aSize,
@@ -314,10 +305,6 @@ private:
*
* It's an error to call Decode() before this image's intrinsic size is
* available. A metadata decode must successfully complete first.
*
* If downscale-during-decode is not enabled for this image (i.e., if
* mDownscaleDuringDecode is false), it is an error to pass an @aSize value
* different from this image's intrinsic size.
*/
NS_IMETHOD Decode(const gfx::IntSize& aSize, uint32_t aFlags);
@@ -341,11 +328,17 @@ private:
nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode);
/**
* In catastrophic circumstances like a GPU driver crash, we may lose our
* frames even if they're locked. RecoverFromLossOfFrames discards all
* existing frames and redecodes using the provided @aSize and @aFlags.
* In catastrophic circumstances like a GPU driver crash, the contents of our
* frames may become invalid. If the information we gathered during the
* metadata decode proves to be wrong due to image corruption, the frames we
* have may violate this class's invariants. Either way, we need to
* immediately discard the invalid frames and redecode so that callers don't
* perceive that we've entered an invalid state.
*
* RecoverFromInvalidFrames discards all existing frames and redecodes using
* the provided @aSize and @aFlags.
*/
void RecoverFromLossOfFrames(const nsIntSize& aSize, uint32_t aFlags);
void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags);
private: // data
nsIntSize mSize;
@@ -401,7 +394,6 @@ private: // data
bool mDiscardable:1; // Is container discardable?
bool mHasSourceData:1; // Do we have source data?
bool mHasBeenDecoded:1; // Decoded at least once?
bool mDownscaleDuringDecode:1;
// Whether we're waiting to start animation. If we get a StartAnimation() call
// but we don't yet have more than one frame, mPendingAnimation is set so that
@@ -423,22 +415,10 @@ private: // data
// Scaling.
//////////////////////////////////////////////////////////////////////////////
// Initiates an HQ scale for the given frame, if possible.
void RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize);
// Determines whether we can perform an HQ scale with the given parameters.
bool CanScale(GraphicsFilter aFilter, const nsIntSize& aSize,
uint32_t aFlags);
// Determines whether we can downscale during decode with the given
// parameters.
bool CanDownscaleDuringDecode(const nsIntSize& aSize, uint32_t aFlags);
// Called by the HQ scaler when a new scaled frame is ready.
void NotifyNewScaledFrame();
friend class ScaleRunner;
// Error handling.
void DoError();
+33 -47
View File
@@ -19,6 +19,7 @@
#include "mozilla/Pair.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Tuple.h"
#include "nsIMemoryReporter.h"
#include "gfx2DGlue.h"
#include "gfxPattern.h" // Workaround for flaw in bug 921753 part 2.
@@ -133,17 +134,14 @@ public:
CachedSurface(imgFrame* aSurface,
const Cost aCost,
const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
const Lifetime aLifetime)
const SurfaceKey& aSurfaceKey)
: mSurface(aSurface)
, mCost(aCost)
, mImageKey(aImageKey)
, mSurfaceKey(aSurfaceKey)
, mLifetime(aLifetime)
{
MOZ_ASSERT(!IsPlaceholder() ||
(mCost == sPlaceholderCost && mLifetime == Lifetime::Transient),
"Placeholder should have trivial cost and transient lifetime");
MOZ_ASSERT(!IsPlaceholder() || mCost == sPlaceholderCost,
"Placeholder should have trivial cost");
MOZ_ASSERT(mImageKey, "Must have a valid image key");
}
@@ -163,7 +161,7 @@ public:
return; // Can't lock a placeholder.
}
if (aLocked && mLifetime == Lifetime::Persistent) {
if (aLocked) {
// This may fail, and that's OK. We make no guarantees about whether
// locking is successful if you call SurfaceCache::LockImage() after
// SurfaceCache::Insert().
@@ -180,7 +178,6 @@ public:
SurfaceKey GetSurfaceKey() const { return mSurfaceKey; }
CostEntry GetCostEntry() { return image::CostEntry(this, mCost); }
nsExpirationState* GetExpirationState() { return &mExpirationState; }
Lifetime GetLifetime() const { return mLifetime; }
bool IsDecoded() const
{
@@ -228,7 +225,6 @@ private:
const Cost mCost;
const ImageKey mImageKey;
const SurfaceKey mSurfaceKey;
const Lifetime mLifetime;
};
/**
@@ -257,9 +253,8 @@ public:
void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
{
MOZ_ASSERT(aSurface, "Should have a surface");
MOZ_ASSERT(!mLocked || aSurface->GetLifetime() != Lifetime::Persistent ||
aSurface->IsLocked(),
"Inserting an unlocked persistent surface for a locked image");
MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(),
"Inserting an unlocked surface for a locked image");
mSurfaces.Put(aKey, aSurface);
}
@@ -279,10 +274,9 @@ public:
return surface.forget();
}
MOZ_WARN_UNUSED_RESULT // See bug 1185044.
Pair<already_AddRefed<CachedSurface>, MatchType>
LookupBestMatch(const SurfaceKey& aSurfaceKey,
const Maybe<uint32_t>& aAlternateFlags)
const Maybe<SurfaceFlags>& aAlternateFlags)
{
// Try for an exact match first.
nsRefPtr<CachedSurface> exactMatch;
@@ -334,13 +328,13 @@ private:
struct MatchContext
{
MatchContext(const SurfaceKey& aIdealKey,
const Maybe<uint32_t>& aAlternateFlags)
const Maybe<SurfaceFlags>& aAlternateFlags)
: mIdealKey(aIdealKey)
, mAlternateFlags(aAlternateFlags)
{ }
const SurfaceKey& mIdealKey;
const Maybe<uint32_t> mAlternateFlags;
const Maybe<SurfaceFlags> mAlternateFlags;
nsRefPtr<CachedSurface> mBestMatch;
};
@@ -468,8 +462,7 @@ public:
InsertOutcome Insert(imgFrame* aSurface,
const Cost aCost,
const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
Lifetime aLifetime)
const SurfaceKey& aSurfaceKey)
{
// If this is a duplicate surface, refuse to replace the original.
// XXX(seth): Calling Lookup() and then RemoveSurface() does the lookup
@@ -510,12 +503,12 @@ public:
}
nsRefPtr<CachedSurface> surface =
new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime);
new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey);
// We require that locking succeed if the image is locked and the surface is
// persistent; the caller may need to know this to handle errors correctly.
if (cache->IsLocked() && aLifetime == Lifetime::Persistent) {
MOZ_ASSERT(!surface->IsPlaceholder(), "Placeholders should be transient");
// We require that locking succeed if the image is locked and we're not
// inserting a placeholder; the caller may need to know this to handle
// errors correctly.
if (cache->IsLocked() && !surface->IsPlaceholder()) {
surface->SetLocked(true);
if (!surface->IsLocked()) {
return InsertOutcome::FAILURE;
@@ -538,8 +531,8 @@ public:
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
// If the surface was persistent, tell its image that we discarded it.
if (aSurface->GetLifetime() == Lifetime::Persistent) {
// If the surface was not a placeholder, tell its image that we discarded it.
if (!aSurface->IsPlaceholder()) {
static_cast<Image*>(imageKey)->OnSurfaceDiscarded();
}
@@ -640,7 +633,7 @@ public:
LookupResult LookupBestMatch(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
const Maybe<uint32_t>& aAlternateFlags)
const Maybe<SurfaceFlags>& aAlternateFlags)
{
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
if (!cache) {
@@ -658,11 +651,8 @@ public:
DrawableFrameRef ref;
MatchType matchType = MatchType::NOT_FOUND;
while (true) {
// XXX(seth): This code is begging for std::tie. See bug 1184385.
Pair<already_AddRefed<CachedSurface>, MatchType> lookupResult =
Tie(surface, matchType) =
cache->LookupBestMatch(aSurfaceKey, aAlternateFlags);
surface = lookupResult.first();
matchType = lookupResult.second();
if (!surface) {
return LookupResult(matchType); // Lookup in the per-image cache missed.
@@ -776,9 +766,8 @@ public:
void DiscardAll()
{
// Remove in order of cost because mCosts is an array and the other data
// structures are all hash tables. Note that locked surfaces (persistent
// surfaces belonging to locked images) are not removed, since they aren't
// present in mCosts.
// structures are all hash tables. Note that locked surfaces are not
// removed, since they aren't present in mCosts.
while (!mCosts.IsEmpty()) {
Remove(mCosts.LastElement().GetSurface());
}
@@ -812,8 +801,7 @@ public:
void LockSurface(CachedSurface* aSurface)
{
if (aSurface->GetLifetime() == Lifetime::Transient ||
aSurface->IsLocked()) {
if (aSurface->IsPlaceholder() || aSurface->IsLocked()) {
return;
}
@@ -836,8 +824,7 @@ public:
CachedSurface* aSurface,
void* aCache)
{
if (aSurface->GetLifetime() == Lifetime::Transient ||
!aSurface->IsLocked()) {
if (aSurface->IsPlaceholder() || !aSurface->IsLocked()) {
return PL_DHASH_NEXT;
}
@@ -912,9 +899,9 @@ private:
// This is similar to CanHold() except that it takes into account the costs of
// locked surfaces. It's used internally in Insert(), but it's not exposed
// publicly because if we start permitting multithreaded access to the surface
// cache, which seems likely, then the result would be meaningless: another
// thread could insert a persistent surface or lock an image at any time.
// publicly because we permit multithreaded access to the surface cache, which
// means that the result would be meaningless: another thread could insert a
// surface or lock an image at any time.
bool CanHoldAfterDiscarding(const Cost aCost) const
{
return aCost <= mMaxCost - mLockedCost;
@@ -1051,7 +1038,8 @@ SurfaceCache::Shutdown()
/* static */ LookupResult
SurfaceCache::Lookup(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
const Maybe<uint32_t>& aAlternateFlags /* = Nothing() */)
const Maybe<SurfaceFlags>& aAlternateFlags
/* = Nothing() */)
{
if (!sInstance) {
return LookupResult(MatchType::NOT_FOUND);
@@ -1071,7 +1059,7 @@ SurfaceCache::Lookup(const ImageKey aImageKey,
/* static */ LookupResult
SurfaceCache::LookupBestMatch(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
const Maybe<uint32_t>& aAlternateFlags
const Maybe<SurfaceFlags>& aAlternateFlags
/* = Nothing() */)
{
if (!sInstance) {
@@ -1085,8 +1073,7 @@ SurfaceCache::LookupBestMatch(const ImageKey aImageKey,
/* static */ InsertOutcome
SurfaceCache::Insert(imgFrame* aSurface,
const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
Lifetime aLifetime)
const SurfaceKey& aSurfaceKey)
{
if (!sInstance) {
return InsertOutcome::FAILURE;
@@ -1094,7 +1081,7 @@ SurfaceCache::Insert(imgFrame* aSurface,
MutexAutoLock lock(sInstance->GetMutex());
Cost cost = ComputeCost(aSurface->GetSize(), aSurface->GetBytesPerPixel());
return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey);
}
/* static */ InsertOutcome
@@ -1106,8 +1093,7 @@ SurfaceCache::InsertPlaceholder(const ImageKey aImageKey,
}
MutexAutoLock lock(sInstance->GetMutex());
return sInstance->Insert(nullptr, sPlaceholderCost, aImageKey, aSurfaceKey,
Lifetime::Transient);
return sInstance->Insert(nullptr, sPlaceholderCost, aImageKey, aSurfaceKey);
}
/* static */ bool
+51 -60
View File
@@ -19,6 +19,7 @@
#include "nsCOMPtr.h" // for already_AddRefed
#include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize
#include "mozilla/gfx/2D.h" // for SourceSurface
#include "SurfaceFlags.h"
#include "SVGImageContext.h" // for SVGImageContext
namespace mozilla {
@@ -59,16 +60,16 @@ public:
{
uint32_t hash = HashGeneric(mSize.width, mSize.height);
hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0));
hash = AddToHash(hash, mAnimationTime, mFlags);
hash = AddToHash(hash, mAnimationTime, uint32_t(mFlags));
return hash;
}
IntSize Size() const { return mSize; }
Maybe<SVGImageContext> SVGContext() const { return mSVGContext; }
float AnimationTime() const { return mAnimationTime; }
uint32_t Flags() const { return mFlags; }
SurfaceFlags Flags() const { return mFlags; }
SurfaceKey WithNewFlags(uint32_t aFlags) const
SurfaceKey WithNewFlags(SurfaceFlags aFlags) const
{
return SurfaceKey(mSize, mSVGContext, mAnimationTime, aFlags);
}
@@ -77,7 +78,7 @@ private:
SurfaceKey(const IntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
const float aAnimationTime,
const uint32_t aFlags)
const SurfaceFlags aFlags)
: mSize(aSize)
, mSVGContext(aSVGContext)
, mAnimationTime(aAnimationTime)
@@ -88,7 +89,9 @@ private:
return aSIC.Hash();
}
friend SurfaceKey RasterSurfaceKey(const IntSize&, uint32_t, uint32_t);
friend SurfaceKey RasterSurfaceKey(const IntSize&,
SurfaceFlags,
uint32_t);
friend SurfaceKey VectorSurfaceKey(const IntSize&,
const Maybe<SVGImageContext>&,
float);
@@ -96,12 +99,12 @@ private:
IntSize mSize;
Maybe<SVGImageContext> mSVGContext;
float mAnimationTime;
uint32_t mFlags;
SurfaceFlags mFlags;
};
inline SurfaceKey
RasterSurfaceKey(const gfx::IntSize& aSize,
uint32_t aFlags,
SurfaceFlags aFlags,
uint32_t aFrameNum)
{
return SurfaceKey(aSize, Nothing(), float(aFrameNum), aFlags);
@@ -115,14 +118,9 @@ VectorSurfaceKey(const gfx::IntSize& aSize,
// We don't care about aFlags for VectorImage because none of the flags we
// have right now influence VectorImage's rendering. If we add a new flag that
// *does* affect how a VectorImage renders, we'll have to change this.
return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0);
return SurfaceKey(aSize, aSVGContext, aAnimationTime, DefaultSurfaceFlags());
}
enum class Lifetime : uint8_t {
Transient,
Persistent
};
enum class InsertOutcome : uint8_t {
SUCCESS, // Success (but see Insert documentation).
FAILURE, // Couldn't insert (e.g., for capacity reasons).
@@ -143,9 +141,8 @@ enum class InsertOutcome : uint8_t {
* cache. This is most often because losing the data could harm the user
* experience (for example, we often don't want to allow surfaces that are
* currently visible to expire) or because it's not possible to rematerialize
* the surface. SurfaceCache supports this through the use of image locking and
* surface lifetimes; see the comments for Insert() and LockImage() for more
* details.
* the surface. SurfaceCache supports this through the use of image locking; see
* the comments for Insert() and LockImage() for more details.
*
* Any image which stores surfaces in the SurfaceCache *must* ensure that it
* calls RemoveImage() before it is destroyed. See the comments for
@@ -175,8 +172,8 @@ struct SurfaceCache
* If the imgFrame was found in the cache, but had stored its surface in a
* volatile buffer which was discarded by the OS, then it is automatically
* removed from the cache and an empty LookupResult is returned. Note that
* this will never happen to persistent surfaces associated with a locked
* image; the cache keeps a strong reference to such surfaces internally.
* this will never happen to surfaces associated with a locked image; the
* cache keeps a strong reference to such surfaces internally.
*
* @param aImageKey Key data identifying which image the surface belongs
* to.
@@ -196,7 +193,8 @@ struct SurfaceCache
*/
static LookupResult Lookup(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
const Maybe<uint32_t>& aAlternateFlags = Nothing());
const Maybe<SurfaceFlags>& aAlternateFlags
= Nothing());
/**
* Looks up the best matching surface in the cache and returns a drawable
@@ -224,7 +222,7 @@ struct SurfaceCache
*/
static LookupResult LookupBestMatch(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
const Maybe<uint32_t>& aAlternateFlags
const Maybe<SurfaceFlags>& aAlternateFlags
= Nothing());
/**
@@ -232,32 +230,30 @@ struct SurfaceCache
* SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT.
* If a matching placeholder is already present, the placeholder is removed.
*
* Each surface in the cache has a lifetime, either Transient or Persistent.
* Transient surfaces can expire from the cache at any time. Persistent
* surfaces, on the other hand, will never expire as long as they remain
* locked, but if they become unlocked, can expire just like transient
* surfaces. When it is first inserted, a persistent surface is locked if its
* associated image is locked. When that image is later unlocked, the surface
* becomes unlocked too. To become locked again at that point, two things must
* happen: the image must become locked again (via LockImage()), and the
* surface must be touched again (via one of the Lookup() functions).
* Surfaces will never expire as long as they remain locked, but if they
* become unlocked, they can expire either because the SurfaceCache runs out
* of capacity or because they've gone too long without being used. When it
* is first inserted, a surface is locked if its associated image is locked.
* When that image is later unlocked, the surface becomes unlocked too. To
* become locked again at that point, two things must happen: the image must
* become locked again (via LockImage()), and the surface must be touched
* again (via one of the Lookup() functions).
*
* All of this means that a very particular procedure has to be followed for
* surfaces which cannot be rematerialized. First, they must be inserted
* with a persistent lifetime *after* the image is locked with LockImage(); if
* you use the other order, the surfaces might expire before LockImage() gets
* called or before the surface is touched again by Lookup(). Second, the
* image they are associated with must never be unlocked.
* *after* the image is locked with LockImage(); if you use the other order,
* the surfaces might expire before LockImage() gets called or before the
* surface is touched again by Lookup(). Second, the image they are associated
* with must never be unlocked.
*
* If a surface cannot be rematerialized, it may be important to know whether
* it was inserted into the cache successfully. Insert() returns FAILURE if it
* failed to insert the surface, which could happen because of capacity
* reasons, or because it was already freed by the OS. If you aren't inserting
* a surface with persistent lifetime, or if the surface isn't associated with
* a locked image, checking for SUCCESS or FAILURE is useless: the surface
* might expire immediately after being inserted, even though Insert()
* returned SUCCESS. Thus, many callers do not need to check the result of
* Insert() at all.
* reasons, or because it was already freed by the OS. If the surface isn't
* associated with a locked image, checking for SUCCESS or FAILURE is useless:
* the surface might expire immediately after being inserted, even though
* Insert() returned SUCCESS. Thus, many callers do not need to check the
* result of Insert() at all.
*
* @param aTarget The new surface (wrapped in an imgFrame) to insert into
* the cache.
@@ -265,9 +261,6 @@ struct SurfaceCache
* to.
* @param aSurfaceKey Key data which uniquely identifies the requested
* surface.
* @param aLifetime Whether this is a transient surface that can always be
* allowed to expire, or a persistent surface that
* shouldn't expire if the image is locked.
* @return SUCCESS if the surface was inserted successfully. (But see above
* for more information about when you should check this.)
* FAILURE if the surface could not be inserted, e.g. for capacity
@@ -278,8 +271,7 @@ struct SurfaceCache
*/
static InsertOutcome Insert(imgFrame* aSurface,
const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
Lifetime aLifetime);
const SurfaceKey& aSurfaceKey);
/**
* Insert a placeholder for a surface into the cache. If a surface with the
@@ -329,17 +321,16 @@ struct SurfaceCache
static bool CanHold(size_t aSize);
/**
* Locks an image. Any of the image's persistent surfaces which are either
* inserted or accessed while the image is locked will not expire.
* Locks an image. Any of the image's surfaces which are either inserted or
* accessed while the image is locked will not expire.
*
* Locking an image does not automatically lock that image's existing
* surfaces. A call to LockImage() guarantees that persistent surfaces which
* are inserted afterward will not expire before the next call to
* UnlockImage() or UnlockSurfaces() for that image. Surfaces that are
* accessed via Lookup() or LookupBestMatch() after a LockImage() call will
* also not expire until the next UnlockImage() or UnlockSurfaces() call for
* that image. Any other surfaces owned by the image may expire at any time,
* whether they are persistent or transient.
* surfaces. A call to LockImage() guarantees that surfaces which are inserted
* afterward will not expire before the next call to UnlockImage() or
* UnlockSurfaces() for that image. Surfaces that are accessed via Lookup() or
* LookupBestMatch() after a LockImage() call will also not expire until the
* next UnlockImage() or UnlockSurfaces() call for that image. Any other
* surfaces owned by the image may expire at any time.
*
* Regardless of locking, any of an image's surfaces may be removed using
* RemoveSurface(), and all of an image's surfaces are removed by
@@ -377,10 +368,10 @@ struct SurfaceCache
* expiring.
*
* This is intended to be used in situations where it's no longer clear that
* all of the persistent surfaces owned by an image are needed. Calling
* UnlockSurfaces() and then taking some action that will cause Lookup() to
* touch any surfaces that are still useful will permit the remaining surfaces
* to expire from the cache.
* all of the surfaces owned by an image are needed. Calling UnlockSurfaces()
* and then taking some action that will cause Lookup() to touch any surfaces
* that are still useful will permit the remaining surfaces to expire from the
* cache.
*
* If the image is unlocked, this has no effect.
*
@@ -421,9 +412,9 @@ struct SurfaceCache
/**
* Evicts all evictable surfaces from the cache.
*
* All surfaces are evictable except for persistent surfaces associated with
* locked images. Non-evictable surfaces can only be removed by
* RemoveSurface() or RemoveImage().
* All surfaces are evictable except for surfaces associated with locked
* images. Non-evictable surfaces can only be removed by RemoveSurface() or
* RemoveImage().
*/
static void DiscardAll();
+56
View File
@@ -0,0 +1,56 @@
/* -*- 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_SurfaceFlags_h
#define mozilla_image_SurfaceFlags_h
#include "imgIContainer.h"
#include "mozilla/TypedEnumBits.h"
namespace mozilla {
namespace image {
/**
* Flags that change the output a decoder generates. Because different
* combinations of these flags result in logically different surfaces, these
* flags must be taken into account in SurfaceCache lookups.
*/
enum class SurfaceFlags : uint8_t
{
NO_PREMULTIPLY_ALPHA = 1 << 0,
NO_COLORSPACE_CONVERSION = 1 << 1
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags)
/**
* @return the default set of surface flags.
*/
inline SurfaceFlags
DefaultSurfaceFlags()
{
return SurfaceFlags();
}
/**
* Given a set of imgIContainer FLAG_* flags, returns a set of SurfaceFlags with
* the corresponding flags set.
*/
inline SurfaceFlags
ToSurfaceFlags(uint32_t aFlags)
{
SurfaceFlags flags = DefaultSurfaceFlags();
if (aFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
flags |= SurfaceFlags::NO_PREMULTIPLY_ALPHA;
}
if (aFlags & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
flags |= SurfaceFlags::NO_COLORSPACE_CONVERSION;
}
return flags;
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfaceFlags_h
+27 -19
View File
@@ -659,19 +659,8 @@ VectorImage::IsOpaque()
/* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame,
* in uint32_t aFlags; */
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
VectorImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags)
{
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
if (aWhichFrame > FRAME_MAX_VALUE) {
return nullptr;
}
if (mError || !mIsFullyLoaded) {
return nullptr;
}
// Look up height & width
// ----------------------
SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem();
@@ -686,12 +675,32 @@ VectorImage::GetFrame(uint32_t aWhichFrame,
return nullptr;
}
return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
VectorImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
if (aSize.IsEmpty()) {
return nullptr;
}
if (aWhichFrame > FRAME_MAX_VALUE) {
return nullptr;
}
if (mError || !mIsFullyLoaded) {
return nullptr;
}
// Make our surface the size of what will ultimately be drawn to it.
// (either the full image size, or the restricted region)
RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
CreateOffscreenContentDrawTarget(IntSize(imageIntSize.width,
imageIntSize.height),
SurfaceFormat::B8G8R8A8);
CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
if (!dt) {
NS_ERROR("Could not create a DrawTarget");
return nullptr;
@@ -699,8 +708,8 @@ VectorImage::GetFrame(uint32_t aWhichFrame,
nsRefPtr<gfxContext> context = new gfxContext(dt);
auto result = Draw(context, imageIntSize,
ImageRegion::Create(imageIntSize),
auto result = Draw(context, aSize,
ImageRegion::Create(aSize),
aWhichFrame, GraphicsFilter::FILTER_NEAREST,
Nothing(), aFlags);
@@ -922,8 +931,7 @@ VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams)
SurfaceCache::Insert(frame, ImageKey(this),
VectorSurfaceKey(aParams.size,
aParams.svgContext,
aParams.animationTime),
Lifetime::Persistent);
aParams.animationTime));
// Draw.
nsRefPtr<gfxDrawable> drawable =
-14
View File
@@ -64,20 +64,6 @@ nsBMPDecoder::~nsBMPDecoder()
}
}
nsresult
nsBMPDecoder::SetTargetSize(const nsIntSize& aSize)
{
// Make sure the size is reasonable.
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
return NS_ERROR_FAILURE;
}
// Create a downscaler that we'll filter our output through.
mDownscaler.emplace(aSize);
return NS_OK;
}
// Sets whether or not the BMP will use alpha data
void
nsBMPDecoder::SetUseAlphaData(bool useAlphaData)
+1 -5
View File
@@ -9,7 +9,6 @@
#include "BMPFileHeaders.h"
#include "Decoder.h"
#include "Downscaler.h"
#include "gfxColor.h"
#include "nsAutoPtr.h"
@@ -25,8 +24,6 @@ class nsBMPDecoder : public Decoder
public:
~nsBMPDecoder();
nsresult SetTargetSize(const nsIntSize& aSize) override;
// Specifies whether or not the BMP file will contain alpha data
// If set to true and the BMP is 32BPP, the alpha data will be
// retrieved from the 4th byte of image data per pixel
@@ -43,6 +40,7 @@ public:
// Obtains the internal output image buffer
uint32_t* GetImageData();
size_t GetImageDataLength() const { return mImageDataLength; }
// Obtains the size of the compressed image resource
int32_t GetCompressedImageSize() const;
@@ -77,8 +75,6 @@ private:
char mRawBuf[BIH_INTERNAL_LENGTH::WIN_V3]; //< If this is changed,
// WriteInternal() MUST be updated
Maybe<Downscaler> mDownscaler;
uint32_t mLOH; //< Length of the header
uint32_t mNumColors; //< The number of used colors, i.e. the number of
+139 -26
View File
@@ -99,6 +99,40 @@ nsGIFDecoder2::~nsGIFDecoder2()
moz_free(mGIFStruct.hold);
}
uint8_t*
nsGIFDecoder2::GetCurrentRowBuffer()
{
if (!mDownscaler) {
MOZ_ASSERT(!mDeinterlacer, "Deinterlacer without downscaler?");
uint32_t bpp = mGIFStruct.images_decoded == 0 ? sizeof(uint32_t)
: sizeof(uint8_t);
return mImageData + mGIFStruct.irow * mGIFStruct.width * bpp;
}
if (!mDeinterlacer) {
return mDownscaler->RowBuffer();
}
return mDeinterlacer->RowBuffer(mGIFStruct.irow);
}
uint8_t*
nsGIFDecoder2::GetRowBuffer(uint32_t aRow)
{
MOZ_ASSERT(mGIFStruct.images_decoded == 0,
"Calling GetRowBuffer on a frame other than the first suggests "
"we're deinterlacing animated frames");
MOZ_ASSERT(!mDownscaler || mDeinterlacer,
"Can't get buffer for a specific row if downscaling "
"but not deinterlacing");
if (mDownscaler) {
return mDeinterlacer->RowBuffer(aRow);
}
return mImageData + aRow * mGIFStruct.width * sizeof(uint32_t);
}
void
nsGIFDecoder2::FinishInternal()
{
@@ -128,6 +162,15 @@ nsGIFDecoder2::FlushImageData(uint32_t fromRow, uint32_t rows)
void
nsGIFDecoder2::FlushImageData()
{
if (mDownscaler) {
if (mDownscaler->HasInvalidation()) {
DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
PostInvalidation(invalidRect.mOriginalSizeRect,
Some(invalidRect.mTargetSizeRect));
}
return;
}
switch (mCurrentPass - mLastFlushedPass) {
case 0: // same pass
if (mCurrentRow - mLastFlushedRow) {
@@ -202,21 +245,44 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
CheckForTransparency(frameRect);
// Make sure there's no animation if we're downscaling.
MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation());
IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
: GetSize();
// Rescale the frame rect for the target size.
IntRect targetFrameRect = frameRect;
if (mDownscaler) {
IntSize originalSize = GetSize();
targetFrameRect.ScaleRoundOut(double(targetSize.width) / originalSize.width,
double(targetSize.height) / originalSize.height);
}
// Use correct format, RGB for first frame, PAL for following frames
// and include transparency to allow for optimization of opaque images
nsresult rv = NS_OK;
if (mGIFStruct.images_decoded) {
// Image data is stored with original depth and palette.
rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
frameRect, format, aDepth);
rv = AllocateFrame(mGIFStruct.images_decoded, targetSize,
targetFrameRect, format, aDepth);
} else {
// Regardless of depth of input, the first frame is decoded into 24bit RGB.
rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
frameRect, format);
rv = AllocateFrame(mGIFStruct.images_decoded, targetSize,
targetFrameRect, format);
}
mCurrentFrameIndex = mGIFStruct.images_decoded;
if (NS_FAILED(rv)) {
return rv;
}
if (mDownscaler) {
rv = mDownscaler->BeginFrame(frameRect.Size(), mImageData,
mGIFStruct.is_transparent);
}
return rv;
}
@@ -237,10 +303,15 @@ nsGIFDecoder2::EndImageFrame()
// This will clear the remaining bits of the placeholder. (Bug 37589)
const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset;
if (realFrameHeight < mGIFStruct.screen_height) {
nsIntRect r(0, realFrameHeight,
mGIFStruct.screen_width,
mGIFStruct.screen_height - realFrameHeight);
PostInvalidation(r);
if (mDownscaler) {
IntRect targetRect = IntRect(IntPoint(), mDownscaler->TargetSize());
PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect));
} else {
nsIntRect r(0, realFrameHeight,
mGIFStruct.screen_width,
mGIFStruct.screen_height - realFrameHeight);
PostInvalidation(r);
}
}
// The first frame was preallocated with alpha; if it wasn't transparent, we
@@ -291,8 +362,11 @@ nsGIFDecoder2::EndImageFrame()
uint32_t
nsGIFDecoder2::OutputRow()
{
int drow_start, drow_end;
drow_start = drow_end = mGIFStruct.irow;
// Initialize the region in which we're duplicating rows (for the
// Haeberli-inspired hack below) to |irow|, which is the row we're writing to
// now.
int drow_start = mGIFStruct.irow;
int drow_end = mGIFStruct.irow;
// Protect against too much image data
if ((unsigned)drow_start >= mGIFStruct.height) {
@@ -329,8 +403,7 @@ nsGIFDecoder2::OutputRow()
}
// Row to process
const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width;
uint8_t* rowp = mImageData + (mGIFStruct.irow * bpr);
uint8_t* rowp = GetCurrentRowBuffer();
// Convert color indices to Cairo pixels
uint8_t* from = rowp + mGIFStruct.width;
@@ -351,12 +424,24 @@ nsGIFDecoder2::OutputRow()
}
}
// Duplicate rows
// If we're downscaling but not deinterlacing, we're done with this row and
// can commit it now. Otherwise, we'll let Deinterlacer do the committing
// when we call PropagatePassToDownscaler() at the end of this pass.
if (mDownscaler && !mDeinterlacer) {
mDownscaler->CommitRow();
}
if (drow_end > drow_start) {
// irow is the current row filled
// Duplicate rows if needed to reduce the "venetian blind" effect mentioned
// above. This writes out scanlines of the image in a way that isn't ordered
// vertically, which is incompatible with the filter that we use for
// downscale-during-decode, so we can't do this if we're downscaling.
MOZ_ASSERT_IF(mDownscaler, mDeinterlacer);
const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width;
for (int r = drow_start; r <= drow_end; r++) {
// Skip the row we wrote to above; that's what we're copying *from*.
if (r != int(mGIFStruct.irow)) {
memcpy(mImageData + (r * bpr), rowp, bpr);
memcpy(GetRowBuffer(r), rowp, bpr);
}
}
}
@@ -369,9 +454,12 @@ nsGIFDecoder2::OutputRow()
}
if (!mGIFStruct.interlaced) {
MOZ_ASSERT(!mDeinterlacer);
mGIFStruct.irow++;
} else {
static const uint8_t kjump[5] = { 1, 8, 8, 4, 2 };
int currentPass = mGIFStruct.ipass;
do {
// Row increments resp. per 8,8,4,2 rows
mGIFStruct.irow += kjump[mGIFStruct.ipass];
@@ -381,6 +469,15 @@ nsGIFDecoder2::OutputRow()
mGIFStruct.ipass++;
}
} while (mGIFStruct.irow >= mGIFStruct.height);
// We've finished a pass. If we're downscaling, it's time to propagate the
// rows we've decoded so far from our Deinterlacer to our Downscaler.
if (mGIFStruct.ipass > currentPass && mDownscaler) {
MOZ_ASSERT(mDeinterlacer);
mDeinterlacer->PropagatePassToDownscaler(*mDownscaler);
FlushImageData();
mDownscaler->ResetForNextProgressivePass();
}
}
return --mGIFStruct.rows_remaining;
@@ -413,17 +510,13 @@ nsGIFDecoder2::DoLzw(const uint8_t* q)
uint8_t* stack = mGIFStruct.stack;
uint8_t* rowp = mGIFStruct.rowp;
uint32_t bpr = mGIFStruct.width;
if (!mGIFStruct.images_decoded) {
bpr *= sizeof(uint32_t);
}
uint8_t* rowend = mImageData + (bpr * mGIFStruct.irow) + mGIFStruct.width;
uint8_t* rowend = GetCurrentRowBuffer() + mGIFStruct.width;
#define OUTPUT_ROW() \
PR_BEGIN_MACRO \
if (!OutputRow()) \
goto END; \
rowp = mImageData + mGIFStruct.irow * bpr; \
rowp = GetCurrentRowBuffer(); \
rowend = rowp + mGIFStruct.width; \
PR_END_MACRO
@@ -835,10 +928,15 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
mGIFStruct.is_transparent = *q & 0x1;
mGIFStruct.tpixel = q[3];
mGIFStruct.disposal_method = ((*q) >> 2) & 0x7;
// Some specs say 3rd bit (value 4), other specs say value 3
// Let's choose 3 (the more popular)
if (mGIFStruct.disposal_method == 4) {
// Some specs say 3rd bit (value 4), other specs say value 3.
// Let's choose 3 (the more popular).
mGIFStruct.disposal_method = 3;
} else if (mGIFStruct.disposal_method > 4) {
// This GIF is using a disposal method which is undefined in the spec.
// Treat it as DisposalMethod::NOT_SPECIFIED.
mGIFStruct.disposal_method = 0;
}
{
@@ -929,12 +1027,19 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// below, so that RasterImage can detect that this happened.
PostIsAnimated(/* aFirstFrameTimeout = */ 0);
}
if (IsFirstFrameDecode()) {
// We're about to get a second frame, but we only want the first. Stop
// decoding now.
mGIFStruct.state = gif_done;
break;
}
if (mDownscaler) {
MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
"for an animated image?");
mDownscaler.reset();
}
}
// Get image offsets, with respect to the screen origin
@@ -1020,14 +1125,22 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// offset. Otherwise, the area may never be refreshed and the
// placeholder will remain on the screen. (Bug 37589)
if (mGIFStruct.y_offset > 0) {
nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
PostInvalidation(r);
if (mDownscaler) {
IntRect targetRect = IntRect(IntPoint(), mDownscaler->TargetSize());
PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect));
} else {
nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
PostInvalidation(r);
}
}
}
if (q[8] & 0x40) {
mGIFStruct.interlaced = true;
mGIFStruct.ipass = 1;
if (mDownscaler) {
mDeinterlacer.emplace(mDownscaler->OriginalSize());
}
} else {
mGIFStruct.interlaced = false;
mGIFStruct.ipass = 0;
@@ -1039,7 +1152,7 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// Clear state from last image
mGIFStruct.irow = 0;
mGIFStruct.rows_remaining = mGIFStruct.height;
mGIFStruct.rowp = mImageData;
mGIFStruct.rowp = GetCurrentRowBuffer();
// Depth of colors is determined by colormap
// (q[8] & 0x80) indicates local colormap
+5 -1
View File
@@ -8,7 +8,7 @@
#define mozilla_image_decoders_nsGIFDecoder2_h
#include "Decoder.h"
#include "Deinterlacer.h"
#include "GIF2.h"
#include "nsCOMPtr.h"
@@ -34,6 +34,9 @@ private:
// Decoders should only be instantiated via DecoderFactory.
explicit nsGIFDecoder2(RasterImage* aImage);
uint8_t* GetCurrentRowBuffer();
uint8_t* GetRowBuffer(uint32_t aRow);
// These functions will be called when the decoder has a decoded row,
// frame size information, etc.
void BeginGIF();
@@ -67,6 +70,7 @@ private:
bool mSawTransparency;
gif_struct mGIFStruct;
Maybe<Deinterlacer> mDeinterlacer;
};
} // namespace image
+510 -408
View File
@@ -14,13 +14,14 @@
#include "RasterImage.h"
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
#define ICONCOUNTOFFSET 4
#define DIRENTRYOFFSET 6
#define BITMAPINFOSIZE 40
#define PREFICONSIZE 16
// Constants.
static const uint32_t ICOHEADERSIZE = 6;
static const uint32_t BITMAPINFOSIZE = 40;
// ----------------------------------------
// Actual Data Processing
@@ -57,22 +58,20 @@ nsICODecoder::GetNumColors()
return numColors;
}
nsICODecoder::nsICODecoder(RasterImage* aImage)
: Decoder(aImage)
{
mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
mIsPNG = false;
mRow = nullptr;
mOldLine = mCurLine = 1; // Otherwise decoder will never start
}
nsICODecoder::~nsICODecoder()
{
if (mRow) {
moz_free(mRow);
}
}
: Decoder(aImage)
, mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE))
, mBiggestResourceColorDepth(0)
, mBestResourceDelta(INT_MIN)
, mBestResourceColorDepth(0)
, mNumIcons(0)
, mCurrIcon(0)
, mBPP(0)
, mMaskRowSize(0)
, mCurrMaskLine(0)
, mIsCursor(false)
, mHasMaskAlpha(false)
{ }
void
nsICODecoder::FinishInternal()
@@ -80,11 +79,6 @@ nsICODecoder::FinishInternal()
// We shouldn't be called in error cases
MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
// Finish the internally used decoder as well.
if (mContainedDecoder && !mContainedDecoder->HasError()) {
mContainedDecoder->FinishInternal();
}
GetFinalStateFromContainedDecoder();
}
@@ -101,6 +95,9 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
return;
}
// Finish the internally used decoder.
mContainedDecoder->CompleteDecode();
mDecodeDone = mContainedDecoder->GetDecodeDone();
mDataError = mDataError || mContainedDecoder->HasDataError();
mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError()
@@ -109,6 +106,8 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
mProgress |= mContainedDecoder->TakeProgress();
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete());
}
// Returns a buffer filled with the bitmap file header in little endian:
@@ -206,10 +205,11 @@ nsICODecoder::CheckAndFixBitmapSize(int8_t* bih)
}
// The BMP information header's bits per pixel should be trusted
// more than what we have. Usually the ICO's BPP is set to 0
// more than what we have. Usually the ICO's BPP is set to 0.
int32_t
nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
nsICODecoder::ReadBPP(const char* aBIH)
{
const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
int32_t bitsPerPixel;
memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
@@ -217,22 +217,459 @@ nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
}
int32_t
nsICODecoder::ExtractBIHSizeFromBitmap(int8_t* bih)
nsICODecoder::ReadBIHSize(const char* aBIH)
{
const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
int32_t headerSize;
memcpy(&headerSize, bih, sizeof(headerSize));
NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
return headerSize;
}
void
nsICODecoder::SetHotSpotIfCursor()
LexerTransition<ICOState>
nsICODecoder::ReadHeader(const char* aData)
{
if (!mIsCursor) {
return;
// If the third byte is 1, this is an icon. If 2, a cursor.
if ((aData[2] != 1) && (aData[2] != 2)) {
return Transition::Terminate(ICOState::FAILURE);
}
mIsCursor = (aData[2] == 2);
// The fifth and sixth bytes specify the number of resources in the file.
mNumIcons =
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aData + 4));
if (mNumIcons == 0) {
return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
}
mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
// Downscale-during-decode can end up decoding different resources in the ICO
// file depending on the target size. Since the resources are not necessarily
// scaled versions of the same image, some may be transparent and some may not
// be. We could be precise about transparency if we decoded the metadata of
// every resource, but for now we don't and it's safest to assume that
// transparency could be present.
PostHasTransparency();
return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
}
size_t
nsICODecoder::FirstResourceOffset() const
{
MOZ_ASSERT(mNumIcons > 0,
"Calling FirstResourceOffset before processing header");
// The first resource starts right after the directory, which starts right
// after the ICO header.
return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
}
LexerTransition<ICOState>
nsICODecoder::ReadDirEntry(const char* aData)
{
mCurrIcon++;
// Read the directory entry.
IconDirEntry e;
memset(&e, 0, sizeof(e));
memcpy(&e.mWidth, aData, sizeof(e.mWidth));
memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight));
memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount));
memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved));
memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes));
e.mPlanes = LittleEndian::readUint16(&e.mPlanes);
memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount));
e.mBitCount = LittleEndian::readUint16(&e.mBitCount);
memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes));
e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes);
memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset));
e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset);
// Determine if this is the biggest resource we've seen so far. We always use
// the biggest resource for the intrinsic size, and if we're not downscaling,
// we select it as the best resource as well.
IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
if (e.mBitCount >= mBiggestResourceColorDepth &&
entrySize.width * entrySize.height >=
mBiggestResourceSize.width * mBiggestResourceSize.height) {
mBiggestResourceSize = entrySize;
mBiggestResourceColorDepth = e.mBitCount;
mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot);
if (!mDownscaler) {
mDirEntry = e;
}
}
if (mDownscaler) {
// Calculate the delta between this resource's size and the desired size, so
// we can see if it is better than our current-best option. In the case of
// several equally-good resources, we use the last one. "Better" in this
// case is determined by |delta|, a measure of the difference in size
// between the entry we've found and the downscaler's target size. We will
// choose the smallest resource that is >= the target size (i.e. we assume
// it's better to downscale a larger icon than to upscale a smaller one).
IntSize desiredSize = mDownscaler->TargetSize();
int32_t delta = entrySize.width - desiredSize.width +
entrySize.height - desiredSize.height;
if (e.mBitCount >= mBestResourceColorDepth &&
((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
(delta >= 0 && delta <= mBestResourceDelta))) {
mBestResourceDelta = delta;
mBestResourceColorDepth = e.mBitCount;
mDirEntry = e;
}
}
if (mCurrIcon == mNumIcons) {
// Ensure the resource we selected has an offset past the ICO headers.
if (mDirEntry.mImageOffset < FirstResourceOffset()) {
return Transition::Terminate(ICOState::FAILURE);
}
// If this is a cursor, set the hotspot. We use the hotspot from the biggest
// resource since we also use that resource for the intrinsic size.
if (mIsCursor) {
mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
mBiggestResourceHotSpot.height);
}
// We always report the biggest resource's size as the intrinsic size; this
// is necessary for downscale-during-decode to work since we won't even
// attempt to *upscale* while decoding.
PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
if (IsMetadataDecode()) {
return Transition::Terminate(ICOState::SUCCESS);
}
// If the resource we selected matches the downscaler's target size
// perfectly, we don't need to do any downscaling.
if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) {
mDownscaler.reset();
}
size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
ICOState::SKIP_TO_RESOURCE,
offsetToResource);
}
return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
}
LexerTransition<ICOState>
nsICODecoder::SniffResource(const char* aData)
{
// We use the first PNGSIGNATURESIZE bytes to determine whether this resource
// is a PNG or a BMP.
bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes,
PNGSIGNATURESIZE);
if (isPNG) {
// Create a PNG decoder which will do the rest of the work for us.
mContainedDecoder = new nsPNGDecoder(mImage);
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
if (mDownscaler) {
mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
}
mContainedDecoder->Init();
if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
return Transition::Terminate(ICOState::FAILURE);
}
if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
return Transition::Terminate(ICOState::FAILURE);
}
// Read in the rest of the PNG unbuffered.
size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
ICOState::READ_PNG,
toRead);
} else {
// Create a BMP decoder which will do most of the work for us; the exception
// is the AND mask, which isn't present in standalone BMPs.
nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
mContainedDecoder = bmpDecoder;
bmpDecoder->SetUseAlphaData(true);
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
if (mDownscaler) {
mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
}
mContainedDecoder->Init();
// Make sure we have a sane size for the bitmap information header.
int32_t bihSize = ReadBIHSize(aData);
if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
return Transition::Terminate(ICOState::FAILURE);
}
// Buffer the first part of the bitmap information header.
memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
// Read in the rest of the bitmap information header.
return Transition::To(ICOState::READ_BIH,
BITMAPINFOSIZE - PNGSIGNATURESIZE);
}
}
LexerTransition<ICOState>
nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
{
if (!WriteToContainedDecoder(aData, aLen)) {
return Transition::Terminate(ICOState::FAILURE);
}
// Raymond Chen says that 32bpp only are valid PNG ICOs
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
return Transition::Terminate(ICOState::FAILURE);
}
return Transition::ContinueUnbuffered(ICOState::READ_PNG);
}
LexerTransition<ICOState>
nsICODecoder::ReadBIH(const char* aData)
{
// Buffer the rest of the bitmap information header.
memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
// Extracting the BPP from the BIH header; it should be trusted over the one
// we have from the ICO header.
mBPP = ReadBPP(mBIHraw);
// The ICO format when containing a BMP does not include the 14 byte
// bitmap file header. To use the code of the BMP decoder we need to
// generate this header ourselves and feed it to the BMP decoder.
int8_t bfhBuffer[BMPFILEHEADERSIZE];
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
return Transition::Terminate(ICOState::FAILURE);
}
if (!WriteToContainedDecoder(reinterpret_cast<const char*>(bfhBuffer),
sizeof(bfhBuffer))) {
return Transition::Terminate(ICOState::FAILURE);
}
// Verify that the BIH width and height values match the ICO directory entry,
// and fix the BIH height value to compensate for the fact that the underlying
// BMP decoder doesn't know about AND masks.
if (!CheckAndFixBitmapSize(reinterpret_cast<int8_t*>(mBIHraw))) {
return Transition::Terminate(ICOState::FAILURE);
}
// Write out the BMP's bitmap info header.
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
return Transition::Terminate(ICOState::FAILURE);
}
// Sometimes the ICO BPP header field is not filled out so we should trust the
// contained resource over our own information.
// XXX(seth): Is this ever different than the value we obtained from
// ReadBPP() above?
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
mBPP = bmpDecoder->GetBitsPerPixel();
// Check to make sure we have valid color settings.
uint16_t numColors = GetNumColors();
if (numColors == uint16_t(-1)) {
return Transition::Terminate(ICOState::FAILURE);
}
// Do we have an AND mask on this BMP? If so, we need to read it after we read
// the BMP data itself.
uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes;
ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
: ICOState::FINISHED_RESOURCE;
// Read in the rest of the BMP unbuffered.
return Transition::ToUnbuffered(afterBMPState,
ICOState::READ_BMP,
bmpDataLength);
}
LexerTransition<ICOState>
nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
{
if (!WriteToContainedDecoder(aData, aLen)) {
return Transition::Terminate(ICOState::FAILURE);
}
return Transition::ContinueUnbuffered(ICOState::READ_BMP);
}
LexerTransition<ICOState>
nsICODecoder::PrepareForMask()
{
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
uint16_t numColors = GetNumColors();
MOZ_ASSERT(numColors != uint16_t(-1));
// Determine the length of the AND mask.
uint32_t bmpLengthWithHeader =
BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes);
uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader;
// If we have a 32-bpp BMP with alpha data, we ignore the AND mask. We can
// also obviously ignore it if the image has zero width or zero height.
if ((bmpDecoder->GetBitsPerPixel() == 32 && bmpDecoder->HasAlphaData()) ||
GetRealWidth() == 0 || GetRealHeight() == 0) {
return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
ICOState::SKIP_MASK,
maskLength);
}
// Compute the row size for the mask.
mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
// If the expected size of the AND mask is larger than its actual size, then
// we must have a truncated (and therefore corrupt) AND mask.
uint32_t expectedLength = mMaskRowSize * GetRealHeight();
if (maskLength < expectedLength) {
return Transition::Terminate(ICOState::FAILURE);
}
// If we're downscaling, the mask is the wrong size for the surface we've
// produced, so we need to downscale the mask into a temporary buffer and then
// combine the mask's alpha values with the color values from the image.
if (mDownscaler) {
MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
mDownscaler->TargetSize().width *
mDownscaler->TargetSize().height *
sizeof(uint32_t));
mMaskBuffer = MakeUnique<uint8_t[]>(bmpDecoder->GetImageDataLength());
nsresult rv = mDownscaler->BeginFrame(GetRealSize(),
mMaskBuffer.get(),
/* aHasAlpha = */ true,
/* aFlipVertically = */ true);
if (NS_FAILED(rv)) {
return Transition::Terminate(ICOState::FAILURE);
}
}
mCurrMaskLine = GetRealHeight();
return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
}
LexerTransition<ICOState>
nsICODecoder::ReadMaskRow(const char* aData)
{
mCurrMaskLine--;
uint8_t sawTransparency = 0;
// Get the mask row we're reading.
const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
const uint8_t* maskRowEnd = mask + mMaskRowSize;
// Get the corresponding row of the mask buffer (if we're downscaling) or the
// decoded image data (if we're not).
uint32_t* decoded = nullptr;
if (mDownscaler) {
// Initialize the row to all white and fully opaque.
memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t));
decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
} else {
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
uint32_t* imageData = bmpDecoder->GetImageData();
if (!imageData) {
return Transition::Terminate(ICOState::FAILURE);
}
decoded = imageData + mCurrMaskLine * GetRealWidth();
}
MOZ_ASSERT(decoded);
uint32_t* decodedRowEnd = decoded + GetRealWidth();
// Iterate simultaneously through the AND mask and the image data.
while (mask < maskRowEnd) {
uint8_t idx = *mask++;
sawTransparency |= idx;
for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
// Clear pixel completely for transparency.
if (idx & bit) {
*decoded = 0;
}
decoded++;
}
}
if (mDownscaler) {
mDownscaler->CommitRow();
}
// If any bits are set in sawTransparency, then we know at least one pixel was
// transparent.
if (sawTransparency) {
mHasMaskAlpha = true;
}
if (mCurrMaskLine == 0) {
return Transition::To(ICOState::FINISH_MASK, 0);
}
return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
}
LexerTransition<ICOState>
nsICODecoder::FinishMask()
{
// If we're downscaling, we now have the appropriate alpha values in
// mMaskBuffer. We just need to transfer them to the image.
if (mDownscaler) {
// Retrieve the image data.
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
if (!imageData) {
return Transition::Terminate(ICOState::FAILURE);
}
// Iterate through the alpha values, copying from mask to image.
MOZ_ASSERT(mMaskBuffer);
MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) {
imageData[i] = mMaskBuffer[i];
}
}
// If the mask contained any transparent pixels, record that fact.
if (mHasMaskAlpha) {
PostHasTransparency();
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
bmpDecoder->SetHasAlphaData();
}
return Transition::To(ICOState::FINISHED_RESOURCE, 0);
}
LexerTransition<ICOState>
nsICODecoder::FinishResource()
{
// Make sure the actual size of the resource matches the size in the directory
// entry. If not, we consider the image corrupt.
if (mContainedDecoder->HasSize() &&
mContainedDecoder->GetSize() != GetRealSize()) {
return Transition::Terminate(ICOState::FAILURE);
}
return Transition::Terminate(ICOState::SUCCESS);
}
void
@@ -242,368 +679,52 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
MOZ_ASSERT(aBuffer);
MOZ_ASSERT(aCount > 0);
while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
if ((*aBuffer != 1) && (*aBuffer != 2)) {
PostDataError();
return;
Maybe<ICOState> terminalState =
mLexer.Lex(aBuffer, aCount,
[=](ICOState aState, const char* aData, size_t aLength) {
switch (aState) {
case ICOState::HEADER:
return ReadHeader(aData);
case ICOState::DIR_ENTRY:
return ReadDirEntry(aData);
case ICOState::SKIP_TO_RESOURCE:
return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
case ICOState::FOUND_RESOURCE:
return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE);
case ICOState::SNIFF_RESOURCE:
return SniffResource(aData);
case ICOState::READ_PNG:
return ReadPNG(aData, aLength);
case ICOState::READ_BIH:
return ReadBIH(aData);
case ICOState::READ_BMP:
return ReadBMP(aData, aLength);
case ICOState::PREPARE_FOR_MASK:
return PrepareForMask();
case ICOState::READ_MASK_ROW:
return ReadMaskRow(aData);
case ICOState::FINISH_MASK:
return FinishMask();
case ICOState::SKIP_MASK:
return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
case ICOState::FINISHED_RESOURCE:
return FinishResource();
default:
MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
return Transition::Terminate(ICOState::FAILURE);
}
mIsCursor = (*aBuffer == 2);
}
mPos++; aBuffer++; aCount--;
});
if (!terminalState) {
return; // Need more data.
}
if (mPos == ICONCOUNTOFFSET && aCount >= 2) {
mNumIcons =
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aBuffer));
aBuffer += 2;
mPos += 2;
aCount -= 2;
}
if (mNumIcons == 0) {
return; // Nothing to do.
}
uint16_t colorDepth = 0;
// If we didn't get a #-moz-resolution, default to PREFICONSIZE.
if (mResolution.width == 0 && mResolution.height == 0) {
mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE);
}
// A measure of the difference in size between the entry we've found
// and the requested size. We will choose the smallest image that is
// >= requested size (i.e. we assume it's better to downscale a larger
// icon than to upscale a smaller one).
int32_t diff = INT_MIN;
// Loop through each entry's dir entry
while (mCurrIcon < mNumIcons) {
if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) &&
mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
uint32_t toCopy = sizeof(mDirEntryArray) -
(mPos - DIRENTRYOFFSET - mCurrIcon *
sizeof(mDirEntryArray));
if (toCopy > aCount) {
toCopy = aCount;
}
memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
mPos += toCopy;
aCount -= toCopy;
aBuffer += toCopy;
}
if (aCount == 0) {
return; // Need more data
}
IconDirEntry e;
if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) +
(mCurrIcon * sizeof(mDirEntryArray))) {
mCurrIcon++;
ProcessDirEntry(e);
// We can't use GetRealWidth and GetRealHeight here because those operate
// on mDirEntry, here we are going through each item in the directory.
// Calculate the delta between this image's size and the desired size,
// so we can see if it is better than our current-best option.
// In the case of several equally-good images, we use the last one.
int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - mResolution.width +
(e.mHeight == 0 ? 256 : e.mHeight) - mResolution.height;
if (e.mBitCount >= colorDepth &&
((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) {
diff = delta;
mImageOffset = e.mImageOffset;
// ensure mImageOffset is >= size of the direntry headers (bug #245631)
uint32_t minImageOffset = DIRENTRYOFFSET +
mNumIcons * sizeof(mDirEntryArray);
if (mImageOffset < minImageOffset) {
PostDataError();
return;
}
colorDepth = e.mBitCount;
memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
}
}
}
if (mPos < mImageOffset) {
// Skip to (or at least towards) the desired image offset
uint32_t toSkip = mImageOffset - mPos;
if (toSkip > aCount) {
toSkip = aCount;
}
mPos += toSkip;
aBuffer += toSkip;
aCount -= toSkip;
}
// If we are within the first PNGSIGNATURESIZE bytes of the image data,
// then we have either a BMP or a PNG. We use the first PNGSIGNATURESIZE
// bytes to determine which one we have.
if (mCurrIcon == mNumIcons && mPos >= mImageOffset &&
mPos < mImageOffset + PNGSIGNATURESIZE) {
uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
if (toCopy > aCount) {
toCopy = aCount;
}
memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
mPos += toCopy;
aCount -= toCopy;
aBuffer += toCopy;
mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
PNGSIGNATURESIZE);
if (mIsPNG) {
mContainedDecoder = new nsPNGDecoder(mImage);
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
if (mFirstFrameDecode) {
mContainedDecoder->SetIsFirstFrameDecode();
}
mContainedDecoder->Init();
if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
return;
}
}
}
// If we have a PNG, let the PNG decoder do all of the rest of the work
if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
if (!WriteToContainedDecoder(aBuffer, aCount)) {
return;
}
if (!HasSize() && mContainedDecoder->HasSize()) {
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
}
mPos += aCount;
aBuffer += aCount;
aCount = 0;
// Raymond Chen says that 32bpp only are valid PNG ICOs
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
if (!IsMetadataDecode() &&
!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
PostDataError();
}
if (*terminalState == ICOState::FAILURE) {
PostDataError();
return;
}
// We've processed all of the icon dir entries and are within the
// bitmap info size
if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset &&
mPos >= mImageOffset + PNGSIGNATURESIZE &&
mPos < mImageOffset + BITMAPINFOSIZE) {
// As we were decoding, we did not know if we had a PNG signature or the
// start of a bitmap information header. At this point we know we had
// a bitmap information header and not a PNG signature, so fill the bitmap
// information header with the data it should already have.
memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE);
// We've found the icon.
uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
if (toCopy > aCount) {
toCopy = aCount;
}
memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
mPos += toCopy;
aCount -= toCopy;
aBuffer += toCopy;
}
// If we have a BMP inside the ICO and we have read the BIH header
if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
// Make sure we have a sane value for the bitmap information header
int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast<int8_t*>
(mBIHraw));
if (bihSize != BITMAPINFOSIZE) {
PostDataError();
return;
}
// We are extracting the BPP from the BIH header as it should be trusted
// over the one we have from the icon header
mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
// Init the bitmap decoder which will do most of the work for us
// It will do everything except the AND mask which isn't present in bitmaps
// bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
mContainedDecoder = bmpDecoder;
bmpDecoder->SetUseAlphaData(true);
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
if (mFirstFrameDecode) {
mContainedDecoder->SetIsFirstFrameDecode();
}
mContainedDecoder->Init();
// The ICO format when containing a BMP does not include the 14 byte
// bitmap file header. To use the code of the BMP decoder we need to
// generate this header ourselves and feed it to the BMP decoder.
int8_t bfhBuffer[BMPFILEHEADERSIZE];
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
PostDataError();
return;
}
if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
return;
}
// Setup the cursor hot spot if one is present
SetHotSpotIfCursor();
// Verify that the BIH width and height values match the ICO directory entry,
// and fix the BIH height value to compensate for the fact that the underlying
// BMP decoder doesn't know about AND masks.
if (!CheckAndFixBitmapSize(reinterpret_cast<int8_t*>(mBIHraw))) {
PostDataError();
return;
}
// Write out the BMP's bitmap info header
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
return;
}
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
// We have the size. If we're doing a metadata decode, we're done.
if (IsMetadataDecode()) {
return;
}
// Sometimes the ICO BPP header field is not filled out
// so we should trust the contained resource over our own
// information.
mBPP = bmpDecoder->GetBitsPerPixel();
// Check to make sure we have valid color settings
uint16_t numColors = GetNumColors();
if (numColors == (uint16_t)-1) {
PostDataError();
return;
}
}
// If we have a BMP
if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
uint16_t numColors = GetNumColors();
if (numColors == (uint16_t)-1) {
PostDataError();
return;
}
// Feed the actual image data (not including headers) into the BMP decoder
uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE +
static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
GetCompressedImageSize() +
4 * numColors;
// If we are feeding in the core image data, but we have not yet
// reached the ICO's 'AND buffer mask'
if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
// Figure out how much data the BMP decoder wants
uint32_t toFeed = bmpDataEnd - mPos;
if (toFeed > aCount) {
toFeed = aCount;
}
if (!WriteToContainedDecoder(aBuffer, toFeed)) {
return;
}
mPos += toFeed;
aCount -= toFeed;
aBuffer += toFeed;
}
// If the bitmap is fully processed, treat any left over data as the ICO's
// 'AND buffer mask' which appears after the bitmap resource.
if (!mIsPNG && mPos >= bmpDataEnd) {
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
// There may be an optional AND bit mask after the data. This is
// only used if the alpha data is not already set. The alpha data
// is used for 32bpp bitmaps as per the comment in ICODecoder.h
// The alpha mask should be checked in all other cases.
if (bmpDecoder->GetBitsPerPixel() != 32 || !bmpDecoder->HasAlphaData()) {
uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
if (mPos == bmpDataEnd) {
mPos++;
mRowBytes = 0;
mCurLine = GetRealHeight();
mRow = (uint8_t*)moz_realloc(mRow, rowSize);
if (!mRow) {
PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
// Ensure memory has been allocated before decoding.
MOZ_ASSERT(mRow, "mRow is null");
if (!mRow) {
PostDataError();
return;
}
uint8_t sawTransparency = 0;
while (mCurLine > 0 && aCount > 0) {
uint32_t toCopy = std::min(rowSize - mRowBytes, aCount);
if (toCopy) {
memcpy(mRow + mRowBytes, aBuffer, toCopy);
aCount -= toCopy;
aBuffer += toCopy;
mRowBytes += toCopy;
}
if (rowSize == mRowBytes) {
mCurLine--;
mRowBytes = 0;
uint32_t* imageData = bmpDecoder->GetImageData();
if (!imageData) {
PostDataError();
return;
}
uint32_t* decoded = imageData + mCurLine * GetRealWidth();
uint32_t* decoded_end = decoded + GetRealWidth();
uint8_t* p = mRow;
uint8_t* p_end = mRow + rowSize;
while (p < p_end) {
uint8_t idx = *p++;
sawTransparency |= idx;
for (uint8_t bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
// Clear pixel completely for transparency.
if (idx & bit) {
*decoded = 0;
}
decoded++;
}
}
}
}
// If any bits are set in sawTransparency, then we know at least one
// pixel was transparent.
if (sawTransparency) {
PostHasTransparency();
bmpDecoder->SetHasAlphaData();
}
}
}
}
MOZ_ASSERT(*terminalState == ICOState::SUCCESS);
}
bool
@@ -613,7 +734,7 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
mProgress |= mContainedDecoder->TakeProgress();
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
if (mContainedDecoder->HasDataError()) {
mDataError = mContainedDecoder->HasDataError();
PostDataError();
}
if (mContainedDecoder->HasDecoderError()) {
PostDecoderError(mContainedDecoder->GetDecoderError());
@@ -621,24 +742,5 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
return !HasError();
}
void
nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
{
memset(&aTarget, 0, sizeof(aTarget));
memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight));
memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount));
memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved));
memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes));
aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes);
memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
sizeof(aTarget.mImageOffset));
aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
}
} // namespace image
} // namespace mozilla
+68 -36
View File
@@ -8,6 +8,7 @@
#define mozilla_image_decoders_nsICODecoder_h
#include "nsAutoPtr.h"
#include "StreamingLexer.h"
#include "Decoder.h"
#include "imgFrame.h"
#include "nsBMPDecoder.h"
@@ -19,28 +20,57 @@ namespace image {
class RasterImage;
enum class ICOState
{
SUCCESS,
FAILURE,
HEADER,
DIR_ENTRY,
SKIP_TO_RESOURCE,
FOUND_RESOURCE,
SNIFF_RESOURCE,
READ_PNG,
READ_BIH,
READ_BMP,
PREPARE_FOR_MASK,
READ_MASK_ROW,
FINISH_MASK,
SKIP_MASK,
FINISHED_RESOURCE
};
class nsICODecoder : public Decoder
{
public:
virtual ~nsICODecoder();
virtual ~nsICODecoder() { }
// Obtains the width of the icon directory entry
uint32_t GetRealWidth() const
/// @return the width of the icon directory entry @aEntry.
static uint32_t GetRealWidth(const IconDirEntry& aEntry)
{
return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth;
return aEntry.mWidth == 0 ? 256 : aEntry.mWidth;
}
// Obtains the height of the icon directory entry
uint32_t GetRealHeight() const
/// @return the width of the selected directory entry (mDirEntry).
uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); }
/// @return the height of the icon directory entry @aEntry.
static uint32_t GetRealHeight(const IconDirEntry& aEntry)
{
return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight;
return aEntry.mHeight == 0 ? 256 : aEntry.mHeight;
}
virtual void SetResolution(const gfx::IntSize& aResolution) override
/// @return the height of the selected directory entry (mDirEntry).
uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
/// @return the size of the selected directory entry (mDirEntry).
gfx::IntSize GetRealSize() const
{
mResolution = aResolution;
return gfx::IntSize(GetRealWidth(), GetRealHeight());
}
/// @return The offset from the beginning of the ICO to the first resource.
size_t FirstResourceOffset() const;
virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
virtual void FinishInternal() override;
virtual void FinishWithErrorInternal() override;
@@ -58,10 +88,6 @@ private:
// Gets decoder state from the contained decoder so it's visible externally.
void GetFinalStateFromContainedDecoder();
// Processes a single dir entry of the icon resource
void ProcessDirEntry(IconDirEntry& aTarget);
// Sets the hotspot property of if we have a cursor
void SetHotSpotIfCursor();
// Creates a bitmap file header buffer, returns true if successful
bool FillBitmapFileHeaderBuffer(int8_t* bfh);
/**
@@ -74,36 +100,42 @@ private:
*/
bool CheckAndFixBitmapSize(int8_t* aBIH);
// Extract bitmap info header size count from BMP information header
int32_t ExtractBIHSizeFromBitmap(int8_t* bih);
int32_t ReadBIHSize(const char* aBIH);
// Extract bit count from BMP information header
int32_t ExtractBPPFromBitmap(int8_t* bih);
int32_t ReadBPP(const char* aBIH);
// Calculates the row size in bytes for the AND mask table
uint32_t CalcAlphaRowSize();
// Obtains the number of colors from the BPP, mBPP must be filled in
uint16_t GetNumColors();
gfx::IntSize mResolution; // The requested -moz-resolution for this icon.
uint16_t mBPP; // Stores the images BPP
uint32_t mPos; // Keeps track of the position we have decoded up until
uint16_t mNumIcons; // Stores the number of icons in the ICO file
uint16_t mCurrIcon; // Stores the current dir entry index we are processing
uint32_t mImageOffset; // Stores the offset of the image data we want
uint8_t* mRow; // Holds one raw line of the image
int32_t mCurLine; // Line index of the image that's currently being decoded
uint32_t mRowBytes; // How many bytes of the row were already received
int32_t mOldLine; // Previous index of the line
nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
LexerTransition<ICOState> ReadHeader(const char* aData);
LexerTransition<ICOState> ReadDirEntry(const char* aData);
LexerTransition<ICOState> SniffResource(const char* aData);
LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
LexerTransition<ICOState> ReadBIH(const char* aData);
LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
LexerTransition<ICOState> PrepareForMask();
LexerTransition<ICOState> ReadMaskRow(const char* aData);
LexerTransition<ICOState> FinishMask();
LexerTransition<ICOState> FinishResource();
char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
IconDirEntry mDirEntry; // Holds a decoded dir entry
// Holds the potential bytes that can be a PNG signature
char mSignature[PNGSIGNATURESIZE];
// Holds the potential bytes for a bitmap information header
char mBIHraw[40];
// Stores whether or not the icon file we are processing has type 1 (icon)
bool mIsCursor;
// Stores whether or not the contained resource is a PNG
bool mIsPNG;
StreamingLexer<ICOState, 32> mLexer; // The lexer.
nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
UniquePtr<uint8_t[]> mMaskBuffer; // A temporary buffer for the alpha mask.
char mBIHraw[40]; // The bitmap information header.
IconDirEntry mDirEntry; // The dir entry for the selected resource.
gfx::IntSize mBiggestResourceSize; // Used to select the intrinsic size.
gfx::IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size.
uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
int32_t mBestResourceDelta; // Used to select the best resource.
uint16_t mBestResourceColorDepth; // Used to select the best resource.
uint16_t mNumIcons; // Stores the number of icons in the ICO file.
uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
uint16_t mBPP; // The BPP of the resource we're decoding.
uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask.
uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing.
bool mIsCursor; // Is this ICO a cursor?
bool mHasMaskAlpha; // Did the BMP alpha mask have any transparency?
};
} // namespace image
+76 -23
View File
@@ -12,15 +12,20 @@
#include "RasterImage.h"
#include <algorithm>
using namespace mozilla::gfx;
using std::min;
namespace mozilla {
namespace image {
nsIconDecoder::nsIconDecoder(RasterImage* aImage)
: Decoder(aImage),
mWidth(-1),
mHeight(-1),
mPixBytesRead(0),
mState(iconStateStart)
: Decoder(aImage)
, mExpectedDataLength(0)
, mPixBytesRead(0)
, mState(iconStateStart)
, mWidth(-1)
, mHeight(-1)
{
// Nothing to do
}
@@ -33,10 +38,6 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
{
MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
// We put this here to avoid errors about crossing initialization with case
// jumps on linux.
uint32_t bytesToRead = 0;
// Loop until the input data is gone
while (aCount > 0) {
switch (mState) {
@@ -73,9 +74,16 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
break;
}
// The input is 32bpp, so we expect 4 bytes of data per pixel.
mExpectedDataLength = mWidth * mHeight * 4;
{
MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
nsresult rv = AllocateBasicFrame();
IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
: GetSize();
nsresult rv = AllocateFrame(0, targetSize,
IntRect(IntPoint(), targetSize),
gfx::SurfaceFormat::B8G8R8A8);
if (NS_FAILED(rv)) {
mState = iconStateFinished;
return;
@@ -84,6 +92,16 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
MOZ_ASSERT(mImageData, "Should have a buffer now");
if (mDownscaler) {
nsresult rv = mDownscaler->BeginFrame(GetSize(),
mImageData,
/* aHasAlpha = */ true);
if (NS_FAILED(rv)) {
mState = iconStateFinished;
return;
}
}
// Book Keeping
aBuffer++;
aCount--;
@@ -93,25 +111,60 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
case iconStateReadPixels: {
// How many bytes are we reading?
bytesToRead = std::min(aCount, mImageDataLength - mPixBytesRead);
uint32_t bytesToRead = min(aCount, mExpectedDataLength - mPixBytesRead);
// Copy the bytes
memcpy(mImageData + mPixBytesRead, aBuffer, bytesToRead);
if (mDownscaler) {
uint8_t* row = mDownscaler->RowBuffer();
const uint32_t bytesPerRow = mWidth * 4;
const uint32_t rowOffset = mPixBytesRead % bytesPerRow;
// Performance isn't critical here, so our update rectangle is
// always the full icon
nsIntRect r(0, 0, mWidth, mHeight);
// Update global state; we're about to read |bytesToRead| bytes.
aCount -= bytesToRead;
mPixBytesRead += bytesToRead;
// Invalidate
PostInvalidation(r);
if (rowOffset > 0) {
// Finish the current row.
const uint32_t remaining = bytesPerRow - rowOffset;
memcpy(row + rowOffset, aBuffer, remaining);
aBuffer += remaining;
bytesToRead -= remaining;
mDownscaler->CommitRow();
}
// Book Keeping
aBuffer += bytesToRead;
aCount -= bytesToRead;
mPixBytesRead += bytesToRead;
// Copy the bytes a row at a time.
while (bytesToRead > bytesPerRow) {
memcpy(row, aBuffer, bytesPerRow);
aBuffer += bytesPerRow;
bytesToRead -= bytesPerRow;
mDownscaler->CommitRow();
}
// Copy any leftover bytes. (Leaving the current row incomplete.)
if (bytesToRead > 0) {
memcpy(row, aBuffer, bytesToRead);
aBuffer += bytesPerRow;
bytesToRead -= bytesPerRow;
}
if (mDownscaler->HasInvalidation()) {
DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
PostInvalidation(invalidRect.mOriginalSizeRect,
Some(invalidRect.mTargetSizeRect));
}
} else {
// Copy all the bytes at once.
memcpy(mImageData + mPixBytesRead, aBuffer, bytesToRead);
aBuffer += bytesToRead;
aCount -= bytesToRead;
mPixBytesRead += bytesToRead;
// Invalidate. Performance isn't critical here, so our update
// rectangle is always the full icon.
PostInvalidation(IntRect(0, 0, mWidth, mHeight));
}
// If we've got all the pixel bytes, we're finished
if (mPixBytesRead == mImageDataLength) {
if (mPixBytesRead == mExpectedDataLength) {
PostFrameStop();
PostDecodeDone();
mState = iconStateFinished;
+3 -3
View File
@@ -47,11 +47,11 @@ private:
// Decoders should only be instantiated via DecoderFactory.
explicit nsIconDecoder(RasterImage* aImage);
public:
uint8_t mWidth;
uint8_t mHeight;
uint32_t mExpectedDataLength;
uint32_t mPixBytesRead;
uint32_t mState;
uint8_t mWidth;
uint8_t mHeight;
};
enum {
+1 -15
View File
@@ -136,25 +136,11 @@ nsJPEGDecoder::SpeedHistogram()
return Telemetry::IMAGE_DECODE_SPEED_JPEG;
}
nsresult
nsJPEGDecoder::SetTargetSize(const nsIntSize& aSize)
{
// Make sure the size is reasonable.
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
return NS_ERROR_FAILURE;
}
// Create a downscaler that we'll filter our output through.
mDownscaler.emplace(aSize);
return NS_OK;
}
void
nsJPEGDecoder::InitInternal()
{
mCMSMode = gfxPlatform::GetCMSMode();
if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
mCMSMode = eCMSMode_Off;
}
-5
View File
@@ -15,7 +15,6 @@
#include "Decoder.h"
#include "Downscaler.h"
#include "nsAutoPtr.h"
#include "nsIInputStream.h"
@@ -55,8 +54,6 @@ class nsJPEGDecoder : public Decoder
public:
virtual ~nsJPEGDecoder();
virtual nsresult SetTargetSize(const nsIntSize& aSize) override;
virtual void SetSampleSize(int aSampleSize) override
{
mSampleSize = aSampleSize;
@@ -73,8 +70,6 @@ protected:
Orientation ReadOrientationFromEXIF();
void OutputScanlines(bool* suspend);
Maybe<Downscaler> mDownscaler;
private:
friend class DecoderFactory;
+2 -16
View File
@@ -148,20 +148,6 @@ nsPNGDecoder::~nsPNGDecoder()
}
}
nsresult
nsPNGDecoder::SetTargetSize(const nsIntSize& aSize)
{
// Make sure the size is reasonable.
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
return NS_ERROR_FAILURE;
}
// Create a downscaler that we'll filter our output through.
mDownscaler.emplace(aSize);
return NS_OK;
}
void
nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
const IntRect& aFrameRect)
@@ -262,11 +248,11 @@ void
nsPNGDecoder::InitInternal()
{
mCMSMode = gfxPlatform::GetCMSMode();
if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
mCMSMode = eCMSMode_Off;
}
mDisablePremultipliedAlpha =
GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
static png_byte color_chunks[]=
-4
View File
@@ -8,7 +8,6 @@
#define mozilla_image_decoders_nsPNGDecoder_h
#include "Decoder.h"
#include "Downscaler.h"
#include "gfxTypes.h"
@@ -27,8 +26,6 @@ class nsPNGDecoder : public Decoder
public:
virtual ~nsPNGDecoder();
virtual nsresult SetTargetSize(const nsIntSize& aSize) override;
virtual void InitInternal() override;
virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
virtual Telemetry::ID SpeedHistogram() override;
@@ -85,7 +82,6 @@ private:
public:
png_structp mPNG;
Maybe<Downscaler> mDownscaler;
png_infop mInfo;
nsIntRect mFrameRect;
uint8_t* mCMSLine;
+2 -88
View File
@@ -205,7 +205,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
static_cast<uint8_t*>(moz_malloc(PaletteDataLength() +
(mSize.width * mSize.height)));
if (!mPalettedImageData) {
NS_WARNING("Call to malloc for paletted image data should succeed");
NS_WARNING("malloc for paletted image data should succeed");
}
NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
} else {
@@ -796,96 +796,10 @@ imgFrame::LockImageData()
return NS_OK;
}
double imgPixelSize = mSize.width * mSize.height;
if (imgPixelSize < (8092 * 8092)) {
// We should be safe to Deoptimize at this size (<64Mpix)
return Deoptimize();
}
MOZ_ASSERT_UNREACHABLE("It's illegal to re-lock an optimized imgFrame");
return NS_ERROR_FAILURE;
}
nsresult
imgFrame::Deoptimize()
{
MOZ_ASSERT(NS_IsMainThread());
mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(!mImageSurface);
if (!mImageSurface) {
if (mVBuf) {
VolatileBufferPtr<uint8_t> ref(mVBuf);
if (ref.WasBufferPurged()) {
return NS_ERROR_FAILURE;
}
mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
if (!mImageSurface) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
if (mOptSurface || mSinglePixel || mFormat == SurfaceFormat::R5G6B5) {
SurfaceFormat format = mFormat;
if (mFormat == SurfaceFormat::R5G6B5) {
format = SurfaceFormat::B8G8R8A8;
}
// Recover the pixels
RefPtr<VolatileBuffer> buf =
AllocateBufferForImage(mSize, format);
if (!buf) {
return NS_ERROR_OUT_OF_MEMORY;
}
RefPtr<DataSourceSurface> surf =
CreateLockedSurface(buf, mSize, format);
if (!surf) {
return NS_ERROR_OUT_OF_MEMORY;
}
DataSourceSurface::MappedSurface mapping;
if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
gfxCriticalError() << "imgFrame::Deoptimize failed to map surface";
return NS_ERROR_FAILURE;
}
RefPtr<DrawTarget> target =
Factory::CreateDrawTargetForData(BackendType::CAIRO,
mapping.mData,
mSize,
mapping.mStride,
format);
if (!target) {
gfxWarning() <<
"imgFrame::Deoptimize failed in CreateDrawTargetForData";
return NS_ERROR_OUT_OF_MEMORY;
}
Rect rect(0, 0, mSize.width, mSize.height);
if (mSinglePixel) {
target->FillRect(rect, ColorPattern(mSinglePixelColor),
DrawOptions(1.0f, CompositionOp::OP_SOURCE));
} else if (mFormat == SurfaceFormat::R5G6B5) {
target->DrawSurface(mImageSurface, rect, rect);
} else {
target->DrawSurface(mOptSurface, rect, rect,
DrawSurfaceOptions(),
DrawOptions(1.0f, CompositionOp::OP_SOURCE));
}
target->Flush();
surf->Unmap();
mFormat = format;
mVBuf = buf;
mImageSurface = surf;
mOptSurface = nullptr;
}
}
mVBufPtr = mVBuf;
return NS_OK;
}
void
imgFrame::AssertImageDataLocked() const
{
+3 -1
View File
@@ -274,7 +274,6 @@ private: // methods
nsresult LockImageData();
nsresult UnlockImageData();
nsresult Optimize();
nsresult Deoptimize();
void AssertImageDataLocked() const;
@@ -448,6 +447,9 @@ private:
* This may be considerably more expensive than is necessary just for drawing,
* so only use this when you need to read or write the raw underlying image data
* that the imgFrame holds.
*
* Once all an imgFrame's RawAccessFrameRefs go out of scope, new
* RawAccessFrameRefs cannot be created.
*/
class RawAccessFrameRef final
{
+16 -1
View File
@@ -119,7 +119,7 @@ native nsIntSizeByVal(nsIntSize);
*
* Internally, imgIContainer also manages animation of images.
*/
[scriptable, builtinclass, uuid(4880727a-5673-44f7-b248-f6c86e22a434)]
[scriptable, builtinclass, uuid(4e5a0547-6c54-4051-8b52-1f2fdd667696)]
interface imgIContainer : nsISupports
{
/**
@@ -267,6 +267,21 @@ interface imgIContainer : nsISupports
[noscript, notxpcom] TempRefSourceSurface getFrame(in uint32_t aWhichFrame,
in uint32_t aFlags);
/**
* Get a surface for the given frame at the specified size. Matching the
* requested size is best effort; it's not guaranteed that the surface you get
* will be a perfect match. (Some reasons you may get a surface of a different
* size include: if you requested upscaling, if downscale-during-decode is
* disabled, or if you didn't request the first frame.)
*
* @param aSize The desired size.
* @param aWhichFrame Frame specifier of the FRAME_* variety.
* @param aFlags Flags of the FLAG_* variety
*/
[noscript, notxpcom] TempRefSourceSurface getFrameAtSize([const] in nsIntSize aSize,
in uint32_t aWhichFrame,
in uint32_t aFlags);
/**
* Whether this image is opaque (i.e., needs a background painted behind it).
*/
+3 -2
View File
@@ -268,9 +268,10 @@ private:
surfacePathPrefix.Append("@");
surfacePathPrefix.AppendFloat(counter.Key().AnimationTime());
if (counter.Key().Flags() != imgIContainer::DECODE_FLAGS_DEFAULT) {
if (counter.Key().Flags() != DefaultSurfaceFlags()) {
surfacePathPrefix.Append(", flags:");
surfacePathPrefix.AppendInt(counter.Key().Flags(), /* aRadix = */ 16);
surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
/* aRadix = */ 16);
}
} else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
surfacePathPrefix.Append(", compositing frame");
+19 -17
View File
@@ -198,26 +198,27 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer,
return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
}
// Use frame 0 from the image container.
RefPtr<SourceSurface> frame =
aContainer->GetFrame(imgIContainer::FRAME_FIRST,
imgIContainer::FLAG_SYNC_DECODE);
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
int32_t frameWidth = frame->GetSize().width;
int32_t frameHeight = frame->GetSize().height;
// Retrieve the image's size.
int32_t imageWidth = 0;
int32_t imageHeight = 0;
aContainer->GetWidth(&imageWidth);
aContainer->GetHeight(&imageHeight);
// If the given width or height is zero we'll replace it with the image's
// original dimensions.
if (aScaledWidth == 0) {
aScaledWidth = frameWidth;
} else if (aScaledHeight == 0) {
aScaledHeight = frameHeight;
}
IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth,
aScaledHeight == 0 ? imageHeight : aScaledHeight);
// Use frame 0 from the image container.
RefPtr<SourceSurface> frame =
aContainer->GetFrameAtSize(scaledSize,
imgIContainer::FRAME_FIRST,
imgIContainer::FLAG_HIGH_QUALITY_SCALING |
imgIContainer::FLAG_SYNC_DECODE);
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
RefPtr<DataSourceSurface> dataSurface =
Factory::CreateDataSourceSurface(IntSize(aScaledWidth, aScaledHeight),
SurfaceFormat::B8G8R8A8);
Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
if (NS_WARN_IF(!dataSurface)) {
return NS_ERROR_FAILURE;
}
@@ -238,9 +239,10 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer,
return NS_ERROR_OUT_OF_MEMORY;
}
IntSize frameSize = frame->GetSize();
dt->DrawSurface(frame,
Rect(0, 0, aScaledWidth, aScaledHeight),
Rect(0, 0, frameWidth, frameHeight),
Rect(0, 0, scaledSize.width, scaledSize.height),
Rect(0, 0, frameSize.width, frameSize.height),
DrawSurfaceOptions(),
DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+2
View File
@@ -48,6 +48,7 @@ EXPORTS += [
'IProgressObserver.h',
'Orientation.h',
'SurfaceCache.h',
'SurfaceFlags.h',
]
UNIFIED_SOURCES += [
@@ -55,6 +56,7 @@ UNIFIED_SOURCES += [
'DecodePool.cpp',
'Decoder.cpp',
'DecoderFactory.cpp',
'Deinterlacer.cpp',
'DynamicImage.cpp',
'FrameAnimator.cpp',
'FrozenImage.cpp',
+11 -1
View File
@@ -46,4 +46,14 @@ load multiple-png-hassize.ico
# Asserts in the debug build
load 856616.gif
skip-if(AddressSanitizer) load 944353.jpg
skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg
# Bug 1160801: Ensure that we handle invalid disposal types.
load invalid-disposal-method-1.gif
load invalid-disposal-method-2.gif
load invalid-disposal-method-3.gif
# Ensure we handle ICO directory entries which specify the wrong size for the
# contained resource.
load invalid_ico_height.ico
load invalid_ico_width.ico
Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B

+1 -2
View File
@@ -212,8 +212,7 @@ TEST(ImageDecoders, ICOSingleChunk)
CheckDecoderSingleChunk(GreenICOTestCase());
}
// XXX(seth): Disabled. We'll fix this in bug 1196066.
TEST(ImageDecoders, DISABLED_ICOMultiChunk)
TEST(ImageDecoders, ICOMultiChunk)
{
CheckDecoderMultiChunk(GreenICOTestCase());
}
+3 -3
View File
@@ -111,7 +111,7 @@ CheckMetadata(const ImageTestCase& aTestCase,
// Create a full decoder, so we can compare the result.
decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
imgIContainer::DECODE_FLAGS_DEFAULT);
DefaultSurfaceFlags());
ASSERT_TRUE(decoder != nullptr);
if (aBMPAlpha == BMPAlpha::ENABLED) {
@@ -241,14 +241,14 @@ TEST(ImageMetadata, NoFrameDelayGIFFullDecode)
LookupResult firstFrameLookupResult =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
imgIContainer::DECODE_FLAGS_DEFAULT,
DefaultSurfaceFlags(),
/* aFrameNum = */ 0));
EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type());
LookupResult secondFrameLookupResult =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
imgIContainer::DECODE_FLAGS_DEFAULT,
DefaultSurfaceFlags(),
/* aFrameNum = */ 1));
EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type());
}
@@ -57,8 +57,9 @@ function testFiles() {
yield ["opaque.bmp", false];
// ICO files which contain BMPs have an additional type of transparency - the
// AND mask - that warrants separate testing.
yield ["ico-bmp-opaque.ico", false];
// AND mask - that warrants separate testing. (Although, after bug 1201796,
// all ICOs are considered transparent.)
yield ["ico-bmp-opaque.ico", true];
yield ["ico-bmp-transparent.ico", true];
// SVGs are always transparent.
+8 -9
View File
@@ -24,8 +24,8 @@
# behavior than other platforms, and may require "fuzzy-if(winWidget,...)".
# RUN TESTS NOT AFFECTED BY HIGH QUALITY DOWNSCALING:
# ===================================================
# RUN TESTS NOT AFFECTED BY DOWNSCALE-DURING-DECODE:
# ==================================================
== downscale-svg-1a.html downscale-svg-1-ref.html?80
fuzzy(80,468) == downscale-svg-1b.html downscale-svg-1-ref.html?72
== downscale-svg-1c.html downscale-svg-1-ref.html?64
@@ -33,9 +33,9 @@ fuzzy(17,208) fuzzy-if(B2G,255,207) == downscale-svg-1d.html downscale-svg-1-ref
fuzzy(78,216) == downscale-svg-1e.html downscale-svg-1-ref.html?40
fuzzy(51,90) == downscale-svg-1f.html downscale-svg-1-ref.html?24
# RUN TESTS WITH HIGH QUALITY DOWNSCALING DISABLED:
# =================================================
default-preferences pref(image.high_quality_downscaling.enabled,false)
# RUN TESTS WITH DOWNSCALE-DURING-DECODE DISABLED:
# ================================================
default-preferences pref(image.downscale-during-decode.enabled,false)
fuzzy-if(winWidget,16,20) fuzzy-if(cocoaWidget,106,31) == downscale-1.html downscale-1-ref.html
@@ -90,10 +90,9 @@ fuzzy(20,999) fails-if(OSX>=1008) != downscale-2e.html?205,53,bottom about:blank
== downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal
== downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal
# RUN TESTS WITH HIGH QUALITY DOWNSCALING ENABLED:
# ================================================
# High-quality downscaling enabled:
default-preferences pref(image.high_quality_downscaling.enabled,true)
# RUN TESTS WITH DOWNSCALE-DURING-DECODE ENABLED:
# ===============================================
default-preferences pref(image.downscale-during-decode.enabled,true)
fuzzy(31,127) fuzzy-if(d2d,31,147) == downscale-1.html downscale-1-ref.html # intermittently 147 pixels on win7 accelerated only (not win8)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -8,8 +8,3 @@
== wrapper.html?invalid-compression-RLE8.ico about:blank
# Invalid compression value
== wrapper.html?invalid-compression.ico about:blank
# Invalid ICO width and heigth should be ignored if the
# contained BMP is correct.
== invalid_ico_height.ico 16x16.png
== invalid_ico_width.ico 16x16.png
+1 -12
View File
@@ -1,14 +1,3 @@
# ICO BMP and PNG mixed tests
== mixed-bmp-png.ico mixed-bmp-png.png
# Using media fragments to select different resolutions
== mixed-bmp-png.ico#-moz-resolution=8,8 mixed-bmp-png.png
== mixed-bmp-png.ico#test=true&-moz-resolution=8,8&other mixed-bmp-png.png
== mixed-bmp-png.ico#-moz-resolution=32,32 mixed-bmp-png32.png
== mixed-bmp-png.ico#-moz-resolution=39,39 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=40,40 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=48,48 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=64,64 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=64 mixed-bmp-png.png # Bad syntax will fall back to lowest resolution
== mixed-bmp-png.ico mixed-bmp-png48.png
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 950 B

+7 -5
View File
@@ -169,7 +169,7 @@ var encodedBytes = streamToArray(istream);
var refName = "image1png16x16.jpg";
var refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1078);
do_check_eq(istream.available(), 1051);
var referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
@@ -228,7 +228,7 @@ encodedBytes = streamToArray(istream);
refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 948);
do_check_eq(istream.available(), 950);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
@@ -691,8 +691,10 @@ var errsrc = "none";
try {
container = imgTools.decodeImage(istream, inMimeType);
// We should never hit this - decodeImage throws an assertion because the
// image decoded doesn't have enough frames.
// We expect to hit an error during encoding because the ICO header of the
// image is fine, but the actual resources are corrupt. Since decodeImage()
// only performs a metadata decode, it doesn't decode far enough to realize
// this, but we'll find out when we do a full decode during encodeImage().
try {
istream = imgTools.encodeImage(container, "image/png");
} catch (e) {
@@ -704,7 +706,7 @@ try {
errsrc = "decode";
}
do_check_eq(errsrc, "decode");
do_check_eq(errsrc, "encode");
checkExpectedError(/NS_ERROR_FAILURE/, err);
+2 -2
View File
@@ -22,8 +22,8 @@
branch.setBoolPref("extensions.blocklist.enabled", false);
// Make url-classifier updates so rare that they won't affect tests
branch.setIntPref("urlclassifier.updateinterval", 172800);
// Disable high-quality downscaling, since it makes reftests more difficult.
branch.setBoolPref("image.high_quality_downscaling.enabled", false);
// Disable downscale-during-decode, since it makes reftests more difficult.
branch.setBoolPref("image.downscale-during-decode.enabled", false);
// Disable the single-color optimization, since it can cause intermittent
// oranges and it causes many of our tests to test a different code path
// than the one that normal images on the web use.
+1 -11
View File
@@ -4297,21 +4297,11 @@ pref("image.cache.timeweight", 500);
pref("image.decode-immediately.enabled", false);
// Whether we attempt to downscale images during decoding.
pref("image.downscale-during-decode.enabled", false);
pref("image.downscale-during-decode.enabled", true);
// The default Accept header sent for images loaded over HTTP(S)
pref("image.http.accept", "image/webp,image/png,image/*;q=0.8,*/*;q=0.5");
pref("image.high_quality_downscaling.enabled", true);
// The minimum percent downscaling we'll use high-quality downscaling on,
// interpreted as a floating-point number / 1000.
pref("image.high_quality_downscaling.min_factor", 335);
// The maximum memory size which we'll use high-quality uspcaling on,
// interpreted as number of decoded bytes.
pref("image.high_quality_upscaling.max_size", 20971520);
// The threshold for inferring that changes to an <img> element's |src|
// attribute by JavaScript represent an animation, in milliseconds. If the |src|
// attribute is changing more frequently than this value, then we enter a
Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 979 B