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