From f1d1e166698f31013fbe05ca1e3570d0fa9287c9 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 18 May 2022 11:52:08 +0800 Subject: [PATCH] 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) --- b2g/app/b2g.js | 2 +- dom/canvas/CanvasRenderingContext2D.cpp | 22 +- dom/canvas/CanvasRenderingContext2D.h | 9 + dom/canvas/WebGLContext.cpp | 24 +- dom/canvas/WebGLContext.h | 7 + .../nsICanvasRenderingContextInternal.h | 7 + dom/html/HTMLCanvasElement.cpp | 15 + dom/html/HTMLCanvasElement.h | 7 + dom/media/CanvasCaptureMediaStream.cpp | 40 +- dom/media/CanvasCaptureMediaStream.h | 5 +- gfx/thebes/gfxPrefs.h | 5 +- image/ClippedImage.cpp | 10 + image/ClippedImage.h | 4 + image/DecodePool.cpp | 24 +- image/Decoder.cpp | 41 +- image/Decoder.h | 80 +- image/DecoderFactory.cpp | 40 +- image/DecoderFactory.h | 41 +- image/DecoderFlags.h | 42 + image/Deinterlacer.cpp | 45 + image/Deinterlacer.h | 50 + image/Downscaler.cpp | 2 + image/Downscaler.h | 1 + image/DynamicImage.cpp | 14 +- image/FrameAnimator.cpp | 6 +- image/FrozenImage.cpp | 8 + image/FrozenImage.h | 4 + image/Image.h | 11 +- image/ImageFactory.cpp | 47 +- image/ImageOps.cpp | 4 +- image/ImageWrapper.cpp | 8 + image/OrientedImage.cpp | 10 + image/OrientedImage.h | 4 + image/RasterImage.cpp | 484 +++------ image/RasterImage.h | 64 +- image/SurfaceCache.cpp | 80 +- image/SurfaceCache.h | 111 +-- image/SurfaceFlags.h | 56 ++ image/VectorImage.cpp | 46 +- image/decoders/nsBMPDecoder.cpp | 14 - image/decoders/nsBMPDecoder.h | 6 +- image/decoders/nsGIFDecoder2.cpp | 165 +++- image/decoders/nsGIFDecoder2.h | 6 +- image/decoders/nsICODecoder.cpp | 918 ++++++++++-------- image/decoders/nsICODecoder.h | 104 +- image/decoders/nsIconDecoder.cpp | 99 +- image/decoders/nsIconDecoder.h | 6 +- image/decoders/nsJPEGDecoder.cpp | 16 +- image/decoders/nsJPEGDecoder.h | 5 - image/decoders/nsPNGDecoder.cpp | 18 +- image/decoders/nsPNGDecoder.h | 4 - image/imgFrame.cpp | 90 +- image/imgFrame.h | 4 +- image/imgIContainer.idl | 17 +- image/imgLoader.cpp | 5 +- image/imgTools.cpp | 36 +- image/moz.build | 2 + image/test/crashtests/crashtests.list | 12 +- .../crashtests/invalid-disposal-method-1.gif | Bin 0 -> 167 bytes .../crashtests/invalid-disposal-method-2.gif | Bin 0 -> 167 bytes .../crashtests/invalid-disposal-method-3.gif | Bin 0 -> 167 bytes .../invalid_ico_height.ico | Bin .../invalid_ico_width.ico | Bin image/test/gtest/TestDecoders.cpp | 3 +- image/test/gtest/TestMetadata.cpp | 6 +- .../test/mochitest/test_has_transparency.html | 5 +- image/test/reftest/downscaling/reftest.list | 17 +- image/test/reftest/ico/cur/pointer.cur | Bin 13942 -> 4286 bytes .../ico/ico-bmp-corrupted/reftest.list | 5 - image/test/reftest/ico/ico-mixed/reftest.list | 13 +- .../reftest/ico/ico-png/transparent-png.ico | Bin 3084 -> 1150 bytes image/test/unit/image1png16x16.jpg | Bin 1078 -> 1051 bytes image/test/unit/image2jpg16x16.png | Bin 948 -> 950 bytes image/test/unit/test_imgtools.js | 12 +- layout/tools/reftest/reftest-preferences.js | 4 +- modules/libpref/init/all.js | 12 +- .../favicons/expected-favicon-big32.jpg.png | Bin 948 -> 950 bytes .../favicons/expected-favicon-big64.png.png | Bin 868 -> 979 bytes 78 files changed, 1660 insertions(+), 1424 deletions(-) create mode 100644 image/DecoderFlags.h create mode 100644 image/Deinterlacer.cpp create mode 100644 image/Deinterlacer.h create mode 100644 image/SurfaceFlags.h create mode 100644 image/test/crashtests/invalid-disposal-method-1.gif create mode 100644 image/test/crashtests/invalid-disposal-method-2.gif create mode 100644 image/test/crashtests/invalid-disposal-method-3.gif rename image/test/{reftest/ico/ico-bmp-corrupted => crashtests}/invalid_ico_height.ico (100%) rename image/test/{reftest/ico/ico-bmp-corrupted => crashtests}/invalid_ico_width.ico (100%) diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 5cad275a66..909d5a43f2 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -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. diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 712a1c2f23..e1cc382ba5 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -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) diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 5aa11746c2..dee05fd671 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -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 mThebesSurface; diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index 2335603ce6..4e3ad24f86 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -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& 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& retval) { diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index 7bd055474a..845d813df2 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -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& retval); @@ -1031,6 +1037,7 @@ protected: WebGLContextOptions mOptions; bool mInvalidated; + bool mCapturedFrameInvalidated; bool mResetLayer; bool mOptionsFrozen; bool mMinCapability; diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h index 801fd2651b..8849756816 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.h +++ b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -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; diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index 2186ece72b..8c226b62bd 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -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 HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha) { diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index b6387c8763..2d90da704b 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -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: diff --git a/dom/media/CanvasCaptureMediaStream.cpp b/dom/media/CanvasCaptureMediaStream.cpp index d8f06b790e..c46134e78a 100644 --- a/dom/media/CanvasCaptureMediaStream.cpp +++ b/dom/media/CanvasCaptureMediaStream.cpp @@ -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 opt = gfxPlatform::GetPlatform() - ->ScreenReferenceDrawTarget()->OptimizeSourceSurface(snapshot); - if (!opt) { + RefPtr data = snapshot->GetDataSurface(); + if (!data) { return NS_ERROR_FAILURE; } + RefPtr 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 image = new layers::CairoImage(); image->SetData(imageData); diff --git a/dom/media/CanvasCaptureMediaStream.h b/dom/media/CanvasCaptureMediaStream.h index bb4aa13ba5..049f2827a8 100644 --- a/dom/media/CanvasCaptureMediaStream.h +++ b/dom/media/CanvasCaptureMediaStream.h @@ -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; diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 9d99f70485..4ce72f80bb 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -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); diff --git a/image/ClippedImage.cpp b/image/ClippedImage.cpp index eeb71263f7..de24f49d34 100644 --- a/image/ClippedImage.cpp +++ b/image/ClippedImage.cpp @@ -220,6 +220,16 @@ ClippedImage::GetFrame(uint32_t aWhichFrame, return GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags); } +NS_IMETHODIMP_(already_AddRefed) +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 ClippedImage::GetFrameInternal(const nsIntSize& aSize, const Maybe& aSVGContext, diff --git a/image/ClippedImage.h b/image/ClippedImage.h index 88f7d476f3..c4dba021f7 100644 --- a/image/ClippedImage.h +++ b/image/ClippedImage.h @@ -37,6 +37,10 @@ public: NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override; NS_IMETHOD_(already_AddRefed) GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + 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) diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp index 209b481bf0..bfd7eb373f 100644 --- a/image/DecodePool.cpp +++ b/image/DecodePool.cpp @@ -47,35 +47,37 @@ public: static void Dispatch(RasterImage* aImage, Progress aProgress, const nsIntRect& aInvalidRect, - uint32_t aFlags) + SurfaceFlags aSurfaceFlags) { MOZ_ASSERT(aImage); nsCOMPtr 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 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; } diff --git a/image/Decoder.cpp b/image/Decoder.cpp index b6199bc62c..21c35044aa 100644 --- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -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 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)); } diff --git a/image/Decoder.h b/image/Decoder.h index 7e3d8d896a..ccd3c4e004 100644 --- a/image/Decoder.h +++ b/image/Decoder.h @@ -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 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; diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 79c2690e34..bad977cb02 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -134,34 +134,30 @@ DecoderFactory::CreateDecoder(DecoderType aType, RasterImage* aImage, SourceBuffer* aSourceBuffer, const Maybe& 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 = GetDecoder(aType, aImage, aIsRedecode); + nsRefPtr 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 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 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())) { diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 2fc55b6fb6..415f9fcc5a 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -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 CreateDecoder(DecoderType aType, RasterImage* aImage, SourceBuffer* aSourceBuffer, const Maybe& 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 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 CreateAnonymousMetadataDecoder(DecoderType aType, diff --git a/image/DecoderFlags.h b/image/DecoderFlags.h new file mode 100644 index 0000000000..c4a4df0e59 --- /dev/null +++ b/image/DecoderFlags.h @@ -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 diff --git a/image/Deinterlacer.cpp b/image/Deinterlacer.cpp new file mode 100644 index 0000000000..96a6512c3a --- /dev/null +++ b/image/Deinterlacer.cpp @@ -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(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 diff --git a/image/Deinterlacer.h b/image/Deinterlacer.h new file mode 100644 index 0000000000..74ffd86ea4 --- /dev/null +++ b/image/Deinterlacer.h @@ -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 mBuffer; +}; + + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_Deinterlacer_h diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp index 132feb9a31..43541e1d8e 100644 --- a/image/Downscaler.cpp +++ b/image/Downscaler.cpp @@ -287,5 +287,7 @@ Downscaler::DownscaleInputLine() } } + + } // namespace image } // namespace mozilla diff --git a/image/Downscaler.h b/image/Downscaler.h index 3f1365bf18..53dea0f0a2 100644 --- a/image/Downscaler.h +++ b/image/Downscaler.h @@ -162,6 +162,7 @@ public: #endif // MOZ_ENABLE_SKIA + } // namespace image } // namespace mozilla diff --git a/image/DynamicImage.cpp b/image/DynamicImage.cpp index 52dcf3316a..87608f41bb 100644 --- a/image/DynamicImage.cpp +++ b/image/DynamicImage.cpp @@ -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) +DynamicImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ RefPtr 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 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); diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index 316e2e1151..a77a9b667c 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -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(); diff --git a/image/FrozenImage.cpp b/image/FrozenImage.cpp index 628e33d74d..cf0dbf72b5 100644 --- a/image/FrozenImage.cpp +++ b/image/FrozenImage.cpp @@ -44,6 +44,14 @@ FrozenImage::GetFrame(uint32_t aWhichFrame, return InnerImage()->GetFrame(FRAME_FIRST, aFlags); } +NS_IMETHODIMP_(already_AddRefed) +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) { diff --git a/image/FrozenImage.h b/image/FrozenImage.h index ee6e4bd631..ff54ce666d 100644 --- a/image/FrozenImage.h +++ b/image/FrozenImage.h @@ -37,6 +37,10 @@ public: NS_IMETHOD GetAnimated(bool* aAnimated) override; NS_IMETHOD_(already_AddRefed) GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + 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) diff --git a/image/Image.h b/image/Image.h index 67ac064f4b..5e5c325e67 100644 --- a/image/Image.h +++ b/image/Image.h @@ -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 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; diff --git a/image/ImageFactory.cpp b/image/ImageFactory.cpp index eabeaf29f3..fd053383b4 100644 --- a/image/ImageFactory.cpp +++ b/image/ImageFactory.cpp @@ -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 static already_AddRefed -BadImage(nsRefPtr& image) +BadImage(const char* aMessage, nsRefPtr& aImage) { - image->SetHasError(); - return image.forget(); + NS_WARNING(aMessage); + aImage->SetHasError(); + return aImage.forget(); } /* static */ already_AddRefed @@ -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(); } diff --git a/image/ImageOps.cpp b/image/ImageOps.cpp index 0a1e460c88..0e7833e258 100644 --- a/image/ImageOps.cpp +++ b/image/ImageOps.cpp @@ -116,7 +116,9 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream, DecoderType decoderType = DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get()); nsRefPtr decoder = - DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aFlags); + DecoderFactory::CreateAnonymousDecoder(decoderType, + sourceBuffer, + ToSurfaceFlags(aFlags)); if (!decoder) { return nullptr; } diff --git a/image/ImageWrapper.cpp b/image/ImageWrapper.cpp index 89878f6fae..02139b7306 100644 --- a/image/ImageWrapper.cpp +++ b/image/ImageWrapper.cpp @@ -174,6 +174,14 @@ ImageWrapper::GetFrame(uint32_t aWhichFrame, return mInnerImage->GetFrame(aWhichFrame, aFlags); } +NS_IMETHODIMP_(already_AddRefed) +ImageWrapper::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags); +} + NS_IMETHODIMP_(bool) ImageWrapper::IsOpaque() { diff --git a/image/OrientedImage.cpp b/image/OrientedImage.cpp index b2d13bee38..5de600b8b3 100644 --- a/image/OrientedImage.cpp +++ b/image/OrientedImage.cpp @@ -122,6 +122,16 @@ OrientedImage::GetFrame(uint32_t aWhichFrame, return target->Snapshot(); } +NS_IMETHODIMP_(already_AddRefed) +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) { diff --git a/image/OrientedImage.h b/image/OrientedImage.h index e1f321fc15..cbfe308d20 100644 --- a/image/OrientedImage.h +++ b/image/OrientedImage.h @@ -34,6 +34,10 @@ public: NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override; NS_IMETHOD_(already_AddRefed) GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + 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) diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index 8417347290..b056ad0302 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -52,6 +52,7 @@ #include "mozilla/Services.h" #include #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 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 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 mImage; - RawAccessFrameRef mSrcRef; - RawAccessFrameRef mDstRef; - const IntSize mDstSize; - uint32_t mImageFlags; - ScaleState mState; -}; - -static nsCOMPtr 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 alternateFlags; + Maybe 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 srcSurf = frameRef->GetSurface(); if (!srcSurf) { - RecoverFromLossOfFrames(mSize, aFlags); + RecoverFromInvalidFrames(mSize, aFlags); return nullptr; } @@ -703,14 +563,28 @@ NS_IMETHODIMP_(already_AddRefed) 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) +RasterImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget(); } Pair> -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()); + } + if (aWhichFrame > FRAME_MAX_VALUE) { return MakePair(DrawResult::BAD_ARGS, RefPtr()); } @@ -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()); @@ -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 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 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()); + return MakePair(drawResult, nsRefPtr()); } CairoImage::Data cairoData; GetWidth(&cairoData.mSize.width); GetHeight(&cairoData.mSize.height); - cairoData.mSourceSurface = result.second(); + cairoData.mSourceSurface = surface; nsRefPtr image = aContainer->CreateImage(ImageFormat::CAIRO_SURFACE); @@ -776,7 +653,7 @@ RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags) static_cast(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 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 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 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; 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(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 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 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; } diff --git a/image/RasterImage.h b/image/RasterImage.h index b2aa41d17d..4aad6e72eb 100644 --- a/image/RasterImage.h +++ b/image/RasterImage.h @@ -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 @@ -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 CopyFrame(uint32_t aWhichFrame, uint32_t aFlags); Pair> - 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(); diff --git a/image/SurfaceCache.cpp b/image/SurfaceCache.cpp index 31e9f6bd21..3a9c49e9cb 100644 --- a/image/SurfaceCache.cpp +++ b/image/SurfaceCache.cpp @@ -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, MatchType> LookupBestMatch(const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags) + const Maybe& aAlternateFlags) { // Try for an exact match first. nsRefPtr exactMatch; @@ -334,13 +328,13 @@ private: struct MatchContext { MatchContext(const SurfaceKey& aIdealKey, - const Maybe& aAlternateFlags) + const Maybe& aAlternateFlags) : mIdealKey(aIdealKey) , mAlternateFlags(aAlternateFlags) { } const SurfaceKey& mIdealKey; - const Maybe mAlternateFlags; + const Maybe mAlternateFlags; nsRefPtr 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 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 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(imageKey)->OnSurfaceDiscarded(); } @@ -640,7 +633,7 @@ public: LookupResult LookupBestMatch(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags) + const Maybe& aAlternateFlags) { nsRefPtr 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, 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& aAlternateFlags /* = Nothing() */) + const Maybe& 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& aAlternateFlags + const Maybe& 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 diff --git a/image/SurfaceCache.h b/image/SurfaceCache.h index 772badee0f..b8f8f8f346 100644 --- a/image/SurfaceCache.h +++ b/image/SurfaceCache.h @@ -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 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& 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&, float); @@ -96,12 +99,12 @@ private: IntSize mSize; Maybe 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& aAlternateFlags = Nothing()); + const Maybe& 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& aAlternateFlags + const Maybe& 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(); diff --git a/image/SurfaceFlags.h b/image/SurfaceFlags.h new file mode 100644 index 0000000000..ac1c5e6646 --- /dev/null +++ b/image/SurfaceFlags.h @@ -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 diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index 15262e5055..c9bcca1706 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -659,19 +659,8 @@ VectorImage::IsOpaque() /* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame, * in uint32_t aFlags; */ NS_IMETHODIMP_(already_AddRefed) -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) +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 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 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 drawable = diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp index c136b246eb..e8674dc047 100644 --- a/image/decoders/nsBMPDecoder.cpp +++ b/image/decoders/nsBMPDecoder.cpp @@ -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) diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h index ad02e73123..f803302e01 100644 --- a/image/decoders/nsBMPDecoder.h +++ b/image/decoders/nsBMPDecoder.h @@ -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 mDownscaler; - uint32_t mLOH; //< Length of the header uint32_t mNumColors; //< The number of used colors, i.e. the number of diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp index 46bb35442e..76b6453ce0 100644 --- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -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 diff --git a/image/decoders/nsGIFDecoder2.h b/image/decoders/nsGIFDecoder2.h index 44aff60b8b..5a54b17599 100644 --- a/image/decoders/nsGIFDecoder2.h +++ b/image/decoders/nsGIFDecoder2.h @@ -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 mDeinterlacer; }; } // namespace image diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index 0531395b59..b35351efc9 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -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(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(aBIH); int32_t headerSize; memcpy(&headerSize, bih, sizeof(headerSize)); NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1); return headerSize; } -void -nsICODecoder::SetHotSpotIfCursor() +LexerTransition +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(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 +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 +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(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 +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(mContainedDecoder.get())->IsValidICO()) { + return Transition::Terminate(ICOState::FAILURE); + } + + return Transition::ContinueUnbuffered(ICOState::READ_PNG); +} + +LexerTransition +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(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(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 bmpDecoder = + static_cast(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 +nsICODecoder::ReadBMP(const char* aData, uint32_t aLen) +{ + if (!WriteToContainedDecoder(aData, aLen)) { + return Transition::Terminate(ICOState::FAILURE); + } + + return Transition::ContinueUnbuffered(ICOState::READ_BMP); +} + +LexerTransition +nsICODecoder::PrepareForMask() +{ + nsRefPtr bmpDecoder = + static_cast(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(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 +nsICODecoder::ReadMaskRow(const char* aData) +{ + mCurrMaskLine--; + + uint8_t sawTransparency = 0; + + // Get the mask row we're reading. + const uint8_t* mask = reinterpret_cast(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(mDownscaler->RowBuffer()); + } else { + nsRefPtr bmpDecoder = + static_cast(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 +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 bmpDecoder = + static_cast(mContainedDecoder.get()); + uint8_t* imageData = reinterpret_cast(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 bmpDecoder = + static_cast(mContainedDecoder.get()); + bmpDecoder->SetHasAlphaData(); + } + + return Transition::To(ICOState::FINISHED_RESOURCE, 0); +} + +LexerTransition +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 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(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(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 - (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(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(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(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 bmpDecoder = - static_cast(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>= 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 diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h index 27bf7e6055..ad7f63b5d7 100644 --- a/image/decoders/nsICODecoder.h +++ b/image/decoders/nsICODecoder.h @@ -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 mContainedDecoder; // Contains either a BMP or PNG resource + LexerTransition ReadHeader(const char* aData); + LexerTransition ReadDirEntry(const char* aData); + LexerTransition SniffResource(const char* aData); + LexerTransition ReadPNG(const char* aData, uint32_t aLen); + LexerTransition ReadBIH(const char* aData); + LexerTransition ReadBMP(const char* aData, uint32_t aLen); + LexerTransition PrepareForMask(); + LexerTransition ReadMaskRow(const char* aData); + LexerTransition FinishMask(); + LexerTransition 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 mLexer; // The lexer. + nsRefPtr mContainedDecoder; // Either a BMP or PNG decoder. + UniquePtr 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 diff --git a/image/decoders/nsIconDecoder.cpp b/image/decoders/nsIconDecoder.cpp index a14a4add05..55666f67dd 100644 --- a/image/decoders/nsIconDecoder.cpp +++ b/image/decoders/nsIconDecoder.cpp @@ -12,15 +12,20 @@ #include "RasterImage.h" #include +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; diff --git a/image/decoders/nsIconDecoder.h b/image/decoders/nsIconDecoder.h index 78bef54671..45aadd19d5 100644 --- a/image/decoders/nsIconDecoder.h +++ b/image/decoders/nsIconDecoder.h @@ -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 { diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp index ed1eb9aa83..61d32c0d53 100644 --- a/image/decoders/nsJPEGDecoder.cpp +++ b/image/decoders/nsJPEGDecoder.cpp @@ -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; } diff --git a/image/decoders/nsJPEGDecoder.h b/image/decoders/nsJPEGDecoder.h index d476b43e32..f916c1c8fb 100644 --- a/image/decoders/nsJPEGDecoder.h +++ b/image/decoders/nsJPEGDecoder.h @@ -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 mDownscaler; - private: friend class DecoderFactory; diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp index 445734ce9b..8fde776a05 100644 --- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -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[]= diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h index edd2ef6cef..1061efa651 100644 --- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -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 mDownscaler; png_infop mInfo; nsIntRect mFrameRect; uint8_t* mCMSLine; diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp index 8e5b31d73e..f45a954a71 100644 --- a/image/imgFrame.cpp +++ b/image/imgFrame.cpp @@ -205,7 +205,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize, static_cast(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 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 buf = - AllocateBufferForImage(mSize, format); - if (!buf) { - return NS_ERROR_OUT_OF_MEMORY; - } - - RefPtr 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 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 { diff --git a/image/imgFrame.h b/image/imgFrame.h index 89724c46f4..e5f62d01aa 100644 --- a/image/imgFrame.h +++ b/image/imgFrame.h @@ -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 { diff --git a/image/imgIContainer.idl b/image/imgIContainer.idl index 6495d86312..53d2a1829a 100644 --- a/image/imgIContainer.idl +++ b/image/imgIContainer.idl @@ -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). */ diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index c926a22c87..206f904a81 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -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"); diff --git a/image/imgTools.cpp b/image/imgTools.cpp index 6a2688b3a7..6cbe950fc3 100644 --- a/image/imgTools.cpp +++ b/image/imgTools.cpp @@ -198,26 +198,27 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer, return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream); } - // Use frame 0 from the image container. - RefPtr 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 frame = + aContainer->GetFrameAtSize(scaledSize, + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_HIGH_QUALITY_SCALING | + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); RefPtr 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)); diff --git a/image/moz.build b/image/moz.build index 605a30eec9..093276e66c 100644 --- a/image/moz.build +++ b/image/moz.build @@ -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', diff --git a/image/test/crashtests/crashtests.list b/image/test/crashtests/crashtests.list index d914a54964..8d9ff40cd7 100644 --- a/image/test/crashtests/crashtests.list +++ b/image/test/crashtests/crashtests.list @@ -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 diff --git a/image/test/crashtests/invalid-disposal-method-1.gif b/image/test/crashtests/invalid-disposal-method-1.gif new file mode 100644 index 0000000000000000000000000000000000000000..30c61de188b9c63ea179c008c5171efad638f853 GIT binary patch literal 167 zcmZ?wbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!o|SAp!iSFxhOTUBsE2$JhLQ2!QIn0 zfI$Z+0o0=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) diff --git a/image/test/reftest/ico/cur/pointer.cur b/image/test/reftest/ico/cur/pointer.cur index d37f2f1cc0534d9aec717a087a13dd4eb5b83a8f..025ebaed1ff12279d4e07cfcaf3bd1cbbeb0884b 100644 GIT binary patch delta 58 zcmeyCvrkcnfq@YS6%;_U0>cUc1_m((1_liv1`07a05M1$3MLdOch$P-^vk|Znuo18kuo18kuo18kuo18kuo18k zuo18k_+KN?eXjF9WC+p=xg9q@!TjmV@G!l8`GTJJKBFg(9})U)#=#G8c0;mrb90o- z6VU%k+E&&!1V!J&ZxDijK&ec(RGL+{+EzJsMw$mcnxb;w=K z!Ji@7#l=M`7K_q{Z)0OT&(U*C(BP@m>vhV{&T1d-Fc|91TrNu=eoTBfF7}##dwZL_KJ>qTN3~jwv=8uS*tFPd{+*p2a{18Y zdueTLjaF7x4txMRuk)6|=Zd0%sj|DfOCBF+V`Jlx5Bgrx>w%w3YE6$s!Ok|Be{XM( z+VP=OD#~g0IQJb1Q5=n7`Ry9~zB@d@hw17K~&+ieLLsUS2kq{W$(= z`M1&RNAbsjy%m34*!}q9#O~#f8@ro7j_jKMZ|#rq&lzU*YrOxAdJpW!b<#e?AKCu> zq@1nh@!we@A&l3T6ldqYZYq6M>5J$0`_C#Z)cgZ2QkcL|@ajI*55?mhN~`k<@A(pH zJf(D>(yVa2JRDjc4lNIdmN?J}H7}(!|4u6jc>uA$(?Sqzvk|Znuo18kh(!dj|B*E= z=vREz>`7Q$GJIA>sw>u+8#4tJg~YR&RucUh~$0~eX>56->-iK zS{q{fo12^NIu+N~9L{B{Ja(XUHMY++4mZPxw{K*931flRX4pRG_}vVvtE;lUg0Vw& zmD9ct*}l%vYX+{7Xa=s0xUR*i*F)GoZ&{CG$V^QI_J63h--;nOGZUC?zLswLUWQ@_ zd2O$K&9IF1hq1tGNPhOi%$a!YhiTh;{lm8HwjYIUZ9gj8+I|@KIotAUeE&fr%k%Jh pZ?`p4L@ZYzT@cZy1jz_aNJvRQ>oFqY`~~MOIB&r@iv%PE`3FlvvXTG* diff --git a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list index 69176e192f..1b4cb8ee2d 100644 --- a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list +++ b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list @@ -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 diff --git a/image/test/reftest/ico/ico-mixed/reftest.list b/image/test/reftest/ico/ico-mixed/reftest.list index ddd837530c..36134e40ab 100644 --- a/image/test/reftest/ico/ico-mixed/reftest.list +++ b/image/test/reftest/ico/ico-mixed/reftest.list @@ -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 diff --git a/image/test/reftest/ico/ico-png/transparent-png.ico b/image/test/reftest/ico/ico-png/transparent-png.ico index df422207e7b9918f582896c7d7889c5103adf8f7..cc8a4a31db973df11dd9c21b56d12eabab61cb62 100644 GIT binary patch literal 1150 zcmaiyxl6=Q5XPTq?-pWXVNeSj1+fu4{{&I-s)Fchxt?I<)e353Ct|&btrx77E+|Ai z)<#%eEo2d6Y(zxH`No|P9xP6nyky?|zL__PF(rJ(VutUInJ6=+(il_E&LNKc#d$G? z_x`iXxN+H2>|{A&ZZV$soSUf%?|!w5P7-zE*NK>|fp-tNQ5_<3J~BuC)%hv4wRVzI zT`S($&_wg2y>zqDW9#@)4d%Td2nhLLXQt<9eQhK3t*vePdd(7gV@};e56rskluYi1 zb)hjhFig;dK?~Zqw;9SGuPKk!y~}*{7CpdGTc4o;&4@;7W#LU~AWwUj0N)M{ z4(aSHt*@aL0@=-O>7%3NS@zKz_-K9z79LO=IZsc|G7rq6rs{#xr=OC6*`sDL(;Da% zmfLoPM-M^pBQwO=iy7vC`pZizl7cyK{S0Sg2F`N0pBD}EVY50v=H>q>@DB67YG57ed!eEJ9j@Ch zX9kBJpqYF8_L+CUsxbqNu#cz~dTMXE5Y6U>9C94x?3MR051DCHqY!iL{P%KWPD+hg PEctVL-+tFTFkpWH3iMVW literal 3084 zcmcJRc{tQv8^?df3}dUw-Xz8h3Jv2)C=oM*vB#h+Nuh--MfPQ8h>;4ZC`)3j*+pV( zMJf{WC@EWJvSw+fY(w68>yP)J_qv|Ho=#FDbbcfp38??9(=RCNRZNVI27KK>8yM+mUYX3bNdbr6r;j zcNKF-yVK^0JEQWZvzPSS7A2m0E}=Y&@TQcbq<252=JB}ai#S?S?5*5Qz701a`}evx z2r`kGd_w9(-l-Vt=j6BS+tq?fAS+OyQ1GL3g2QTFU{f+6T`~?w z63$&t)haR-7%4^vT~gvy*oi3#G`07N$OIf2UZmqyJx1=f<$Pn-HO2o5&Uk8bLDgk2 ziG&*1GHBPLpMGNJjr@^^k&O5*KKjOh<*!IY7iVP7_LdJH>o2l(+6dG9`QP_5H?hH2 zVm&DT2Nxc2fb4HvCPEF?c;?^3<;@>l?m9O6y}-a6b1bf&ZGTcnV$h}l4X_uVW7$EF zZa>s&1Hh|HKp6tP0&wGJbtzg`C(X2$5gqi%rL{$ZqvO;nC)l44J^_ft71Zl=ry=hN zF(ffgi^>Wi;-9Ksyly8B*qHXB)8<7>w+yr2&)dyR7X*@&Nf2!q3iuhTuKO^N$Peb< zrE0pURY2(DlvJfE0Vv_Mu&^a+YVyZBK*0V8S5FiC;P-5go5|YQbZ^!Js1MtvFhaoB z4{tOD_oaAcjj&zfSG`Uh_ekML7|DB9e^i_Zor5e2Ry}M|D{&Vv&&;!A8E-}o=EwZy z*!jSpXzpwjMkdh(dL3yI5`Iq3Ud{{fh1p^VR`(E z_76?a!)NvPCo!v@tva;yq&ABkR!-RWw|Lr(JR!J`>|tyX*ryidefR0(M`hE{@W~f{ z+h!^e@MhoO3a2%M&kl~u`r9?~`J;3gL<4%XEO-n7EHMaV1b zqBotXgEy#|{i&Uq{IY@ITNpFao(VBMkd3B+??HWozxO*K3vl^>qVU}P79$zWmuu91Ok|db%ajZF> zYK$cL>cI-r_IFF{K3~!NZOI#p-;J7{+mZhlcE{Dl#-l>J=pwz@p-MHVC8Etsd>|{F zauoMjaD0VIy|?QT76fk_rh6F(EV)$m2G67dpQ6$qA68MtR1f2B)f_>1%zGS~iwlid zP`+Qif}H0^T0Idg8<4|Z?e-Y5x-=vL?oXiSQheM>fUl5d_`Pjd>Mp(tGxy_sG0f5n z%x@B3*q5?ozwHAFYnG^xV`GXX1XWVp9==WjVk;P<@}HxwkMVD7#(9^&BnxB6?bfRa z8$h=e^PO1G#(LR$Sj^*RA&m&0Du!kBQu8)z`MSMb+b29q6*~XaUQFBzOEJxCB^lydd(T?UU3`B5qkx>(tZb{V^2ag zr3M?v4#>zW+tQ`%@~-VwNlc&3diri-yfltA_!F(-YGU~{xnFV z!A(V}+dw|3YvSPKQn$ib5=F-rD7N?q?s>m7fcee+3DA1tzsWs+)gSI#oO^0$7*Qqg zV&uu8+km1T7(3A!jtvx$04c&KIFe|lD3n}pOxH4fep=q~)X%BAW^?yUTrnnTUp6+^ zyQShO4w+I;po8TDzTvkJ z<)ZorZmjX$(6`5(`Oqg%#O^-f;ZzB~UKn&MB3fws@)bxtD5EnDBygtko2ug;jg20z+-CsF!g(AUy2hq}fPWhqF zvV7h|>!n>-DYM9?-h(0h!J=K$8=~A5LjROBaJc&Z?3FV9@FI-NE7jzcdb_Q!LhMxL zP3S~C$>~fzB+m!^&Nd`>58nvVR_1Y5ge&muBm|!A`e3#eEIO0#uO=n!3cTlfFAwC} z8YOES`P41mP9-(yd)3(@$8EW((>Gef(R>_hqsSTYowqe}!h7f9NcVjdpgud@3N&|r z3+N8*oxP{X>LLE@Fjf9)2$R%z%l4z82b88%JwhC|qswA+Uwht}AcOp;w>`Bnh9X06 z1D>djaYsARZ1|+?RR6^LEn4Go8tI0%VOv==rE5x^tHo(*=9V@iKr5TN#Z}SO9(wLM z%X6FTH&Lz~WlLZ#rCuzXKD8ETptRbmu!|idK~4ljGwk-%c$UMau?*JGf>I0t2#<9b ze=74a^dztzzzu^|`~BF##x?J&brTswz9Nc*#U8g#Hm<|9#BdMW4)%yR>I+$O>~XB^UH zl&jXZ)4S4L*SODD5&6dG#F5Pw2t)ZtIP`MtyEZrON&^+PV--@5>b5mK)Oqnba~DP~ zDW#eVZUo5Z9#1-32~k!Q7FN`PV8`&c`-XH7`Nsa*Nr-FZ3p)27ZiYOztSH%;fPAUO zAHUrDUMKX!0;Q;VFJI$r+(E~zbj*rfGx-K{sV^&C&Y_ zQj}@M^1p+b*}sKz$+e_ovU&elfoKiiQ+TZQ3%F9i{O{nmVs1sr?y~<{57m4r*&X)e=>Px# delta 465 zcmV;?0WSWV2(}2YZ~}i{fIkC%4g5ROv>j{3{twf2oqt)hwVL8BbVYp>GMJI1ju;B< zBYEYyBPZqxE9AIujWL+);pKF&%5dkGzZLJOtMc2LN-@4Y9&J6M2y$umV8w77XdTKS|;qF24@X1(?RHRT0B!7KLL_t(IjYZRaESv`b$MHwHgig#dMQ4m*QjIR!YPV+VqBA7K zOr@Px*`}Ql4SJVzA=u(=8Kn!Q6BWH#6R*`(=Z1N?3){uG3Z)#^bJiQtjAc@qx##)r zkK4=d-(T|m?;{|AB!OtP^6w^%D>U`$Xc`!!u4j~Q`g9nUR)7BcMXQJ;NdO3vB(Y=t z3jo9FAIO_DeAF<4;?^%T=uMPs=SXkXl9*G@SCy4KURkt7B#tH{;&$-G=mbNRdcI+VcLd>f*58%Y8|+Sz38S{mPF9HXK5 zB0Y6k+$nvJ&wpB4$s3*H{OzB}*DaB*>LD^NjDhy!tWQL8udf7g=PAO@_B#6FNR|uy zSuP5~sPv`xV^6Bel@xS#b7^=Q+2{h3W1m>M~~Y z{CFsHV@ct~vciYOlDDX>P?CE6HqkYAc{U;k;ZO?4WGbTMRSdO+BN}5F?>dcOR%J4y z@M0;)gSk&V>3G|Pl5^pFQdUoV?Ene&S_02iAo!iaF))$Pm;|P~4r4WkF?sU@!bD*r zQ<)xEa({j34|Sr^-;ScB_eoS-;cV_zWEIzmNc|E!_Xq@s5K5J2*y;~pQ~wGxKU_c< zt;}WO{EJv}yqG%e%w*Iq?xns;PgW>H$}lv=aU{jXBG?7t=omyteFBUG@p$A7rbep} zOxk`1V_w1{_r#R#hB?)liFjwKUvnfU*pox9et!tUUUm!K$YO$-Z4Kn<*g@9jlbO_Y zAc)&r+&LS;_{rz6D7`Vs+*nq4qE7N6C3qii`b2QlBZ!LV{Ve4BVs3PRwJ7wO1lCp_ zAOM($e?%3qo9SqGj2C>EPj%zBJbxY*9OOan5e)eum~;J@&9-A}+!v$nFuH-OfP`SR zihls;>uafdeivOX0)syGOdNONeyl6TWOqzyFR&=LXT5a~hP&SM-b`nE>ra~nNfZ(P ziOjUM(0E`!H=G=3c6x>uS9@*+*wb@jH`??)^nVe;U|T6qHaBdbMA4>E6h%bQ7I|W^ zp!>Rx?v!NOqrz#AkDy!eA;Zm8nCJBWlNlCmr2hsR7{(cPY4g_r0000 Ch09w2 delta 926 zcmV;P17ZBO2eb!}B!7EJL_t(IjYZRKOw>2Z5rLN+6EM|F;)t zzW+VE`Ur?1iXs3aFacNwNBBW!B)6)T?DA^zf34^KqsL6o#D8Y8PdfsLAWkBRQ*{45 zjN(!i!58%;Yg)+fe#rTTK4OcE_(y-i$&(q34D~;~c{U(UFxXPX_2L57Mw}+>yBnmH zYDp>8aoIdTX7gQ?W%Wd!%An+eFYOh+Jm@TfsS!X#VCo6YXF|{?$jJLDiqg!(XfynY z&%Hv@4HE~-YJWLWZRV3JLu2tf+^h28exDIxxcesN&%Nl1b)-Am35!Zf z)1gJ&$_OIq_)+%#s70Prg>y(c{yBPr&#EcRT*uJOb$|4jE+O2hOv9YKl&`6lgl4*T4@-g@7L^q3 zW?R%NUw~8m+B2H3Og(cCk&xW2w&~NMwczDneWc2?aOFSa-}cVnu-4H+}7>p@o*mk z;xu=vbzFPfjyjnG?Lks3hn(nANa=`O#-E9+=}7RRGuD|_r4>WJEvCQjU21jbK%7KC z1b-$cXt@mq5Y5UNTjxc-7an}5!cA!`D7MiPvnHU>-hH1v3 zI6h8WzM5}5-MMBzm!kQvQX;XY!qW!5%$8e`j%ZU?q0diceDuL{O#f3BMVOvuY~U_+ z-(IF7Y(Ir^8HN5b%8!Onqdv!AZ!1hcmlp`|ANF3#ZPy`Xs{jB107*qoM6N<$g0t+$ Apa1{> diff --git a/image/test/unit/test_imgtools.js b/image/test/unit/test_imgtools.js index e9af09322d..64361adfee 100644 --- a/image/test/unit/test_imgtools.js +++ b/image/test/unit/test_imgtools.js @@ -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); diff --git a/layout/tools/reftest/reftest-preferences.js b/layout/tools/reftest/reftest-preferences.js index 576d147675..13058a07a5 100644 --- a/layout/tools/reftest/reftest-preferences.js +++ b/layout/tools/reftest/reftest-preferences.js @@ -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. diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 014a185b6b..0f70034197 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -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 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 diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png index c3a4aee616675d212178f70a97d8ce73fc1813e2..5722223c26fe0024d8c4d18f198114d5fde708bb 100644 GIT binary patch delta 928 zcmV;R17G~K2et>0B!7KLL_t(IjYZRaESv`b$MHwHgig#dMQ4m*QjIR!YPV+VqBA7K zOr@Px*`}Ql4SJVzA=u(=8Kn!Q6BWH#6R*`(=Z1N?3){uG3Z)#^bJiQtjAc@qx##)r zkK4=d-(T|m?;{|AB!OtP^6w^%D>U`$Xc`!!u4j~Q`g9nUR)7BcMXQJ;NdO3vB(Y=t z3jo9FAIO_DeAF<4;?^%T=uMPs=SXkXl9*G@SCy4KURkt7B#tH{;&$-G=mbNRdcI+VcLd>f*58%Y8|+Sz38S{mPF9HXK5 zB0Y6k+$nvJ&wpB4$s3*H{OzB}*DaB*>LD^NjDhy!tWQL8udf7g=PAO@_B#6FNR|uy zSuP5~sPv`xV^6Bel@xS#b7^=Q+2{h3W1m>M~~Y z{CFsHV@ct~vciYOlDDX>P?CE6HqkYAc{U;k;ZO?4WGbTMRSdO+BN}5F?>dcOR%J4y z@M0;)gSk&V>3G|Pl5^pFQdUoV?Ene&S_02iAo!iaF))$Pm;|P~4r4WkF?sU@!bD*r zQ<)xEa({j34|Sr^-;ScB_eoS-;cV_zWEIzmNc|E!_Xq@s5K5J2*y;~pQ~wGxKU_c< zt;}WO{EJv}yqG%e%w*Iq?xns;PgW>H$}lv=aU{jXBG?7t=omyteFBUG@p$A7rbep} zOxk`1V_w1{_r#R#hB?)liFjwKUvnfU*pox9et!tUUUm!K$YO$-Z4Kn<*g@9jlbO_Y zAc)&r+&LS;_{rz6D7`Vs+*nq4qE7N6C3qii`b2QlBZ!LV{Ve4BVs3PRwJ7wO1lCp_ zAOM($e?%3qo9SqGj2C>EPj%zBJbxY*9OOan5e)eum~;J@&9-A}+!v$nFuH-OfP`SR zihls;>uafdeivOX0)syGOdNONeyl6TWOqzyFR&=LXT5a~hP&SM-b`nE>ra~nNfZ(P ziOjUM(0E`!H=G=3c6x>uS9@*+*wb@jH`??)^nVe;U|T6qHaBdbMA4>E6h%bQ7I|W^ zp!>Rx?v!NOqrz#AkDy!eA;Zm8nCJBWlNlCmr2hsR7{(cPY4g_r0000 Ch09w2 delta 926 zcmV;P17ZBO2eb!}B!7EJL_t(IjYZRKOw>2Z5rLN+6EM|F;)t zzW+VE`Ur?1iXs3aFacNwNBBW!B)6)T?DA^zf34^KqsL6o#D8Y8PdfsLAWkBRQ*{45 zjN(!i!58%;Yg)+fe#rTTK4OcE_(y-i$&(q34D~;~c{U(UFxXPX_2L57Mw}+>yBnmH zYDp>8aoIdTX7gQ?W%Wd!%An+eFYOh+Jm@TfsS!X#VCo6YXF|{?$jJLDiqg!(XfynY z&%Hv@4HE~-YJWLWZRV3JLu2tf+^h28exDIxxcesN&%Nl1b)-Am35!Zf z)1gJ&$_OIq_)+%#s70Prg>y(c{yBPr&#EcRT*uJOb$|4jE+O2hOv9YKl&`6lgl4*T4@-g@7L^q3 zW?R%NUw~8m+B2H3Og(cCk&xW2w&~NMwczDneWc2?aOFSa-}cVnu-4H+}7>p@o*mk z;xu=vbzFPfjyjnG?Lks3hn(nANa=`O#-E9+=}7RRGuD|_r4>WJEvCQjU21jbK%7KC z1b-$cXt@mq5Y5UNTjxc-7an}5!cA!`D7MiPvnHU>-hH1v3 zI6h8WzM5}5-MMBzm!kQvQX;XY!qW!5%$8e`j%ZU?q0diceDuL{O#f3BMVOvuY~U_+ z-(IF7Y(Ir^8HN5b%8!Onqdv!AZ!1hcmlp`|ANF3#ZPy`Xs{jB107*qoM6N<$g0t+$ Apa1{> diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png b/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png index 03a01c6de84a633c5a4a73a9b0187d92f4ba9125..238973189040030ed5071fb3f9ed8c141a802211 100644 GIT binary patch delta 957 zcmV;u148`d2Ga+SB!8MoL_t(Ijct>EOwJmI1V^Q0v@d&lWZtMLqkxI zKw)0BNHAcx108b5uMk8)GDWlkq)eEN;mAn@K~N|lxs#A76ne^!1Ood3EcmAa?)!b8 z{&2EcJ%7H>-p`&to`;BtKqS#>wbryEosUb*g~ZUpwvrdpntzW{*!j1K&Uid_QAk$w ze^U5myQ}q=Yocm~77SOW@%hj5sJt@={TFu-k#>4!Q{dh*A}5FcKqfy6jz16;QADu~ zV-c;hIM>;Q(#95{X7pF#h_eoOQLGVp!PjrNQ=fSTaAeRF&eCvy$NE{xvuM zJnkjT=c$(GGUez=aOIO_?{Q({mpE|UjEoy*PP=xOg9=)BtFh8D(zXT*EnbV-xVY!2 zE8TDN)G?pM@eGAHmhLW&89jI;Rn5O=yo}RDsfeuq9)Drwt|Z zW$wMSO~C9c+>DHlj^)vY%Jdx!l+)r`i=0G`)#_dGHiF1gl>VfvW34Z%ZK|XsS zfA&pmjDIxp{br4b*-d==gu?)JZ*%_Yw`@0ln5VXU6Kux~o&-Zu5`yxtAS$mO2hY@D ztG)u(%IzTA5R_)V#psX;7+=NL&u(m|dM{b=RH_n_@48!tUSG|lJBS}7twwix0PYzz zm?-c@l6Ds)Rxg5VUgJyEDI$98zyn)QTOL(Lhkxklfd1|F%TtGy;)FpZPU-A17;Dd! z&)Xu)-y6P)mq4@@Bnf0=cqqo2Y?=FgdH%sZ%bpOI2qGfMMdznF9wsKj6yW) z+7ICAnKme`9311Nlw9l}y?Dw|+Ec2Mb`|~?=MlMsEaG{@vf&MHN_Jaas_f0VlYj3vEh+urn;pRkUON>d zG&|3GV?VHeC`Q%`o#;}k0{;me@;vMoF=muG zxg6GVHS9t}EFvPtgn#+puoG2rGF@xuQfm=iZ#PnTEW*y75lqECDgtM4cMNiPe?G}z zIu^ZblCzDgK1}#BZ^EkLz*_eKlHWhT^qLKl1^N|_lT+&D>Yxs; z)e(L3?u*j*kbOMS{8w{mgNdFsTHMdov&A1`>%JaJ{2@wxU!Yr7g2tN9yREyq^1g{{ zb6R>2=6{Yztw_~OO$MssAnLX-wOLRV2QiP6S;bC5aT09bju>+hZ4TiBg(Qy}SQvD% zd7btZ5s}o9O{v5^HdMt)VwaieN-LcXJqNSXFfYwRbUBDNhfk}r`6Hqwc_a&HLPXYy zh)B0aew-|~qBhuwd#uDgOPOtOlDGOXT#gnN8GlL$xL1&B zg5!)!!@1%N#>&!B8*RirX{cU1iI?sr(XSK64Vvoqab)_X#ek!MiD6~Hyb{AHQhM&vfVVg#bM|YktamN`fu}} X%mBPK59a(S00000NkvXXu0mjfquQ!&