mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-30 12:27:29 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
291
image/AnimationSurfaceProvider.cpp
Normal file
291
image/AnimationSurfaceProvider.cpp
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/* -*- 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 "AnimationSurfaceProvider.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
#include "nsProxyRelease.h"
|
||||
|
||||
#include "Decoder.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
NotNull<Decoder*> aDecoder)
|
||||
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
|
||||
AvailabilityState::StartAsPlaceholder())
|
||||
, mImage(aImage.get())
|
||||
, mDecodingMutex("AnimationSurfaceProvider::mDecoder")
|
||||
, mDecoder(aDecoder.get())
|
||||
, mFramesMutex("AnimationSurfaceProvider::mFrames")
|
||||
{
|
||||
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
|
||||
"Use MetadataDecodingTask for metadata decodes");
|
||||
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
|
||||
"Use DecodedSurfaceProvider for single-frame image decodes");
|
||||
}
|
||||
|
||||
AnimationSurfaceProvider::~AnimationSurfaceProvider()
|
||||
{
|
||||
DropImageReference();
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::DropImageReference()
|
||||
{
|
||||
if (!mImage) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
// RasterImage objects need to be destroyed on the main thread. We also need
|
||||
// to destroy them asynchronously, because if our surface cache entry is
|
||||
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
|
||||
// destructor may call into the surface cache while whatever code caused us to
|
||||
// get evicted is holding the surface cache lock, causing deadlock.
|
||||
RefPtr<RasterImage> image = mImage;
|
||||
mImage = nullptr;
|
||||
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
|
||||
}
|
||||
|
||||
DrawableFrameRef
|
||||
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
if (mFrames.IsEmpty()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
// If we don't have that frame, return an empty frame ref.
|
||||
if (aFrame >= mFrames.Length()) {
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
// We've got the requested frame. Return it.
|
||||
MOZ_ASSERT(mFrames[aFrame]);
|
||||
return mFrames[aFrame]->DrawableRef();
|
||||
}
|
||||
|
||||
bool
|
||||
AnimationSurfaceProvider::IsFinished() const
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFrames.IsEmpty()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
|
||||
return false;
|
||||
}
|
||||
|
||||
// As long as we have at least one finished frame, we're finished.
|
||||
return mFrames[0]->IsFinished();
|
||||
}
|
||||
|
||||
size_t
|
||||
AnimationSurfaceProvider::LogicalSizeInBytes() const
|
||||
{
|
||||
// When decoding animated images, we need at most three live surfaces: the
|
||||
// composited surface, the previous composited surface for
|
||||
// DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding
|
||||
// into. The composited surfaces are always BGRA. Although the surface we're
|
||||
// decoding into may be paletted, and may be smaller than the real size of the
|
||||
// image, we assume the worst case here.
|
||||
// XXX(seth): Note that this is actually not accurate yet; we're storing the
|
||||
// full sequence of frames, not just the three live surfaces mentioned above.
|
||||
// Unfortunately there's no way to know in advance how many frames an
|
||||
// animation has, so we really can't do better here. This will become correct
|
||||
// once bug 1289954 is complete.
|
||||
IntSize size = GetSurfaceKey().Size();
|
||||
return 3 * size.width * size.height * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
||||
size_t& aHeapSizeOut,
|
||||
size_t& aNonHeapSizeOut)
|
||||
{
|
||||
// Note that the surface cache lock is already held here, and then we acquire
|
||||
// mFramesMutex. For this method, this ordering is unavoidable, which means
|
||||
// that we must be careful to always use the same ordering elsewhere.
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
for (const RawAccessFrameRef& frame : mFrames) {
|
||||
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::Run()
|
||||
{
|
||||
MutexAutoLock lock(mDecodingMutex);
|
||||
|
||||
if (!mDecoder || !mImage) {
|
||||
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// Run the decoder.
|
||||
LexerResult result = mDecoder->Decode(WrapNotNull(this));
|
||||
|
||||
if (result.is<TerminalState>()) {
|
||||
// We may have a new frame now, but it's not guaranteed - a decoding
|
||||
// failure or truncated data may mean that no new frame got produced.
|
||||
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
|
||||
// we call CheckForNewFrameAtTerminalState(), which handles both of these
|
||||
// possibilities.
|
||||
CheckForNewFrameAtTerminalState();
|
||||
|
||||
// We're done!
|
||||
FinishDecoding();
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify for the progress we've made so far.
|
||||
if (mDecoder->HasProgress()) {
|
||||
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
}
|
||||
|
||||
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
|
||||
// We can't make any more progress right now. The decoder itself will ensure
|
||||
// that we get reenqueued when more data is available; just return for now.
|
||||
return;
|
||||
}
|
||||
|
||||
// There's new output available - a new frame! Grab it.
|
||||
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
|
||||
CheckForNewFrameAtYield();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
bool justGotFirstFrame = false;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
// Try to get the new frame from the decoder.
|
||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||
if (!frame) {
|
||||
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
|
||||
return;
|
||||
}
|
||||
|
||||
// We should've gotten a different frame than last time.
|
||||
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
|
||||
mFrames.LastElement().get() != frame.get());
|
||||
|
||||
// Append the new frame to the list.
|
||||
mFrames.AppendElement(Move(frame));
|
||||
|
||||
if (mFrames.Length() == 1) {
|
||||
justGotFirstFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (justGotFirstFrame) {
|
||||
AnnounceSurfaceAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
bool justGotFirstFrame = false;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
|
||||
return; // We already have this one.
|
||||
}
|
||||
|
||||
// Append the new frame to the list.
|
||||
mFrames.AppendElement(Move(frame));
|
||||
|
||||
if (mFrames.Length() == 1) {
|
||||
justGotFirstFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (justGotFirstFrame) {
|
||||
AnnounceSurfaceAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::AnnounceSurfaceAvailable()
|
||||
{
|
||||
mFramesMutex.AssertNotCurrentThreadOwns();
|
||||
MOZ_ASSERT(mImage);
|
||||
|
||||
// We just got the first frame; let the surface cache know. We deliberately do
|
||||
// this outside of mFramesMutex to avoid a potential deadlock with
|
||||
// AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex
|
||||
// and then the surface cache lock, while the memory reporting code would
|
||||
// acquire the surface cache lock and then mFramesMutex.
|
||||
SurfaceCache::SurfaceAvailable(WrapNotNull(this));
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::FinishDecoding()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mImage);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
// Send notifications.
|
||||
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
|
||||
// Destroy our decoder; we don't need it anymore.
|
||||
mDecoder = nullptr;
|
||||
|
||||
// We don't need a reference to our image anymore, either, and we don't want
|
||||
// one. We may be stored in the surface cache for a long time after decoding
|
||||
// finishes. If we don't drop our reference to the image, we'll end up
|
||||
// keeping it alive as long as we remain in the surface cache, which could
|
||||
// greatly extend the image's lifetime - in fact, if the image isn't
|
||||
// discardable, it'd result in a leak!
|
||||
DropImageReference();
|
||||
}
|
||||
|
||||
bool
|
||||
AnimationSurfaceProvider::ShouldPreferSyncRun() const
|
||||
{
|
||||
MutexAutoLock lock(mDecodingMutex);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
104
image/AnimationSurfaceProvider.h
Normal file
104
image/AnimationSurfaceProvider.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider for animated images.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_AnimationSurfaceProvider_h
|
||||
#define mozilla_image_AnimationSurfaceProvider_h
|
||||
|
||||
#include "FrameAnimator.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "ISurfaceProvider.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider that manages the decoding of animated images and
|
||||
* dynamically generates surfaces for the current playback state of the
|
||||
* animation.
|
||||
*/
|
||||
class AnimationSurfaceProvider final
|
||||
: public ISurfaceProvider
|
||||
, public IDecodingTask
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override)
|
||||
|
||||
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
NotNull<Decoder*> aDecoder);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ISurfaceProvider implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
// We use the ISurfaceProvider constructor of DrawableSurface to indicate that
|
||||
// our surfaces are computed lazily.
|
||||
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
|
||||
|
||||
bool IsFinished() const override;
|
||||
size_t LogicalSizeInBytes() const override;
|
||||
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
||||
size_t& aHeapSizeOut,
|
||||
size_t& aNonHeapSizeOut) override;
|
||||
|
||||
protected:
|
||||
DrawableFrameRef DrawableRef(size_t aFrame) override;
|
||||
|
||||
// Animation frames are always locked. This is because we only want to release
|
||||
// their memory atomically (due to the surface cache discarding them). If they
|
||||
// were unlocked, the OS could end up releasing the memory of random frames
|
||||
// from the middle of the animation, which is not worth the complexity of
|
||||
// dealing with.
|
||||
bool IsLocked() const override { return true; }
|
||||
void SetLocked(bool) override { }
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// IDecodingTask implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
void Run() override;
|
||||
bool ShouldPreferSyncRun() const override;
|
||||
|
||||
// Full decodes are low priority compared to metadata decodes because they
|
||||
// don't block layout or page load.
|
||||
TaskPriority Priority() const override { return TaskPriority::eLow; }
|
||||
|
||||
private:
|
||||
virtual ~AnimationSurfaceProvider();
|
||||
|
||||
void DropImageReference();
|
||||
void CheckForNewFrameAtYield();
|
||||
void CheckForNewFrameAtTerminalState();
|
||||
void AnnounceSurfaceAvailable();
|
||||
void FinishDecoding();
|
||||
|
||||
/// The image associated with our decoder.
|
||||
RefPtr<RasterImage> mImage;
|
||||
|
||||
/// A mutex to protect mDecoder. Always taken before mFramesMutex.
|
||||
mutable Mutex mDecodingMutex;
|
||||
|
||||
/// The decoder used to decode this animation.
|
||||
RefPtr<Decoder> mDecoder;
|
||||
|
||||
/// A mutex to protect mFrames. Always taken after mDecodingMutex.
|
||||
mutable Mutex mFramesMutex;
|
||||
|
||||
/// The frames of this animation, in order.
|
||||
nsTArray<RawAccessFrameRef> mFrames;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_AnimationSurfaceProvider_h
|
||||
38
image/BMPHeaders.h
Normal file
38
image/BMPHeaders.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* 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_BMPHeaders_h
|
||||
#define mozilla_image_BMPHeaders_h
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
namespace bmp {
|
||||
|
||||
// The length of the file header as defined in the BMP spec.
|
||||
static const size_t FILE_HEADER_LENGTH = 14;
|
||||
|
||||
// This lengths of the info header for the different BMP versions.
|
||||
struct InfoHeaderLength {
|
||||
enum {
|
||||
WIN_V2 = 12,
|
||||
WIN_V3 = 40,
|
||||
WIN_V4 = 108,
|
||||
WIN_V5 = 124,
|
||||
|
||||
// OS2_V1 is omitted; it's the same as WIN_V2.
|
||||
OS2_V2_MIN = 16, // Minimum allowed value for OS2v2.
|
||||
OS2_V2_MAX = 64, // Maximum allowed value for OS2v2.
|
||||
|
||||
WIN_ICO = WIN_V3,
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace bmp
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_BMPHeaders_h
|
||||
565
image/ClippedImage.cpp
Normal file
565
image/ClippedImage.cpp
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
/* -*- 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 "ClippedImage.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <new> // Workaround for bug in VS10; see bug 981264.
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "gfxDrawable.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/Pair.h"
|
||||
#include "mozilla/Tuple.h"
|
||||
|
||||
#include "ImageRegion.h"
|
||||
#include "Orientation.h"
|
||||
#include "SVGImageContext.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
using layers::LayerManager;
|
||||
using layers::ImageContainer;
|
||||
using std::make_pair;
|
||||
using std::max;
|
||||
using std::modf;
|
||||
using std::pair;
|
||||
|
||||
namespace image {
|
||||
|
||||
class ClippedImageCachedSurface
|
||||
{
|
||||
public:
|
||||
ClippedImageCachedSurface(already_AddRefed<SourceSurface> aSurface,
|
||||
const nsIntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
float aFrame,
|
||||
uint32_t aFlags,
|
||||
DrawResult aDrawResult)
|
||||
: mSurface(aSurface)
|
||||
, mSize(aSize)
|
||||
, mSVGContext(aSVGContext)
|
||||
, mFrame(aFrame)
|
||||
, mFlags(aFlags)
|
||||
, mDrawResult(aDrawResult)
|
||||
{
|
||||
MOZ_ASSERT(mSurface, "Must have a valid surface");
|
||||
}
|
||||
|
||||
bool Matches(const nsIntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
float aFrame,
|
||||
uint32_t aFlags) const
|
||||
{
|
||||
return mSize == aSize &&
|
||||
mSVGContext == aSVGContext &&
|
||||
mFrame == aFrame &&
|
||||
mFlags == aFlags;
|
||||
}
|
||||
|
||||
already_AddRefed<SourceSurface> Surface() const
|
||||
{
|
||||
RefPtr<SourceSurface> surf(mSurface);
|
||||
return surf.forget();
|
||||
}
|
||||
|
||||
DrawResult GetDrawResult() const
|
||||
{
|
||||
return mDrawResult;
|
||||
}
|
||||
|
||||
bool NeedsRedraw() const
|
||||
{
|
||||
return mDrawResult != DrawResult::SUCCESS &&
|
||||
mDrawResult != DrawResult::BAD_IMAGE;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<SourceSurface> mSurface;
|
||||
const nsIntSize mSize;
|
||||
Maybe<SVGImageContext> mSVGContext;
|
||||
const float mFrame;
|
||||
const uint32_t mFlags;
|
||||
const DrawResult mDrawResult;
|
||||
};
|
||||
|
||||
class DrawSingleTileCallback : public gfxDrawingCallback
|
||||
{
|
||||
public:
|
||||
DrawSingleTileCallback(ClippedImage* aImage,
|
||||
const nsIntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
: mImage(aImage)
|
||||
, mSize(aSize)
|
||||
, mSVGContext(aSVGContext)
|
||||
, mWhichFrame(aWhichFrame)
|
||||
, mFlags(aFlags)
|
||||
, mDrawResult(DrawResult::NOT_READY)
|
||||
{
|
||||
MOZ_ASSERT(mImage, "Must have an image to clip");
|
||||
}
|
||||
|
||||
virtual bool operator()(gfxContext* aContext,
|
||||
const gfxRect& aFillRect,
|
||||
const SamplingFilter aSamplingFilter,
|
||||
const gfxMatrix& aTransform)
|
||||
{
|
||||
MOZ_ASSERT(aTransform.IsIdentity(),
|
||||
"Caller is probably CreateSamplingRestrictedDrawable, "
|
||||
"which should not happen");
|
||||
|
||||
// Draw the image. |gfxCallbackDrawable| always calls this function with
|
||||
// arguments that guarantee we never tile.
|
||||
mDrawResult =
|
||||
mImage->DrawSingleTile(aContext, mSize, ImageRegion::Create(aFillRect),
|
||||
mWhichFrame, aSamplingFilter, mSVGContext, mFlags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DrawResult GetDrawResult() { return mDrawResult; }
|
||||
|
||||
private:
|
||||
RefPtr<ClippedImage> mImage;
|
||||
const nsIntSize mSize;
|
||||
const Maybe<SVGImageContext>& mSVGContext;
|
||||
const uint32_t mWhichFrame;
|
||||
const uint32_t mFlags;
|
||||
DrawResult mDrawResult;
|
||||
};
|
||||
|
||||
ClippedImage::ClippedImage(Image* aImage,
|
||||
nsIntRect aClip,
|
||||
const Maybe<nsSize>& aSVGViewportSize)
|
||||
: ImageWrapper(aImage)
|
||||
, mClip(aClip)
|
||||
{
|
||||
MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
|
||||
MOZ_ASSERT_IF(aSVGViewportSize,
|
||||
aImage->GetType() == imgIContainer::TYPE_VECTOR);
|
||||
if (aSVGViewportSize) {
|
||||
mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels(
|
||||
nsPresContext::AppUnitsPerCSSPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
ClippedImage::~ClippedImage()
|
||||
{ }
|
||||
|
||||
bool
|
||||
ClippedImage::ShouldClip()
|
||||
{
|
||||
// We need to evaluate the clipping region against the image's width and
|
||||
// height once they're available to determine if it's valid and whether we
|
||||
// actually need to do any work. We may fail if the image's width and height
|
||||
// aren't available yet, in which case we'll try again later.
|
||||
if (mShouldClip.isNothing()) {
|
||||
int32_t width, height;
|
||||
RefPtr<ProgressTracker> progressTracker =
|
||||
InnerImage()->GetProgressTracker();
|
||||
if (InnerImage()->HasError()) {
|
||||
// If there's a problem with the inner image we'll let it handle
|
||||
// everything.
|
||||
mShouldClip.emplace(false);
|
||||
} else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
|
||||
// Clamp the clipping region to the size of the SVG viewport.
|
||||
nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize);
|
||||
|
||||
mClip = mClip.Intersect(svgViewportRect);
|
||||
|
||||
// If the clipping region is the same size as the SVG viewport size
|
||||
// we don't have to do anything.
|
||||
mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect));
|
||||
} else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
|
||||
NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
|
||||
// Clamp the clipping region to the size of the underlying image.
|
||||
mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
|
||||
|
||||
// If the clipping region is the same size as the underlying image we
|
||||
// don't have to do anything.
|
||||
mShouldClip.emplace(!mClip.IsEqualInterior(nsIntRect(0, 0, width,
|
||||
height)));
|
||||
} else if (progressTracker &&
|
||||
!(progressTracker->GetProgress() & FLAG_LOAD_COMPLETE)) {
|
||||
// The image just hasn't finished loading yet. We don't yet know whether
|
||||
// clipping with be needed or not for now. Just return without memorizing
|
||||
// anything.
|
||||
return false;
|
||||
} else {
|
||||
// We have a fully loaded image without a clearly defined width and
|
||||
// height. This can happen with SVG images.
|
||||
mShouldClip.emplace(false);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mShouldClip.isSome(), "Should have computed a result");
|
||||
return *mShouldClip;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(ClippedImage, ImageWrapper)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClippedImage::GetWidth(int32_t* aWidth)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->GetWidth(aWidth);
|
||||
}
|
||||
|
||||
*aWidth = mClip.width;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClippedImage::GetHeight(int32_t* aHeight)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->GetHeight(aHeight);
|
||||
}
|
||||
|
||||
*aHeight = mClip.height;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClippedImage::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->GetIntrinsicSize(aSize);
|
||||
}
|
||||
|
||||
*aSize = nsSize(mClip.width, mClip.height);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClippedImage::GetIntrinsicRatio(nsSize* aRatio)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->GetIntrinsicRatio(aRatio);
|
||||
}
|
||||
|
||||
*aRatio = nsSize(mClip.width, mClip.height);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
ClippedImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
DrawResult result;
|
||||
RefPtr<SourceSurface> surface;
|
||||
Tie(result, surface) = GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags);
|
||||
return surface.forget();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Pair<DrawResult, RefPtr<SourceSurface>>
|
||||
ClippedImage::GetFrameInternal(const nsIntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
RefPtr<SourceSurface> surface = InnerImage()->GetFrame(aWhichFrame, aFlags);
|
||||
return MakePair(surface ? DrawResult::SUCCESS : DrawResult::NOT_READY,
|
||||
Move(surface));
|
||||
}
|
||||
|
||||
float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame);
|
||||
if (!mCachedSurface ||
|
||||
!mCachedSurface->Matches(aSize, aSVGContext, frameToDraw, aFlags) ||
|
||||
mCachedSurface->NeedsRedraw()) {
|
||||
// Create a surface to draw into.
|
||||
RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()->
|
||||
CreateOffscreenContentDrawTarget(IntSize(aSize.width, aSize.height),
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
if (!target || !target->IsValid()) {
|
||||
NS_ERROR("Could not create a DrawTarget");
|
||||
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
||||
}
|
||||
|
||||
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
|
||||
MOZ_ASSERT(ctx); // already checked the draw target above
|
||||
|
||||
// Create our callback.
|
||||
RefPtr<DrawSingleTileCallback> drawTileCallback =
|
||||
new DrawSingleTileCallback(this, aSize, aSVGContext, aWhichFrame, aFlags);
|
||||
RefPtr<gfxDrawable> drawable =
|
||||
new gfxCallbackDrawable(drawTileCallback, aSize);
|
||||
|
||||
// Actually draw. The callback will end up invoking DrawSingleTile.
|
||||
gfxUtils::DrawPixelSnapped(ctx, drawable, aSize,
|
||||
ImageRegion::Create(aSize),
|
||||
SurfaceFormat::B8G8R8A8,
|
||||
SamplingFilter::LINEAR,
|
||||
imgIContainer::FLAG_CLAMP);
|
||||
|
||||
// Cache the resulting surface.
|
||||
mCachedSurface =
|
||||
MakeUnique<ClippedImageCachedSurface>(target->Snapshot(), aSize, aSVGContext,
|
||||
frameToDraw, aFlags,
|
||||
drawTileCallback->GetDrawResult());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");
|
||||
RefPtr<SourceSurface> surface = mCachedSurface->Surface();
|
||||
return MakePair(mCachedSurface->GetDrawResult(), Move(surface));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
ClippedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
|
||||
ClippedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
// XXX(seth): We currently don't have a way of clipping the result of
|
||||
// GetImageContainer. We work around this by always returning null, but if it
|
||||
// ever turns out that ClippedImage is widely used on codepaths that can
|
||||
// actually benefit from GetImageContainer, it would be a good idea to fix
|
||||
// that method for performance reasons.
|
||||
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->GetImageContainer(aManager, aFlags);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool
|
||||
MustCreateSurface(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
const uint32_t aFlags)
|
||||
{
|
||||
gfxRect imageRect(0, 0, aSize.width, aSize.height);
|
||||
bool willTile = !imageRect.Contains(aRegion.Rect()) &&
|
||||
!(aFlags & imgIContainer::FLAG_CLAMP);
|
||||
bool willResample = aContext->CurrentMatrix().HasNonIntegerTranslation() &&
|
||||
(willTile || !aRegion.RestrictionContains(imageRect));
|
||||
return willTile || willResample;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(DrawResult)
|
||||
ClippedImage::Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
|
||||
aSamplingFilter, aSVGContext, aFlags);
|
||||
}
|
||||
|
||||
// Check for tiling. If we need to tile then we need to create a
|
||||
// gfxCallbackDrawable to handle drawing for us.
|
||||
if (MustCreateSurface(aContext, aSize, aRegion, aFlags)) {
|
||||
// Create a temporary surface containing a single tile of this image.
|
||||
// GetFrame will call DrawSingleTile internally.
|
||||
DrawResult result;
|
||||
RefPtr<SourceSurface> surface;
|
||||
Tie(result, surface) =
|
||||
GetFrameInternal(aSize, aSVGContext, aWhichFrame, aFlags);
|
||||
if (!surface) {
|
||||
MOZ_ASSERT(result != DrawResult::SUCCESS);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a drawable from that surface.
|
||||
RefPtr<gfxSurfaceDrawable> drawable =
|
||||
new gfxSurfaceDrawable(surface, aSize);
|
||||
|
||||
// Draw.
|
||||
gfxUtils::DrawPixelSnapped(aContext, drawable, aSize, aRegion,
|
||||
SurfaceFormat::B8G8R8A8, aSamplingFilter);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return DrawSingleTile(aContext, aSize, aRegion, aWhichFrame,
|
||||
aSamplingFilter, aSVGContext, aFlags);
|
||||
}
|
||||
|
||||
DrawResult
|
||||
ClippedImage::DrawSingleTile(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags),
|
||||
"Shouldn't need to create a surface");
|
||||
|
||||
gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height);
|
||||
nsIntSize size(aSize), innerSize(aSize);
|
||||
bool needScale = false;
|
||||
if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
|
||||
innerSize = *mSVGViewportSize;
|
||||
needScale = true;
|
||||
} else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) &&
|
||||
NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) {
|
||||
needScale = true;
|
||||
} else {
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"If ShouldClip() led us to draw then we should never get here");
|
||||
}
|
||||
|
||||
if (needScale) {
|
||||
double scaleX = aSize.width / clip.width;
|
||||
double scaleY = aSize.height / clip.height;
|
||||
|
||||
// Map the clip and size to the scale requested by the caller.
|
||||
clip.Scale(scaleX, scaleY);
|
||||
size = innerSize;
|
||||
size.Scale(scaleX, scaleY);
|
||||
}
|
||||
|
||||
// We restrict our drawing to only the clipping region, and translate so that
|
||||
// the clipping region is placed at the position the caller expects.
|
||||
ImageRegion region(aRegion);
|
||||
region.MoveBy(clip.x, clip.y);
|
||||
region = region.Intersect(clip);
|
||||
|
||||
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
|
||||
aContext->Multiply(gfxMatrix::Translation(-clip.x, -clip.y));
|
||||
|
||||
auto unclipViewport = [&](const SVGImageContext& aOldContext) {
|
||||
// Map the viewport to the inner image. Note that we don't take the aSize
|
||||
// parameter of imgIContainer::Draw into account, just the clipping region.
|
||||
// The size in pixels at which the output will ultimately be drawn is
|
||||
// irrelevant here since the purpose of the SVG viewport size is to
|
||||
// determine what *region* of the SVG document will be drawn.
|
||||
CSSIntSize vSize(aOldContext.GetViewportSize());
|
||||
vSize.width = ceil(vSize.width * double(innerSize.width) / mClip.width);
|
||||
vSize.height =
|
||||
ceil(vSize.height * double(innerSize.height) / mClip.height);
|
||||
|
||||
return SVGImageContext(vSize,
|
||||
aOldContext.GetPreserveAspectRatio());
|
||||
};
|
||||
|
||||
return InnerImage()->Draw(aContext, size, region,
|
||||
aWhichFrame, aSamplingFilter,
|
||||
aSVGContext.map(unclipViewport),
|
||||
aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClippedImage::RequestDiscard()
|
||||
{
|
||||
// We're very aggressive about discarding.
|
||||
mCachedSurface = nullptr;
|
||||
|
||||
return InnerImage()->RequestDiscard();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(Orientation)
|
||||
ClippedImage::GetOrientation()
|
||||
{
|
||||
// XXX(seth): This should not actually be here; this is just to work around a
|
||||
// what appears to be a bug in MSVC's linker.
|
||||
return InnerImage()->GetOrientation();
|
||||
}
|
||||
|
||||
nsIntSize
|
||||
ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
|
||||
aSamplingFilter, aFlags);
|
||||
}
|
||||
|
||||
int32_t imgWidth, imgHeight;
|
||||
bool needScale = false;
|
||||
bool forceUniformScaling = false;
|
||||
if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
|
||||
imgWidth = mSVGViewportSize->width;
|
||||
imgHeight = mSVGViewportSize->height;
|
||||
needScale = true;
|
||||
forceUniformScaling = (aFlags & imgIContainer::FLAG_FORCE_UNIFORM_SCALING);
|
||||
} else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
|
||||
NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
|
||||
needScale = true;
|
||||
}
|
||||
|
||||
if (needScale) {
|
||||
// To avoid ugly sampling artifacts, ClippedImage needs the image size to
|
||||
// be chosen such that the clipping region lies on pixel boundaries.
|
||||
|
||||
// First, we select a scale that's good for ClippedImage. An integer
|
||||
// multiple of the size of the clipping region is always fine.
|
||||
IntSize scale = IntSize::Ceil(aDest.width / mClip.width,
|
||||
aDest.height / mClip.height);
|
||||
|
||||
if (forceUniformScaling) {
|
||||
scale.width = scale.height = max(scale.height, scale.width);
|
||||
}
|
||||
|
||||
// Determine the size we'd prefer to render the inner image at, and ask the
|
||||
// inner image what size we should actually use.
|
||||
gfxSize desiredSize(imgWidth * scale.width, imgHeight * scale.height);
|
||||
nsIntSize innerDesiredSize =
|
||||
InnerImage()->OptimalImageSizeForDest(desiredSize, aWhichFrame,
|
||||
aSamplingFilter, aFlags);
|
||||
|
||||
// To get our final result, we take the inner image's desired size and
|
||||
// determine how large the clipped region would be at that scale. (Again, we
|
||||
// ensure an integer multiple of the size of the clipping region.)
|
||||
IntSize finalScale = IntSize::Ceil(double(innerDesiredSize.width) / imgWidth,
|
||||
double(innerDesiredSize.height) / imgHeight);
|
||||
return mClip.Size() * finalScale;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(false,
|
||||
"If ShouldClip() led us to draw then we should never get here");
|
||||
return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
|
||||
aSamplingFilter, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(nsIntRect)
|
||||
ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
{
|
||||
if (!ShouldClip()) {
|
||||
return InnerImage()->GetImageSpaceInvalidationRect(aRect);
|
||||
}
|
||||
|
||||
nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
|
||||
rect = rect.Intersect(mClip);
|
||||
rect.MoveBy(-mClip.x, -mClip.y);
|
||||
return rect;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
101
image/ClippedImage.h
Normal file
101
image/ClippedImage.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- 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_ClippedImage_h
|
||||
#define mozilla_image_ClippedImage_h
|
||||
|
||||
#include "ImageWrapper.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class ClippedImageCachedSurface;
|
||||
class DrawSingleTileCallback;
|
||||
|
||||
/**
|
||||
* An Image wrapper that clips an image against a rectangle. Right now only
|
||||
* absolute coordinates in pixels are supported.
|
||||
*
|
||||
* XXX(seth): There a known (performance, not correctness) issue with
|
||||
* GetImageContainer. See the comments for that method for more information.
|
||||
*/
|
||||
class ClippedImage : public ImageWrapper
|
||||
{
|
||||
typedef gfx::SourceSurface SourceSurface;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_IMETHOD GetWidth(int32_t* aWidth) override;
|
||||
NS_IMETHOD GetHeight(int32_t* aHeight) override;
|
||||
NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
|
||||
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>)
|
||||
GetImageContainer(layers::LayerManager* aManager,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD RequestDiscard() override;
|
||||
NS_IMETHOD_(Orientation) GetOrientation() override;
|
||||
NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
override;
|
||||
nsIntSize OptimalImageSizeForDest(const gfxSize& aDest,
|
||||
uint32_t aWhichFrame,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags) override;
|
||||
|
||||
protected:
|
||||
ClippedImage(Image* aImage, nsIntRect aClip,
|
||||
const Maybe<nsSize>& aSVGViewportSize);
|
||||
|
||||
virtual ~ClippedImage();
|
||||
|
||||
private:
|
||||
Pair<DrawResult, RefPtr<SourceSurface>>
|
||||
GetFrameInternal(const nsIntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags);
|
||||
bool ShouldClip();
|
||||
DrawResult DrawSingleTile(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags);
|
||||
|
||||
// If we are forced to draw a temporary surface, we cache it here.
|
||||
UniquePtr<ClippedImageCachedSurface> mCachedSurface;
|
||||
|
||||
nsIntRect mClip; // The region to clip to.
|
||||
Maybe<bool> mShouldClip; // Memoized ShouldClip() if present.
|
||||
Maybe<nsIntSize> mSVGViewportSize; // If we're clipping a VectorImage, this
|
||||
// is the size of viewport of that image.
|
||||
friend class DrawSingleTileCallback;
|
||||
friend class ImageOps;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ClippedImage_h
|
||||
250
image/CopyOnWrite.h
Normal file
250
image/CopyOnWrite.h
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* CopyOnWrite<T> allows code to safely read from a data structure without
|
||||
* worrying that reentrant code will modify it.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_CopyOnWrite_h
|
||||
#define mozilla_image_CopyOnWrite_h
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation Details
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
class CopyOnWriteValue final
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(CopyOnWriteValue)
|
||||
|
||||
explicit CopyOnWriteValue(T* aValue) : mValue(aValue) { }
|
||||
explicit CopyOnWriteValue(already_AddRefed<T>& aValue) : mValue(aValue) { }
|
||||
explicit CopyOnWriteValue(already_AddRefed<T>&& aValue) : mValue(aValue) { }
|
||||
explicit CopyOnWriteValue(const RefPtr<T>& aValue) : mValue(aValue) { }
|
||||
explicit CopyOnWriteValue(RefPtr<T>&& aValue) : mValue(aValue) { }
|
||||
|
||||
T* get() { return mValue.get(); }
|
||||
const T* get() const { return mValue.get(); }
|
||||
|
||||
bool HasReaders() const { return mReaders > 0; }
|
||||
bool HasWriter() const { return mWriter; }
|
||||
bool HasUsers() const { return HasReaders() || HasWriter(); }
|
||||
|
||||
void LockForReading() { MOZ_ASSERT(!HasWriter()); mReaders++; }
|
||||
void UnlockForReading() { MOZ_ASSERT(HasReaders()); mReaders--; }
|
||||
|
||||
struct MOZ_STACK_CLASS AutoReadLock
|
||||
{
|
||||
explicit AutoReadLock(CopyOnWriteValue* aValue)
|
||||
: mValue(aValue)
|
||||
{
|
||||
mValue->LockForReading();
|
||||
}
|
||||
~AutoReadLock() { mValue->UnlockForReading(); }
|
||||
CopyOnWriteValue<T>* mValue;
|
||||
};
|
||||
|
||||
void LockForWriting() { MOZ_ASSERT(!HasUsers()); mWriter = true; }
|
||||
void UnlockForWriting() { MOZ_ASSERT(HasWriter()); mWriter = false; }
|
||||
|
||||
struct MOZ_STACK_CLASS AutoWriteLock
|
||||
{
|
||||
explicit AutoWriteLock(CopyOnWriteValue* aValue)
|
||||
: mValue(aValue)
|
||||
{
|
||||
mValue->LockForWriting();
|
||||
}
|
||||
~AutoWriteLock() { mValue->UnlockForWriting(); }
|
||||
CopyOnWriteValue<T>* mValue;
|
||||
};
|
||||
|
||||
private:
|
||||
CopyOnWriteValue(const CopyOnWriteValue&) = delete;
|
||||
CopyOnWriteValue(CopyOnWriteValue&&) = delete;
|
||||
|
||||
~CopyOnWriteValue() { }
|
||||
|
||||
RefPtr<T> mValue;
|
||||
uint64_t mReaders = 0;
|
||||
bool mWriter = false;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Public API
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* CopyOnWrite<T> allows code to safely read from a data structure without
|
||||
* worrying that reentrant code will modify it. If reentrant code would modify
|
||||
* the data structure while other code is reading from it, a copy is made so
|
||||
* that readers can continue to use the old version.
|
||||
*
|
||||
* Note that it's legal to nest a writer inside any number of readers, but
|
||||
* nothing can be nested inside a writer. This is because it's assumed that the
|
||||
* state of the contained data structure may not be consistent during the write.
|
||||
*
|
||||
* This is a main-thread-only data structure.
|
||||
*
|
||||
* To work with CopyOnWrite<T>, a type T needs to be reference counted and to
|
||||
* support copy construction.
|
||||
*/
|
||||
template <typename T>
|
||||
class CopyOnWrite final
|
||||
{
|
||||
typedef detail::CopyOnWriteValue<T> CopyOnWriteValue;
|
||||
|
||||
public:
|
||||
explicit CopyOnWrite(T* aValue)
|
||||
: mValue(new CopyOnWriteValue(aValue))
|
||||
{ }
|
||||
|
||||
explicit CopyOnWrite(already_AddRefed<T>& aValue)
|
||||
: mValue(new CopyOnWriteValue(aValue))
|
||||
{ }
|
||||
|
||||
explicit CopyOnWrite(already_AddRefed<T>&& aValue)
|
||||
: mValue(new CopyOnWriteValue(aValue))
|
||||
{ }
|
||||
|
||||
explicit CopyOnWrite(const RefPtr<T>& aValue)
|
||||
: mValue(new CopyOnWriteValue(aValue))
|
||||
{ }
|
||||
|
||||
explicit CopyOnWrite(RefPtr<T>&& aValue)
|
||||
: mValue(new CopyOnWriteValue(aValue))
|
||||
{ }
|
||||
|
||||
/// @return true if it's safe to read at this time.
|
||||
bool CanRead() const { return !mValue->HasWriter(); }
|
||||
|
||||
/**
|
||||
* Read from the contained data structure using the function @aReader.
|
||||
* @aReader will be passed a pointer of type |const T*|. It's not legal to
|
||||
* call this while a writer is active.
|
||||
*
|
||||
* @return whatever value @aReader returns, or nothing if @aReader is a void
|
||||
* function.
|
||||
*/
|
||||
template <typename ReadFunc>
|
||||
auto Read(ReadFunc aReader) const
|
||||
-> decltype(aReader(static_cast<const T*>(nullptr)))
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(CanRead());
|
||||
|
||||
// Run the provided function while holding a read lock.
|
||||
RefPtr<CopyOnWriteValue> cowValue = mValue;
|
||||
typename CopyOnWriteValue::AutoReadLock lock(cowValue);
|
||||
return aReader(cowValue->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the contained data structure using the function @aReader.
|
||||
* @aReader will be passed a pointer of type |const T*|. If it's currently not
|
||||
* possible to read because a writer is currently active, @aOnError will be
|
||||
* called instead.
|
||||
*
|
||||
* @return whatever value @aReader or @aOnError returns (their return types
|
||||
* must be consistent), or nothing if the provided functions are void.
|
||||
*/
|
||||
template <typename ReadFunc, typename ErrorFunc>
|
||||
auto Read(ReadFunc aReader, ErrorFunc aOnError) const
|
||||
-> decltype(aReader(static_cast<const T*>(nullptr)))
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!CanRead()) {
|
||||
return aOnError();
|
||||
}
|
||||
|
||||
return Read(aReader);
|
||||
}
|
||||
|
||||
/// @return true if it's safe to write at this time.
|
||||
bool CanWrite() const { return !mValue->HasWriter(); }
|
||||
|
||||
/**
|
||||
* Write to the contained data structure using the function @aWriter.
|
||||
* @aWriter will be passed a pointer of type |T*|. It's not legal to call this
|
||||
* while another writer is active.
|
||||
*
|
||||
* If readers are currently active, they will be able to continue reading from
|
||||
* a copy of the old version of the data structure. The copy will be destroyed
|
||||
* when all its readers finish. Later readers and writers will see the
|
||||
* version of the data structure produced by the most recent call to Write().
|
||||
*
|
||||
* @return whatever value @aWriter returns, or nothing if @aWriter is a void
|
||||
* function.
|
||||
*/
|
||||
template <typename WriteFunc>
|
||||
auto Write(WriteFunc aWriter)
|
||||
-> decltype(aWriter(static_cast<T*>(nullptr)))
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(CanWrite());
|
||||
|
||||
// If there are readers, we need to copy first.
|
||||
if (mValue->HasReaders()) {
|
||||
mValue = new CopyOnWriteValue(new T(*mValue->get()));
|
||||
}
|
||||
|
||||
// Run the provided function while holding a write lock.
|
||||
RefPtr<CopyOnWriteValue> cowValue = mValue;
|
||||
typename CopyOnWriteValue::AutoWriteLock lock(cowValue);
|
||||
return aWriter(cowValue->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the contained data structure using the function @aWriter.
|
||||
* @aWriter will be passed a pointer of type |T*|. If it's currently not
|
||||
* possible to write because a writer is currently active, @aOnError will be
|
||||
* called instead.
|
||||
*
|
||||
* If readers are currently active, they will be able to continue reading from
|
||||
* a copy of the old version of the data structure. The copy will be destroyed
|
||||
* when all its readers finish. Later readers and writers will see the
|
||||
* version of the data structure produced by the most recent call to Write().
|
||||
*
|
||||
* @return whatever value @aWriter or @aOnError returns (their return types
|
||||
* must be consistent), or nothing if the provided functions are void.
|
||||
*/
|
||||
template <typename WriteFunc, typename ErrorFunc>
|
||||
auto Write(WriteFunc aWriter, ErrorFunc aOnError)
|
||||
-> decltype(aWriter(static_cast<T*>(nullptr)))
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!CanWrite()) {
|
||||
return aOnError();
|
||||
}
|
||||
|
||||
return Write(aWriter);
|
||||
}
|
||||
|
||||
private:
|
||||
CopyOnWrite(const CopyOnWrite&) = delete;
|
||||
CopyOnWrite(CopyOnWrite&&) = delete;
|
||||
|
||||
RefPtr<CopyOnWriteValue> mValue;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_CopyOnWrite_h
|
||||
340
image/DecodePool.cpp
Normal file
340
image/DecodePool.cpp
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
/* -*- 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 "DecodePool.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIThreadPool.h"
|
||||
#include "nsThreadManager.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
#include "prsystem.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
|
||||
#include "Decoder.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "RasterImage.h"
|
||||
|
||||
using std::max;
|
||||
using std::min;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DecodePool implementation.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
|
||||
/* static */ uint32_t DecodePool::sNumCores = 0;
|
||||
|
||||
NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
|
||||
|
||||
struct Work
|
||||
{
|
||||
enum class Type {
|
||||
TASK,
|
||||
SHUTDOWN
|
||||
} mType;
|
||||
|
||||
RefPtr<IDecodingTask> mTask;
|
||||
};
|
||||
|
||||
class DecodePoolImpl
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
|
||||
|
||||
DecodePoolImpl()
|
||||
: mMonitor("DecodePoolImpl")
|
||||
, mShuttingDown(false)
|
||||
{ }
|
||||
|
||||
/// Initialize the current thread for use by the decode pool.
|
||||
void InitCurrentThread()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
mThreadNaming.SetThreadPoolName(NS_LITERAL_CSTRING("ImgDecoder"));
|
||||
}
|
||||
|
||||
/// Shut down the provided decode pool thread.
|
||||
static void ShutdownThread(nsIThread* aThisThread)
|
||||
{
|
||||
// Threads have to be shut down from another thread, so we'll ask the
|
||||
// main thread to do it for us.
|
||||
NS_DispatchToMainThread(NewRunnableMethod(aThisThread, &nsIThread::Shutdown));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests shutdown. New work items will be dropped on the floor, and all
|
||||
* decode pool threads will be shut down once existing work items have been
|
||||
* processed.
|
||||
*/
|
||||
void RequestShutdown()
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
mShuttingDown = true;
|
||||
mMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
/// Pushes a new decode work item.
|
||||
void PushWork(IDecodingTask* aTask)
|
||||
{
|
||||
MOZ_ASSERT(aTask);
|
||||
RefPtr<IDecodingTask> task(aTask);
|
||||
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
||||
if (mShuttingDown) {
|
||||
// Drop any new work on the floor if we're shutting down.
|
||||
return;
|
||||
}
|
||||
|
||||
if (task->Priority() == TaskPriority::eHigh) {
|
||||
mHighPriorityQueue.AppendElement(Move(task));
|
||||
} else {
|
||||
mLowPriorityQueue.AppendElement(Move(task));
|
||||
}
|
||||
|
||||
mMonitor.Notify();
|
||||
}
|
||||
|
||||
/// Pops a new work item, blocking if necessary.
|
||||
Work PopWork()
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
|
||||
do {
|
||||
if (!mHighPriorityQueue.IsEmpty()) {
|
||||
return PopWorkFromQueue(mHighPriorityQueue);
|
||||
}
|
||||
|
||||
if (!mLowPriorityQueue.IsEmpty()) {
|
||||
return PopWorkFromQueue(mLowPriorityQueue);
|
||||
}
|
||||
|
||||
if (mShuttingDown) {
|
||||
Work work;
|
||||
work.mType = Work::Type::SHUTDOWN;
|
||||
return work;
|
||||
}
|
||||
|
||||
// Nothing to do; block until some work is available.
|
||||
mMonitor.Wait();
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private:
|
||||
~DecodePoolImpl() { }
|
||||
|
||||
Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue)
|
||||
{
|
||||
Work work;
|
||||
work.mType = Work::Type::TASK;
|
||||
work.mTask = aQueue.LastElement().forget();
|
||||
aQueue.RemoveElementAt(aQueue.Length() - 1);
|
||||
|
||||
return work;
|
||||
}
|
||||
|
||||
nsThreadPoolNaming mThreadNaming;
|
||||
|
||||
// mMonitor guards the queues and mShuttingDown.
|
||||
Monitor mMonitor;
|
||||
nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
|
||||
nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
|
||||
bool mShuttingDown;
|
||||
};
|
||||
|
||||
class DecodePoolWorker : public Runnable
|
||||
{
|
||||
public:
|
||||
explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { }
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
mImpl->InitCurrentThread();
|
||||
|
||||
nsCOMPtr<nsIThread> thisThread;
|
||||
nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
|
||||
|
||||
do {
|
||||
Work work = mImpl->PopWork();
|
||||
switch (work.mType) {
|
||||
case Work::Type::TASK:
|
||||
work.mTask->Run();
|
||||
break;
|
||||
|
||||
case Work::Type::SHUTDOWN:
|
||||
DecodePoolImpl::ShutdownThread(thisThread);
|
||||
return NS_OK;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown work type");
|
||||
}
|
||||
} while (true);
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<DecodePoolImpl> mImpl;
|
||||
};
|
||||
|
||||
/* static */ void
|
||||
DecodePool::Initialize()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
|
||||
DecodePool::Singleton();
|
||||
}
|
||||
|
||||
/* static */ DecodePool*
|
||||
DecodePool::Singleton()
|
||||
{
|
||||
if (!sSingleton) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
sSingleton = new DecodePool();
|
||||
ClearOnShutdown(&sSingleton);
|
||||
}
|
||||
|
||||
return sSingleton;
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
DecodePool::NumberOfCores()
|
||||
{
|
||||
return sNumCores;
|
||||
}
|
||||
|
||||
DecodePool::DecodePool()
|
||||
: mImpl(new DecodePoolImpl)
|
||||
, mMutex("image::DecodePool")
|
||||
{
|
||||
// Determine the number of threads we want.
|
||||
int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
|
||||
uint32_t limit;
|
||||
if (prefLimit <= 0) {
|
||||
int32_t numCores = NumberOfCores();
|
||||
if (numCores <= 1) {
|
||||
limit = 1;
|
||||
} else if (numCores == 2) {
|
||||
// On an otherwise mostly idle system, having two image decoding threads
|
||||
// doubles decoding performance, so it's worth doing on dual-core devices,
|
||||
// even if under load we can't actually get that level of parallelism.
|
||||
limit = 2;
|
||||
} else {
|
||||
limit = numCores - 1;
|
||||
}
|
||||
} else {
|
||||
limit = static_cast<uint32_t>(prefLimit);
|
||||
}
|
||||
if (limit > 32) {
|
||||
limit = 32;
|
||||
}
|
||||
|
||||
// Initialize the thread pool.
|
||||
for (uint32_t i = 0 ; i < limit ; ++i) {
|
||||
nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(thread), worker);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread,
|
||||
"Should successfully create image decoding threads");
|
||||
mThreads.AppendElement(Move(thread));
|
||||
}
|
||||
|
||||
// Initialize the I/O thread.
|
||||
nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
|
||||
"Should successfully create image I/O thread");
|
||||
|
||||
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
|
||||
if (obsSvc) {
|
||||
obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
|
||||
}
|
||||
}
|
||||
|
||||
DecodePool::~DecodePool()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
|
||||
{
|
||||
MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
|
||||
|
||||
nsTArray<nsCOMPtr<nsIThread>> threads;
|
||||
nsCOMPtr<nsIThread> ioThread;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
threads.SwapElements(mThreads);
|
||||
ioThread.swap(mIOThread);
|
||||
}
|
||||
|
||||
mImpl->RequestShutdown();
|
||||
|
||||
for (uint32_t i = 0 ; i < threads.Length() ; ++i) {
|
||||
threads[i]->Shutdown();
|
||||
}
|
||||
|
||||
if (ioThread) {
|
||||
ioThread->Shutdown();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DecodePool::AsyncRun(IDecodingTask* aTask)
|
||||
{
|
||||
MOZ_ASSERT(aTask);
|
||||
mImpl->PushWork(aTask);
|
||||
}
|
||||
|
||||
void
|
||||
DecodePool::SyncRunIfPreferred(IDecodingTask* aTask)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTask);
|
||||
|
||||
if (aTask->ShouldPreferSyncRun()) {
|
||||
aTask->Run();
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncRun(aTask);
|
||||
}
|
||||
|
||||
void
|
||||
DecodePool::SyncRunIfPossible(IDecodingTask* aTask)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTask);
|
||||
aTask->Run();
|
||||
}
|
||||
|
||||
already_AddRefed<nsIEventTarget>
|
||||
DecodePool::GetIOEventTarget()
|
||||
{
|
||||
MutexAutoLock threadPoolLock(mMutex);
|
||||
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
|
||||
return target.forget();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
105
image/DecodePool.h
Normal file
105
image/DecodePool.h
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* DecodePool manages the threads used for decoding raster images.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_DecodePool_h
|
||||
#define mozilla_image_DecodePool_h
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIEventTarget.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
class nsIThread;
|
||||
class nsIThreadPool;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
class DecodePoolImpl;
|
||||
class IDecodingTask;
|
||||
|
||||
/**
|
||||
* DecodePool is a singleton class that manages decoding of raster images. It
|
||||
* owns a pool of image decoding threads that are used for asynchronous
|
||||
* decoding.
|
||||
*
|
||||
* DecodePool allows callers to run a decoder, handling management of the
|
||||
* decoder's lifecycle and whether it executes on the main thread,
|
||||
* off-main-thread in the image decoding thread pool, or on some combination of
|
||||
* the two.
|
||||
*/
|
||||
class DecodePool : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
/// Initializes the singleton instance. Should be called from the main thread.
|
||||
static void Initialize();
|
||||
|
||||
/// Returns the singleton instance.
|
||||
static DecodePool* Singleton();
|
||||
|
||||
/// @return the number of processor cores we have available. This is not the
|
||||
/// same as the number of decoding threads we're actually using.
|
||||
static uint32_t NumberOfCores();
|
||||
|
||||
/// Ask the DecodePool to run @aTask asynchronously and return immediately.
|
||||
void AsyncRun(IDecodingTask* aTask);
|
||||
|
||||
/**
|
||||
* Run @aTask synchronously if the task would prefer it. It's up to the task
|
||||
* itself to make this decision; @see IDecodingTask::ShouldPreferSyncRun(). If
|
||||
* @aTask doesn't prefer it, just run @aTask asynchronously and return
|
||||
* immediately.
|
||||
*/
|
||||
void SyncRunIfPreferred(IDecodingTask* aTask);
|
||||
|
||||
/**
|
||||
* Run @aTask synchronously. This does not guarantee that @aTask will complete
|
||||
* synchronously. If, for example, @aTask doesn't yet have the data it needs to
|
||||
* run synchronously, it may recover by scheduling an async task to finish up
|
||||
* the work when the remaining data is available.
|
||||
*/
|
||||
void SyncRunIfPossible(IDecodingTask* aTask);
|
||||
|
||||
/**
|
||||
* Returns an event target interface to the DecodePool's I/O thread. Callers
|
||||
* who want to deliver data to workers on the DecodePool can use this event
|
||||
* target.
|
||||
*
|
||||
* @return An nsIEventTarget interface to the thread pool's I/O thread.
|
||||
*/
|
||||
already_AddRefed<nsIEventTarget> GetIOEventTarget();
|
||||
|
||||
private:
|
||||
friend class DecodePoolWorker;
|
||||
|
||||
DecodePool();
|
||||
virtual ~DecodePool();
|
||||
|
||||
static StaticRefPtr<DecodePool> sSingleton;
|
||||
static uint32_t sNumCores;
|
||||
|
||||
RefPtr<DecodePoolImpl> mImpl;
|
||||
|
||||
// mMutex protects mThreads and mIOThread.
|
||||
Mutex mMutex;
|
||||
nsTArray<nsCOMPtr<nsIThread>> mThreads;
|
||||
nsCOMPtr<nsIThread> mIOThread;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DecodePool_h
|
||||
224
image/DecodedSurfaceProvider.cpp
Normal file
224
image/DecodedSurfaceProvider.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/* -*- 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 "DecodedSurfaceProvider.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
#include "nsProxyRelease.h"
|
||||
|
||||
#include "Decoder.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
NotNull<Decoder*> aDecoder)
|
||||
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
|
||||
AvailabilityState::StartAsPlaceholder())
|
||||
, mImage(aImage.get())
|
||||
, mMutex("mozilla::image::DecodedSurfaceProvider")
|
||||
, mDecoder(aDecoder.get())
|
||||
{
|
||||
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
|
||||
"Use MetadataDecodingTask for metadata decodes");
|
||||
MOZ_ASSERT(mDecoder->IsFirstFrameDecode(),
|
||||
"Use AnimationSurfaceProvider for animation decodes");
|
||||
}
|
||||
|
||||
DecodedSurfaceProvider::~DecodedSurfaceProvider()
|
||||
{
|
||||
DropImageReference();
|
||||
}
|
||||
|
||||
void
|
||||
DecodedSurfaceProvider::DropImageReference()
|
||||
{
|
||||
if (!mImage) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
// RasterImage objects need to be destroyed on the main thread. We also need
|
||||
// to destroy them asynchronously, because if our surface cache entry is
|
||||
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
|
||||
// destructor may call into the surface cache while whatever code caused us to
|
||||
// get evicted is holding the surface cache lock, causing deadlock.
|
||||
RefPtr<RasterImage> image = mImage;
|
||||
mImage = nullptr;
|
||||
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
|
||||
}
|
||||
|
||||
DrawableFrameRef
|
||||
DecodedSurfaceProvider::DrawableRef(size_t aFrame)
|
||||
{
|
||||
MOZ_ASSERT(aFrame == 0,
|
||||
"Requesting an animation frame from a DecodedSurfaceProvider?");
|
||||
|
||||
// We depend on SurfaceCache::SurfaceAvailable() to provide synchronization
|
||||
// for methods that touch |mSurface|; after SurfaceAvailable() is called,
|
||||
// |mSurface| should be non-null and shouldn't be mutated further until we get
|
||||
// destroyed. That means that the assertions below are very important; we'll
|
||||
// end up with data races if these assumptions are violated.
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
if (!mSurface) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no surface");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
return mSurface->DrawableRef();
|
||||
}
|
||||
|
||||
bool
|
||||
DecodedSurfaceProvider::IsFinished() const
|
||||
{
|
||||
// See DrawableRef() for commentary on these assertions.
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mSurface) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
return mSurface->IsFinished();
|
||||
}
|
||||
|
||||
void
|
||||
DecodedSurfaceProvider::SetLocked(bool aLocked)
|
||||
{
|
||||
// See DrawableRef() for commentary on these assertions.
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling SetLocked() on a placeholder");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mSurface) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling SetLocked() when we have no surface");
|
||||
return;
|
||||
}
|
||||
|
||||
if (aLocked == IsLocked()) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
// If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep any
|
||||
// volatile buffer it owns in memory.
|
||||
mLockRef = aLocked ? mSurface->DrawableRef()
|
||||
: DrawableFrameRef();
|
||||
}
|
||||
|
||||
size_t
|
||||
DecodedSurfaceProvider::LogicalSizeInBytes() const
|
||||
{
|
||||
// Single frame images are always 32bpp.
|
||||
IntSize size = GetSurfaceKey().Size();
|
||||
return size.width * size.height * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
void
|
||||
DecodedSurfaceProvider::Run()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (!mDecoder || !mImage) {
|
||||
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the decoder.
|
||||
LexerResult result = mDecoder->Decode(WrapNotNull(this));
|
||||
|
||||
// If there's a new surface available, announce it to the surface cache.
|
||||
CheckForNewSurface();
|
||||
|
||||
if (result.is<TerminalState>()) {
|
||||
FinishDecoding();
|
||||
return; // We're done.
|
||||
}
|
||||
|
||||
// Notify for the progress we've made so far.
|
||||
if (mDecoder->HasProgress()) {
|
||||
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
}
|
||||
|
||||
MOZ_ASSERT(result.is<Yield>());
|
||||
|
||||
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
|
||||
// We can't make any more progress right now. The decoder itself will ensure
|
||||
// that we get reenqueued when more data is available; just return for now.
|
||||
return;
|
||||
}
|
||||
|
||||
// Single-frame images shouldn't yield for any reason except NEED_MORE_DATA.
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected yield for single-frame image");
|
||||
mDecoder->TerminateFailure();
|
||||
FinishDecoding();
|
||||
}
|
||||
|
||||
void
|
||||
DecodedSurfaceProvider::CheckForNewSurface()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
if (mSurface) {
|
||||
// Single-frame images should produce no more than one surface, so if we
|
||||
// have one, it should be the same one the decoder is working on.
|
||||
MOZ_ASSERT(mSurface.get() == mDecoder->GetCurrentFrameRef().get(),
|
||||
"DecodedSurfaceProvider and Decoder have different surfaces?");
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't have a surface yet; try to get one from the decoder.
|
||||
mSurface = mDecoder->GetCurrentFrameRef().get();
|
||||
if (!mSurface) {
|
||||
return; // No surface yet.
|
||||
}
|
||||
|
||||
// We just got a surface for the first time; let the surface cache know.
|
||||
MOZ_ASSERT(mImage);
|
||||
SurfaceCache::SurfaceAvailable(WrapNotNull(this));
|
||||
}
|
||||
|
||||
void
|
||||
DecodedSurfaceProvider::FinishDecoding()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mImage);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
// Send notifications.
|
||||
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
|
||||
// Destroy our decoder; we don't need it anymore. (And if we don't destroy it,
|
||||
// our surface can never be optimized, because the decoder has a
|
||||
// RawAccessFrameRef to it.)
|
||||
mDecoder = nullptr;
|
||||
|
||||
// We don't need a reference to our image anymore, either, and we don't want
|
||||
// one. We may be stored in the surface cache for a long time after decoding
|
||||
// finishes. If we don't drop our reference to the image, we'll end up
|
||||
// keeping it alive as long as we remain in the surface cache, which could
|
||||
// greatly extend the image's lifetime - in fact, if the image isn't
|
||||
// discardable, it'd result in a leak!
|
||||
DropImageReference();
|
||||
}
|
||||
|
||||
bool
|
||||
DecodedSurfaceProvider::ShouldPreferSyncRun() const
|
||||
{
|
||||
return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
89
image/DecodedSurfaceProvider.h
Normal file
89
image/DecodedSurfaceProvider.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider implemented for single-frame decoded surfaces.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_DecodedSurfaceProvider_h
|
||||
#define mozilla_image_DecodedSurfaceProvider_h
|
||||
|
||||
#include "IDecodingTask.h"
|
||||
#include "ISurfaceProvider.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider that manages the decoding of a single-frame image and
|
||||
* stores the resulting surface.
|
||||
*/
|
||||
class DecodedSurfaceProvider final
|
||||
: public ISurfaceProvider
|
||||
, public IDecodingTask
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodedSurfaceProvider, override)
|
||||
|
||||
DecodedSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
NotNull<Decoder*> aDecoder);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ISurfaceProvider implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
bool IsFinished() const override;
|
||||
size_t LogicalSizeInBytes() const override;
|
||||
|
||||
protected:
|
||||
DrawableFrameRef DrawableRef(size_t aFrame) override;
|
||||
bool IsLocked() const override { return bool(mLockRef); }
|
||||
void SetLocked(bool aLocked) override;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// IDecodingTask implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
void Run() override;
|
||||
bool ShouldPreferSyncRun() const override;
|
||||
|
||||
// Full decodes are low priority compared to metadata decodes because they
|
||||
// don't block layout or page load.
|
||||
TaskPriority Priority() const override { return TaskPriority::eLow; }
|
||||
|
||||
|
||||
private:
|
||||
virtual ~DecodedSurfaceProvider();
|
||||
|
||||
void DropImageReference();
|
||||
void CheckForNewSurface();
|
||||
void FinishDecoding();
|
||||
|
||||
/// The image associated with our decoder. Dropped after decoding.
|
||||
RefPtr<RasterImage> mImage;
|
||||
|
||||
/// Mutex protecting access to mDecoder.
|
||||
Mutex mMutex;
|
||||
|
||||
/// The decoder that will generate our surface. Dropped after decoding.
|
||||
RefPtr<Decoder> mDecoder;
|
||||
|
||||
/// Our surface. Initially null until it's generated by the decoder.
|
||||
RefPtr<imgFrame> mSurface;
|
||||
|
||||
/// A drawable reference to our service; used for locking.
|
||||
DrawableFrameRef mLockRef;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DecodedSurfaceProvider_h
|
||||
521
image/Decoder.cpp
Normal file
521
image/Decoder.cpp
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "Decoder.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "DecodePool.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "ISurfaceProvider.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
using mozilla::gfx::IntSize;
|
||||
using mozilla::gfx::SurfaceFormat;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final
|
||||
{
|
||||
public:
|
||||
explicit AutoRecordDecoderTelemetry(Decoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
{
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
// Begin recording telemetry data.
|
||||
mStartTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
~AutoRecordDecoderTelemetry()
|
||||
{
|
||||
// Finish telemetry.
|
||||
mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime);
|
||||
}
|
||||
|
||||
private:
|
||||
Decoder* mDecoder;
|
||||
TimeStamp mStartTime;
|
||||
};
|
||||
|
||||
Decoder::Decoder(RasterImage* aImage)
|
||||
: mImageData(nullptr)
|
||||
, mImageDataLength(0)
|
||||
, mColormap(nullptr)
|
||||
, mColormapSize(0)
|
||||
, mImage(aImage)
|
||||
, mProgress(NoProgress)
|
||||
, mFrameCount(0)
|
||||
, mLoopLength(FrameTimeout::Zero())
|
||||
, mDecoderFlags(DefaultDecoderFlags())
|
||||
, mSurfaceFlags(DefaultSurfaceFlags())
|
||||
, mInitialized(false)
|
||||
, mMetadataDecode(false)
|
||||
, mHaveExplicitOutputSize(false)
|
||||
, mInFrame(false)
|
||||
, mFinishedNewFrame(false)
|
||||
, mReachedTerminalState(false)
|
||||
, mDecodeDone(false)
|
||||
, mError(false)
|
||||
, mShouldReportError(false)
|
||||
{ }
|
||||
|
||||
Decoder::~Decoder()
|
||||
{
|
||||
MOZ_ASSERT(mProgress == NoProgress || !mImage,
|
||||
"Destroying Decoder without taking all its progress changes");
|
||||
MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
|
||||
"Destroying Decoder without taking all its invalidations");
|
||||
mInitialized = false;
|
||||
|
||||
if (mImage && !NS_IsMainThread()) {
|
||||
// Dispatch mImage to main thread to prevent it from being destructed by the
|
||||
// decode thread.
|
||||
NS_ReleaseOnMainThread(mImage.forget());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Common implementation of the decoder interface.
|
||||
*/
|
||||
|
||||
nsresult
|
||||
Decoder::Init()
|
||||
{
|
||||
// No re-initializing
|
||||
MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
|
||||
|
||||
// All decoders must have a SourceBufferIterator.
|
||||
MOZ_ASSERT(mIterator);
|
||||
|
||||
// Metadata decoders must not set an output size.
|
||||
MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);
|
||||
|
||||
// All decoders must be anonymous except for metadata decoders.
|
||||
// XXX(seth): Soon that exception will be removed.
|
||||
MOZ_ASSERT_IF(mImage, IsMetadataDecode());
|
||||
|
||||
// Implementation-specific initialization.
|
||||
nsresult rv = InitInternal();
|
||||
|
||||
mInitialized = true;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
LexerResult
|
||||
Decoder::Decode(IResumable* aOnResume /* = nullptr */)
|
||||
{
|
||||
MOZ_ASSERT(mInitialized, "Should be initialized here");
|
||||
MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
|
||||
|
||||
// If we're already done, don't attempt to keep decoding.
|
||||
if (GetDecodeDone()) {
|
||||
return LexerResult(HasError() ? TerminalState::FAILURE
|
||||
: TerminalState::SUCCESS);
|
||||
}
|
||||
|
||||
LexerResult lexerResult(TerminalState::FAILURE);
|
||||
{
|
||||
PROFILER_LABEL("ImageDecoder", "Decode", js::ProfileEntry::Category::GRAPHICS);
|
||||
AutoRecordDecoderTelemetry telemetry(this);
|
||||
|
||||
lexerResult = DoDecode(*mIterator, aOnResume);
|
||||
};
|
||||
|
||||
if (lexerResult.is<Yield>()) {
|
||||
// We either need more data to continue (in which case either @aOnResume or
|
||||
// the caller will reschedule us to run again later), or the decoder is
|
||||
// yielding to allow the caller access to some intermediate output.
|
||||
return lexerResult;
|
||||
}
|
||||
|
||||
// We reached a terminal state; we're now done decoding.
|
||||
MOZ_ASSERT(lexerResult.is<TerminalState>());
|
||||
mReachedTerminalState = true;
|
||||
|
||||
// If decoding failed, record that fact.
|
||||
if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) {
|
||||
PostError();
|
||||
}
|
||||
|
||||
// Perform final cleanup.
|
||||
CompleteDecode();
|
||||
|
||||
return LexerResult(HasError() ? TerminalState::FAILURE
|
||||
: TerminalState::SUCCESS);
|
||||
}
|
||||
|
||||
LexerResult
|
||||
Decoder::TerminateFailure()
|
||||
{
|
||||
PostError();
|
||||
|
||||
// Perform final cleanup if need be.
|
||||
if (!mReachedTerminalState) {
|
||||
mReachedTerminalState = true;
|
||||
CompleteDecode();
|
||||
}
|
||||
|
||||
return LexerResult(TerminalState::FAILURE);
|
||||
}
|
||||
|
||||
bool
|
||||
Decoder::ShouldSyncDecode(size_t aByteLimit)
|
||||
{
|
||||
MOZ_ASSERT(aByteLimit > 0);
|
||||
MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
|
||||
|
||||
return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::CompleteDecode()
|
||||
{
|
||||
// Implementation-specific finalization.
|
||||
nsresult rv = BeforeFinishInternal();
|
||||
if (NS_FAILED(rv)) {
|
||||
PostError();
|
||||
}
|
||||
|
||||
rv = HasError() ? FinishWithErrorInternal()
|
||||
: FinishInternal();
|
||||
if (NS_FAILED(rv)) {
|
||||
PostError();
|
||||
}
|
||||
|
||||
if (IsMetadataDecode()) {
|
||||
// If this was a metadata decode and we never got a size, the decode failed.
|
||||
if (!HasSize()) {
|
||||
PostError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the implementation left us mid-frame, finish that up. Note that it may
|
||||
// have left us transparent.
|
||||
if (mInFrame) {
|
||||
PostHasTransparency();
|
||||
PostFrameStop();
|
||||
}
|
||||
|
||||
// If PostDecodeDone() has not been called, we may need to send teardown
|
||||
// notifications if it is unrecoverable.
|
||||
if (!mDecodeDone) {
|
||||
// We should always report an error to the console in this case.
|
||||
mShouldReportError = true;
|
||||
|
||||
if (GetCompleteFrameCount() > 0) {
|
||||
// We're usable if we have at least one complete frame, so do exactly
|
||||
// what we should have when the decoder completed.
|
||||
PostHasTransparency();
|
||||
PostDecodeDone();
|
||||
} else {
|
||||
// We're not usable. Record some final progress indicating the error.
|
||||
mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (mDecodeDone) {
|
||||
MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
|
||||
|
||||
// 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() &&
|
||||
!(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
|
||||
mCurrentFrame) {
|
||||
mCurrentFrame->SetOptimizable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::SetOutputSize(const gfx::IntSize& aSize)
|
||||
{
|
||||
mOutputSize = Some(aSize);
|
||||
mHaveExplicitOutputSize = true;
|
||||
}
|
||||
|
||||
Maybe<gfx::IntSize>
|
||||
Decoder::ExplicitOutputSize() const
|
||||
{
|
||||
MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize);
|
||||
return mHaveExplicitOutputSize ? mOutputSize : Nothing();
|
||||
}
|
||||
|
||||
Maybe<uint32_t>
|
||||
Decoder::TakeCompleteFrameCount()
|
||||
{
|
||||
const bool finishedNewFrame = mFinishedNewFrame;
|
||||
mFinishedNewFrame = false;
|
||||
return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing();
|
||||
}
|
||||
|
||||
DecoderFinalStatus
|
||||
Decoder::FinalStatus() const
|
||||
{
|
||||
return DecoderFinalStatus(IsMetadataDecode(),
|
||||
GetDecodeDone(),
|
||||
HasError(),
|
||||
ShouldReportError());
|
||||
}
|
||||
|
||||
DecoderTelemetry
|
||||
Decoder::Telemetry() const
|
||||
{
|
||||
MOZ_ASSERT(mIterator);
|
||||
return DecoderTelemetry(SpeedHistogram(),
|
||||
mIterator->ByteCount(),
|
||||
mIterator->ChunkCount(),
|
||||
mDecodeTime);
|
||||
}
|
||||
|
||||
nsresult
|
||||
Decoder::AllocateFrame(uint32_t aFrameNum,
|
||||
const gfx::IntSize& aOutputSize,
|
||||
const gfx::IntRect& aFrameRect,
|
||||
gfx::SurfaceFormat aFormat,
|
||||
uint8_t aPaletteDepth)
|
||||
{
|
||||
mCurrentFrame = AllocateFrameInternal(aFrameNum, aOutputSize, aFrameRect,
|
||||
aFormat, aPaletteDepth,
|
||||
mCurrentFrame.get());
|
||||
|
||||
if (mCurrentFrame) {
|
||||
// Gather the raw pointers the decoders will use.
|
||||
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
|
||||
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
|
||||
|
||||
// We should now be on |aFrameNum|. (Note that we're comparing the frame
|
||||
// number, which is zero-based, with the frame count, which is one-based.)
|
||||
MOZ_ASSERT(aFrameNum + 1 == mFrameCount);
|
||||
|
||||
// If we're past the first frame, PostIsAnimated() should've been called.
|
||||
MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
|
||||
|
||||
// Update our state to reflect the new frame.
|
||||
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
|
||||
mInFrame = true;
|
||||
}
|
||||
|
||||
return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RawAccessFrameRef
|
||||
Decoder::AllocateFrameInternal(uint32_t aFrameNum,
|
||||
const gfx::IntSize& aOutputSize,
|
||||
const gfx::IntRect& aFrameRect,
|
||||
SurfaceFormat aFormat,
|
||||
uint8_t aPaletteDepth,
|
||||
imgFrame* aPreviousFrame)
|
||||
{
|
||||
if (HasError()) {
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
|
||||
if (aFrameNum != mFrameCount) {
|
||||
MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
|
||||
if (aOutputSize.width <= 0 || aOutputSize.height <= 0 ||
|
||||
aFrameRect.width <= 0 || aFrameRect.height <= 0) {
|
||||
NS_WARNING("Trying to add frame with zero or negative size");
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
|
||||
NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame());
|
||||
bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
|
||||
if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat,
|
||||
aPaletteDepth, nonPremult))) {
|
||||
NS_WARNING("imgFrame::Init should succeed");
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
|
||||
RawAccessFrameRef ref = frame->RawAccessRef();
|
||||
if (!ref) {
|
||||
frame->Abort();
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
|
||||
if (aFrameNum == 1) {
|
||||
MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
|
||||
aPreviousFrame->SetRawAccessOnly();
|
||||
|
||||
// If we dispose of the first frame by clearing it, then the first frame's
|
||||
// refresh area is all of itself.
|
||||
// RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
|
||||
AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
|
||||
if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
|
||||
previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
|
||||
previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
|
||||
mFirstFrameRefreshArea = previousFrameData.mRect;
|
||||
}
|
||||
}
|
||||
|
||||
if (aFrameNum > 0) {
|
||||
ref->SetRawAccessOnly();
|
||||
|
||||
// Some GIFs are huge but only have a small area that they animate. We only
|
||||
// need to refresh that small area when frame 0 comes around again.
|
||||
mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, frame->GetRect());
|
||||
}
|
||||
|
||||
mFrameCount++;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hook stubs. Override these as necessary in decoder implementations.
|
||||
*/
|
||||
|
||||
nsresult Decoder::InitInternal() { return NS_OK; }
|
||||
nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
|
||||
nsresult Decoder::FinishInternal() { return NS_OK; }
|
||||
nsresult Decoder::FinishWithErrorInternal() { return NS_OK; }
|
||||
|
||||
/*
|
||||
* Progress Notifications
|
||||
*/
|
||||
|
||||
void
|
||||
Decoder::PostSize(int32_t aWidth,
|
||||
int32_t aHeight,
|
||||
Orientation aOrientation /* = Orientation()*/)
|
||||
{
|
||||
// Validate.
|
||||
MOZ_ASSERT(aWidth >= 0, "Width can't be negative!");
|
||||
MOZ_ASSERT(aHeight >= 0, "Height can't be negative!");
|
||||
|
||||
// Set our intrinsic size.
|
||||
mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
|
||||
|
||||
// Set our output size if it's not already set.
|
||||
if (!mOutputSize) {
|
||||
mOutputSize = Some(IntSize(aWidth, aHeight));
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mOutputSize->width <= aWidth && mOutputSize->height <= aHeight,
|
||||
"Output size will result in upscaling");
|
||||
|
||||
// Create a downscaler if we need to downscale. This is used by legacy
|
||||
// decoders that haven't been converted to use SurfacePipe yet.
|
||||
// XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe.
|
||||
if (mOutputSize->width < aWidth || mOutputSize->height < aHeight) {
|
||||
mDownscaler.emplace(*mOutputSize);
|
||||
}
|
||||
|
||||
// Record this notification.
|
||||
mProgress |= FLAG_SIZE_AVAILABLE;
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::PostHasTransparency()
|
||||
{
|
||||
mProgress |= FLAG_HAS_TRANSPARENCY;
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout)
|
||||
{
|
||||
mProgress |= FLAG_IS_ANIMATED;
|
||||
mImageMetadata.SetHasAnimation();
|
||||
mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::PostFrameStop(Opacity aFrameOpacity
|
||||
/* = Opacity::SOME_TRANSPARENCY */,
|
||||
DisposalMethod aDisposalMethod
|
||||
/* = DisposalMethod::KEEP */,
|
||||
FrameTimeout aTimeout /* = FrameTimeout::Forever() */,
|
||||
BlendMethod aBlendMethod /* = BlendMethod::OVER */,
|
||||
const Maybe<nsIntRect>& aBlendRect /* = Nothing() */)
|
||||
{
|
||||
// We should be mid-frame
|
||||
MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
|
||||
MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
|
||||
MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
|
||||
|
||||
// Update our state.
|
||||
mInFrame = false;
|
||||
mFinishedNewFrame = true;
|
||||
|
||||
mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
|
||||
aBlendMethod, aBlendRect);
|
||||
|
||||
mProgress |= FLAG_FRAME_COMPLETE;
|
||||
|
||||
mLoopLength += aTimeout;
|
||||
|
||||
// If we're not sending partial invalidations, then we send an invalidation
|
||||
// here when the first frame is complete.
|
||||
if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
|
||||
mInvalidRect.UnionRect(mInvalidRect,
|
||||
IntRect(IntPoint(), Size()));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::PostInvalidation(const gfx::IntRect& aRect,
|
||||
const Maybe<gfx::IntRect>& aRectAtOutputSize
|
||||
/* = Nothing() */)
|
||||
{
|
||||
// We should be mid-frame
|
||||
MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
|
||||
MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
|
||||
|
||||
// Record this invalidation, unless we're not sending partial invalidations
|
||||
// or we're past the first frame.
|
||||
if (ShouldSendPartialInvalidations() && mFrameCount == 1) {
|
||||
mInvalidRect.UnionRect(mInvalidRect, aRect);
|
||||
mCurrentFrame->ImageUpdated(aRectAtOutputSize.valueOr(aRect));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
|
||||
{
|
||||
MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
|
||||
MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
|
||||
MOZ_ASSERT(!mDecodeDone, "Decode already done!");
|
||||
mDecodeDone = true;
|
||||
|
||||
mImageMetadata.SetLoopCount(aLoopCount);
|
||||
|
||||
// Some metadata that we track should take into account every frame in the
|
||||
// image. If this is a first-frame-only decode, our accumulated loop length
|
||||
// and first frame refresh area only includes the first frame, so it's not
|
||||
// correct and we don't record it.
|
||||
if (!IsFirstFrameDecode()) {
|
||||
mImageMetadata.SetLoopLength(mLoopLength);
|
||||
mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea);
|
||||
}
|
||||
|
||||
mProgress |= FLAG_DECODE_COMPLETE;
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::PostError()
|
||||
{
|
||||
mError = true;
|
||||
|
||||
if (mInFrame) {
|
||||
MOZ_ASSERT(mCurrentFrame);
|
||||
MOZ_ASSERT(mFrameCount > 0);
|
||||
mCurrentFrame->Abort();
|
||||
mInFrame = false;
|
||||
--mFrameCount;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
562
image/Decoder.h
Normal file
562
image/Decoder.h
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
/* -*- 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_Decoder_h
|
||||
#define mozilla_image_Decoder_h
|
||||
|
||||
#include "FrameAnimator.h"
|
||||
#include "RasterImage.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "DecodePool.h"
|
||||
#include "DecoderFlags.h"
|
||||
#include "Downscaler.h"
|
||||
#include "ImageMetadata.h"
|
||||
#include "Orientation.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "StreamingLexer.h"
|
||||
#include "SurfaceFlags.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace Telemetry {
|
||||
enum ID : uint32_t;
|
||||
} // namespace Telemetry
|
||||
|
||||
namespace image {
|
||||
|
||||
struct DecoderFinalStatus final
|
||||
{
|
||||
DecoderFinalStatus(bool aWasMetadataDecode,
|
||||
bool aFinished,
|
||||
bool aHadError,
|
||||
bool aShouldReportError)
|
||||
: mWasMetadataDecode(aWasMetadataDecode)
|
||||
, mFinished(aFinished)
|
||||
, mHadError(aHadError)
|
||||
, mShouldReportError(aShouldReportError)
|
||||
{ }
|
||||
|
||||
/// True if this was a metadata decode.
|
||||
const bool mWasMetadataDecode : 1;
|
||||
|
||||
/// True if this decoder finished, whether successfully or due to failure.
|
||||
const bool mFinished : 1;
|
||||
|
||||
/// True if this decoder encountered an error.
|
||||
const bool mHadError : 1;
|
||||
|
||||
/// True if this decoder encountered the kind of error that should be reported
|
||||
/// to the console.
|
||||
const bool mShouldReportError : 1;
|
||||
};
|
||||
|
||||
struct DecoderTelemetry final
|
||||
{
|
||||
DecoderTelemetry(Maybe<Telemetry::ID> aSpeedHistogram,
|
||||
size_t aBytesDecoded,
|
||||
uint32_t aChunkCount,
|
||||
TimeDuration aDecodeTime)
|
||||
: mSpeedHistogram(aSpeedHistogram)
|
||||
, mBytesDecoded(aBytesDecoded)
|
||||
, mChunkCount(aChunkCount)
|
||||
, mDecodeTime(aDecodeTime)
|
||||
{ }
|
||||
|
||||
/// @return our decoder's speed, in KBps.
|
||||
int32_t Speed() const
|
||||
{
|
||||
return mBytesDecoded / (1024 * mDecodeTime.ToSeconds());
|
||||
}
|
||||
|
||||
/// @return our decoder's decode time, in microseconds.
|
||||
int32_t DecodeTimeMicros() { return mDecodeTime.ToMicroseconds(); }
|
||||
|
||||
/// The per-image-format telemetry ID for recording our decoder's speed, or
|
||||
/// Nothing() if we don't record speed telemetry for this kind of decoder.
|
||||
const Maybe<Telemetry::ID> mSpeedHistogram;
|
||||
|
||||
/// The number of bytes of input our decoder processed.
|
||||
const size_t mBytesDecoded;
|
||||
|
||||
/// The number of chunks our decoder's input was divided into.
|
||||
const uint32_t mChunkCount;
|
||||
|
||||
/// The amount of time our decoder spent inside DoDecode().
|
||||
const TimeDuration mDecodeTime;
|
||||
};
|
||||
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
|
||||
|
||||
explicit Decoder(RasterImage* aImage);
|
||||
|
||||
/**
|
||||
* Initialize an image decoder. Decoders may not be re-initialized.
|
||||
*
|
||||
* @return NS_OK if the decoder could be initialized successfully.
|
||||
*/
|
||||
nsresult Init();
|
||||
|
||||
/**
|
||||
* Decodes, reading all data currently available in the SourceBuffer.
|
||||
*
|
||||
* If more data is needed and @aOnResume is non-null, Decode() will schedule
|
||||
* @aOnResume to be called when more data is available.
|
||||
*
|
||||
* @return a LexerResult which may indicate:
|
||||
* - the image has been successfully decoded (TerminalState::SUCCESS), or
|
||||
* - the image has failed to decode (TerminalState::FAILURE), or
|
||||
* - the decoder is yielding until it gets more data (Yield::NEED_MORE_DATA), or
|
||||
* - the decoder is yielding to allow the caller to access intermediate
|
||||
* output (Yield::OUTPUT_AVAILABLE).
|
||||
*/
|
||||
LexerResult Decode(IResumable* aOnResume = nullptr);
|
||||
|
||||
/**
|
||||
* Terminate this decoder in a failure state, just as if the decoder
|
||||
* implementation had returned TerminalState::FAILURE from DoDecode().
|
||||
*
|
||||
* XXX(seth): This method should be removed ASAP; it exists only because
|
||||
* RasterImage::FinalizeDecoder() requires an actual Decoder object as an
|
||||
* argument, so we can't simply tell RasterImage a decode failed except via an
|
||||
* intervening decoder. We'll fix this in bug 1291071.
|
||||
*/
|
||||
LexerResult TerminateFailure();
|
||||
|
||||
/**
|
||||
* Given a maximum number of bytes we're willing to decode, @aByteLimit,
|
||||
* returns true if we should attempt to run this decoder synchronously.
|
||||
*/
|
||||
bool ShouldSyncDecode(size_t aByteLimit);
|
||||
|
||||
/**
|
||||
* Gets the invalidation region accumulated by the decoder so far, and clears
|
||||
* the decoder's invalidation region. This means that each call to
|
||||
* TakeInvalidRect() returns only the invalidation region accumulated since
|
||||
* the last call to TakeInvalidRect().
|
||||
*/
|
||||
nsIntRect TakeInvalidRect()
|
||||
{
|
||||
nsIntRect invalidRect = mInvalidRect;
|
||||
mInvalidRect.SetEmpty();
|
||||
return invalidRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress changes accumulated by the decoder so far, and clears
|
||||
* them. This means that each call to TakeProgress() returns only the changes
|
||||
* accumulated since the last call to TakeProgress().
|
||||
*/
|
||||
Progress TakeProgress()
|
||||
{
|
||||
Progress progress = mProgress;
|
||||
mProgress = NoProgress;
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there's any progress to report.
|
||||
*/
|
||||
bool HasProgress() const
|
||||
{
|
||||
return mProgress != NoProgress || !mInvalidRect.IsEmpty() || mFinishedNewFrame;
|
||||
}
|
||||
|
||||
/*
|
||||
* State.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If we're doing a metadata decode, we only decode the image's headers, which
|
||||
* is enough to determine the image's intrinsic size. A metadata decode is
|
||||
* enabled by calling SetMetadataDecode() *before* calling Init().
|
||||
*/
|
||||
void SetMetadataDecode(bool aMetadataDecode)
|
||||
{
|
||||
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
|
||||
mMetadataDecode = aMetadataDecode;
|
||||
}
|
||||
bool IsMetadataDecode() const { return mMetadataDecode; }
|
||||
|
||||
/**
|
||||
* Sets the output size of this decoder. If this is smaller than the intrinsic
|
||||
* size of the image, we'll downscale it while decoding. For memory usage
|
||||
* reasons, upscaling is forbidden and will trigger assertions in debug
|
||||
* builds.
|
||||
*
|
||||
* Not calling SetOutputSize() means that we should just decode at the
|
||||
* intrinsic size, whatever it is.
|
||||
*
|
||||
* If SetOutputSize() was called, ExplicitOutputSize() can be used to
|
||||
* determine the value that was passed to it.
|
||||
*
|
||||
* This must be called before Init() is called.
|
||||
*/
|
||||
void SetOutputSize(const gfx::IntSize& aSize);
|
||||
|
||||
/**
|
||||
* @return the output size of this decoder. If this is smaller than the
|
||||
* intrinsic size, then the image will be downscaled during the decoding
|
||||
* process.
|
||||
*
|
||||
* Illegal to call if HasSize() returns false.
|
||||
*/
|
||||
gfx::IntSize OutputSize() const { MOZ_ASSERT(HasSize()); return *mOutputSize; }
|
||||
|
||||
/**
|
||||
* @return either the size passed to SetOutputSize() or Nothing(), indicating
|
||||
* that SetOutputSize() was not explicitly called.
|
||||
*/
|
||||
Maybe<gfx::IntSize> ExplicitOutputSize() const;
|
||||
|
||||
/**
|
||||
* Set the requested sample size for this decoder. Used to implement the
|
||||
* -moz-sample-size media fragment.
|
||||
*
|
||||
* XXX(seth): Support for -moz-sample-size will be removed in bug 1120056.
|
||||
*/
|
||||
virtual void SetSampleSize(int aSampleSize) { }
|
||||
|
||||
/**
|
||||
* Set an iterator to the SourceBuffer which will feed data to this decoder.
|
||||
* This must always be called before calling Init(). (And only before Init().)
|
||||
*
|
||||
* XXX(seth): We should eliminate this method and pass a SourceBufferIterator
|
||||
* to the various decoder constructors instead.
|
||||
*/
|
||||
void SetIterator(SourceBufferIterator&& aIterator)
|
||||
{
|
||||
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
|
||||
mIterator.emplace(Move(aIterator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this decoder send partial invalidations?
|
||||
*/
|
||||
bool ShouldSendPartialInvalidations() const
|
||||
{
|
||||
return !(mDecoderFlags & DecoderFlags::IS_REDECODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we stop decoding after the first frame?
|
||||
*/
|
||||
bool IsFirstFrameDecode() const
|
||||
{
|
||||
return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of complete animation frames which have been decoded so
|
||||
* far, if it has changed since the last call to TakeCompleteFrameCount();
|
||||
* otherwise, returns Nothing().
|
||||
*/
|
||||
Maybe<uint32_t> TakeCompleteFrameCount();
|
||||
|
||||
// The number of frames we have, including anything in-progress. Thus, this
|
||||
// is only 0 if we haven't begun any frames.
|
||||
uint32_t GetFrameCount() { return mFrameCount; }
|
||||
|
||||
// Did we discover that the image we're decoding is animated?
|
||||
bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
|
||||
|
||||
// Error tracking
|
||||
bool HasError() const { return mError; }
|
||||
bool ShouldReportError() const { return mShouldReportError; }
|
||||
|
||||
/// Did we finish decoding enough that calling Decode() again would be useless?
|
||||
bool GetDecodeDone() const
|
||||
{
|
||||
return mReachedTerminalState || mDecodeDone ||
|
||||
(mMetadataDecode && HasSize()) || HasError();
|
||||
}
|
||||
|
||||
/// Are we in the middle of a frame right now? Used for assertions only.
|
||||
bool InFrame() const { return mInFrame; }
|
||||
|
||||
enum DecodeStyle {
|
||||
PROGRESSIVE, // produce intermediate frames representing the partial
|
||||
// state of the image
|
||||
SEQUENTIAL // decode to final image immediately
|
||||
};
|
||||
|
||||
/**
|
||||
* 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; }
|
||||
|
||||
/// @return true if we know the intrinsic size of the image we're decoding.
|
||||
bool HasSize() const { return mImageMetadata.HasSize(); }
|
||||
|
||||
/**
|
||||
* @return the intrinsic size of the image we're decoding.
|
||||
*
|
||||
* Illegal to call if HasSize() returns false.
|
||||
*/
|
||||
gfx::IntSize Size() const
|
||||
{
|
||||
MOZ_ASSERT(HasSize());
|
||||
return mImageMetadata.GetSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an IntRect which covers the entire area of this image at its
|
||||
* intrinsic size, appropriate for use as a frame rect when the image itself
|
||||
* does not specify one.
|
||||
*
|
||||
* Illegal to call if HasSize() returns false.
|
||||
*/
|
||||
gfx::IntRect FullFrame() const
|
||||
{
|
||||
return gfx::IntRect(gfx::IntPoint(), Size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an IntRect which covers the entire area of this image at its size
|
||||
* after scaling - that is, at its output size.
|
||||
*
|
||||
* XXX(seth): This is only used for decoders which are using the old
|
||||
* Downscaler code instead of SurfacePipe, since the old AllocateFrame() and
|
||||
* Downscaler APIs required that the frame rect be specified in output space.
|
||||
* We should remove this once all decoders use SurfacePipe.
|
||||
*
|
||||
* Illegal to call if HasSize() returns false.
|
||||
*/
|
||||
gfx::IntRect FullOutputFrame() const
|
||||
{
|
||||
return gfx::IntRect(gfx::IntPoint(), OutputSize());
|
||||
}
|
||||
|
||||
/// @return final status information about this decoder. Should be called
|
||||
/// after we decide we're not going to run the decoder anymore.
|
||||
DecoderFinalStatus FinalStatus() const;
|
||||
|
||||
/// @return the metadata we collected about this image while decoding.
|
||||
const ImageMetadata& GetImageMetadata() { return mImageMetadata; }
|
||||
|
||||
/// @return performance telemetry we collected while decoding.
|
||||
DecoderTelemetry Telemetry() const;
|
||||
|
||||
/**
|
||||
* @return a weak pointer to the image associated with this decoder. Illegal
|
||||
* to call if this decoder is not associated with an image.
|
||||
*/
|
||||
NotNull<RasterImage*> GetImage() const { return WrapNotNull(mImage.get()); }
|
||||
|
||||
/**
|
||||
* @return a possibly-null weak pointer to the image associated with this
|
||||
* decoder. May be called even if this decoder is not associated with an
|
||||
* image.
|
||||
*/
|
||||
RasterImage* GetImageMaybeNull() const { return mImage.get(); }
|
||||
|
||||
RawAccessFrameRef GetCurrentFrameRef()
|
||||
{
|
||||
return mCurrentFrame ? mCurrentFrame->RawAccessRef()
|
||||
: RawAccessFrameRef();
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
friend class AutoRecordDecoderTelemetry;
|
||||
friend class nsICODecoder;
|
||||
friend class PalettedSurfaceSink;
|
||||
friend class SurfaceSink;
|
||||
|
||||
virtual ~Decoder();
|
||||
|
||||
/*
|
||||
* Internal hooks. Decoder implementations may override these and
|
||||
* only these methods.
|
||||
*
|
||||
* BeforeFinishInternal() can be used to detect if decoding is in an
|
||||
* incomplete state, e.g. due to file truncation, in which case it should
|
||||
* return a failing nsresult.
|
||||
*/
|
||||
virtual nsresult InitInternal();
|
||||
virtual LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||
IResumable* aOnResume) = 0;
|
||||
virtual nsresult BeforeFinishInternal();
|
||||
virtual nsresult FinishInternal();
|
||||
virtual nsresult FinishWithErrorInternal();
|
||||
|
||||
/**
|
||||
* @return the per-image-format telemetry ID for recording this decoder's
|
||||
* speed, or Nothing() if we don't record speed telemetry for this kind of
|
||||
* decoder.
|
||||
*/
|
||||
virtual Maybe<Telemetry::ID> SpeedHistogram() const { return Nothing(); }
|
||||
|
||||
|
||||
/*
|
||||
* Progress notifications.
|
||||
*/
|
||||
|
||||
// Called by decoders when they determine the size of the image. Informs
|
||||
// the image of its size and sends notifications.
|
||||
void PostSize(int32_t aWidth,
|
||||
int32_t aHeight,
|
||||
Orientation aOrientation = Orientation());
|
||||
|
||||
// Called by decoders if they determine that the image has transparency.
|
||||
//
|
||||
// This should be fired as early as possible to allow observers to do things
|
||||
// that affect content, so it's necessarily pessimistic - if there's a
|
||||
// possibility that the image has transparency, for example because its header
|
||||
// specifies that it has an alpha channel, we fire PostHasTransparency
|
||||
// immediately. PostFrameStop's aFrameOpacity argument, on the other hand, is
|
||||
// only used internally to ImageLib. Because PostFrameStop isn't delivered
|
||||
// until the entire frame has been decoded, decoders may take into account the
|
||||
// actual contents of the frame and give a more accurate result.
|
||||
void PostHasTransparency();
|
||||
|
||||
// Called by decoders if they determine that the image is animated.
|
||||
//
|
||||
// @param aTimeout The time for which the first frame should be shown before
|
||||
// we advance to the next frame.
|
||||
void PostIsAnimated(FrameTimeout aFirstFrameTimeout);
|
||||
|
||||
// Called by decoders when they end a frame. Informs the image, sends
|
||||
// notifications, and does internal book-keeping.
|
||||
// Specify whether this frame is opaque as an optimization.
|
||||
// For animated images, specify the disposal, blend method and timeout for
|
||||
// this frame.
|
||||
void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
|
||||
DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
|
||||
FrameTimeout aTimeout = FrameTimeout::Forever(),
|
||||
BlendMethod aBlendMethod = BlendMethod::OVER,
|
||||
const Maybe<nsIntRect>& aBlendRect = Nothing());
|
||||
|
||||
/**
|
||||
* Called by the decoders when they have a region to invalidate. We may not
|
||||
* actually pass these invalidations on right away.
|
||||
*
|
||||
* @param aRect The invalidation rect in the coordinate system of the unscaled
|
||||
* image (that is, the image at its intrinsic size).
|
||||
* @param aRectAtOutputSize If not Nothing(), the invalidation rect in the
|
||||
* coordinate system of the scaled image (that is,
|
||||
* the image at our output size). This must
|
||||
* be supplied if we're downscaling during decode.
|
||||
*/
|
||||
void PostInvalidation(const gfx::IntRect& aRect,
|
||||
const Maybe<gfx::IntRect>& aRectAtOutputSize = Nothing());
|
||||
|
||||
// Called by the decoders when they have successfully decoded the image. This
|
||||
// may occur as the result of the decoder getting to the appropriate point in
|
||||
// the stream, or by us calling FinishInternal().
|
||||
//
|
||||
// May not be called mid-frame.
|
||||
//
|
||||
// For animated images, specify the loop count. -1 means loop forever, 0
|
||||
// means a single iteration, stopping on the last frame.
|
||||
void PostDecodeDone(int32_t aLoopCount = 0);
|
||||
|
||||
/**
|
||||
* Allocates a new frame, making it our current frame if successful.
|
||||
*
|
||||
* The @aFrameNum parameter only exists as a sanity check; it's illegal to
|
||||
* create a new frame anywhere but immediately after the existing frames.
|
||||
*
|
||||
* If a non-paletted frame is desired, pass 0 for aPaletteDepth.
|
||||
*/
|
||||
nsresult AllocateFrame(uint32_t aFrameNum,
|
||||
const gfx::IntSize& aOutputSize,
|
||||
const gfx::IntRect& aFrameRect,
|
||||
gfx::SurfaceFormat aFormat,
|
||||
uint8_t aPaletteDepth = 0);
|
||||
|
||||
private:
|
||||
/// Report that an error was encountered while decoding.
|
||||
void PostError();
|
||||
|
||||
/**
|
||||
* CompleteDecode() finishes up the decoding process after Decode() determines
|
||||
* that we're finished. It records final progress and does all the cleanup
|
||||
* that's possible off-main-thread.
|
||||
*/
|
||||
void CompleteDecode();
|
||||
|
||||
/// @return the number of complete frames we have. Does not include the
|
||||
/// current frame if it's unfinished.
|
||||
uint32_t GetCompleteFrameCount()
|
||||
{
|
||||
if (mFrameCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mInFrame ? mFrameCount - 1 : mFrameCount;
|
||||
}
|
||||
|
||||
RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
|
||||
const gfx::IntSize& aOutputSize,
|
||||
const gfx::IntRect& aFrameRect,
|
||||
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
|
||||
uint32_t mColormapSize;
|
||||
|
||||
private:
|
||||
RefPtr<RasterImage> mImage;
|
||||
Maybe<SourceBufferIterator> mIterator;
|
||||
RawAccessFrameRef mCurrentFrame;
|
||||
ImageMetadata mImageMetadata;
|
||||
gfx::IntRect mInvalidRect; // Tracks an invalidation region in the current frame.
|
||||
Maybe<gfx::IntSize> mOutputSize; // The size of our output surface.
|
||||
Progress mProgress;
|
||||
|
||||
uint32_t mFrameCount; // Number of frames, including anything in-progress
|
||||
FrameTimeout mLoopLength; // Length of a single loop of this image.
|
||||
gfx::IntRect mFirstFrameRefreshArea; // The area of the image that needs to
|
||||
// be invalidated when the animation loops.
|
||||
|
||||
// Telemetry data for this decoder.
|
||||
TimeDuration mDecodeTime;
|
||||
|
||||
DecoderFlags mDecoderFlags;
|
||||
SurfaceFlags mSurfaceFlags;
|
||||
|
||||
bool mInitialized : 1;
|
||||
bool mMetadataDecode : 1;
|
||||
bool mHaveExplicitOutputSize : 1;
|
||||
bool mInFrame : 1;
|
||||
bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since
|
||||
// the last call to TakeCompleteFrameCount().
|
||||
bool mReachedTerminalState : 1;
|
||||
bool mDecodeDone : 1;
|
||||
bool mError : 1;
|
||||
bool mShouldReportError : 1;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_Decoder_h
|
||||
350
image/DecoderFactory.cpp
Normal file
350
image/DecoderFactory.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
/* -*- 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 "DecoderFactory.h"
|
||||
|
||||
#include "nsMimeTypes.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include "AnimationSurfaceProvider.h"
|
||||
#include "Decoder.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "nsPNGDecoder.h"
|
||||
#include "nsGIFDecoder2.h"
|
||||
#include "nsJPEGDecoder.h"
|
||||
#include "nsBMPDecoder.h"
|
||||
#include "nsICODecoder.h"
|
||||
#include "nsIconDecoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
namespace image {
|
||||
|
||||
/* static */ DecoderType
|
||||
DecoderFactory::GetDecoderType(const char* aMimeType)
|
||||
{
|
||||
// By default we don't know.
|
||||
DecoderType type = DecoderType::UNKNOWN;
|
||||
|
||||
// PNG
|
||||
if (!strcmp(aMimeType, IMAGE_PNG)) {
|
||||
type = DecoderType::PNG;
|
||||
} else if (!strcmp(aMimeType, IMAGE_X_PNG)) {
|
||||
type = DecoderType::PNG;
|
||||
} else if (!strcmp(aMimeType, IMAGE_APNG)) {
|
||||
type = DecoderType::PNG;
|
||||
|
||||
// GIF
|
||||
} else if (!strcmp(aMimeType, IMAGE_GIF)) {
|
||||
type = DecoderType::GIF;
|
||||
|
||||
// JPEG
|
||||
} else if (!strcmp(aMimeType, IMAGE_JPEG)) {
|
||||
type = DecoderType::JPEG;
|
||||
} else if (!strcmp(aMimeType, IMAGE_PJPEG)) {
|
||||
type = DecoderType::JPEG;
|
||||
} else if (!strcmp(aMimeType, IMAGE_JPG)) {
|
||||
type = DecoderType::JPEG;
|
||||
|
||||
// BMP
|
||||
} else if (!strcmp(aMimeType, IMAGE_BMP)) {
|
||||
type = DecoderType::BMP;
|
||||
} else if (!strcmp(aMimeType, IMAGE_BMP_MS)) {
|
||||
type = DecoderType::BMP;
|
||||
|
||||
// ICO
|
||||
} else if (!strcmp(aMimeType, IMAGE_ICO)) {
|
||||
type = DecoderType::ICO;
|
||||
} else if (!strcmp(aMimeType, IMAGE_ICO_MS)) {
|
||||
type = DecoderType::ICO;
|
||||
|
||||
// Icon
|
||||
} else if (!strcmp(aMimeType, IMAGE_ICON_MS)) {
|
||||
type = DecoderType::ICON;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Decoder>
|
||||
DecoderFactory::GetDecoder(DecoderType aType,
|
||||
RasterImage* aImage,
|
||||
bool aIsRedecode)
|
||||
{
|
||||
RefPtr<Decoder> decoder;
|
||||
|
||||
switch (aType) {
|
||||
case DecoderType::PNG:
|
||||
decoder = new nsPNGDecoder(aImage);
|
||||
break;
|
||||
case DecoderType::GIF:
|
||||
decoder = new nsGIFDecoder2(aImage);
|
||||
break;
|
||||
case DecoderType::JPEG:
|
||||
// If we have all the data we don't want to waste cpu time doing
|
||||
// a progressive decode.
|
||||
decoder = new nsJPEGDecoder(aImage,
|
||||
aIsRedecode ? Decoder::SEQUENTIAL
|
||||
: Decoder::PROGRESSIVE);
|
||||
break;
|
||||
case DecoderType::BMP:
|
||||
decoder = new nsBMPDecoder(aImage);
|
||||
break;
|
||||
case DecoderType::ICO:
|
||||
decoder = new nsICODecoder(aImage);
|
||||
break;
|
||||
case DecoderType::ICON:
|
||||
decoder = new nsIconDecoder(aImage);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
|
||||
}
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<IDecodingTask>
|
||||
DecoderFactory::CreateDecoder(DecoderType aType,
|
||||
NotNull<RasterImage*> aImage,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
const IntSize& aIntrinsicSize,
|
||||
const IntSize& aOutputSize,
|
||||
DecoderFlags aDecoderFlags,
|
||||
SurfaceFlags aSurfaceFlags,
|
||||
int aSampleSize)
|
||||
{
|
||||
if (aType == DecoderType::UNKNOWN) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create an anonymous decoder. Interaction with the SurfaceCache and the
|
||||
// owning RasterImage will be mediated by DecodedSurfaceProvider.
|
||||
RefPtr<Decoder> decoder =
|
||||
GetDecoder(aType, nullptr, bool(aDecoderFlags & DecoderFlags::IS_REDECODE));
|
||||
MOZ_ASSERT(decoder, "Should have a decoder now");
|
||||
|
||||
// Initialize the decoder.
|
||||
decoder->SetMetadataDecode(false);
|
||||
decoder->SetIterator(aSourceBuffer->Iterator());
|
||||
decoder->SetOutputSize(aOutputSize);
|
||||
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY);
|
||||
decoder->SetSurfaceFlags(aSurfaceFlags);
|
||||
decoder->SetSampleSize(aSampleSize);
|
||||
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a DecodedSurfaceProvider which will manage the decoding process and
|
||||
// make this decoder's output available in the surface cache.
|
||||
SurfaceKey surfaceKey =
|
||||
RasterSurfaceKey(aOutputSize, aSurfaceFlags, PlaybackType::eStatic);
|
||||
NotNull<RefPtr<DecodedSurfaceProvider>> provider =
|
||||
WrapNotNull(new DecodedSurfaceProvider(aImage,
|
||||
surfaceKey,
|
||||
WrapNotNull(decoder)));
|
||||
|
||||
// Attempt to insert the surface provider into the surface cache right away so
|
||||
// we won't trigger any more decoders with the same parameters.
|
||||
if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Return the surface provider in its IDecodingTask guise.
|
||||
RefPtr<IDecodingTask> task = provider.get();
|
||||
return task.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<IDecodingTask>
|
||||
DecoderFactory::CreateAnimationDecoder(DecoderType aType,
|
||||
NotNull<RasterImage*> aImage,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
const IntSize& aIntrinsicSize,
|
||||
DecoderFlags aDecoderFlags,
|
||||
SurfaceFlags aSurfaceFlags)
|
||||
{
|
||||
if (aType == DecoderType::UNKNOWN) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
|
||||
"Calling CreateAnimationDecoder for non-animating DecoderType");
|
||||
|
||||
// Create an anonymous decoder. Interaction with the SurfaceCache and the
|
||||
// owning RasterImage will be mediated by AnimationSurfaceProvider.
|
||||
RefPtr<Decoder> decoder = GetDecoder(aType, nullptr, /* aIsRedecode = */ true);
|
||||
MOZ_ASSERT(decoder, "Should have a decoder now");
|
||||
|
||||
// Initialize the decoder.
|
||||
decoder->SetMetadataDecode(false);
|
||||
decoder->SetIterator(aSourceBuffer->Iterator());
|
||||
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE);
|
||||
decoder->SetSurfaceFlags(aSurfaceFlags);
|
||||
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create an AnimationSurfaceProvider which will manage the decoding process
|
||||
// and make this decoder's output available in the surface cache.
|
||||
SurfaceKey surfaceKey =
|
||||
RasterSurfaceKey(aIntrinsicSize, aSurfaceFlags, PlaybackType::eAnimated);
|
||||
NotNull<RefPtr<AnimationSurfaceProvider>> provider =
|
||||
WrapNotNull(new AnimationSurfaceProvider(aImage,
|
||||
surfaceKey,
|
||||
WrapNotNull(decoder)));
|
||||
|
||||
// Attempt to insert the surface provider into the surface cache right away so
|
||||
// we won't trigger any more decoders with the same parameters.
|
||||
if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Return the surface provider in its IDecodingTask guise.
|
||||
RefPtr<IDecodingTask> task = provider.get();
|
||||
return task.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<IDecodingTask>
|
||||
DecoderFactory::CreateMetadataDecoder(DecoderType aType,
|
||||
NotNull<RasterImage*> aImage,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
int aSampleSize)
|
||||
{
|
||||
if (aType == DecoderType::UNKNOWN) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Decoder> decoder =
|
||||
GetDecoder(aType, aImage, /* aIsRedecode = */ false);
|
||||
MOZ_ASSERT(decoder, "Should have a decoder now");
|
||||
|
||||
// Initialize the decoder.
|
||||
decoder->SetMetadataDecode(true);
|
||||
decoder->SetIterator(aSourceBuffer->Iterator());
|
||||
decoder->SetSampleSize(aSampleSize);
|
||||
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<IDecodingTask> task = new MetadataDecodingTask(WrapNotNull(decoder));
|
||||
return task.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Decoder>
|
||||
DecoderFactory::CreateDecoderForICOResource(DecoderType aType,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
NotNull<nsICODecoder*> aICODecoder,
|
||||
const Maybe<uint32_t>& aDataOffset
|
||||
/* = Nothing() */)
|
||||
{
|
||||
// Create the decoder.
|
||||
RefPtr<Decoder> decoder;
|
||||
switch (aType) {
|
||||
case DecoderType::BMP:
|
||||
MOZ_ASSERT(aDataOffset);
|
||||
decoder = new nsBMPDecoder(aICODecoder->GetImageMaybeNull(), *aDataOffset);
|
||||
break;
|
||||
|
||||
case DecoderType::PNG:
|
||||
MOZ_ASSERT(!aDataOffset);
|
||||
decoder = new nsPNGDecoder(aICODecoder->GetImageMaybeNull());
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Invalid ICO resource decoder type");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(decoder);
|
||||
|
||||
// Initialize the decoder, copying settings from @aICODecoder.
|
||||
MOZ_ASSERT(!aICODecoder->IsMetadataDecode());
|
||||
decoder->SetMetadataDecode(aICODecoder->IsMetadataDecode());
|
||||
decoder->SetIterator(aSourceBuffer->Iterator());
|
||||
decoder->SetOutputSize(aICODecoder->OutputSize());
|
||||
decoder->SetDecoderFlags(aICODecoder->GetDecoderFlags());
|
||||
decoder->SetSurfaceFlags(aICODecoder->GetSurfaceFlags());
|
||||
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Decoder>
|
||||
DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
const Maybe<IntSize>& aOutputSize,
|
||||
SurfaceFlags aSurfaceFlags)
|
||||
{
|
||||
if (aType == DecoderType::UNKNOWN) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Decoder> decoder =
|
||||
GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
|
||||
MOZ_ASSERT(decoder, "Should have a decoder now");
|
||||
|
||||
// Initialize the decoder.
|
||||
decoder->SetMetadataDecode(false);
|
||||
decoder->SetIterator(aSourceBuffer->Iterator());
|
||||
|
||||
// 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.
|
||||
decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY;
|
||||
|
||||
decoder->SetDecoderFlags(decoderFlags);
|
||||
decoder->SetSurfaceFlags(aSurfaceFlags);
|
||||
|
||||
// Set an output size for downscale-during-decode if requested.
|
||||
if (aOutputSize) {
|
||||
decoder->SetOutputSize(*aOutputSize);
|
||||
}
|
||||
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Decoder>
|
||||
DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
|
||||
NotNull<SourceBuffer*> aSourceBuffer)
|
||||
{
|
||||
if (aType == DecoderType::UNKNOWN) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Decoder> decoder =
|
||||
GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
|
||||
MOZ_ASSERT(decoder, "Should have a decoder now");
|
||||
|
||||
// Initialize the decoder.
|
||||
decoder->SetMetadataDecode(true);
|
||||
decoder->SetIterator(aSourceBuffer->Iterator());
|
||||
decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY);
|
||||
|
||||
if (NS_FAILED(decoder->Init())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
193
image/DecoderFactory.h
Normal file
193
image/DecoderFactory.h
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/* -*- 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_DecoderFactory_h
|
||||
#define mozilla_image_DecoderFactory_h
|
||||
|
||||
#include "DecoderFlags.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "SurfaceFlags.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
class IDecodingTask;
|
||||
class nsICODecoder;
|
||||
class RasterImage;
|
||||
class SourceBuffer;
|
||||
|
||||
/**
|
||||
* The type of decoder; this is usually determined from a MIME type using
|
||||
* DecoderFactory::GetDecoderType().
|
||||
*/
|
||||
enum class DecoderType
|
||||
{
|
||||
PNG,
|
||||
GIF,
|
||||
JPEG,
|
||||
BMP,
|
||||
ICO,
|
||||
ICON,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class DecoderFactory
|
||||
{
|
||||
public:
|
||||
/// @return the type of decoder which is appropriate for @aMimeType.
|
||||
static DecoderType GetDecoderType(const char* aMimeType);
|
||||
|
||||
/**
|
||||
* Creates and initializes a decoder for non-animated images of type @aType.
|
||||
* (If the image *is* animated, only the first frame will be decoded.) The
|
||||
* decoder will send notifications to @aImage.
|
||||
*
|
||||
* @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.
|
||||
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
|
||||
* from.
|
||||
* @param aIntrinsicSize The intrinsic size of the image, normally obtained
|
||||
* during the metadata decode.
|
||||
* @param aOutputSize The output size for the decoder. If this is smaller than
|
||||
* the intrinsic size, the decoder will downscale the
|
||||
* image.
|
||||
* @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).
|
||||
*/
|
||||
static already_AddRefed<IDecodingTask>
|
||||
CreateDecoder(DecoderType aType,
|
||||
NotNull<RasterImage*> aImage,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
const gfx::IntSize& aIntrinsicSize,
|
||||
const gfx::IntSize& aOutputSize,
|
||||
DecoderFlags aDecoderFlags,
|
||||
SurfaceFlags aSurfaceFlags,
|
||||
int aSampleSize);
|
||||
|
||||
/**
|
||||
* Creates and initializes a decoder for animated images of type @aType.
|
||||
* The decoder will send notifications to @aImage.
|
||||
*
|
||||
* @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.
|
||||
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
|
||||
* from.
|
||||
* @param aIntrinsicSize The intrinsic size of the image, normally obtained
|
||||
* during the metadata decode.
|
||||
* @param aDecoderFlags Flags specifying the behavior of this decoder.
|
||||
* @param aSurfaceFlags Flags specifying the type of output this decoder
|
||||
* should produce.
|
||||
*/
|
||||
static already_AddRefed<IDecodingTask>
|
||||
CreateAnimationDecoder(DecoderType aType,
|
||||
NotNull<RasterImage*> aImage,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
const gfx::IntSize& aIntrinsicSize,
|
||||
DecoderFlags aDecoderFlags,
|
||||
SurfaceFlags aSurfaceFlags);
|
||||
|
||||
/**
|
||||
* Creates and initializes a metadata decoder of type @aType. This decoder
|
||||
* will only decode the image's header, extracting metadata like the size of
|
||||
* the image. No actual image data will be decoded and no surfaces will be
|
||||
* allocated. The decoder will send notifications to @aImage.
|
||||
*
|
||||
* @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.
|
||||
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
|
||||
* from.
|
||||
* @param aSampleSize The sample size requested using #-moz-samplesize (or 0
|
||||
* if none).
|
||||
*/
|
||||
static already_AddRefed<IDecodingTask>
|
||||
CreateMetadataDecoder(DecoderType aType,
|
||||
NotNull<RasterImage*> aImage,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
int aSampleSize);
|
||||
|
||||
/**
|
||||
* Creates and initializes a decoder for an ICO resource, which may be either
|
||||
* a BMP or PNG image.
|
||||
*
|
||||
* @param aType Which type of decoder to create. This must be either BMP or
|
||||
* PNG.
|
||||
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
|
||||
* from.
|
||||
* @param aICODecoder The ICO decoder which is controlling this resource
|
||||
* decoder. @aICODecoder's settings will be copied to the
|
||||
* resource decoder, so the two decoders will have the
|
||||
* same decoder flags, surface flags, target size, and
|
||||
* other parameters.
|
||||
* @param aDataOffset If @aType is BMP, specifies the offset at which data
|
||||
* begins in the BMP resource. Must be Some() if and only
|
||||
* if @aType is BMP.
|
||||
*/
|
||||
static already_AddRefed<Decoder>
|
||||
CreateDecoderForICOResource(DecoderType aType,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
NotNull<nsICODecoder*> aICODecoder,
|
||||
const Maybe<uint32_t>& aDataOffset = Nothing());
|
||||
|
||||
/**
|
||||
* Creates and initializes an anonymous decoder (one which isn't associated
|
||||
* with an Image object). Only the first frame of the image will be decoded.
|
||||
*
|
||||
* @param aType Which type of decoder to create - JPEG, PNG, etc.
|
||||
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
|
||||
* from.
|
||||
* @param aOutputSize If Some(), the output size for the decoder. If this is
|
||||
* smaller than the intrinsic size, the decoder will
|
||||
* downscale the image. If Nothing(), the output size will
|
||||
* be the intrinsic size.
|
||||
* @param aSurfaceFlags Flags specifying the type of output this decoder
|
||||
* should produce.
|
||||
*/
|
||||
static already_AddRefed<Decoder>
|
||||
CreateAnonymousDecoder(DecoderType aType,
|
||||
NotNull<SourceBuffer*> aSourceBuffer,
|
||||
const Maybe<gfx::IntSize>& aOutputSize,
|
||||
SurfaceFlags aSurfaceFlags);
|
||||
|
||||
/**
|
||||
* Creates and initializes an anonymous metadata decoder (one which isn't
|
||||
* associated with an Image object). This decoder will only decode the image's
|
||||
* header, extracting metadata like the size of the image. No actual image
|
||||
* data will be decoded and no surfaces will be allocated.
|
||||
*
|
||||
* @param aType Which type of decoder to create - JPEG, PNG, etc.
|
||||
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
|
||||
* from.
|
||||
*/
|
||||
static already_AddRefed<Decoder>
|
||||
CreateAnonymousMetadataDecoder(DecoderType aType,
|
||||
NotNull<SourceBuffer*> aSourceBuffer);
|
||||
|
||||
private:
|
||||
virtual ~DecoderFactory() = 0;
|
||||
|
||||
/**
|
||||
* An internal method which allocates a new decoder of the requested @aType.
|
||||
*/
|
||||
static already_AddRefed<Decoder> GetDecoder(DecoderType aType,
|
||||
RasterImage* aImage,
|
||||
bool aIsRedecode);
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DecoderFactory_h
|
||||
42
image/DecoderFlags.h
Normal file
42
image/DecoderFlags.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_image_DecoderFlags_h
|
||||
#define mozilla_image_DecoderFlags_h
|
||||
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* Flags that influence decoder behavior. Note that these flags *don't*
|
||||
* influence the logical content of the surfaces that the decoder generates, so
|
||||
* they're not in a factor in SurfaceCache lookups and the like. These flags
|
||||
* instead either influence which surfaces are generated at all or the tune the
|
||||
* decoder's behavior for a particular scenario.
|
||||
*/
|
||||
enum class DecoderFlags : uint8_t
|
||||
{
|
||||
FIRST_FRAME_ONLY = 1 << 0,
|
||||
IS_REDECODE = 1 << 1,
|
||||
IMAGE_IS_TRANSIENT = 1 << 2,
|
||||
ASYNC_NOTIFY = 1 << 3
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags)
|
||||
|
||||
/**
|
||||
* @return the default set of decode flags.
|
||||
*/
|
||||
inline DecoderFlags
|
||||
DefaultDecoderFlags()
|
||||
{
|
||||
return DecoderFlags();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DecoderFlags_h
|
||||
352
image/Downscaler.cpp
Normal file
352
image/Downscaler.cpp
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
/* -*- 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"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include "gfxPrefs.h"
|
||||
#include "image_operations.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/SSE.h"
|
||||
#include "mozilla/mips.h"
|
||||
#include "convolver.h"
|
||||
#include "skia/include/core/SkTypes.h"
|
||||
|
||||
using std::max;
|
||||
using std::swap;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using gfx::IntRect;
|
||||
|
||||
namespace image {
|
||||
|
||||
Downscaler::Downscaler(const nsIntSize& aTargetSize)
|
||||
: mTargetSize(aTargetSize)
|
||||
, mOutputBuffer(nullptr)
|
||||
, mXFilter(MakeUnique<skia::ConvolutionFilter1D>())
|
||||
, mYFilter(MakeUnique<skia::ConvolutionFilter1D>())
|
||||
, mWindowCapacity(0)
|
||||
, mHasAlpha(true)
|
||||
, mFlipVertically(false)
|
||||
{
|
||||
MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
|
||||
"Downscaling even though downscale-during-decode is disabled?");
|
||||
MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0,
|
||||
"Invalid target size");
|
||||
}
|
||||
|
||||
Downscaler::~Downscaler()
|
||||
{
|
||||
ReleaseWindow();
|
||||
}
|
||||
|
||||
void
|
||||
Downscaler::ReleaseWindow()
|
||||
{
|
||||
if (!mWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < mWindowCapacity; ++i) {
|
||||
delete[] mWindow[i];
|
||||
}
|
||||
|
||||
mWindow = nullptr;
|
||||
mWindowCapacity = 0;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
|
||||
const Maybe<nsIntRect>& aFrameRect,
|
||||
uint8_t* aOutputBuffer,
|
||||
bool aHasAlpha,
|
||||
bool aFlipVertically /* = false */)
|
||||
{
|
||||
MOZ_ASSERT(aOutputBuffer);
|
||||
MOZ_ASSERT(mTargetSize != aOriginalSize,
|
||||
"Created a downscaler, but not downscaling?");
|
||||
MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width,
|
||||
"Created a downscaler, but width is larger");
|
||||
MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height,
|
||||
"Created a downscaler, but height is larger");
|
||||
MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0,
|
||||
"Invalid original size");
|
||||
|
||||
// Only downscale from reasonable sizes to avoid using too much memory/cpu
|
||||
// downscaling and decoding. 1 << 20 == 1,048,576 seems a reasonable limit.
|
||||
if (aOriginalSize.width > (1 << 20) || aOriginalSize.height > (1 << 20)) {
|
||||
NS_WARNING("Trying to downscale image frame that is too large");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize));
|
||||
MOZ_ASSERT(mFrameRect.x >= 0 && mFrameRect.y >= 0 &&
|
||||
mFrameRect.width >= 0 && mFrameRect.height >= 0,
|
||||
"Frame rect must have non-negative components");
|
||||
MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
|
||||
.Contains(mFrameRect),
|
||||
"Frame rect must fit inside image");
|
||||
MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
|
||||
.IsEqualEdges(mFrameRect),
|
||||
aHasAlpha);
|
||||
|
||||
mOriginalSize = aOriginalSize;
|
||||
mScale = gfxSize(double(mOriginalSize.width) / mTargetSize.width,
|
||||
double(mOriginalSize.height) / mTargetSize.height);
|
||||
mOutputBuffer = aOutputBuffer;
|
||||
mHasAlpha = aHasAlpha;
|
||||
mFlipVertically = aFlipVertically;
|
||||
|
||||
ReleaseWindow();
|
||||
|
||||
auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
|
||||
|
||||
skia::resize::ComputeFilters(resizeMethod,
|
||||
mOriginalSize.width, mTargetSize.width,
|
||||
0, mTargetSize.width,
|
||||
mXFilter.get());
|
||||
|
||||
if (mXFilter->max_filter() <= 0 || mXFilter->num_values() != mTargetSize.width) {
|
||||
NS_WARNING("Failed to compute filters for image downscaling");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
skia::resize::ComputeFilters(resizeMethod,
|
||||
mOriginalSize.height, mTargetSize.height,
|
||||
0, mTargetSize.height,
|
||||
mYFilter.get());
|
||||
|
||||
if (mYFilter->max_filter() <= 0 || mYFilter->num_values() != mTargetSize.height) {
|
||||
NS_WARNING("Failed to compute filters for image downscaling");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Allocate the buffer, which contains scanlines of the original image.
|
||||
// pad by 15 to handle overreads by the simd code
|
||||
size_t bufferLen = mOriginalSize.width * sizeof(uint32_t) + 15;
|
||||
mRowBuffer.reset(new (fallible) uint8_t[bufferLen]);
|
||||
if (MOZ_UNLIKELY(!mRowBuffer)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Zero buffer to keep valgrind happy.
|
||||
memset(mRowBuffer.get(), 0, bufferLen);
|
||||
|
||||
// Allocate the window, which contains horizontally downscaled scanlines. (We
|
||||
// can store scanlines which are already downscale because our downscaling
|
||||
// filter is separable.)
|
||||
mWindowCapacity = mYFilter->max_filter();
|
||||
mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
|
||||
if (MOZ_UNLIKELY(!mWindow)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bool anyAllocationFailed = false;
|
||||
// pad by 15 to handle overreads by the simd code
|
||||
const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15;
|
||||
for (int32_t i = 0; i < mWindowCapacity; ++i) {
|
||||
mWindow[i] = new (fallible) uint8_t[rowSize];
|
||||
anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(anyAllocationFailed)) {
|
||||
// We intentionally iterate through the entire array even if an allocation
|
||||
// fails, to ensure that all the pointers in it are either valid or nullptr.
|
||||
// That in turn ensures that ReleaseWindow() can clean up correctly.
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
ResetForNextProgressivePass();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
Downscaler::SkipToRow(int32_t aRow)
|
||||
{
|
||||
if (mCurrentInLine < aRow) {
|
||||
ClearRow();
|
||||
do {
|
||||
CommitRow();
|
||||
} while (mCurrentInLine < aRow);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Downscaler::ResetForNextProgressivePass()
|
||||
{
|
||||
mPrevInvalidatedLine = 0;
|
||||
mCurrentOutLine = 0;
|
||||
mCurrentInLine = 0;
|
||||
mLinesInBuffer = 0;
|
||||
|
||||
if (mFrameRect.IsEmpty()) {
|
||||
// Our frame rect is zero size; commit rows until the end of the image.
|
||||
SkipToRow(mOriginalSize.height - 1);
|
||||
} else {
|
||||
// If we have a vertical offset, commit rows to shift us past it.
|
||||
SkipToRow(mFrameRect.y);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
GetFilterOffsetAndLength(UniquePtr<skia::ConvolutionFilter1D>& aFilter,
|
||||
int32_t aOutputImagePosition,
|
||||
int32_t* aFilterOffsetOut,
|
||||
int32_t* aFilterLengthOut)
|
||||
{
|
||||
MOZ_ASSERT(aOutputImagePosition < aFilter->num_values());
|
||||
aFilter->FilterForValue(aOutputImagePosition,
|
||||
aFilterOffsetOut,
|
||||
aFilterLengthOut);
|
||||
}
|
||||
|
||||
void
|
||||
Downscaler::ClearRestOfRow(uint32_t aStartingAtCol)
|
||||
{
|
||||
MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width));
|
||||
uint32_t bytesToClear = (mOriginalSize.width - aStartingAtCol)
|
||||
* sizeof(uint32_t);
|
||||
memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)),
|
||||
0, bytesToClear);
|
||||
}
|
||||
|
||||
void
|
||||
Downscaler::CommitRow()
|
||||
{
|
||||
MOZ_ASSERT(mOutputBuffer, "Should have a current frame");
|
||||
MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input");
|
||||
|
||||
if (mCurrentOutLine < mTargetSize.height) {
|
||||
int32_t filterOffset = 0;
|
||||
int32_t filterLength = 0;
|
||||
GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
|
||||
&filterOffset, &filterLength);
|
||||
|
||||
int32_t inLineToRead = filterOffset + mLinesInBuffer;
|
||||
MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input");
|
||||
if (mCurrentInLine == inLineToRead) {
|
||||
skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter,
|
||||
mWindow[mLinesInBuffer++], mHasAlpha,
|
||||
supports_sse2() || supports_mmi());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
|
||||
"Writing past end of output");
|
||||
|
||||
while (mLinesInBuffer == filterLength) {
|
||||
DownscaleInputLine();
|
||||
|
||||
if (mCurrentOutLine == mTargetSize.height) {
|
||||
break; // We're done.
|
||||
}
|
||||
|
||||
GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
|
||||
&filterOffset, &filterLength);
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentInLine += 1;
|
||||
|
||||
// If we're at the end of the part of the original image that has data, commit
|
||||
// rows to shift us to the end.
|
||||
if (mCurrentInLine == (mFrameRect.y + mFrameRect.height)) {
|
||||
SkipToRow(mOriginalSize.height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Downscaler::HasInvalidation() const
|
||||
{
|
||||
return mCurrentOutLine > mPrevInvalidatedLine;
|
||||
}
|
||||
|
||||
DownscalerInvalidRect
|
||||
Downscaler::TakeInvalidRect()
|
||||
{
|
||||
if (MOZ_UNLIKELY(!HasInvalidation())) {
|
||||
return DownscalerInvalidRect();
|
||||
}
|
||||
|
||||
DownscalerInvalidRect invalidRect;
|
||||
|
||||
// Compute the target size invalid rect.
|
||||
if (mFlipVertically) {
|
||||
// We need to flip it. This will implicitly flip the original size invalid
|
||||
// rect, since we compute it by scaling this rect.
|
||||
invalidRect.mTargetSizeRect =
|
||||
IntRect(0, mTargetSize.height - mCurrentOutLine,
|
||||
mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
|
||||
} else {
|
||||
invalidRect.mTargetSizeRect =
|
||||
IntRect(0, mPrevInvalidatedLine,
|
||||
mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
|
||||
}
|
||||
|
||||
mPrevInvalidatedLine = mCurrentOutLine;
|
||||
|
||||
// Compute the original size invalid rect.
|
||||
invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect;
|
||||
invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.width, mScale.height);
|
||||
|
||||
return invalidRect;
|
||||
}
|
||||
|
||||
void
|
||||
Downscaler::DownscaleInputLine()
|
||||
{
|
||||
typedef skia::ConvolutionFilter1D::Fixed FilterValue;
|
||||
|
||||
MOZ_ASSERT(mOutputBuffer);
|
||||
MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
|
||||
"Writing past end of output");
|
||||
|
||||
int32_t filterOffset = 0;
|
||||
int32_t filterLength = 0;
|
||||
MOZ_ASSERT(mCurrentOutLine < mYFilter->num_values());
|
||||
auto filterValues =
|
||||
mYFilter->FilterForValue(mCurrentOutLine, &filterOffset, &filterLength);
|
||||
|
||||
int32_t currentOutLine = mFlipVertically
|
||||
? mTargetSize.height - (mCurrentOutLine + 1)
|
||||
: mCurrentOutLine;
|
||||
MOZ_ASSERT(currentOutLine >= 0);
|
||||
|
||||
uint8_t* outputLine =
|
||||
&mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)];
|
||||
skia::ConvolveVertically(static_cast<const FilterValue*>(filterValues),
|
||||
filterLength, mWindow.get(), mXFilter->num_values(),
|
||||
outputLine, mHasAlpha, supports_sse2() || supports_mmi());
|
||||
|
||||
mCurrentOutLine += 1;
|
||||
|
||||
if (mCurrentOutLine == mTargetSize.height) {
|
||||
// We're done.
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newFilterOffset = 0;
|
||||
int32_t newFilterLength = 0;
|
||||
GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
|
||||
&newFilterOffset, &newFilterLength);
|
||||
|
||||
int diff = newFilterOffset - filterOffset;
|
||||
MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
|
||||
|
||||
// Shift the buffer. We're just moving pointers here, so this is cheap.
|
||||
mLinesInBuffer -= diff;
|
||||
mLinesInBuffer = max(mLinesInBuffer, 0);
|
||||
for (int32_t i = 0; i < mLinesInBuffer; ++i) {
|
||||
swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
189
image/Downscaler.h
Normal file
189
image/Downscaler.h
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* Downscaler is a high-quality, streaming image downscaler based upon Skia's
|
||||
* scaling implementation.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_Downscaler_h
|
||||
#define mozilla_image_Downscaler_h
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "gfxPoint.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
namespace skia {
|
||||
class ConvolutionFilter1D;
|
||||
} // namespace skia
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* DownscalerInvalidRect wraps two invalidation rects: one in terms of the
|
||||
* original image size, and one in terms of the target size.
|
||||
*/
|
||||
struct DownscalerInvalidRect
|
||||
{
|
||||
nsIntRect mOriginalSizeRect;
|
||||
nsIntRect mTargetSizeRect;
|
||||
};
|
||||
|
||||
#ifdef MOZ_ENABLE_SKIA
|
||||
|
||||
/**
|
||||
* Downscaler is a high-quality, streaming image downscaler based upon Skia's
|
||||
* scaling implementation.
|
||||
*
|
||||
* Decoders can construct a Downscaler once they know their target size, then
|
||||
* call BeginFrame() for each frame they decode. They should write a decoded row
|
||||
* into the buffer returned by RowBuffer(), and then call CommitRow() to signal
|
||||
* that they have finished.
|
||||
*
|
||||
|
||||
* Because invalidations need to be computed in terms of the scaled version of
|
||||
* the image, Downscaler also tracks them. Decoders can call HasInvalidation()
|
||||
* and TakeInvalidRect() instead of tracking invalidations themselves.
|
||||
*/
|
||||
class Downscaler
|
||||
{
|
||||
public:
|
||||
/// Constructs a new Downscaler which to scale to size @aTargetSize.
|
||||
explicit Downscaler(const nsIntSize& aTargetSize);
|
||||
~Downscaler();
|
||||
|
||||
const nsIntSize& OriginalSize() const { return mOriginalSize; }
|
||||
const nsIntSize& TargetSize() const { return mTargetSize; }
|
||||
const nsIntSize FrameSize() const { return nsIntSize(mFrameRect.width, mFrameRect.height); }
|
||||
const gfxSize& Scale() const { return mScale; }
|
||||
|
||||
/**
|
||||
* Begins a new frame and reinitializes the Downscaler.
|
||||
*
|
||||
* @param aOriginalSize The original size of this frame, before scaling.
|
||||
* @param aFrameRect The region of the original image which has data.
|
||||
* Every pixel outside @aFrameRect is considered blank and
|
||||
* has zero alpha.
|
||||
* @param aOutputBuffer The buffer to which the Downscaler should write its
|
||||
* output; this is the same buffer where the Decoder
|
||||
* would write its output when not downscaling during
|
||||
* decode.
|
||||
* @param aHasAlpha Whether or not this frame has an alpha channel.
|
||||
* Performance is a little better if it doesn't have one.
|
||||
* @param aFlipVertically If true, output rows will be written to the output
|
||||
* buffer in reverse order vertically, which matches
|
||||
* the way they are stored in some image formats.
|
||||
*/
|
||||
nsresult BeginFrame(const nsIntSize& aOriginalSize,
|
||||
const Maybe<nsIntRect>& aFrameRect,
|
||||
uint8_t* aOutputBuffer,
|
||||
bool aHasAlpha,
|
||||
bool aFlipVertically = false);
|
||||
|
||||
bool IsFrameComplete() const { return mCurrentInLine >= mOriginalSize.height; }
|
||||
|
||||
/// Retrieves the buffer into which the Decoder should write each row.
|
||||
uint8_t* RowBuffer()
|
||||
{
|
||||
return mRowBuffer.get() + mFrameRect.x * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/// Clears the current row buffer.
|
||||
void ClearRow() { ClearRestOfRow(0); }
|
||||
|
||||
/// Clears the current row buffer starting at @aStartingAtCol.
|
||||
void ClearRestOfRow(uint32_t aStartingAtCol);
|
||||
|
||||
/// Signals that the decoder has finished writing a row into the row buffer.
|
||||
void CommitRow();
|
||||
|
||||
/// Returns true if there is a non-empty invalid rect available.
|
||||
bool HasInvalidation() const;
|
||||
|
||||
/// Takes the Downscaler's current invalid rect and resets it.
|
||||
DownscalerInvalidRect TakeInvalidRect();
|
||||
|
||||
/**
|
||||
* Resets the Downscaler's position in the image, for a new progressive pass
|
||||
* over the same frame. Because the same data structures can be reused, this
|
||||
* is more efficient than calling BeginFrame.
|
||||
*/
|
||||
void ResetForNextProgressivePass();
|
||||
|
||||
private:
|
||||
void DownscaleInputLine();
|
||||
void ReleaseWindow();
|
||||
void SkipToRow(int32_t aRow);
|
||||
|
||||
nsIntSize mOriginalSize;
|
||||
nsIntSize mTargetSize;
|
||||
nsIntRect mFrameRect;
|
||||
gfxSize mScale;
|
||||
|
||||
uint8_t* mOutputBuffer;
|
||||
|
||||
UniquePtr<uint8_t[]> mRowBuffer;
|
||||
UniquePtr<uint8_t*[]> mWindow;
|
||||
|
||||
UniquePtr<skia::ConvolutionFilter1D> mXFilter;
|
||||
UniquePtr<skia::ConvolutionFilter1D> mYFilter;
|
||||
|
||||
int32_t mWindowCapacity;
|
||||
|
||||
int32_t mLinesInBuffer;
|
||||
int32_t mPrevInvalidatedLine;
|
||||
int32_t mCurrentOutLine;
|
||||
int32_t mCurrentInLine;
|
||||
|
||||
bool mHasAlpha : 1;
|
||||
bool mFlipVertically : 1;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* Downscaler requires Skia to work, so we provide a dummy implementation if
|
||||
* Skia is disabled that asserts if constructed.
|
||||
*/
|
||||
|
||||
class Downscaler
|
||||
{
|
||||
public:
|
||||
explicit Downscaler(const nsIntSize&)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(false, "Skia is not enabled");
|
||||
}
|
||||
|
||||
const nsIntSize& OriginalSize() const { return nsIntSize(); }
|
||||
const nsIntSize& TargetSize() const { return nsIntSize(); }
|
||||
const gfxSize& Scale() const { return gfxSize(1.0, 1.0); }
|
||||
|
||||
nsresult BeginFrame(const nsIntSize&, const Maybe<nsIntRect>&, uint8_t*, bool, bool = false)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
bool IsFrameComplete() const { return false; }
|
||||
uint8_t* RowBuffer() { return nullptr; }
|
||||
void ClearRow() { }
|
||||
void ClearRestOfRow(uint32_t) { }
|
||||
void CommitRow() { }
|
||||
bool HasInvalidation() const { return false; }
|
||||
DownscalerInvalidRect TakeInvalidRect() { return DownscalerInvalidRect(); }
|
||||
void ResetForNextProgressivePass() { }
|
||||
const nsIntSize FrameSize() const { return nsIntSize(0, 0); }
|
||||
};
|
||||
|
||||
#endif // MOZ_ENABLE_SKIA
|
||||
|
||||
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_Downscaler_h
|
||||
380
image/DownscalingFilter.h
Normal file
380
image/DownscalingFilter.h
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* DownscalingSurfaceFilter is a SurfaceFilter implementation for use with
|
||||
* SurfacePipe which performs Lanczos downscaling.
|
||||
*
|
||||
* It's in this header file, separated from the other SurfaceFilters, because
|
||||
* some preprocessor magic is necessary to ensure that there aren't compilation
|
||||
* issues on platforms where Skia is unavailable.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_DownscalingFilter_h
|
||||
#define mozilla_image_DownscalingFilter_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/SSE.h"
|
||||
#include "mozilla/mips.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "gfxPrefs.h"
|
||||
|
||||
#ifdef MOZ_ENABLE_SKIA
|
||||
#include "convolver.h"
|
||||
#include "image_operations.h"
|
||||
#include "skia/include/core/SkTypes.h"
|
||||
#endif
|
||||
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// DownscalingFilter
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename Next> class DownscalingFilter;
|
||||
|
||||
/**
|
||||
* A configuration struct for DownscalingConfig.
|
||||
*/
|
||||
struct DownscalingConfig
|
||||
{
|
||||
template <typename Next> using Filter = DownscalingFilter<Next>;
|
||||
gfx::IntSize mInputSize; /// The size of the input image. We'll downscale
|
||||
/// from this size to the input size of the next
|
||||
/// SurfaceFilter in the chain.
|
||||
gfx::SurfaceFormat mFormat; /// The pixel format - BGRA or BGRX. (BGRX has
|
||||
/// slightly better performance.)
|
||||
};
|
||||
|
||||
#ifndef MOZ_ENABLE_SKIA
|
||||
|
||||
/**
|
||||
* DownscalingFilter requires Skia. This is a fallback implementation for
|
||||
* non-Skia builds that fails when Configure() is called (which will prevent
|
||||
* SurfacePipeFactory from returning an instance of it) and crashes if a caller
|
||||
* manually constructs an instance and attempts to actually use it. Callers
|
||||
* should avoid this by ensuring that they do not request downscaling in
|
||||
* non-Skia builds.
|
||||
*/
|
||||
template <typename Next>
|
||||
class DownscalingFilter final : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
|
||||
|
||||
template <typename... Rest>
|
||||
nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override { MOZ_CRASH(); return nullptr; }
|
||||
uint8_t* DoAdvanceRow() override { MOZ_CRASH(); return nullptr; }
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* DownscalingFilter performs Lanczos downscaling, taking image input data at one size
|
||||
* and outputting it rescaled to a different size.
|
||||
*
|
||||
* The 'Next' template parameter specifies the next filter in the chain.
|
||||
*/
|
||||
template <typename Next>
|
||||
class DownscalingFilter final : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
DownscalingFilter()
|
||||
: mXFilter(MakeUnique<skia::ConvolutionFilter1D>())
|
||||
, mYFilter(MakeUnique<skia::ConvolutionFilter1D>())
|
||||
, mWindowCapacity(0)
|
||||
, mRowsInWindow(0)
|
||||
, mInputRow(0)
|
||||
, mOutputRow(0)
|
||||
, mHasAlpha(true)
|
||||
{
|
||||
MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
|
||||
"Downscaling even though downscale-during-decode is disabled?");
|
||||
}
|
||||
|
||||
~DownscalingFilter()
|
||||
{
|
||||
ReleaseWindow();
|
||||
}
|
||||
|
||||
template <typename... Rest>
|
||||
nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
|
||||
{
|
||||
nsresult rv = mNext.Configure(aRest...);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mNext.IsValidPalettedPipe()) {
|
||||
NS_WARNING("Created a downscaler for a paletted surface?");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (mNext.InputSize() == aConfig.mInputSize) {
|
||||
NS_WARNING("Created a downscaler, but not downscaling?");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (mNext.InputSize().width > aConfig.mInputSize.width) {
|
||||
NS_WARNING("Created a downscaler, but width is larger");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (mNext.InputSize().height > aConfig.mInputSize.height) {
|
||||
NS_WARNING("Created a downscaler, but height is larger");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (aConfig.mInputSize.width <= 0 || aConfig.mInputSize.height <= 0) {
|
||||
NS_WARNING("Invalid input size for DownscalingFilter");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
mInputSize = aConfig.mInputSize;
|
||||
gfx::IntSize outputSize = mNext.InputSize();
|
||||
mScale = gfxSize(double(mInputSize.width) / outputSize.width,
|
||||
double(mInputSize.height) / outputSize.height);
|
||||
mHasAlpha = aConfig.mFormat == gfx::SurfaceFormat::B8G8R8A8;
|
||||
|
||||
ReleaseWindow();
|
||||
|
||||
auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
|
||||
|
||||
skia::resize::ComputeFilters(resizeMethod,
|
||||
mInputSize.width, outputSize.width,
|
||||
0, outputSize.width,
|
||||
mXFilter.get());
|
||||
|
||||
skia::resize::ComputeFilters(resizeMethod,
|
||||
mInputSize.height, outputSize.height,
|
||||
0, outputSize.height,
|
||||
mYFilter.get());
|
||||
|
||||
// Allocate the buffer, which contains scanlines of the input image.
|
||||
mRowBuffer.reset(new (fallible)
|
||||
uint8_t[PaddedWidthInBytes(mInputSize.width)]);
|
||||
if (MOZ_UNLIKELY(!mRowBuffer)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Clear the buffer to avoid writing uninitialized memory to the output.
|
||||
memset(mRowBuffer.get(), 0, PaddedWidthInBytes(mInputSize.width));
|
||||
|
||||
// Allocate the window, which contains horizontally downscaled scanlines. (We
|
||||
// can store scanlines which are already downscaled because our downscaling
|
||||
// filter is separable.)
|
||||
mWindowCapacity = mYFilter->max_filter();
|
||||
mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
|
||||
if (MOZ_UNLIKELY(!mWindow)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Allocate the "window" of recent rows that we keep in memory as input for
|
||||
// the downscaling code. We intentionally iterate through the entire array
|
||||
// even if an allocation fails, to ensure that all the pointers in it are
|
||||
// either valid or nullptr. That in turn ensures that ReleaseWindow() can
|
||||
// clean up correctly.
|
||||
bool anyAllocationFailed = false;
|
||||
const uint32_t windowRowSizeInBytes = PaddedWidthInBytes(outputSize.width);
|
||||
for (int32_t i = 0; i < mWindowCapacity; ++i) {
|
||||
mWindow[i] = new (fallible) uint8_t[windowRowSizeInBytes];
|
||||
anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(anyAllocationFailed)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
ConfigureFilter(mInputSize, sizeof(uint32_t));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
|
||||
{
|
||||
Maybe<SurfaceInvalidRect> invalidRect = mNext.TakeInvalidRect();
|
||||
|
||||
if (invalidRect) {
|
||||
// Compute the input space invalid rect by scaling.
|
||||
invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.width, mScale.height);
|
||||
}
|
||||
|
||||
return invalidRect;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override
|
||||
{
|
||||
mNext.ResetToFirstRow();
|
||||
|
||||
mInputRow = 0;
|
||||
mOutputRow = 0;
|
||||
mRowsInWindow = 0;
|
||||
|
||||
return GetRowPointer();
|
||||
}
|
||||
|
||||
uint8_t* DoAdvanceRow() override
|
||||
{
|
||||
if (mInputRow >= mInputSize.height) {
|
||||
NS_WARNING("Advancing DownscalingFilter past the end of the input");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mOutputRow >= mNext.InputSize().height) {
|
||||
NS_WARNING("Advancing DownscalingFilter past the end of the output");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int32_t filterOffset = 0;
|
||||
int32_t filterLength = 0;
|
||||
GetFilterOffsetAndLength(mYFilter, mOutputRow,
|
||||
&filterOffset, &filterLength);
|
||||
|
||||
int32_t inputRowToRead = filterOffset + mRowsInWindow;
|
||||
MOZ_ASSERT(mInputRow <= inputRowToRead, "Reading past end of input");
|
||||
if (mInputRow == inputRowToRead) {
|
||||
skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter,
|
||||
mWindow[mRowsInWindow++], mHasAlpha,
|
||||
supports_sse2() || supports_mmi());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
|
||||
"Writing past end of output");
|
||||
|
||||
while (mRowsInWindow == filterLength) {
|
||||
DownscaleInputRow();
|
||||
|
||||
if (mOutputRow == mNext.InputSize().height) {
|
||||
break; // We're done.
|
||||
}
|
||||
|
||||
GetFilterOffsetAndLength(mYFilter, mOutputRow,
|
||||
&filterOffset, &filterLength);
|
||||
}
|
||||
|
||||
mInputRow++;
|
||||
|
||||
return mInputRow < mInputSize.height ? GetRowPointer()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* GetRowPointer() const { return mRowBuffer.get(); }
|
||||
|
||||
static uint32_t PaddedWidthInBytes(uint32_t aLogicalWidth)
|
||||
{
|
||||
// Convert from width in BGRA/BGRX pixels to width in bytes, padding by 15
|
||||
// to handle overreads by the SIMD code inside Skia.
|
||||
return aLogicalWidth * sizeof(uint32_t) + 15;
|
||||
}
|
||||
|
||||
static void
|
||||
GetFilterOffsetAndLength(UniquePtr<skia::ConvolutionFilter1D>& aFilter,
|
||||
int32_t aOutputImagePosition,
|
||||
int32_t* aFilterOffsetOut,
|
||||
int32_t* aFilterLengthOut)
|
||||
{
|
||||
MOZ_ASSERT(aOutputImagePosition < aFilter->num_values());
|
||||
aFilter->FilterForValue(aOutputImagePosition,
|
||||
aFilterOffsetOut,
|
||||
aFilterLengthOut);
|
||||
}
|
||||
|
||||
void DownscaleInputRow()
|
||||
{
|
||||
typedef skia::ConvolutionFilter1D::Fixed FilterValue;
|
||||
|
||||
MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
|
||||
"Writing past end of output");
|
||||
|
||||
int32_t filterOffset = 0;
|
||||
int32_t filterLength = 0;
|
||||
MOZ_ASSERT(mOutputRow < mYFilter->num_values());
|
||||
auto filterValues =
|
||||
mYFilter->FilterForValue(mOutputRow, &filterOffset, &filterLength);
|
||||
|
||||
mNext.template WriteUnsafeComputedRow<uint32_t>([&](uint32_t* aRow,
|
||||
uint32_t aLength) {
|
||||
skia::ConvolveVertically(static_cast<const FilterValue*>(filterValues),
|
||||
filterLength, mWindow.get(), mXFilter->num_values(),
|
||||
reinterpret_cast<uint8_t*>(aRow), mHasAlpha,
|
||||
supports_sse2() || supports_mmi());
|
||||
});
|
||||
|
||||
mOutputRow++;
|
||||
|
||||
if (mOutputRow == mNext.InputSize().height) {
|
||||
return; // We're done.
|
||||
}
|
||||
|
||||
int32_t newFilterOffset = 0;
|
||||
int32_t newFilterLength = 0;
|
||||
GetFilterOffsetAndLength(mYFilter, mOutputRow,
|
||||
&newFilterOffset, &newFilterLength);
|
||||
|
||||
int diff = newFilterOffset - filterOffset;
|
||||
MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
|
||||
|
||||
// Shift the buffer. We're just moving pointers here, so this is cheap.
|
||||
mRowsInWindow -= diff;
|
||||
mRowsInWindow = std::max(mRowsInWindow, 0);
|
||||
for (int32_t i = 0; i < mRowsInWindow; ++i) {
|
||||
std::swap(mWindow[i], mWindow[filterLength - mRowsInWindow + i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ReleaseWindow()
|
||||
{
|
||||
if (!mWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < mWindowCapacity; ++i) {
|
||||
delete[] mWindow[i];
|
||||
}
|
||||
|
||||
mWindow = nullptr;
|
||||
mWindowCapacity = 0;
|
||||
}
|
||||
|
||||
Next mNext; /// The next SurfaceFilter in the chain.
|
||||
|
||||
gfx::IntSize mInputSize; /// The size of the input image.
|
||||
gfxSize mScale; /// The scale factors in each dimension.
|
||||
/// Computed from @mInputSize and
|
||||
/// the next filter's input size.
|
||||
|
||||
UniquePtr<uint8_t[]> mRowBuffer; /// The buffer into which input is written.
|
||||
UniquePtr<uint8_t*[]> mWindow; /// The last few rows which were written.
|
||||
|
||||
UniquePtr<skia::ConvolutionFilter1D> mXFilter; /// The Lanczos filter in X.
|
||||
UniquePtr<skia::ConvolutionFilter1D> mYFilter; /// The Lanczos filter in Y.
|
||||
|
||||
int32_t mWindowCapacity; /// How many rows the window contains.
|
||||
|
||||
int32_t mRowsInWindow; /// How many rows we've buffered in the window.
|
||||
int32_t mInputRow; /// The current row we're reading. (0-indexed)
|
||||
int32_t mOutputRow; /// The current row we're writing. (0-indexed)
|
||||
|
||||
bool mHasAlpha; /// If true, the image has transparency.
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DownscalingFilter_h
|
||||
89
image/DrawResult.h
Normal file
89
image/DrawResult.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- 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_DrawResult_h
|
||||
#define mozilla_image_DrawResult_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Likely.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An enumeration representing the result of a drawing operation.
|
||||
*
|
||||
* Most users of DrawResult will only be interested in whether the value is
|
||||
* SUCCESS or not. The other values are primarily useful for debugging and error
|
||||
* handling.
|
||||
*
|
||||
* SUCCESS: We successfully drew a completely decoded frame of the requested
|
||||
* size. Drawing again with FLAG_SYNC_DECODE would not change the result.
|
||||
*
|
||||
* INCOMPLETE: We successfully drew a frame that was partially decoded. (Note
|
||||
* that successfully drawing a partially decoded frame may not actually draw any
|
||||
* pixels!) Drawing again with FLAG_SYNC_DECODE would improve the result.
|
||||
*
|
||||
* WRONG_SIZE: We successfully drew a wrongly-sized frame that had to be scaled.
|
||||
* This is only returned if drawing again with FLAG_SYNC_DECODE would improve
|
||||
* the result; if the size requested was larger than the intrinsic size of the
|
||||
* image, for example, we would generally have to scale whether FLAG_SYNC_DECODE
|
||||
* was specified or not, and therefore we would not return WRONG_SIZE.
|
||||
*
|
||||
* NOT_READY: We failed to draw because no decoded version of the image was
|
||||
* available. Drawing again with FLAG_SYNC_DECODE would improve the result.
|
||||
* (Though FLAG_SYNC_DECODE will not necessarily work until after the image's
|
||||
* load event!)
|
||||
*
|
||||
* TEMPORARY_ERROR: We failed to draw due to a temporary error. Drawing may
|
||||
* succeed at a later time.
|
||||
*
|
||||
* BAD_IMAGE: We failed to draw because the image has an error. This is a
|
||||
* permanent condition.
|
||||
*
|
||||
* BAD_ARGS: We failed to draw because bad arguments were passed to draw().
|
||||
*/
|
||||
enum class MOZ_MUST_USE_TYPE DrawResult : uint8_t
|
||||
{
|
||||
SUCCESS,
|
||||
INCOMPLETE,
|
||||
WRONG_SIZE,
|
||||
NOT_READY,
|
||||
TEMPORARY_ERROR,
|
||||
BAD_IMAGE,
|
||||
BAD_ARGS
|
||||
};
|
||||
|
||||
/**
|
||||
* You can combine DrawResults with &. By analogy to bitwise-&, the result is
|
||||
* DrawResult::SUCCESS only if both operands are DrawResult::SUCCESS. Otherwise,
|
||||
* a failing DrawResult is returned; we favor the left operand's failure when
|
||||
* deciding which failure to return, with the exception that we always prefer
|
||||
* any other kind of failure over DrawResult::BAD_IMAGE, since other failures
|
||||
* are recoverable and we want to know if any recoverable failures occurred.
|
||||
*/
|
||||
inline DrawResult
|
||||
operator&(const DrawResult aLeft, const DrawResult aRight)
|
||||
{
|
||||
if (MOZ_LIKELY(aLeft == DrawResult::SUCCESS)) {
|
||||
return aRight;
|
||||
}
|
||||
if (aLeft == DrawResult::BAD_IMAGE && aRight != DrawResult::SUCCESS) {
|
||||
return aRight;
|
||||
}
|
||||
return aLeft;
|
||||
}
|
||||
|
||||
inline DrawResult&
|
||||
operator&=(DrawResult& aLeft, const DrawResult aRight)
|
||||
{
|
||||
aLeft = aLeft & aRight;
|
||||
return aLeft;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DrawResult_h
|
||||
347
image/DynamicImage.cpp
Normal file
347
image/DynamicImage.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
/* -*- 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 "DynamicImage.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "ImageRegion.h"
|
||||
#include "Orientation.h"
|
||||
#include "SVGImageContext.h"
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using mozilla::layers::LayerManager;
|
||||
using mozilla::layers::ImageContainer;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
// Inherited methods from Image.
|
||||
|
||||
already_AddRefed<ProgressTracker>
|
||||
DynamicImage::GetProgressTracker()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t
|
||||
DynamicImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
DynamicImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
// We can't report anything useful because gfxDrawable doesn't expose this
|
||||
// information.
|
||||
}
|
||||
|
||||
void
|
||||
DynamicImage::IncrementAnimationConsumers()
|
||||
{ }
|
||||
|
||||
void
|
||||
DynamicImage::DecrementAnimationConsumers()
|
||||
{ }
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t
|
||||
DynamicImage::GetAnimationConsumers()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
nsresult
|
||||
DynamicImage::OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DynamicImage::OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DynamicImage::OnSurfaceDiscarded()
|
||||
{ }
|
||||
|
||||
void
|
||||
DynamicImage::SetInnerWindowID(uint64_t aInnerWindowId)
|
||||
{ }
|
||||
|
||||
uint64_t
|
||||
DynamicImage::InnerWindowID() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
DynamicImage::HasError()
|
||||
{
|
||||
return !mDrawable;
|
||||
}
|
||||
|
||||
void
|
||||
DynamicImage::SetHasError()
|
||||
{ }
|
||||
|
||||
ImageURL*
|
||||
DynamicImage::GetURI()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Methods inherited from XPCOM interfaces.
|
||||
|
||||
NS_IMPL_ISUPPORTS(DynamicImage, imgIContainer)
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetWidth(int32_t* aWidth)
|
||||
{
|
||||
*aWidth = mDrawable->Size().width;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetHeight(int32_t* aHeight)
|
||||
{
|
||||
*aHeight = mDrawable->Size().height;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
IntSize intSize(mDrawable->Size());
|
||||
*aSize = nsSize(intSize.width, intSize.height);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetIntrinsicRatio(nsSize* aSize)
|
||||
{
|
||||
IntSize intSize(mDrawable->Size());
|
||||
*aSize = nsSize(intSize.width, intSize.height);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(Orientation)
|
||||
DynamicImage::GetOrientation()
|
||||
{
|
||||
return Orientation();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetType(uint16_t* aType)
|
||||
{
|
||||
*aType = imgIContainer::TYPE_RASTER;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetAnimated(bool* aAnimated)
|
||||
{
|
||||
*aAnimated = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
DynamicImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
IntSize 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(aSize, SurfaceFormat::B8G8R8A8);
|
||||
if (!dt || !dt->IsValid()) {
|
||||
gfxWarning() <<
|
||||
"DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget";
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
|
||||
MOZ_ASSERT(context); // already checked the draw target above
|
||||
|
||||
auto result = Draw(context, aSize, ImageRegion::Create(aSize),
|
||||
aWhichFrame, SamplingFilter::POINT, Nothing(), aFlags);
|
||||
|
||||
return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
DynamicImage::WillDrawOpaqueNow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
DynamicImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
|
||||
DynamicImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(DrawResult)
|
||||
DynamicImage::Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(!aSize.IsEmpty(), "Unexpected empty size");
|
||||
|
||||
IntSize drawableSize(mDrawable->Size());
|
||||
|
||||
if (aSize == drawableSize) {
|
||||
gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, aRegion,
|
||||
SurfaceFormat::B8G8R8A8, aSamplingFilter);
|
||||
return DrawResult::SUCCESS;
|
||||
}
|
||||
|
||||
gfxSize scale(double(aSize.width) / drawableSize.width,
|
||||
double(aSize.height) / drawableSize.height);
|
||||
|
||||
ImageRegion region(aRegion);
|
||||
region.Scale(1.0 / scale.width, 1.0 / scale.height);
|
||||
|
||||
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
|
||||
aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
|
||||
|
||||
gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, region,
|
||||
SurfaceFormat::B8G8R8A8, aSamplingFilter);
|
||||
return DrawResult::SUCCESS;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::StartDecoding()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::LockImage()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::UnlockImage()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::RequestDiscard()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
DynamicImage::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetAnimationMode(uint16_t* aAnimationMode)
|
||||
{
|
||||
*aAnimationMode = kNormalAnimMode;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::ResetAnimation()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(float)
|
||||
DynamicImage::GetFrameIndex(uint32_t aWhichFrame)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(int32_t)
|
||||
DynamicImage::GetFirstFrameDelay()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
DynamicImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
|
||||
{ }
|
||||
|
||||
nsIntSize
|
||||
DynamicImage::OptimalImageSizeForDest(const gfxSize& aDest,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
IntSize size(mDrawable->Size());
|
||||
return nsIntSize(size.width, size.height);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(nsIntRect)
|
||||
DynamicImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
{
|
||||
return aRect;
|
||||
}
|
||||
|
||||
already_AddRefed<imgIContainer>
|
||||
DynamicImage::Unwrap()
|
||||
{
|
||||
nsCOMPtr<imgIContainer> self(this);
|
||||
return self.forget();
|
||||
}
|
||||
|
||||
void
|
||||
DynamicImage::PropagateUseCounters(nsIDocument*)
|
||||
{
|
||||
// No use counters.
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
75
image/DynamicImage.h
Normal file
75
image/DynamicImage.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* -*- 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_DynamicImage_h
|
||||
#define mozilla_image_DynamicImage_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "gfxDrawable.h"
|
||||
#include "Image.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An Image that is dynamically created. The content of the image is provided by
|
||||
* a gfxDrawable. It's anticipated that most uses of DynamicImage will be
|
||||
* ephemeral.
|
||||
*/
|
||||
class DynamicImage : public Image
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_IMGICONTAINER
|
||||
|
||||
explicit DynamicImage(gfxDrawable* aDrawable)
|
||||
: mDrawable(aDrawable)
|
||||
{
|
||||
MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap");
|
||||
}
|
||||
|
||||
// Inherited methods from Image.
|
||||
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
|
||||
virtual size_t SizeOfSourceWithComputedFallback(
|
||||
MallocSizeOf aMallocSizeOf) const override;
|
||||
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
virtual void IncrementAnimationConsumers() override;
|
||||
virtual void DecrementAnimationConsumers() override;
|
||||
#ifdef DEBUG
|
||||
virtual uint32_t GetAnimationConsumers() override;
|
||||
#endif
|
||||
|
||||
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount) override;
|
||||
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart) override;
|
||||
|
||||
virtual void OnSurfaceDiscarded() override;
|
||||
|
||||
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
|
||||
virtual uint64_t InnerWindowID() const override;
|
||||
|
||||
virtual bool HasError() override;
|
||||
virtual void SetHasError() override;
|
||||
|
||||
virtual ImageURL* GetURI() override;
|
||||
|
||||
private:
|
||||
virtual ~DynamicImage() { }
|
||||
|
||||
RefPtr<gfxDrawable> mDrawable;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_DynamicImage_h
|
||||
887
image/FrameAnimator.cpp
Normal file
887
image/FrameAnimator.cpp
Normal file
|
|
@ -0,0 +1,887 @@
|
|||
/* -*- 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 "FrameAnimator.h"
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "LookupResult.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "RasterImage.h"
|
||||
|
||||
#include "pixman.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// AnimationState implementation.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
AnimationState::SetDoneDecoding(bool aDone)
|
||||
{
|
||||
mDoneDecoding = aDone;
|
||||
}
|
||||
|
||||
void
|
||||
AnimationState::ResetAnimation()
|
||||
{
|
||||
mCurrentAnimationFrameIndex = 0;
|
||||
}
|
||||
|
||||
void
|
||||
AnimationState::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
mAnimationMode = aAnimationMode;
|
||||
}
|
||||
|
||||
void
|
||||
AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
|
||||
{
|
||||
if (aFrameCount <= mFrameCount) {
|
||||
// Nothing to do. Since we can redecode animated images, we may see the same
|
||||
// sequence of updates replayed again, so seeing a smaller frame count than
|
||||
// what we already know about doesn't indicate an error.
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mDoneDecoding, "Adding new frames after decoding is finished?");
|
||||
MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?");
|
||||
|
||||
mFrameCount = aFrameCount;
|
||||
}
|
||||
|
||||
Maybe<uint32_t>
|
||||
AnimationState::FrameCount() const
|
||||
{
|
||||
return mDoneDecoding ? Some(mFrameCount) : Nothing();
|
||||
}
|
||||
|
||||
void
|
||||
AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea)
|
||||
{
|
||||
mFirstFrameRefreshArea = aRefreshArea;
|
||||
}
|
||||
|
||||
void
|
||||
AnimationState::InitAnimationFrameTimeIfNecessary()
|
||||
{
|
||||
if (mCurrentAnimationFrameTime.IsNull()) {
|
||||
mCurrentAnimationFrameTime = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationState::SetAnimationFrameTime(const TimeStamp& aTime)
|
||||
{
|
||||
mCurrentAnimationFrameTime = aTime;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AnimationState::GetCurrentAnimationFrameIndex() const
|
||||
{
|
||||
return mCurrentAnimationFrameIndex;
|
||||
}
|
||||
|
||||
FrameTimeout
|
||||
AnimationState::LoopLength() const
|
||||
{
|
||||
// If we don't know the loop length yet, we have to treat it as infinite.
|
||||
if (!mLoopLength) {
|
||||
return FrameTimeout::Forever();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mDoneDecoding, "We know the loop length but decoding isn't done?");
|
||||
|
||||
// If we're not looping, a single loop time has no meaning.
|
||||
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
|
||||
return FrameTimeout::Forever();
|
||||
}
|
||||
|
||||
return *mLoopLength;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// FrameAnimator implementation.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TimeStamp
|
||||
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
|
||||
{
|
||||
TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
|
||||
FrameTimeout timeout = GetTimeoutForFrame(aState.mCurrentAnimationFrameIndex);
|
||||
|
||||
if (timeout == FrameTimeout::Forever()) {
|
||||
// We need to return a sentinel value in this case, because our logic
|
||||
// doesn't work correctly if we have an infinitely long timeout. We use one
|
||||
// year in the future as the sentinel because it works with the loop in
|
||||
// RequestRefresh() below.
|
||||
// XXX(seth): It'd be preferable to make our logic work correctly with
|
||||
// infinitely long timeouts.
|
||||
return TimeStamp::NowLoRes() +
|
||||
TimeDuration::FromMilliseconds(31536000.0);
|
||||
}
|
||||
|
||||
TimeDuration durationOfTimeout =
|
||||
TimeDuration::FromMilliseconds(double(timeout.AsMilliseconds()));
|
||||
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
|
||||
|
||||
return currentFrameEndTime;
|
||||
}
|
||||
|
||||
RefreshResult
|
||||
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
|
||||
{
|
||||
NS_ASSERTION(aTime <= TimeStamp::Now(),
|
||||
"Given time appears to be in the future");
|
||||
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
|
||||
|
||||
RefreshResult ret;
|
||||
|
||||
// Determine what the next frame is, taking into account looping.
|
||||
uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex;
|
||||
uint32_t nextFrameIndex = currentFrameIndex + 1;
|
||||
|
||||
// Check if we're at the end of the loop. (FrameCount() returns Nothing() if
|
||||
// we don't know the total count yet.)
|
||||
if (aState.FrameCount() == Some(nextFrameIndex)) {
|
||||
// If we are not looping forever, initialize the loop counter
|
||||
if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) {
|
||||
aState.mLoopRemainingCount = aState.LoopCount();
|
||||
}
|
||||
|
||||
// If animation mode is "loop once", or we're at end of loop counter,
|
||||
// it's time to stop animating.
|
||||
if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
|
||||
aState.mLoopRemainingCount == 0) {
|
||||
ret.mAnimationFinished = true;
|
||||
}
|
||||
|
||||
nextFrameIndex = 0;
|
||||
|
||||
if (aState.mLoopRemainingCount > 0) {
|
||||
aState.mLoopRemainingCount--;
|
||||
}
|
||||
|
||||
// If we're done, exit early.
|
||||
if (ret.mAnimationFinished) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextFrameIndex >= aState.KnownFrameCount()) {
|
||||
// We've already advanced to the last decoded frame, nothing more we can do.
|
||||
// We're blocked by network/decoding from displaying the animation at the
|
||||
// rate specified, so that means the frame we are displaying (the latest
|
||||
// available) is the frame we want to be displaying at this time. So we
|
||||
// update the current animation time. If we didn't update the current
|
||||
// animation time then it could lag behind, which would indicate that we are
|
||||
// behind in the animation and should try to catch up. When we are done
|
||||
// decoding (and thus can loop around back to the start of the animation) we
|
||||
// would then jump to a random point in the animation to try to catch up.
|
||||
// But we were never behind in the animation.
|
||||
aState.mCurrentAnimationFrameTime = aTime;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// There can be frames in the surface cache with index >= KnownFrameCount()
|
||||
// which GetRawFrame() can access because an async decoder has decoded them,
|
||||
// but which AnimationState doesn't know about yet because we haven't received
|
||||
// the appropriate notification on the main thread. Make sure we stay in sync
|
||||
// with AnimationState.
|
||||
MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
|
||||
RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
|
||||
|
||||
// We should always check to see if we have the next frame even if we have
|
||||
// previously finished decoding. If we needed to redecode (e.g. due to a draw
|
||||
// failure) we would have discarded all the old frames and may not yet have
|
||||
// the new ones.
|
||||
if (!nextFrame || !nextFrame->IsFinished()) {
|
||||
// Uh oh, the frame we want to show is currently being decoded (partial)
|
||||
// Wait until the next refresh driver tick and try again
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) {
|
||||
ret.mAnimationFinished = true;
|
||||
}
|
||||
|
||||
if (nextFrameIndex == 0) {
|
||||
ret.mDirtyRect = aState.FirstFrameRefreshArea();
|
||||
} else {
|
||||
MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
|
||||
|
||||
// Change frame
|
||||
if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
|
||||
// something went wrong, move on to next
|
||||
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
|
||||
nextFrame->SetCompositingFailed(true);
|
||||
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
|
||||
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
nextFrame->SetCompositingFailed(false);
|
||||
}
|
||||
|
||||
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
|
||||
|
||||
// If we can get closer to the current time by a multiple of the image's loop
|
||||
// time, we should. We can only do this if we're done decoding; otherwise, we
|
||||
// don't know the full loop length, and LoopLength() will have to return
|
||||
// FrameTimeout::Forever().
|
||||
FrameTimeout loopTime = aState.LoopLength();
|
||||
if (loopTime != FrameTimeout::Forever()) {
|
||||
TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
|
||||
if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
|
||||
// Explicitly use integer division to get the floor of the number of
|
||||
// loops.
|
||||
uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds())
|
||||
/ loopTime.AsMilliseconds();
|
||||
aState.mCurrentAnimationFrameTime +=
|
||||
TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds());
|
||||
}
|
||||
}
|
||||
|
||||
// Set currentAnimationFrameIndex at the last possible moment
|
||||
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
// If we're here, we successfully advanced the frame.
|
||||
ret.mFrameAdvanced = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
RefreshResult
|
||||
FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime)
|
||||
{
|
||||
// only advance the frame if the current time is greater than or
|
||||
// equal to the current frame's end time.
|
||||
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
|
||||
|
||||
// By default, an empty RefreshResult.
|
||||
RefreshResult ret;
|
||||
|
||||
while (currentFrameEndTime <= aTime) {
|
||||
TimeStamp oldFrameEndTime = currentFrameEndTime;
|
||||
|
||||
RefreshResult frameRes = AdvanceFrame(aState, aTime);
|
||||
|
||||
// Accumulate our result for returning to callers.
|
||||
ret.Accumulate(frameRes);
|
||||
|
||||
currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
|
||||
|
||||
// If we didn't advance a frame, and our frame end time didn't change,
|
||||
// then we need to break out of this loop & wait for the frame(s)
|
||||
// to finish downloading.
|
||||
if (!frameRes.mFrameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LookupResult
|
||||
FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
|
||||
{
|
||||
// If we have a composited version of this frame, return that.
|
||||
if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
|
||||
return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
|
||||
MatchType::EXACT);
|
||||
}
|
||||
|
||||
// Otherwise return the raw frame. DoBlend is required to ensure that we only
|
||||
// hit this case if the frame is not paletted and doesn't require compositing.
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(mImage),
|
||||
RasterSurfaceKey(mSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Seek to the appropriate frame. If seeking fails, it means that we couldn't
|
||||
// get the frame we're looking for; treat this as if the lookup failed.
|
||||
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
|
||||
return LookupResult(MatchType::NOT_FOUND);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
|
||||
"About to return a paletted frame");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FrameTimeout
|
||||
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
|
||||
{
|
||||
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
|
||||
if (frame) {
|
||||
AnimationData data = frame->GetAnimationData();
|
||||
return data.mTimeout;
|
||||
}
|
||||
|
||||
NS_WARNING("No frame; called GetTimeoutForFrame too early?");
|
||||
return FrameTimeout::FromRawMilliseconds(100);
|
||||
}
|
||||
|
||||
static void
|
||||
DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
|
||||
SurfaceMemoryCounterType aType,
|
||||
nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
// Concoct a SurfaceKey for this surface.
|
||||
SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic);
|
||||
|
||||
// Create a counter for this surface.
|
||||
SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
|
||||
|
||||
// Extract the surface's memory usage information.
|
||||
size_t heap = 0, nonHeap = 0;
|
||||
aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap);
|
||||
counter.Values().SetDecodedHeap(heap);
|
||||
counter.Values().SetDecodedNonHeap(nonHeap);
|
||||
|
||||
// Record it.
|
||||
aCounters.AppendElement(counter);
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::CollectSizeOfCompositingSurfaces(
|
||||
nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
if (mCompositingFrame) {
|
||||
DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
|
||||
SurfaceMemoryCounterType::COMPOSITING,
|
||||
aCounters,
|
||||
aMallocSizeOf);
|
||||
}
|
||||
|
||||
if (mCompositingPrevFrame) {
|
||||
DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame,
|
||||
SurfaceMemoryCounterType::COMPOSITING_PREV,
|
||||
aCounters,
|
||||
aMallocSizeOf);
|
||||
}
|
||||
}
|
||||
|
||||
RawAccessFrameRef
|
||||
FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(mImage),
|
||||
RasterSurfaceKey(mSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
if (!result) {
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
|
||||
// Seek to the frame we want. If seeking fails, it means we couldn't get the
|
||||
// frame we're looking for, so we bail here to avoid returning the wrong frame
|
||||
// to the caller.
|
||||
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
|
||||
return RawAccessFrameRef(); // Not available yet.
|
||||
}
|
||||
|
||||
return result.Surface()->RawAccessRef();
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// DoBlend gets called when the timer for animation get fired and we have to
|
||||
// update the composited frame of the animation.
|
||||
bool
|
||||
FrameAnimator::DoBlend(IntRect* aDirtyRect,
|
||||
uint32_t aPrevFrameIndex,
|
||||
uint32_t aNextFrameIndex)
|
||||
{
|
||||
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
|
||||
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
|
||||
|
||||
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
|
||||
|
||||
AnimationData prevFrameData = prevFrame->GetAnimationData();
|
||||
if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
|
||||
!mCompositingPrevFrame) {
|
||||
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
|
||||
}
|
||||
|
||||
IntRect prevRect = prevFrameData.mBlendRect
|
||||
? prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect)
|
||||
: prevFrameData.mRect;
|
||||
|
||||
bool isFullPrevFrame = prevRect.x == 0 && prevRect.y == 0 &&
|
||||
prevRect.width == mSize.width &&
|
||||
prevRect.height == mSize.height;
|
||||
|
||||
// Optimization: DisposeClearAll if the previous frame is the same size as
|
||||
// container and it's clearing itself
|
||||
if (isFullPrevFrame &&
|
||||
(prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
|
||||
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
|
||||
}
|
||||
|
||||
AnimationData nextFrameData = nextFrame->GetAnimationData();
|
||||
|
||||
IntRect nextRect = nextFrameData.mBlendRect
|
||||
? nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect)
|
||||
: nextFrameData.mRect;
|
||||
|
||||
bool isFullNextFrame = nextRect.x == 0 && nextRect.y == 0 &&
|
||||
nextRect.width == mSize.width &&
|
||||
nextRect.height == mSize.height;
|
||||
|
||||
if (!nextFrame->GetIsPaletted()) {
|
||||
// Optimization: Skip compositing if the previous frame wants to clear the
|
||||
// whole image
|
||||
if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
|
||||
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optimization: Skip compositing if this frame is the same size as the
|
||||
// container and it's fully drawing over prev frame (no alpha)
|
||||
if (isFullNextFrame &&
|
||||
(nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
|
||||
!nextFrameData.mHasAlpha) {
|
||||
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate area that needs updating
|
||||
switch (prevFrameData.mDisposalMethod) {
|
||||
default:
|
||||
MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
|
||||
case DisposalMethod::NOT_SPECIFIED:
|
||||
case DisposalMethod::KEEP:
|
||||
*aDirtyRect = nextRect;
|
||||
break;
|
||||
|
||||
case DisposalMethod::CLEAR_ALL:
|
||||
// Whole image container is cleared
|
||||
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
||||
break;
|
||||
|
||||
case DisposalMethod::CLEAR:
|
||||
// Calc area that needs to be redrawn (the combination of previous and
|
||||
// this frame)
|
||||
// XXX - This could be done with multiple framechanged calls
|
||||
// Having prevFrame way at the top of the image, and nextFrame
|
||||
// way at the bottom, and both frames being small, we'd be
|
||||
// telling framechanged to refresh the whole image when only two
|
||||
// small areas are needed.
|
||||
aDirtyRect->UnionRect(nextRect, prevRect);
|
||||
break;
|
||||
|
||||
case DisposalMethod::RESTORE_PREVIOUS:
|
||||
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
||||
break;
|
||||
}
|
||||
|
||||
// Optimization:
|
||||
// Skip compositing if the last composited frame is this frame
|
||||
// (Only one composited frame was made for this animation. Example:
|
||||
// Only Frame 3 of a 10 frame image required us to build a composite frame
|
||||
// On the second loop, we do not need to rebuild the frame
|
||||
// since it's still sitting in compositingFrame)
|
||||
if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool needToBlankComposite = false;
|
||||
|
||||
// Create the Compositing Frame
|
||||
if (!mCompositingFrame) {
|
||||
RefPtr<imgFrame> newFrame = new imgFrame;
|
||||
nsresult rv = newFrame->InitForDecoder(mSize,
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCompositingFrame.reset();
|
||||
return false;
|
||||
}
|
||||
mCompositingFrame = newFrame->RawAccessRef();
|
||||
needToBlankComposite = true;
|
||||
} else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
|
||||
|
||||
// If we are not drawing on top of last composited frame,
|
||||
// then we are building a new composite frame, so let's clear it first.
|
||||
needToBlankComposite = true;
|
||||
}
|
||||
|
||||
AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
|
||||
|
||||
// More optimizations possible when next frame is not transparent
|
||||
// But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
|
||||
// this "no disposal" optimization is not possible,
|
||||
// because the frame in "after disposal operation" state
|
||||
// needs to be stored in compositingFrame, so it can be
|
||||
// copied into compositingPrevFrame later.
|
||||
bool doDisposal = true;
|
||||
if (!nextFrameData.mHasAlpha &&
|
||||
nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
|
||||
if (isFullNextFrame) {
|
||||
// Optimization: No need to dispose prev.frame when
|
||||
// next frame is full frame and not transparent.
|
||||
doDisposal = false;
|
||||
// No need to blank the composite frame
|
||||
needToBlankComposite = false;
|
||||
} else {
|
||||
if ((prevRect.x >= nextRect.x) && (prevRect.y >= nextRect.y) &&
|
||||
(prevRect.x + prevRect.width <= nextRect.x + nextRect.width) &&
|
||||
(prevRect.y + prevRect.height <= nextRect.y + nextRect.height)) {
|
||||
// Optimization: No need to dispose prev.frame when
|
||||
// next frame fully overlaps previous frame.
|
||||
doDisposal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doDisposal) {
|
||||
// Dispose of previous: clear, restore, or keep (copy)
|
||||
switch (prevFrameData.mDisposalMethod) {
|
||||
case DisposalMethod::CLEAR:
|
||||
if (needToBlankComposite) {
|
||||
// If we just created the composite, it could have anything in its
|
||||
// buffer. Clear whole frame
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
} else {
|
||||
// Only blank out previous frame area (both color & Mask/Alpha)
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
prevRect);
|
||||
}
|
||||
break;
|
||||
|
||||
case DisposalMethod::CLEAR_ALL:
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
break;
|
||||
|
||||
case DisposalMethod::RESTORE_PREVIOUS:
|
||||
// It would be better to copy only the area changed back to
|
||||
// compositingFrame.
|
||||
if (mCompositingPrevFrame) {
|
||||
AnimationData compositingPrevFrameData =
|
||||
mCompositingPrevFrame->GetAnimationData();
|
||||
|
||||
CopyFrameImage(compositingPrevFrameData.mRawData,
|
||||
compositingPrevFrameData.mRect,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
|
||||
// destroy only if we don't need it for this frame's disposal
|
||||
if (nextFrameData.mDisposalMethod !=
|
||||
DisposalMethod::RESTORE_PREVIOUS) {
|
||||
mCompositingPrevFrame.reset();
|
||||
}
|
||||
} else {
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
|
||||
case DisposalMethod::NOT_SPECIFIED:
|
||||
case DisposalMethod::KEEP:
|
||||
// Copy previous frame into compositingFrame before we put the new
|
||||
// frame on top
|
||||
// Assumes that the previous frame represents a full frame (it could be
|
||||
// smaller in size than the container, as long as the frame before it
|
||||
// erased itself)
|
||||
// Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
|
||||
// will always be a valid frame number.
|
||||
if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
|
||||
if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
|
||||
// Just copy the bits
|
||||
CopyFrameImage(prevFrameData.mRawData,
|
||||
prevRect,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
} else {
|
||||
if (needToBlankComposite) {
|
||||
// Only blank composite when prev is transparent or not full.
|
||||
if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
}
|
||||
}
|
||||
DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
|
||||
prevFrameData.mPaletteDataLength,
|
||||
prevFrameData.mHasAlpha,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
prevFrameData.mBlendMethod,
|
||||
prevFrameData.mBlendRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (needToBlankComposite) {
|
||||
// If we just created the composite, it could have anything in its
|
||||
// buffers. Clear them
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
}
|
||||
|
||||
// Check if the frame we are composing wants the previous image restored after
|
||||
// it is done. Don't store it (again) if last frame wanted its image restored
|
||||
// too
|
||||
if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
|
||||
(prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
|
||||
// We are storing the whole image.
|
||||
// It would be better if we just stored the area that nextFrame is going to
|
||||
// overwrite.
|
||||
if (!mCompositingPrevFrame) {
|
||||
RefPtr<imgFrame> newFrame = new imgFrame;
|
||||
nsresult rv = newFrame->InitForDecoder(mSize,
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCompositingPrevFrame.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
mCompositingPrevFrame = newFrame->RawAccessRef();
|
||||
}
|
||||
|
||||
AnimationData compositingPrevFrameData =
|
||||
mCompositingPrevFrame->GetAnimationData();
|
||||
|
||||
CopyFrameImage(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
compositingPrevFrameData.mRawData,
|
||||
compositingPrevFrameData.mRect);
|
||||
|
||||
mCompositingPrevFrame->Finish();
|
||||
}
|
||||
|
||||
// blit next frame into it's correct spot
|
||||
DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
|
||||
nextFrameData.mPaletteDataLength,
|
||||
nextFrameData.mHasAlpha,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
nextFrameData.mBlendMethod,
|
||||
nextFrameData.mBlendRect);
|
||||
|
||||
// Tell the image that it is fully 'downloaded'.
|
||||
mCompositingFrame->Finish();
|
||||
|
||||
mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// Fill aFrame with black. Does also clears the mask.
|
||||
void
|
||||
FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect)
|
||||
{
|
||||
if (!aFrameData) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
void
|
||||
FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect,
|
||||
const IntRect& aRectToClear)
|
||||
{
|
||||
if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 ||
|
||||
aRectToClear.width <= 0 || aRectToClear.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
IntRect toClear = aFrameRect.Intersect(aRectToClear);
|
||||
if (toClear.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bytesPerRow = aFrameRect.width * 4;
|
||||
for (int row = toClear.y; row < toClear.y + toClear.height; ++row) {
|
||||
memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0,
|
||||
toClear.width * 4);
|
||||
}
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// Whether we succeed or fail will not cause a crash, and there's not much
|
||||
// we can do about a failure, so there we don't return a nsresult
|
||||
bool
|
||||
FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc,
|
||||
const IntRect& aRectSrc,
|
||||
uint8_t* aDataDest,
|
||||
const IntRect& aRectDest)
|
||||
{
|
||||
uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4;
|
||||
uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4;
|
||||
|
||||
if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(aDataDest, aDataSrc, dataLengthDest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
|
||||
uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
|
||||
uint8_t* aDstPixels, const IntRect& aDstRect,
|
||||
BlendMethod aBlendMethod, const Maybe<IntRect>& aBlendRect)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aSrcData);
|
||||
NS_ENSURE_ARG_POINTER(aDstPixels);
|
||||
|
||||
// According to both AGIF and APNG specs, offsets are unsigned
|
||||
if (aSrcRect.x < 0 || aSrcRect.y < 0) {
|
||||
NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
// Outside the destination frame, skip it
|
||||
if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aSrcPaletteLength) {
|
||||
// Larger than the destination frame, clip it
|
||||
int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x);
|
||||
int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y);
|
||||
|
||||
// The clipped image must now fully fit within destination image frame
|
||||
NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
|
||||
(aSrcRect.x + width <= aDstRect.width) &&
|
||||
(aSrcRect.y + height <= aDstRect.height),
|
||||
"FrameAnimator::DrawFrameTo: Invalid aSrcRect");
|
||||
|
||||
// clipped image size may be smaller than source, but not larger
|
||||
NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
|
||||
"FrameAnimator::DrawFrameTo: source must be smaller than dest");
|
||||
|
||||
// Get pointers to image data
|
||||
const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
|
||||
uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
|
||||
const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
|
||||
|
||||
// Skip to the right offset
|
||||
dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width);
|
||||
if (!aSrcHasAlpha) {
|
||||
for (int32_t r = height; r > 0; --r) {
|
||||
for (int32_t c = 0; c < width; c++) {
|
||||
dstPixels[c] = colormap[srcPixels[c]];
|
||||
}
|
||||
// Go to the next row in the source resp. destination image
|
||||
srcPixels += aSrcRect.width;
|
||||
dstPixels += aDstRect.width;
|
||||
}
|
||||
} else {
|
||||
for (int32_t r = height; r > 0; --r) {
|
||||
for (int32_t c = 0; c < width; c++) {
|
||||
const uint32_t color = colormap[srcPixels[c]];
|
||||
if (color) {
|
||||
dstPixels[c] = color;
|
||||
}
|
||||
}
|
||||
// Go to the next row in the source resp. destination image
|
||||
srcPixels += aSrcRect.width;
|
||||
dstPixels += aDstRect.width;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pixman_image_t* src =
|
||||
pixman_image_create_bits(
|
||||
aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
|
||||
aSrcRect.width, aSrcRect.height,
|
||||
reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
|
||||
aSrcRect.width * 4);
|
||||
if (!src) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
pixman_image_t* dst =
|
||||
pixman_image_create_bits(PIXMAN_a8r8g8b8,
|
||||
aDstRect.width,
|
||||
aDstRect.height,
|
||||
reinterpret_cast<uint32_t*>(aDstPixels),
|
||||
aDstRect.width * 4);
|
||||
if (!dst) {
|
||||
pixman_image_unref(src);
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// XXX(seth): This is inefficient but we'll remove it quite soon when we
|
||||
// move frame compositing into SurfacePipe. For now we need this because
|
||||
// RemoveFrameRectFilter has transformed PNG frames with frame rects into
|
||||
// imgFrame's with no frame rects, but with a region of 0 alpha where the
|
||||
// frame rect should be. This works really nicely if we're using
|
||||
// BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
|
||||
// area overwriting the previous frame, which makes the animation look
|
||||
// wrong. This quick hack fixes that by first compositing the whle new frame
|
||||
// with BlendMethod::OVER, and then recopying the area that uses
|
||||
// BlendMethod::SOURCE if needed. To make this work, the decoder has to
|
||||
// provide a "blend rect" that tells us where to do this. This is just the
|
||||
// frame rect, but hidden in a way that makes it invisible to most of the
|
||||
// system, so we can keep eliminating dependencies on it.
|
||||
auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC
|
||||
: PIXMAN_OP_OVER;
|
||||
|
||||
if (aBlendMethod == BlendMethod::OVER || !aBlendRect ||
|
||||
(aBlendMethod == BlendMethod::SOURCE && aSrcRect.IsEqualEdges(*aBlendRect))) {
|
||||
// We don't need to do anything clever. (Or, in the case where no blend
|
||||
// rect was specified, we can't.)
|
||||
pixman_image_composite32(op,
|
||||
src,
|
||||
nullptr,
|
||||
dst,
|
||||
0, 0,
|
||||
0, 0,
|
||||
aSrcRect.x, aSrcRect.y,
|
||||
aSrcRect.width, aSrcRect.height);
|
||||
} else {
|
||||
// We need to do the OVER followed by SOURCE trick above.
|
||||
pixman_image_composite32(PIXMAN_OP_OVER,
|
||||
src,
|
||||
nullptr,
|
||||
dst,
|
||||
0, 0,
|
||||
0, 0,
|
||||
aSrcRect.x, aSrcRect.y,
|
||||
aSrcRect.width, aSrcRect.height);
|
||||
pixman_image_composite32(PIXMAN_OP_SRC,
|
||||
src,
|
||||
nullptr,
|
||||
dst,
|
||||
aBlendRect->x, aBlendRect->y,
|
||||
0, 0,
|
||||
aBlendRect->x, aBlendRect->y,
|
||||
aBlendRect->width, aBlendRect->height);
|
||||
}
|
||||
|
||||
pixman_image_unref(src);
|
||||
pixman_image_unref(dst);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
323
image/FrameAnimator.h
Normal file
323
image/FrameAnimator.h
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/* -*- 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_FrameAnimator_h
|
||||
#define mozilla_image_FrameAnimator_h
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "gfxTypes.h"
|
||||
#include "imgFrame.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsRect.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class RasterImage;
|
||||
|
||||
class AnimationState
|
||||
{
|
||||
public:
|
||||
explicit AnimationState(uint16_t aAnimationMode)
|
||||
: mFrameCount(0)
|
||||
, mCurrentAnimationFrameIndex(0)
|
||||
, mLoopRemainingCount(-1)
|
||||
, mLoopCount(-1)
|
||||
, mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0))
|
||||
, mAnimationMode(aAnimationMode)
|
||||
, mDoneDecoding(false)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Call when this image is finished decoding so we know that there aren't any
|
||||
* more frames coming.
|
||||
*/
|
||||
void SetDoneDecoding(bool aDone);
|
||||
|
||||
/**
|
||||
* Call when you need to re-start animating. Ensures we start from the first
|
||||
* frame.
|
||||
*/
|
||||
void ResetAnimation();
|
||||
|
||||
/**
|
||||
* The animation mode of the image.
|
||||
*
|
||||
* Constants defined in imgIContainer.idl.
|
||||
*/
|
||||
void SetAnimationMode(uint16_t aAnimationMode);
|
||||
|
||||
/// Update the number of frames of animation this image is known to have.
|
||||
void UpdateKnownFrameCount(uint32_t aFrameCount);
|
||||
|
||||
/// @return the number of frames of animation we know about so far.
|
||||
uint32_t KnownFrameCount() const { return mFrameCount; }
|
||||
|
||||
/// @return the number of frames this animation has, if we know for sure.
|
||||
/// (In other words, if decoding is finished.) Otherwise, returns Nothing().
|
||||
Maybe<uint32_t> FrameCount() const;
|
||||
|
||||
/**
|
||||
* Get or set the area of the image to invalidate when we loop around to the
|
||||
* first frame.
|
||||
*/
|
||||
void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea);
|
||||
gfx::IntRect FirstFrameRefreshArea() const { return mFirstFrameRefreshArea; }
|
||||
|
||||
/**
|
||||
* If the animation frame time has not yet been set, set it to
|
||||
* TimeStamp::Now().
|
||||
*/
|
||||
void InitAnimationFrameTimeIfNecessary();
|
||||
|
||||
/**
|
||||
* Set the animation frame time to @aTime.
|
||||
*/
|
||||
void SetAnimationFrameTime(const TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* The current frame we're on, from 0 to (numFrames - 1).
|
||||
*/
|
||||
uint32_t GetCurrentAnimationFrameIndex() const;
|
||||
|
||||
/*
|
||||
* Set number of times to loop the image.
|
||||
* @note -1 means loop forever.
|
||||
*/
|
||||
void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
|
||||
int32_t LoopCount() const { return mLoopCount; }
|
||||
|
||||
/// Set the @aLength of a single loop through this image.
|
||||
void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); }
|
||||
|
||||
/**
|
||||
* @return the length of a single loop of this image. If this image is not
|
||||
* finished decoding, is not animated, or it is animated but does not loop,
|
||||
* returns FrameTimeout::Forever().
|
||||
*/
|
||||
FrameTimeout LoopLength() const;
|
||||
|
||||
/*
|
||||
* Get or set the timeout for the first frame. This is used to allow animation
|
||||
* scheduling even before a full decode runs for this image.
|
||||
*/
|
||||
void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; }
|
||||
FrameTimeout FirstFrameTimeout() const { return mFirstFrameTimeout; }
|
||||
|
||||
private:
|
||||
friend class FrameAnimator;
|
||||
|
||||
//! Area of the first frame that needs to be redrawn on subsequent loops.
|
||||
gfx::IntRect mFirstFrameRefreshArea;
|
||||
|
||||
//! the time that the animation advanced to the current frame
|
||||
TimeStamp mCurrentAnimationFrameTime;
|
||||
|
||||
//! The number of frames of animation this image has.
|
||||
uint32_t mFrameCount;
|
||||
|
||||
//! The current frame index we're on, in the range [0, mFrameCount).
|
||||
uint32_t mCurrentAnimationFrameIndex;
|
||||
|
||||
//! number of loops remaining before animation stops (-1 no stop)
|
||||
int32_t mLoopRemainingCount;
|
||||
|
||||
//! The total number of loops for the image.
|
||||
int32_t mLoopCount;
|
||||
|
||||
//! The length of a single loop through this image.
|
||||
Maybe<FrameTimeout> mLoopLength;
|
||||
|
||||
//! The timeout for the first frame of this image.
|
||||
FrameTimeout mFirstFrameTimeout;
|
||||
|
||||
//! The animation mode of this image. Constants defined in imgIContainer.
|
||||
uint16_t mAnimationMode;
|
||||
|
||||
//! Whether this image is done being decoded.
|
||||
bool mDoneDecoding;
|
||||
};
|
||||
|
||||
/**
|
||||
* RefreshResult is used to let callers know how the state of the animation
|
||||
* changed during a call to FrameAnimator::RequestRefresh().
|
||||
*/
|
||||
struct RefreshResult
|
||||
{
|
||||
RefreshResult()
|
||||
: mFrameAdvanced(false)
|
||||
, mAnimationFinished(false)
|
||||
{ }
|
||||
|
||||
/// Merges another RefreshResult's changes into this RefreshResult.
|
||||
void Accumulate(const RefreshResult& aOther)
|
||||
{
|
||||
mFrameAdvanced = mFrameAdvanced || aOther.mFrameAdvanced;
|
||||
mAnimationFinished = mAnimationFinished || aOther.mAnimationFinished;
|
||||
mDirtyRect = mDirtyRect.Union(aOther.mDirtyRect);
|
||||
}
|
||||
|
||||
// The region of the image that has changed.
|
||||
gfx::IntRect mDirtyRect;
|
||||
|
||||
// If true, we changed frames at least once. Note that, due to looping, we
|
||||
// could still have ended up on the same frame!
|
||||
bool mFrameAdvanced : 1;
|
||||
|
||||
// Whether the animation has finished playing.
|
||||
bool mAnimationFinished : 1;
|
||||
};
|
||||
|
||||
class FrameAnimator
|
||||
{
|
||||
public:
|
||||
FrameAnimator(RasterImage* aImage, const gfx::IntSize& aSize)
|
||||
: mImage(aImage)
|
||||
, mSize(aSize)
|
||||
, mLastCompositedFrameIndex(-1)
|
||||
{
|
||||
MOZ_COUNT_CTOR(FrameAnimator);
|
||||
}
|
||||
|
||||
~FrameAnimator()
|
||||
{
|
||||
MOZ_COUNT_DTOR(FrameAnimator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-evaluate what frame we're supposed to be on, and do whatever blending
|
||||
* is necessary to get us to that frame.
|
||||
*
|
||||
* Returns the result of that blending, including whether the current frame
|
||||
* changed and what the resulting dirty rectangle is.
|
||||
*/
|
||||
RefreshResult RequestRefresh(AnimationState& aState, const TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* If we have a composited frame for @aFrameNum, returns it. Otherwise,
|
||||
* returns an empty LookupResult. It is an error to call this method with
|
||||
* aFrameNum == 0, because the first frame is never composited.
|
||||
*/
|
||||
LookupResult GetCompositedFrame(uint32_t aFrameNum);
|
||||
|
||||
/**
|
||||
* Collect an accounting of the memory occupied by the compositing surfaces we
|
||||
* use during animation playback. All of the actual animation frames are
|
||||
* stored in the SurfaceCache, so we don't need to report them here.
|
||||
*/
|
||||
void CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
private: // methods
|
||||
/**
|
||||
* Advances the animation. Typically, this will advance a single frame, but it
|
||||
* may advance multiple frames. This may happen if we have infrequently
|
||||
* "ticking" refresh drivers (e.g. in background tabs), or extremely short-
|
||||
* lived animation frames.
|
||||
*
|
||||
* @param aTime the time that the animation should advance to. This will
|
||||
* typically be <= TimeStamp::Now().
|
||||
*
|
||||
* @returns a RefreshResult that shows whether the frame was successfully
|
||||
* advanced, and its resulting dirty rect.
|
||||
*/
|
||||
RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime);
|
||||
|
||||
/**
|
||||
* Get the @aIndex-th frame in the frame index, ignoring results of blending.
|
||||
*/
|
||||
RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
|
||||
|
||||
/// @return the given frame's timeout.
|
||||
FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const;
|
||||
|
||||
/**
|
||||
* Get the time the frame we're currently displaying is supposed to end.
|
||||
*
|
||||
* In the error case, returns an "infinity" timestamp.
|
||||
*/
|
||||
TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState) const;
|
||||
|
||||
bool DoBlend(gfx::IntRect* aDirtyRect,
|
||||
uint32_t aPrevFrameIndex,
|
||||
uint32_t aNextFrameIndex);
|
||||
|
||||
/** Clears an area of <aFrame> with transparent black.
|
||||
*
|
||||
* @param aFrameData Target Frame data
|
||||
* @param aFrameRect The rectangle of the data pointed ot by aFrameData
|
||||
*
|
||||
* @note Does also clears the transparency mask
|
||||
*/
|
||||
static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect);
|
||||
|
||||
//! @overload
|
||||
static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect,
|
||||
const gfx::IntRect& aRectToClear);
|
||||
|
||||
//! Copy one frame's image and mask into another
|
||||
static bool CopyFrameImage(const uint8_t* aDataSrc, const gfx::IntRect& aRectSrc,
|
||||
uint8_t* aDataDest, const gfx::IntRect& aRectDest);
|
||||
|
||||
/**
|
||||
* Draws one frame's image to into another, at the position specified by
|
||||
* aSrcRect.
|
||||
*
|
||||
* @aSrcData the raw data of the current frame being drawn
|
||||
* @aSrcRect the size of the source frame, and the position of that frame in
|
||||
* the composition frame
|
||||
* @aSrcPaletteLength the length (in bytes) of the palette at the beginning
|
||||
* of the source data (0 if image is not paletted)
|
||||
* @aSrcHasAlpha whether the source data represents an image with alpha
|
||||
* @aDstPixels the raw data of the composition frame where the current frame
|
||||
* is drawn into (32-bit ARGB)
|
||||
* @aDstRect the size of the composition frame
|
||||
* @aBlendMethod the blend method for how to blend src on the composition
|
||||
* frame.
|
||||
*/
|
||||
static nsresult DrawFrameTo(const uint8_t* aSrcData,
|
||||
const gfx::IntRect& aSrcRect,
|
||||
uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
|
||||
uint8_t* aDstPixels, const gfx::IntRect& aDstRect,
|
||||
BlendMethod aBlendMethod,
|
||||
const Maybe<gfx::IntRect>& aBlendRect);
|
||||
|
||||
private: // data
|
||||
//! A weak pointer to our owning image.
|
||||
RasterImage* mImage;
|
||||
|
||||
//! The intrinsic size of the image.
|
||||
gfx::IntSize mSize;
|
||||
|
||||
/** For managing blending of frames
|
||||
*
|
||||
* Some animations will use the compositingFrame to composite images
|
||||
* and just hand this back to the caller when it is time to draw the frame.
|
||||
* NOTE: When clearing compositingFrame, remember to set
|
||||
* lastCompositedFrameIndex to -1. Code assume that if
|
||||
* lastCompositedFrameIndex >= 0 then compositingFrame exists.
|
||||
*/
|
||||
RawAccessFrameRef mCompositingFrame;
|
||||
|
||||
/** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
|
||||
*
|
||||
* The Previous Frame (all frames composited up to the current) needs to be
|
||||
* stored in cases where the image specifies it wants the last frame back
|
||||
* when it's done with the current frame.
|
||||
*/
|
||||
RawAccessFrameRef mCompositingPrevFrame;
|
||||
|
||||
//! Track the last composited frame for Optimizations (See DoComposite code)
|
||||
int32_t mLastCompositedFrameIndex;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_FrameAnimator_h
|
||||
122
image/FrozenImage.cpp
Normal file
122
image/FrozenImage.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/* -*- 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 "FrozenImage.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
using layers::ImageContainer;
|
||||
using layers::LayerManager;
|
||||
|
||||
namespace image {
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(FrozenImage, ImageWrapper)
|
||||
|
||||
void
|
||||
FrozenImage::IncrementAnimationConsumers()
|
||||
{
|
||||
// Do nothing. This will prevent animation from starting if there are no other
|
||||
// instances of this image.
|
||||
}
|
||||
|
||||
void
|
||||
FrozenImage::DecrementAnimationConsumers()
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
FrozenImage::GetAnimated(bool* aAnimated)
|
||||
{
|
||||
bool dummy;
|
||||
nsresult rv = InnerImage()->GetAnimated(&dummy);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
*aAnimated = false;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
FrozenImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
|
||||
FrozenImage::GetImageContainer(layers::LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
// XXX(seth): GetImageContainer does not currently support anything but the
|
||||
// current frame. We work around this by always returning null, but if it ever
|
||||
// turns out that FrozenImage is widely used on codepaths that can actually
|
||||
// benefit from GetImageContainer, it would be a good idea to fix that method
|
||||
// for performance reasons.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(DrawResult)
|
||||
FrozenImage::Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t /* aWhichFrame - ignored */,
|
||||
SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return InnerImage()->Draw(aContext, aSize, aRegion, FRAME_FIRST,
|
||||
aSamplingFilter, aSVGContext, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
FrozenImage::RequestRefresh(const TimeStamp& aTime)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
FrozenImage::GetAnimationMode(uint16_t* aAnimationMode)
|
||||
{
|
||||
*aAnimationMode = kNormalAnimMode;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
FrozenImage::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
// Do nothing.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
FrozenImage::ResetAnimation()
|
||||
{
|
||||
// Do nothing.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(float)
|
||||
FrozenImage::GetFrameIndex(uint32_t aWhichFrame)
|
||||
{
|
||||
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
73
image/FrozenImage.h
Normal file
73
image/FrozenImage.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- 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_FrozenImage_h
|
||||
#define mozilla_image_FrozenImage_h
|
||||
|
||||
#include "ImageWrapper.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An Image wrapper that disables animation, freezing the image at its first
|
||||
* frame. It does this using two strategies. If this is the only instance of the
|
||||
* image, animation will never start, because IncrementAnimationConsumers is
|
||||
* ignored. If there is another instance that is animated, that's still OK,
|
||||
* because any imgIContainer method that is affected by animation gets its
|
||||
* aWhichFrame argument set to FRAME_FIRST when it passes through FrozenImage.
|
||||
*
|
||||
* XXX(seth): There a known (performance, not correctness) issue with
|
||||
* GetImageContainer. See the comments for that method for more information.
|
||||
*/
|
||||
class FrozenImage : public ImageWrapper
|
||||
{
|
||||
typedef gfx::SourceSurface SourceSurface;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
virtual void IncrementAnimationConsumers() override;
|
||||
virtual void DecrementAnimationConsumers() override;
|
||||
|
||||
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>)
|
||||
GetImageContainer(layers::LayerManager* aManager,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(void) RequestRefresh(const TimeStamp& aTime) override;
|
||||
NS_IMETHOD GetAnimationMode(uint16_t* aAnimationMode) override;
|
||||
NS_IMETHOD SetAnimationMode(uint16_t aAnimationMode) override;
|
||||
NS_IMETHOD ResetAnimation() override;
|
||||
NS_IMETHOD_(float) GetFrameIndex(uint32_t aWhichFrame) override;
|
||||
|
||||
protected:
|
||||
explicit FrozenImage(Image* aImage) : ImageWrapper(aImage) { }
|
||||
virtual ~FrozenImage() { }
|
||||
|
||||
private:
|
||||
friend class ImageOps;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_FrozenImage_h
|
||||
81
image/ICOFileHeaders.h
Normal file
81
image/ICOFileHeaders.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/* 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_ICOFileHeaders_h
|
||||
#define mozilla_image_ICOFileHeaders_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
#define ICONFILEHEADERSIZE 6
|
||||
#define ICODIRENTRYSIZE 16
|
||||
#define PNGSIGNATURESIZE 8
|
||||
#define BMPFILEHEADERSIZE 14
|
||||
|
||||
/**
|
||||
* The header that comes right at the start of an icon file. (This
|
||||
* corresponds to the Windows ICONDIR structure.)
|
||||
*/
|
||||
struct IconFileHeader
|
||||
{
|
||||
/**
|
||||
* Must be set to 0;
|
||||
*/
|
||||
uint16_t mReserved;
|
||||
/**
|
||||
* 1 for icon (.ICO) image (or 2 for cursor (.CUR) image (icon with the
|
||||
* addition of a hotspot), but we don't support cursor).
|
||||
*/
|
||||
uint16_t mType;
|
||||
/**
|
||||
* The number of BMP/PNG images contained in the icon file.
|
||||
*/
|
||||
uint16_t mCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* For each BMP/PNG image that the icon file contains there must be a
|
||||
* corresponding icon dir entry. (This corresponds to the Windows
|
||||
* ICONDIRENTRY structure.) These entries are encoded directly after the
|
||||
* IconFileHeader.
|
||||
*/
|
||||
struct IconDirEntry
|
||||
{
|
||||
uint8_t mWidth;
|
||||
uint8_t mHeight;
|
||||
/**
|
||||
* The number of colors in the color palette of the BMP/PNG that this dir
|
||||
* entry corresponds to, or 0 if the image does not use a color palette.
|
||||
*/
|
||||
uint8_t mColorCount;
|
||||
/**
|
||||
* Should be set to 0.
|
||||
*/
|
||||
uint8_t mReserved;
|
||||
union {
|
||||
uint16_t mPlanes; // ICO
|
||||
uint16_t mXHotspot; // CUR
|
||||
};
|
||||
union {
|
||||
uint16_t mBitCount; // ICO (bits per pixel)
|
||||
uint16_t mYHotspot; // CUR
|
||||
};
|
||||
/**
|
||||
* "bytes in resource" is the length of the encoded BMP/PNG that this dir
|
||||
* entry corresponds to.
|
||||
*/
|
||||
uint32_t mBytesInRes;
|
||||
/**
|
||||
* The offset of the start of the encoded BMP/PNG that this dir entry
|
||||
* corresponds to (from the start of the icon file).
|
||||
*/
|
||||
uint32_t mImageOffset;
|
||||
};
|
||||
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ICOFileHeaders_h
|
||||
172
image/IDecodingTask.cpp
Normal file
172
image/IDecodingTask.cpp
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/* -*- 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 "IDecodingTask.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "Decoder.h"
|
||||
#include "DecodePool.h"
|
||||
#include "RasterImage.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using gfx::IntRect;
|
||||
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers for sending notifications to the image associated with a decoder.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* static */ void
|
||||
IDecodingTask::NotifyProgress(NotNull<RasterImage*> aImage,
|
||||
NotNull<Decoder*> aDecoder)
|
||||
{
|
||||
MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode());
|
||||
|
||||
// Capture the decoder's state. If we need to notify asynchronously, it's
|
||||
// important that we don't wait until the lambda actually runs to capture the
|
||||
// state that we're going to notify. That would both introduce data races on
|
||||
// the decoder's state and cause inconsistencies between the NotifyProgress()
|
||||
// calls we make off-main-thread and the notifications that RasterImage
|
||||
// actually receives, which would cause bugs.
|
||||
Progress progress = aDecoder->TakeProgress();
|
||||
IntRect invalidRect = aDecoder->TakeInvalidRect();
|
||||
Maybe<uint32_t> frameCount = aDecoder->TakeCompleteFrameCount();
|
||||
DecoderFlags decoderFlags = aDecoder->GetDecoderFlags();
|
||||
SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags();
|
||||
|
||||
// Synchronously notify if we can.
|
||||
if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) {
|
||||
aImage->NotifyProgress(progress, invalidRect, frameCount,
|
||||
decoderFlags, surfaceFlags);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're forced to notify asynchronously.
|
||||
NotNull<RefPtr<RasterImage>> image = aImage;
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
|
||||
image->NotifyProgress(progress, invalidRect, frameCount,
|
||||
decoderFlags, surfaceFlags);
|
||||
}));
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
IDecodingTask::NotifyDecodeComplete(NotNull<RasterImage*> aImage,
|
||||
NotNull<Decoder*> aDecoder)
|
||||
{
|
||||
MOZ_ASSERT(aDecoder->HasError() || !aDecoder->InFrame(),
|
||||
"Decode complete in the middle of a frame?");
|
||||
|
||||
// Capture the decoder's state.
|
||||
DecoderFinalStatus finalStatus = aDecoder->FinalStatus();
|
||||
ImageMetadata metadata = aDecoder->GetImageMetadata();
|
||||
DecoderTelemetry telemetry = aDecoder->Telemetry();
|
||||
Progress progress = aDecoder->TakeProgress();
|
||||
IntRect invalidRect = aDecoder->TakeInvalidRect();
|
||||
Maybe<uint32_t> frameCount = aDecoder->TakeCompleteFrameCount();
|
||||
DecoderFlags decoderFlags = aDecoder->GetDecoderFlags();
|
||||
SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags();
|
||||
|
||||
// Synchronously notify if we can.
|
||||
if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) {
|
||||
aImage->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress,
|
||||
invalidRect, frameCount, decoderFlags,
|
||||
surfaceFlags);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're forced to notify asynchronously.
|
||||
NotNull<RefPtr<RasterImage>> image = aImage;
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
|
||||
image->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress,
|
||||
invalidRect, frameCount, decoderFlags,
|
||||
surfaceFlags);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// IDecodingTask implementation.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
IDecodingTask::Resume()
|
||||
{
|
||||
DecodePool::Singleton()->AsyncRun(this);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MetadataDecodingTask implementation.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MetadataDecodingTask::MetadataDecodingTask(NotNull<Decoder*> aDecoder)
|
||||
: mMutex("mozilla::image::MetadataDecodingTask")
|
||||
, mDecoder(aDecoder)
|
||||
{
|
||||
MOZ_ASSERT(mDecoder->IsMetadataDecode(),
|
||||
"Use DecodingTask for non-metadata decodes");
|
||||
}
|
||||
|
||||
void
|
||||
MetadataDecodingTask::Run()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
LexerResult result = mDecoder->Decode(WrapNotNull(this));
|
||||
|
||||
if (result.is<TerminalState>()) {
|
||||
NotifyDecodeComplete(mDecoder->GetImage(), mDecoder);
|
||||
return; // We're done.
|
||||
}
|
||||
|
||||
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
|
||||
// We can't make any more progress right now. We also don't want to report
|
||||
// any progress, because it's important that metadata decode results are
|
||||
// delivered atomically. The decoder itself will ensure that we get
|
||||
// reenqueued when more data is available; just return for now.
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Metadata decode yielded for an unexpected reason");
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// AnonymousDecodingTask implementation.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
{ }
|
||||
|
||||
void
|
||||
AnonymousDecodingTask::Run()
|
||||
{
|
||||
while (true) {
|
||||
LexerResult result = mDecoder->Decode(WrapNotNull(this));
|
||||
|
||||
if (result.is<TerminalState>()) {
|
||||
return; // We're done.
|
||||
}
|
||||
|
||||
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
|
||||
// We can't make any more progress right now. Let the caller decide how to
|
||||
// handle it.
|
||||
return;
|
||||
}
|
||||
|
||||
// Right now we don't do anything special for other kinds of yields, so just
|
||||
// keep working.
|
||||
MOZ_ASSERT(result.is<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
126
image/IDecodingTask.h
Normal file
126
image/IDecodingTask.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* An interface for tasks which can execute on the ImageLib DecodePool, and
|
||||
* various implementations.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_IDecodingTask_h
|
||||
#define mozilla_image_IDecodingTask_h
|
||||
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include "imgFrame.h"
|
||||
#include "SourceBuffer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
class RasterImage;
|
||||
|
||||
/// A priority hint that DecodePool can use when scheduling an IDecodingTask.
|
||||
enum class TaskPriority : uint8_t
|
||||
{
|
||||
eLow,
|
||||
eHigh
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface for tasks which can execute on the ImageLib DecodePool.
|
||||
*/
|
||||
class IDecodingTask : public IResumable
|
||||
{
|
||||
public:
|
||||
/// Run the task.
|
||||
virtual void Run() = 0;
|
||||
|
||||
/// @return true if, given the option, this task prefers to run synchronously.
|
||||
virtual bool ShouldPreferSyncRun() const = 0;
|
||||
|
||||
/// @return a priority hint that DecodePool can use when scheduling this task.
|
||||
virtual TaskPriority Priority() const = 0;
|
||||
|
||||
/// A default implementation of IResumable which resubmits the task to the
|
||||
/// DecodePool. Subclasses can override this if they need different behavior.
|
||||
void Resume() override;
|
||||
|
||||
protected:
|
||||
/// Notify @aImage of @aDecoder's progress.
|
||||
static void NotifyProgress(NotNull<RasterImage*> aImage,
|
||||
NotNull<Decoder*> aDecoder);
|
||||
|
||||
/// Notify @aImage that @aDecoder has finished.
|
||||
static void NotifyDecodeComplete(NotNull<RasterImage*> aImage,
|
||||
NotNull<Decoder*> aDecoder);
|
||||
|
||||
virtual ~IDecodingTask() { }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An IDecodingTask implementation for metadata decodes of images.
|
||||
*/
|
||||
class MetadataDecodingTask final : public IDecodingTask
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataDecodingTask, override)
|
||||
|
||||
explicit MetadataDecodingTask(NotNull<Decoder*> aDecoder);
|
||||
|
||||
void Run() override;
|
||||
|
||||
// Metadata decodes are very fast (since they only need to examine an image's
|
||||
// header) so there's no reason to refuse to run them synchronously if the
|
||||
// caller will allow us to.
|
||||
bool ShouldPreferSyncRun() const override { return true; }
|
||||
|
||||
// Metadata decodes run at the highest priority because they block layout and
|
||||
// page load.
|
||||
TaskPriority Priority() const override { return TaskPriority::eHigh; }
|
||||
|
||||
private:
|
||||
virtual ~MetadataDecodingTask() { }
|
||||
|
||||
/// Mutex protecting access to mDecoder.
|
||||
Mutex mMutex;
|
||||
|
||||
NotNull<RefPtr<Decoder>> mDecoder;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An IDecodingTask implementation for anonymous decoders - that is, decoders
|
||||
* with no associated Image object.
|
||||
*/
|
||||
class AnonymousDecodingTask final : public IDecodingTask
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override)
|
||||
|
||||
explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder);
|
||||
|
||||
void Run() override;
|
||||
|
||||
bool ShouldPreferSyncRun() const override { return true; }
|
||||
TaskPriority Priority() const override { return TaskPriority::eLow; }
|
||||
|
||||
// Anonymous decoders normally get all their data at once. We have tests where
|
||||
// they don't; in these situations, the test re-runs them manually. So no
|
||||
// matter what, we don't want to resume by posting a task to the DecodePool.
|
||||
void Resume() override { }
|
||||
|
||||
private:
|
||||
virtual ~AnonymousDecodingTask() { }
|
||||
|
||||
NotNull<RefPtr<Decoder>> mDecoder;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_IDecodingTask_h
|
||||
58
image/IProgressObserver.h
Normal file
58
image/IProgressObserver.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- 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_IProgressObserver_h
|
||||
#define mozilla_image_IProgressObserver_h
|
||||
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An interface for observing changes to image state, as reported by
|
||||
* ProgressTracker.
|
||||
*
|
||||
* This is the ImageLib-internal version of imgINotificationObserver,
|
||||
* essentially, with implementation details that code outside of ImageLib
|
||||
* shouldn't see.
|
||||
*
|
||||
* XXX(seth): It's preferable to avoid adding anything to this interface if
|
||||
* possible. In the long term, it would be ideal to get to a place where we can
|
||||
* just use the imgINotificationObserver interface internally as well.
|
||||
*/
|
||||
class IProgressObserver : public SupportsWeakPtr<IProgressObserver>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(IProgressObserver)
|
||||
|
||||
// Subclasses may or may not be XPCOM classes, so we just require that they
|
||||
// implement AddRef and Release.
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
|
||||
|
||||
// imgINotificationObserver methods:
|
||||
virtual void Notify(int32_t aType, const nsIntRect* aRect = nullptr) = 0;
|
||||
virtual void OnLoadComplete(bool aLastPart) = 0;
|
||||
|
||||
// imgIOnloadBlocker methods:
|
||||
virtual void BlockOnload() = 0;
|
||||
virtual void UnblockOnload() = 0;
|
||||
|
||||
// Other, internal-only methods:
|
||||
virtual void SetHasImage() = 0;
|
||||
virtual bool NotificationsDeferred() const = 0;
|
||||
virtual void SetNotificationsDeferred(bool aDeferNotifications) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~IProgressObserver() { }
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_IProgressObserver_h
|
||||
290
image/ISurfaceProvider.h
Normal file
290
image/ISurfaceProvider.h
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* An interface for objects which can either store a surface or dynamically
|
||||
* generate one, and various implementations.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_ISurfaceProvider_h
|
||||
#define mozilla_image_ISurfaceProvider_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Variant.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
#include "imgFrame.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class CachedSurface;
|
||||
class DrawableSurface;
|
||||
|
||||
/**
|
||||
* An interface for objects which can either store a surface or dynamically
|
||||
* generate one.
|
||||
*/
|
||||
class ISurfaceProvider
|
||||
{
|
||||
public:
|
||||
// Subclasses may or may not be XPCOM classes, so we just require that they
|
||||
// implement AddRef and Release.
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
|
||||
|
||||
/// @return key data used for identifying which image this ISurfaceProvider is
|
||||
/// associated with in the surface cache.
|
||||
ImageKey GetImageKey() const { return mImageKey; }
|
||||
|
||||
/// @return key data used to uniquely identify this ISurfaceProvider's cache
|
||||
/// entry in the surface cache.
|
||||
const SurfaceKey& GetSurfaceKey() const { return mSurfaceKey; }
|
||||
|
||||
/// @return a (potentially lazily computed) drawable reference to a surface.
|
||||
virtual DrawableSurface Surface();
|
||||
|
||||
/// @return true if DrawableRef() will return a completely decoded surface.
|
||||
virtual bool IsFinished() const = 0;
|
||||
|
||||
/// @return the number of bytes of memory this ISurfaceProvider is expected to
|
||||
/// require. Optimizations may result in lower real memory usage. Trivial
|
||||
/// overhead is ignored. Because this value is used in bookkeeping, it's
|
||||
/// important that it be constant over the lifetime of this object.
|
||||
virtual size_t LogicalSizeInBytes() const = 0;
|
||||
|
||||
/// @return the actual number of bytes of memory this ISurfaceProvider is
|
||||
/// using. May vary over the lifetime of the ISurfaceProvider. The default
|
||||
/// implementation is appropriate for static ISurfaceProviders.
|
||||
virtual void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
||||
size_t& aHeapSizeOut,
|
||||
size_t& aNonHeapSizeOut)
|
||||
{
|
||||
DrawableFrameRef ref = DrawableRef(/* aFrame = */ 0);
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
||||
}
|
||||
|
||||
/// @return the availability state of this ISurfaceProvider, which indicates
|
||||
/// whether DrawableRef() could successfully return a surface. Should only be
|
||||
/// called from SurfaceCache code as it relies on SurfaceCache for
|
||||
/// synchronization.
|
||||
AvailabilityState& Availability() { return mAvailability; }
|
||||
const AvailabilityState& Availability() const { return mAvailability; }
|
||||
|
||||
protected:
|
||||
ISurfaceProvider(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
AvailabilityState aAvailability)
|
||||
: mImageKey(aImageKey)
|
||||
, mSurfaceKey(aSurfaceKey)
|
||||
, mAvailability(aAvailability)
|
||||
{
|
||||
MOZ_ASSERT(aImageKey, "Must have a valid image key");
|
||||
}
|
||||
|
||||
virtual ~ISurfaceProvider() { }
|
||||
|
||||
/// @return an eagerly computed drawable reference to a surface. For
|
||||
/// dynamically generated animation surfaces, @aFrame specifies the 0-based
|
||||
/// index of the desired frame.
|
||||
virtual DrawableFrameRef DrawableRef(size_t aFrame) = 0;
|
||||
|
||||
/// @return true if this ISurfaceProvider is locked. (@see SetLocked())
|
||||
/// Should only be called from SurfaceCache code as it relies on SurfaceCache
|
||||
/// for synchronization.
|
||||
virtual bool IsLocked() const = 0;
|
||||
|
||||
/// If @aLocked is true, hint that this ISurfaceProvider is in use and it
|
||||
/// should avoid releasing its resources. Should only be called from
|
||||
/// SurfaceCache code as it relies on SurfaceCache for synchronization.
|
||||
virtual void SetLocked(bool aLocked) = 0;
|
||||
|
||||
private:
|
||||
friend class CachedSurface;
|
||||
friend class DrawableSurface;
|
||||
|
||||
const ImageKey mImageKey;
|
||||
const SurfaceKey mSurfaceKey;
|
||||
AvailabilityState mAvailability;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A reference to a surface (stored in an imgFrame) that holds the surface in
|
||||
* memory, guaranteeing that it can be drawn. If you have a DrawableSurface
|
||||
* |surf| and |if (surf)| returns true, then calls to |surf->Draw()| and
|
||||
* |surf->GetSourceSurface()| are guaranteed to succeed.
|
||||
*
|
||||
* Note that the surface may be computed lazily, so a DrawableSurface should not
|
||||
* be dereferenced (i.e., operator->() should not be called) until you're
|
||||
* sure that you want to draw it.
|
||||
*/
|
||||
class MOZ_STACK_CLASS DrawableSurface final
|
||||
{
|
||||
public:
|
||||
DrawableSurface() : mHaveSurface(false) { }
|
||||
|
||||
explicit DrawableSurface(DrawableFrameRef&& aDrawableRef)
|
||||
: mDrawableRef(Move(aDrawableRef))
|
||||
, mHaveSurface(bool(mDrawableRef))
|
||||
{ }
|
||||
|
||||
explicit DrawableSurface(NotNull<ISurfaceProvider*> aProvider)
|
||||
: mProvider(aProvider)
|
||||
, mHaveSurface(true)
|
||||
{ }
|
||||
|
||||
DrawableSurface(DrawableSurface&& aOther)
|
||||
: mDrawableRef(Move(aOther.mDrawableRef))
|
||||
, mProvider(Move(aOther.mProvider))
|
||||
, mHaveSurface(aOther.mHaveSurface)
|
||||
{
|
||||
aOther.mHaveSurface = false;
|
||||
}
|
||||
|
||||
DrawableSurface& operator=(DrawableSurface&& aOther)
|
||||
{
|
||||
MOZ_ASSERT(this != &aOther, "Self-moves are prohibited");
|
||||
mDrawableRef = Move(aOther.mDrawableRef);
|
||||
mProvider = Move(aOther.mProvider);
|
||||
mHaveSurface = aOther.mHaveSurface;
|
||||
aOther.mHaveSurface = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this DrawableSurface is dynamically generated from an animation, attempt
|
||||
* to seek to frame @aFrame, where @aFrame is a 0-based index into the frames
|
||||
* of the animation. Otherwise, nothing will blow up at runtime, but we assert
|
||||
* in debug builds, since calling this in an unexpected situation probably
|
||||
* indicates a bug.
|
||||
*
|
||||
* @return a successful result if we could obtain frame @aFrame. Note that
|
||||
* |mHaveSurface| being true means that we're guaranteed to have *some* frame,
|
||||
* so the caller can dereference this DrawableSurface even if Seek() fails,
|
||||
* but while nothing will blow up, the frame won't be the one they expect.
|
||||
*/
|
||||
nsresult Seek(size_t aFrame)
|
||||
{
|
||||
MOZ_ASSERT(mHaveSurface, "Trying to seek an empty DrawableSurface?");
|
||||
|
||||
if (!mProvider) {
|
||||
MOZ_ASSERT_UNREACHABLE("Trying to seek a static DrawableSurface?");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mDrawableRef = mProvider->DrawableRef(aFrame);
|
||||
|
||||
return mDrawableRef ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return mHaveSurface; }
|
||||
imgFrame* operator->() { return DrawableRef().get(); }
|
||||
|
||||
private:
|
||||
DrawableSurface(const DrawableSurface& aOther) = delete;
|
||||
DrawableSurface& operator=(const DrawableSurface& aOther) = delete;
|
||||
|
||||
DrawableFrameRef& DrawableRef()
|
||||
{
|
||||
MOZ_ASSERT(mHaveSurface);
|
||||
|
||||
// If we weren't created with a DrawableFrameRef directly, we should've been
|
||||
// created with an ISurfaceProvider which can give us one. Note that if
|
||||
// Seek() has been called, we'll already have a DrawableFrameRef, so we
|
||||
// won't need to get one here.
|
||||
if (!mDrawableRef) {
|
||||
MOZ_ASSERT(mProvider);
|
||||
mDrawableRef = mProvider->DrawableRef(/* aFrame = */ 0);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mDrawableRef);
|
||||
return mDrawableRef;
|
||||
}
|
||||
|
||||
DrawableFrameRef mDrawableRef;
|
||||
RefPtr<ISurfaceProvider> mProvider;
|
||||
bool mHaveSurface;
|
||||
};
|
||||
|
||||
|
||||
// Surface() is implemented here so that DrawableSurface's definition is
|
||||
// visible. This default implementation eagerly obtains a DrawableFrameRef for
|
||||
// the first frame and is intended for static ISurfaceProviders.
|
||||
inline DrawableSurface
|
||||
ISurfaceProvider::Surface()
|
||||
{
|
||||
return DrawableSurface(DrawableRef(/* aFrame = */ 0));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider that stores a single surface.
|
||||
*/
|
||||
class SimpleSurfaceProvider final : public ISurfaceProvider
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SimpleSurfaceProvider, override)
|
||||
|
||||
SimpleSurfaceProvider(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
NotNull<imgFrame*> aSurface)
|
||||
: ISurfaceProvider(aImageKey, aSurfaceKey,
|
||||
AvailabilityState::StartAvailable())
|
||||
, mSurface(aSurface)
|
||||
{
|
||||
MOZ_ASSERT(aSurfaceKey.Size() == mSurface->GetSize());
|
||||
}
|
||||
|
||||
bool IsFinished() const override { return mSurface->IsFinished(); }
|
||||
|
||||
size_t LogicalSizeInBytes() const override
|
||||
{
|
||||
gfx::IntSize size = mSurface->GetSize();
|
||||
return size.width * size.height * mSurface->GetBytesPerPixel();
|
||||
}
|
||||
|
||||
protected:
|
||||
DrawableFrameRef DrawableRef(size_t aFrame) override
|
||||
{
|
||||
MOZ_ASSERT(aFrame == 0,
|
||||
"Requesting an animation frame from a SimpleSurfaceProvider?");
|
||||
return mSurface->DrawableRef();
|
||||
}
|
||||
|
||||
bool IsLocked() const override { return bool(mLockRef); }
|
||||
|
||||
void SetLocked(bool aLocked) override
|
||||
{
|
||||
if (aLocked == IsLocked()) {
|
||||
return; // Nothing changed.
|
||||
}
|
||||
|
||||
// If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep
|
||||
// any volatile buffer it owns in memory.
|
||||
mLockRef = aLocked ? mSurface->DrawableRef()
|
||||
: DrawableFrameRef();
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~SimpleSurfaceProvider() { }
|
||||
|
||||
NotNull<RefPtr<imgFrame>> mSurface;
|
||||
DrawableFrameRef mLockRef;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ISurfaceProvider_h
|
||||
150
image/Image.cpp
Normal file
150
image/Image.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* -*- 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 "Image.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Memory Reporting
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImageMemoryCounter::ImageMemoryCounter(Image* aImage,
|
||||
MallocSizeOf aMallocSizeOf,
|
||||
bool aIsUsed)
|
||||
: mIsUsed(aIsUsed)
|
||||
{
|
||||
MOZ_ASSERT(aImage);
|
||||
|
||||
// Extract metadata about the image.
|
||||
RefPtr<ImageURL> imageURL(aImage->GetURI());
|
||||
if (imageURL) {
|
||||
imageURL->GetSpec(mURI);
|
||||
}
|
||||
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
aImage->GetWidth(&width);
|
||||
aImage->GetHeight(&height);
|
||||
mIntrinsicSize.SizeTo(width, height);
|
||||
|
||||
mType = aImage->GetType();
|
||||
|
||||
// Populate memory counters for source and decoded data.
|
||||
mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aMallocSizeOf));
|
||||
aImage->CollectSizeOfSurfaces(mSurfaces, aMallocSizeOf);
|
||||
|
||||
// Compute totals.
|
||||
for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) {
|
||||
mValues += surfaceCounter.Values();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Image Base Types
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Constructor
|
||||
ImageResource::ImageResource(ImageURL* aURI) :
|
||||
mURI(aURI),
|
||||
mInnerWindowId(0),
|
||||
mAnimationConsumers(0),
|
||||
mAnimationMode(kNormalAnimMode),
|
||||
mInitialized(false),
|
||||
mAnimating(false),
|
||||
mError(false)
|
||||
{ }
|
||||
|
||||
ImageResource::~ImageResource()
|
||||
{
|
||||
// Ask our ProgressTracker to drop its weak reference to us.
|
||||
mProgressTracker->ResetImage();
|
||||
}
|
||||
|
||||
void
|
||||
ImageResource::IncrementAnimationConsumers()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
|
||||
"with DecrementAnimationConsumers");
|
||||
mAnimationConsumers++;
|
||||
}
|
||||
|
||||
void
|
||||
ImageResource::DecrementAnimationConsumers()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
|
||||
"with IncrementAnimationConsumers");
|
||||
MOZ_ASSERT(mAnimationConsumers >= 1,
|
||||
"Invalid no. of animation consumers!");
|
||||
mAnimationConsumers--;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode)
|
||||
{
|
||||
if (mError) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_ENSURE_ARG_POINTER(aAnimationMode);
|
||||
|
||||
*aAnimationMode = mAnimationMode;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode)
|
||||
{
|
||||
if (mError) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_ASSERTION(aAnimationMode == kNormalAnimMode ||
|
||||
aAnimationMode == kDontAnimMode ||
|
||||
aAnimationMode == kLoopOnceAnimMode,
|
||||
"Wrong Animation Mode is being set!");
|
||||
|
||||
mAnimationMode = aAnimationMode;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
ImageResource::HadRecentRefresh(const TimeStamp& aTime)
|
||||
{
|
||||
// Our threshold for "recent" is 1/2 of the default refresh-driver interval.
|
||||
// This ensures that we allow for frame rates at least as fast as the
|
||||
// refresh driver's default rate.
|
||||
static TimeDuration recentThreshold =
|
||||
TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0);
|
||||
|
||||
if (!mLastRefreshTime.IsNull() &&
|
||||
aTime - mLastRefreshTime < recentThreshold) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// else, we can proceed with a refresh.
|
||||
// But first, update our last refresh time:
|
||||
mLastRefreshTime = aTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ImageResource::EvaluateAnimation()
|
||||
{
|
||||
if (!mAnimating && ShouldAnimate()) {
|
||||
nsresult rv = StartAnimation();
|
||||
mAnimating = NS_SUCCEEDED(rv);
|
||||
} else if (mAnimating && !ShouldAnimate()) {
|
||||
StopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
323
image/Image.h
Normal file
323
image/Image.h
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/* -*- 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_Image_h
|
||||
#define mozilla_image_Image_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "ImageURL.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "ProgressTracker.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
class nsIRequest;
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Image;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Memory Reporting
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct MemoryCounter
|
||||
{
|
||||
MemoryCounter()
|
||||
: mSource(0)
|
||||
, mDecodedHeap(0)
|
||||
, mDecodedNonHeap(0)
|
||||
{ }
|
||||
|
||||
void SetSource(size_t aCount) { mSource = aCount; }
|
||||
size_t Source() const { return mSource; }
|
||||
void SetDecodedHeap(size_t aCount) { mDecodedHeap = aCount; }
|
||||
size_t DecodedHeap() const { return mDecodedHeap; }
|
||||
void SetDecodedNonHeap(size_t aCount) { mDecodedNonHeap = aCount; }
|
||||
size_t DecodedNonHeap() const { return mDecodedNonHeap; }
|
||||
|
||||
MemoryCounter& operator+=(const MemoryCounter& aOther)
|
||||
{
|
||||
mSource += aOther.mSource;
|
||||
mDecodedHeap += aOther.mDecodedHeap;
|
||||
mDecodedNonHeap += aOther.mDecodedNonHeap;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t mSource;
|
||||
size_t mDecodedHeap;
|
||||
size_t mDecodedNonHeap;
|
||||
};
|
||||
|
||||
enum class SurfaceMemoryCounterType
|
||||
{
|
||||
NORMAL,
|
||||
COMPOSITING,
|
||||
COMPOSITING_PREV
|
||||
};
|
||||
|
||||
struct SurfaceMemoryCounter
|
||||
{
|
||||
SurfaceMemoryCounter(const SurfaceKey& aKey,
|
||||
bool aIsLocked,
|
||||
SurfaceMemoryCounterType aType =
|
||||
SurfaceMemoryCounterType::NORMAL)
|
||||
: mKey(aKey)
|
||||
, mType(aType)
|
||||
, mIsLocked(aIsLocked)
|
||||
{ }
|
||||
|
||||
const SurfaceKey& Key() const { return mKey; }
|
||||
MemoryCounter& Values() { return mValues; }
|
||||
const MemoryCounter& Values() const { return mValues; }
|
||||
SurfaceMemoryCounterType Type() const { return mType; }
|
||||
bool IsLocked() const { return mIsLocked; }
|
||||
|
||||
private:
|
||||
const SurfaceKey mKey;
|
||||
MemoryCounter mValues;
|
||||
const SurfaceMemoryCounterType mType;
|
||||
const bool mIsLocked;
|
||||
};
|
||||
|
||||
struct ImageMemoryCounter
|
||||
{
|
||||
ImageMemoryCounter(Image* aImage,
|
||||
MallocSizeOf aMallocSizeOf,
|
||||
bool aIsUsed);
|
||||
|
||||
nsCString& URI() { return mURI; }
|
||||
const nsCString& URI() const { return mURI; }
|
||||
const nsTArray<SurfaceMemoryCounter>& Surfaces() const { return mSurfaces; }
|
||||
const gfx::IntSize IntrinsicSize() const { return mIntrinsicSize; }
|
||||
const MemoryCounter& Values() const { return mValues; }
|
||||
uint16_t Type() const { return mType; }
|
||||
bool IsUsed() const { return mIsUsed; }
|
||||
|
||||
bool IsNotable() const
|
||||
{
|
||||
const size_t NotableThreshold = 16 * 1024;
|
||||
size_t total = mValues.Source() + mValues.DecodedHeap()
|
||||
+ mValues.DecodedNonHeap();
|
||||
return total >= NotableThreshold;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCString mURI;
|
||||
nsTArray<SurfaceMemoryCounter> mSurfaces;
|
||||
gfx::IntSize mIntrinsicSize;
|
||||
MemoryCounter mValues;
|
||||
uint16_t mType;
|
||||
const bool mIsUsed;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Image Base Types
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Image : public imgIContainer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Flags for Image initialization.
|
||||
*
|
||||
* Meanings:
|
||||
*
|
||||
* INIT_FLAG_NONE: Lack of flags
|
||||
*
|
||||
* INIT_FLAG_DISCARDABLE: The container should be discardable
|
||||
*
|
||||
* INIT_FLAG_DECODE_IMMEDIATELY: The container should decode as soon as
|
||||
* possible, regardless of what our heuristics say.
|
||||
*
|
||||
* INIT_FLAG_TRANSIENT: The container is likely to exist for only a short time
|
||||
* before being destroyed. (For example, containers for
|
||||
* multipart/x-mixed-replace image parts fall into this category.) If this
|
||||
* flag is set, INIT_FLAG_DISCARDABLE and INIT_FLAG_DECODE_ONLY_ON_DRAW must
|
||||
* not be set.
|
||||
*
|
||||
* 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_SYNC_LOAD = 0x8;
|
||||
|
||||
virtual already_AddRefed<ProgressTracker> GetProgressTracker() = 0;
|
||||
virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
|
||||
|
||||
/**
|
||||
* The size, in bytes, occupied by the compressed source data of the image.
|
||||
* If MallocSizeOf does not work on this platform, uses a fallback approach to
|
||||
* ensure that something reasonable is always returned.
|
||||
*/
|
||||
virtual size_t
|
||||
SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const = 0;
|
||||
|
||||
/**
|
||||
* Collect an accounting of the memory occupied by the image's surfaces (which
|
||||
* together make up its decoded data). Each surface is recorded as a separate
|
||||
* SurfaceMemoryCounter, stored in @aCounters.
|
||||
*/
|
||||
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const = 0;
|
||||
|
||||
virtual void IncrementAnimationConsumers() = 0;
|
||||
virtual void DecrementAnimationConsumers() = 0;
|
||||
#ifdef DEBUG
|
||||
virtual uint32_t GetAnimationConsumers() = 0;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Called from OnDataAvailable when the stream associated with the image has
|
||||
* received new image data. The arguments are the same as OnDataAvailable's,
|
||||
* but by separating this functionality into a different method we don't
|
||||
* interfere with subclasses which wish to implement nsIStreamListener.
|
||||
*
|
||||
* Images should not do anything that could send out notifications until they
|
||||
* have received their first OnImageDataAvailable notification; in
|
||||
* particular, this means that instantiating decoders should be deferred
|
||||
* until OnImageDataAvailable is called.
|
||||
*/
|
||||
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount) = 0;
|
||||
|
||||
/**
|
||||
* Called from OnStopRequest when the image's underlying request completes.
|
||||
*
|
||||
* @param aRequest The completed request.
|
||||
* @param aContext Context from Necko's OnStopRequest.
|
||||
* @param aStatus A success or failure code.
|
||||
* @param aLastPart Whether this is the final part of the underlying request.
|
||||
*/
|
||||
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart) = 0;
|
||||
|
||||
/**
|
||||
* Called when the SurfaceCache discards a surface belonging to this image.
|
||||
*/
|
||||
virtual void OnSurfaceDiscarded() = 0;
|
||||
|
||||
virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
|
||||
virtual uint64_t InnerWindowID() const = 0;
|
||||
|
||||
virtual bool HasError() = 0;
|
||||
virtual void SetHasError() = 0;
|
||||
|
||||
virtual ImageURL* GetURI() = 0;
|
||||
|
||||
virtual void ReportUseCounters() { }
|
||||
};
|
||||
|
||||
class ImageResource : public Image
|
||||
{
|
||||
public:
|
||||
already_AddRefed<ProgressTracker> GetProgressTracker() override
|
||||
{
|
||||
RefPtr<ProgressTracker> progressTracker = mProgressTracker;
|
||||
MOZ_ASSERT(progressTracker);
|
||||
return progressTracker.forget();
|
||||
}
|
||||
|
||||
void SetProgressTracker(
|
||||
ProgressTracker* aProgressTracker) override final
|
||||
{
|
||||
MOZ_ASSERT(aProgressTracker);
|
||||
MOZ_ASSERT(!mProgressTracker);
|
||||
mProgressTracker = aProgressTracker;
|
||||
}
|
||||
|
||||
virtual void IncrementAnimationConsumers() override;
|
||||
virtual void DecrementAnimationConsumers() override;
|
||||
#ifdef DEBUG
|
||||
virtual uint32_t GetAnimationConsumers() override
|
||||
{
|
||||
return mAnimationConsumers;
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual void OnSurfaceDiscarded() override { }
|
||||
|
||||
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override
|
||||
{
|
||||
mInnerWindowId = aInnerWindowId;
|
||||
}
|
||||
virtual uint64_t InnerWindowID() const override { return mInnerWindowId; }
|
||||
|
||||
virtual bool HasError() override { return mError; }
|
||||
virtual void SetHasError() override { mError = true; }
|
||||
|
||||
/*
|
||||
* Returns a non-AddRefed pointer to the URI associated with this image.
|
||||
* Illegal to use off-main-thread.
|
||||
*/
|
||||
virtual ImageURL* GetURI() override { return mURI.get(); }
|
||||
|
||||
protected:
|
||||
explicit ImageResource(ImageURL* aURI);
|
||||
~ImageResource();
|
||||
|
||||
// Shared functionality for implementors of imgIContainer. Every
|
||||
// implementation of attribute animationMode should forward here.
|
||||
nsresult GetAnimationModeInternal(uint16_t* aAnimationMode);
|
||||
nsresult SetAnimationModeInternal(uint16_t aAnimationMode);
|
||||
|
||||
/**
|
||||
* Helper for RequestRefresh.
|
||||
*
|
||||
* If we've had a "recent" refresh (i.e. if this image is being used in
|
||||
* multiple documents & some other document *just* called RequestRefresh() on
|
||||
* this image with a timestamp close to aTime), this method returns true.
|
||||
*
|
||||
* Otherwise, this method updates mLastRefreshTime to aTime & returns false.
|
||||
*/
|
||||
bool HadRecentRefresh(const TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* Decides whether animation should or should not be happening,
|
||||
* and makes sure the right thing is being done.
|
||||
*/
|
||||
virtual void EvaluateAnimation();
|
||||
|
||||
/**
|
||||
* Extended by child classes, if they have additional
|
||||
* conditions for being able to animate.
|
||||
*/
|
||||
virtual bool ShouldAnimate() {
|
||||
return mAnimationConsumers > 0 && mAnimationMode != kDontAnimMode;
|
||||
}
|
||||
|
||||
virtual nsresult StartAnimation() = 0;
|
||||
virtual nsresult StopAnimation() = 0;
|
||||
|
||||
// Member data shared by all implementations of this abstract class
|
||||
RefPtr<ProgressTracker> mProgressTracker;
|
||||
RefPtr<ImageURL> mURI;
|
||||
TimeStamp mLastRefreshTime;
|
||||
uint64_t mInnerWindowId;
|
||||
uint32_t mAnimationConsumers;
|
||||
uint16_t mAnimationMode; // Enum values in imgIContainer
|
||||
bool mInitialized:1; // Have we been initalized?
|
||||
bool mAnimating:1; // Are we currently animating?
|
||||
bool mError:1; // Error handling
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_Image_h
|
||||
167
image/ImageCacheKey.cpp
Normal file
167
image/ImageCacheKey.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/* -*- 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 "ImageCacheKey.h"
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include "File.h"
|
||||
#include "ImageURL.h"
|
||||
#include "nsHostObjectProtocolHandler.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/dom/workers/ServiceWorkerManager.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
namespace image {
|
||||
|
||||
bool
|
||||
URISchemeIs(ImageURL* aURI, const char* aScheme)
|
||||
{
|
||||
bool schemeMatches = false;
|
||||
if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs(aScheme, &schemeMatches)))) {
|
||||
return false;
|
||||
}
|
||||
return schemeMatches;
|
||||
}
|
||||
|
||||
static Maybe<uint64_t>
|
||||
BlobSerial(ImageURL* aURI)
|
||||
{
|
||||
nsAutoCString spec;
|
||||
aURI->GetSpec(spec);
|
||||
|
||||
RefPtr<BlobImpl> blob;
|
||||
if (NS_SUCCEEDED(NS_GetBlobForBlobURISpec(spec, getter_AddRefs(blob))) &&
|
||||
blob) {
|
||||
return Some(blob->GetSerialNumber());
|
||||
}
|
||||
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
ImageCacheKey::ImageCacheKey(nsIURI* aURI,
|
||||
const PrincipalOriginAttributes& aAttrs,
|
||||
nsIDocument* aDocument,
|
||||
nsresult& aRv)
|
||||
: mURI(new ImageURL(aURI, aRv))
|
||||
, mOriginAttributes(aAttrs)
|
||||
, mControlledDocument(GetControlledDocumentToken(aDocument))
|
||||
, mIsChrome(URISchemeIs(mURI, "chrome"))
|
||||
{
|
||||
NS_ENSURE_SUCCESS_VOID(aRv);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (URISchemeIs(mURI, "blob")) {
|
||||
mBlobSerial = BlobSerial(mURI);
|
||||
}
|
||||
|
||||
mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
|
||||
}
|
||||
|
||||
ImageCacheKey::ImageCacheKey(ImageURL* aURI,
|
||||
const PrincipalOriginAttributes& aAttrs,
|
||||
nsIDocument* aDocument)
|
||||
: mURI(aURI)
|
||||
, mOriginAttributes(aAttrs)
|
||||
, mControlledDocument(GetControlledDocumentToken(aDocument))
|
||||
, mIsChrome(URISchemeIs(mURI, "chrome"))
|
||||
{
|
||||
MOZ_ASSERT(aURI);
|
||||
|
||||
if (URISchemeIs(mURI, "blob")) {
|
||||
mBlobSerial = BlobSerial(mURI);
|
||||
}
|
||||
|
||||
mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
|
||||
}
|
||||
|
||||
ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther)
|
||||
: mURI(aOther.mURI)
|
||||
, mBlobSerial(aOther.mBlobSerial)
|
||||
, mOriginAttributes(aOther.mOriginAttributes)
|
||||
, mControlledDocument(aOther.mControlledDocument)
|
||||
, mHash(aOther.mHash)
|
||||
, mIsChrome(aOther.mIsChrome)
|
||||
{ }
|
||||
|
||||
ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther)
|
||||
: mURI(Move(aOther.mURI))
|
||||
, mBlobSerial(Move(aOther.mBlobSerial))
|
||||
, mOriginAttributes(aOther.mOriginAttributes)
|
||||
, mControlledDocument(aOther.mControlledDocument)
|
||||
, mHash(aOther.mHash)
|
||||
, mIsChrome(aOther.mIsChrome)
|
||||
{ }
|
||||
|
||||
bool
|
||||
ImageCacheKey::operator==(const ImageCacheKey& aOther) const
|
||||
{
|
||||
// Don't share the image cache between a controlled document and anything else.
|
||||
if (mControlledDocument != aOther.mControlledDocument) {
|
||||
return false;
|
||||
}
|
||||
// The origin attributes always have to match.
|
||||
if (mOriginAttributes != aOther.mOriginAttributes) {
|
||||
return false;
|
||||
}
|
||||
if (mBlobSerial || aOther.mBlobSerial) {
|
||||
// If at least one of us has a blob serial, just compare the blob serial and
|
||||
// the ref portion of the URIs.
|
||||
return mBlobSerial == aOther.mBlobSerial &&
|
||||
mURI->HasSameRef(*aOther.mURI);
|
||||
}
|
||||
|
||||
// For non-blob URIs, compare the URIs.
|
||||
return *mURI == *aOther.mURI;
|
||||
}
|
||||
|
||||
const char*
|
||||
ImageCacheKey::Spec() const
|
||||
{
|
||||
return mURI->Spec();
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
ImageCacheKey::ComputeHash(ImageURL* aURI,
|
||||
const Maybe<uint64_t>& aBlobSerial,
|
||||
const PrincipalOriginAttributes& aAttrs,
|
||||
void* aControlledDocument)
|
||||
{
|
||||
// Since we frequently call Hash() several times in a row on the same
|
||||
// ImageCacheKey, as an optimization we compute our hash once and store it.
|
||||
|
||||
nsPrintfCString ptr("%p", aControlledDocument);
|
||||
nsAutoCString suffix;
|
||||
aAttrs.CreateSuffix(suffix);
|
||||
|
||||
return AddToHash(0, aURI->ComputeHash(aBlobSerial),
|
||||
HashString(suffix), HashString(ptr));
|
||||
}
|
||||
|
||||
/* static */ void*
|
||||
ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument)
|
||||
{
|
||||
// For non-controlled documents, we just return null. For controlled
|
||||
// documents, we cast the pointer into a void* to avoid dereferencing
|
||||
// it (since we only use it for comparisons), and return it.
|
||||
void* pointer = nullptr;
|
||||
using dom::workers::ServiceWorkerManager;
|
||||
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
||||
if (aDocument && swm) {
|
||||
ErrorResult rv;
|
||||
if (swm->IsControlled(aDocument, rv)) {
|
||||
pointer = aDocument;
|
||||
}
|
||||
}
|
||||
return pointer;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
75
image/ImageCacheKey.h
Normal file
75
image/ImageCacheKey.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* ImageCacheKey is the key type for the image cache (see imgLoader.h).
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_src_ImageCacheKey_h
|
||||
#define mozilla_image_src_ImageCacheKey_h
|
||||
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
class nsIDocument;
|
||||
class nsIURI;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class ImageURL;
|
||||
|
||||
/**
|
||||
* An ImageLib cache entry key.
|
||||
*
|
||||
* We key the cache on the initial URI (before any redirects), with some
|
||||
* canonicalization applied. See ComputeHash() for the details.
|
||||
* Controlled documents do not share their cache entries with
|
||||
* non-controlled documents, or other controlled documents.
|
||||
*/
|
||||
class ImageCacheKey final
|
||||
{
|
||||
public:
|
||||
ImageCacheKey(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs,
|
||||
nsIDocument* aDocument, nsresult& aRv);
|
||||
ImageCacheKey(ImageURL* aURI, const PrincipalOriginAttributes& aAttrs,
|
||||
nsIDocument* aDocument);
|
||||
|
||||
ImageCacheKey(const ImageCacheKey& aOther);
|
||||
ImageCacheKey(ImageCacheKey&& aOther);
|
||||
|
||||
bool operator==(const ImageCacheKey& aOther) const;
|
||||
uint32_t Hash() const { return mHash; }
|
||||
|
||||
/// A weak pointer to the URI spec for this cache entry. For logging only.
|
||||
const char* Spec() const;
|
||||
|
||||
/// Is this cache entry for a chrome image?
|
||||
bool IsChrome() const { return mIsChrome; }
|
||||
|
||||
/// A token indicating which service worker controlled document this entry
|
||||
/// belongs to, if any.
|
||||
void* ControlledDocument() const { return mControlledDocument; }
|
||||
|
||||
private:
|
||||
static uint32_t ComputeHash(ImageURL* aURI,
|
||||
const Maybe<uint64_t>& aBlobSerial,
|
||||
const PrincipalOriginAttributes& aAttrs,
|
||||
void* aControlledDocument);
|
||||
static void* GetControlledDocumentToken(nsIDocument* aDocument);
|
||||
|
||||
RefPtr<ImageURL> mURI;
|
||||
Maybe<uint64_t> mBlobSerial;
|
||||
PrincipalOriginAttributes mOriginAttributes;
|
||||
void* mControlledDocument;
|
||||
uint32_t mHash;
|
||||
bool mIsChrome;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_src_ImageCacheKey_h
|
||||
288
image/ImageFactory.cpp
Normal file
288
image/ImageFactory.cpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
/* -*- 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 "ImageFactory.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "mozilla/Likely.h"
|
||||
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIFileChannel.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsIRequest.h"
|
||||
|
||||
#include "MultipartImage.h"
|
||||
#include "RasterImage.h"
|
||||
#include "VectorImage.h"
|
||||
#include "Image.h"
|
||||
#include "nsMediaFragmentURIParser.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/*static*/ void
|
||||
ImageFactory::Initialize()
|
||||
{ }
|
||||
|
||||
static uint32_t
|
||||
ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
// We default to the static globals.
|
||||
bool isDiscardable = gfxPrefs::ImageMemDiscardable();
|
||||
bool doDecodeImmediately = gfxPrefs::ImageDecodeImmediatelyEnabled();
|
||||
|
||||
// We want UI to be as snappy as possible and not to flicker. Disable
|
||||
// discarding for chrome URLS.
|
||||
bool isChrome = false;
|
||||
rv = uri->SchemeIs("chrome", &isChrome);
|
||||
if (NS_SUCCEEDED(rv) && isChrome) {
|
||||
isDiscardable = false;
|
||||
}
|
||||
|
||||
// We don't want resources like the "loading" icon to be discardable either.
|
||||
bool isResource = false;
|
||||
rv = uri->SchemeIs("resource", &isResource);
|
||||
if (NS_SUCCEEDED(rv) && isResource) {
|
||||
isDiscardable = false;
|
||||
}
|
||||
|
||||
// For multipart/x-mixed-replace, we basically want a direct channel to the
|
||||
// decoder. Disable everything for this case.
|
||||
if (isMultiPart) {
|
||||
isDiscardable = false;
|
||||
}
|
||||
|
||||
// We have all the information we need.
|
||||
uint32_t imageFlags = Image::INIT_FLAG_NONE;
|
||||
if (isDiscardable) {
|
||||
imageFlags |= Image::INIT_FLAG_DISCARDABLE;
|
||||
}
|
||||
if (doDecodeImmediately) {
|
||||
imageFlags |= Image::INIT_FLAG_DECODE_IMMEDIATELY;
|
||||
}
|
||||
if (isMultiPart) {
|
||||
imageFlags |= Image::INIT_FLAG_TRANSIENT;
|
||||
}
|
||||
|
||||
return imageFlags;
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageFactory::CreateImage(nsIRequest* aRequest,
|
||||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
ImageURL* aURI,
|
||||
bool aIsMultiPart,
|
||||
uint32_t aInnerWindowId)
|
||||
{
|
||||
MOZ_ASSERT(gfxPrefs::SingletonExists(),
|
||||
"Pref observers should have been initialized already");
|
||||
|
||||
// Compute the image's initialization flags.
|
||||
uint32_t imageFlags = ComputeImageFlags(aURI, aMimeType, aIsMultiPart);
|
||||
|
||||
// Select the type of image to create based on MIME type.
|
||||
if (aMimeType.EqualsLiteral(IMAGE_SVG_XML)) {
|
||||
return CreateVectorImage(aRequest, aProgressTracker, aMimeType,
|
||||
aURI, imageFlags, aInnerWindowId);
|
||||
} else {
|
||||
return CreateRasterImage(aRequest, aProgressTracker, aMimeType,
|
||||
aURI, imageFlags, aInnerWindowId);
|
||||
}
|
||||
}
|
||||
|
||||
// Marks an image as having an error before returning it.
|
||||
template <typename T>
|
||||
static already_AddRefed<Image>
|
||||
BadImage(const char* aMessage, RefPtr<T>& aImage)
|
||||
{
|
||||
aImage->SetHasError();
|
||||
return aImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
RefPtr<RasterImage> newImage = new RasterImage();
|
||||
|
||||
RefPtr<ProgressTracker> newTracker = new ProgressTracker();
|
||||
newTracker->SetImage(newImage);
|
||||
newImage->SetProgressTracker(newTracker);
|
||||
|
||||
rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_SYNC_LOAD);
|
||||
if (NS_FAILED(rv)) {
|
||||
return BadImage("RasterImage::Init failed", newImage);
|
||||
}
|
||||
|
||||
return newImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<MultipartImage>
|
||||
ImageFactory::CreateMultipartImage(Image* aFirstPart,
|
||||
ProgressTracker* aProgressTracker)
|
||||
{
|
||||
MOZ_ASSERT(aFirstPart);
|
||||
MOZ_ASSERT(aProgressTracker);
|
||||
|
||||
RefPtr<MultipartImage> newImage = new MultipartImage(aFirstPart);
|
||||
aProgressTracker->SetImage(newImage);
|
||||
newImage->SetProgressTracker(aProgressTracker);
|
||||
|
||||
newImage->Init();
|
||||
|
||||
return newImage.forget();
|
||||
}
|
||||
|
||||
int32_t
|
||||
SaturateToInt32(int64_t val)
|
||||
{
|
||||
if (val > INT_MAX) {
|
||||
return INT_MAX;
|
||||
}
|
||||
if (val < INT_MIN) {
|
||||
return INT_MIN;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(val);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
GetContentSize(nsIRequest* aRequest)
|
||||
{
|
||||
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
|
||||
if (channel) {
|
||||
int64_t size;
|
||||
nsresult rv = channel->GetContentLength(&size);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return std::max(SaturateToInt32(size), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the file size as a size hint for file channels.
|
||||
nsCOMPtr<nsIFileChannel> fileChannel(do_QueryInterface(aRequest));
|
||||
if (fileChannel) {
|
||||
nsCOMPtr<nsIFile> file;
|
||||
nsresult rv = fileChannel->GetFile(getter_AddRefs(file));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
int64_t filesize;
|
||||
rv = file->GetFileSize(&filesize);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return std::max(SaturateToInt32(filesize), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback - neither http nor file. We'll use dynamic allocation.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageFactory::CreateRasterImage(nsIRequest* aRequest,
|
||||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
ImageURL* aURI,
|
||||
uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId)
|
||||
{
|
||||
MOZ_ASSERT(aProgressTracker);
|
||||
|
||||
nsresult rv;
|
||||
|
||||
RefPtr<RasterImage> newImage = new RasterImage(aURI);
|
||||
aProgressTracker->SetImage(newImage);
|
||||
newImage->SetProgressTracker(aProgressTracker);
|
||||
|
||||
nsAutoCString ref;
|
||||
aURI->GetRef(ref);
|
||||
net::nsMediaFragmentURIParser parser(ref);
|
||||
if (parser.HasSampleSize()) {
|
||||
/* Get our principal */
|
||||
nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
if (chan) {
|
||||
nsContentUtils::GetSecurityManager()
|
||||
->GetChannelResultPrincipal(chan, getter_AddRefs(principal));
|
||||
}
|
||||
|
||||
if ((principal &&
|
||||
principal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED) ||
|
||||
gfxPrefs::ImageMozSampleSizeEnabled()) {
|
||||
newImage->SetRequestedSampleSize(parser.GetSampleSize());
|
||||
}
|
||||
}
|
||||
|
||||
rv = newImage->Init(aMimeType.get(), aImageFlags);
|
||||
if (NS_FAILED(rv)) {
|
||||
return BadImage("RasterImage::Init failed", newImage);
|
||||
}
|
||||
|
||||
newImage->SetInnerWindowID(aInnerWindowId);
|
||||
|
||||
uint32_t len = GetContentSize(aRequest);
|
||||
|
||||
// Pass anything usable on so that the RasterImage can preallocate
|
||||
// its source buffer.
|
||||
if (len > 0) {
|
||||
// Bound by something reasonable
|
||||
uint32_t sizeHint = std::min<uint32_t>(len, 20000000);
|
||||
rv = newImage->SetSourceSizeHint(sizeHint);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Flush memory, try to get some back, and try again.
|
||||
rv = nsMemory::HeapMinimize(true);
|
||||
nsresult rv2 = newImage->SetSourceSizeHint(sizeHint);
|
||||
// If we've still failed at this point, things are going downhill.
|
||||
if (NS_FAILED(rv) || NS_FAILED(rv2)) {
|
||||
NS_WARNING("About to hit OOM in imagelib!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageFactory::CreateVectorImage(nsIRequest* aRequest,
|
||||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
ImageURL* aURI,
|
||||
uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId)
|
||||
{
|
||||
MOZ_ASSERT(aProgressTracker);
|
||||
|
||||
nsresult rv;
|
||||
|
||||
RefPtr<VectorImage> newImage = new VectorImage(aURI);
|
||||
aProgressTracker->SetImage(newImage);
|
||||
newImage->SetProgressTracker(aProgressTracker);
|
||||
|
||||
rv = newImage->Init(aMimeType.get(), aImageFlags);
|
||||
if (NS_FAILED(rv)) {
|
||||
return BadImage("VectorImage::Init failed", newImage);
|
||||
}
|
||||
|
||||
newImage->SetInnerWindowID(aInnerWindowId);
|
||||
|
||||
rv = newImage->OnStartRequest(aRequest, nullptr);
|
||||
if (NS_FAILED(rv)) {
|
||||
return BadImage("VectorImage::OnStartRequest failed", newImage);
|
||||
}
|
||||
|
||||
return newImage.forget();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
95
image/ImageFactory.h
Normal file
95
image/ImageFactory.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/* -*- 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_ImageFactory_h
|
||||
#define mozilla_image_ImageFactory_h
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsProxyRelease.h"
|
||||
|
||||
class nsCString;
|
||||
class nsIRequest;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Image;
|
||||
class ImageURL;
|
||||
class MultipartImage;
|
||||
class ProgressTracker;
|
||||
|
||||
class ImageFactory
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Registers vars with Preferences. Should only be called on the main thread.
|
||||
*/
|
||||
static void Initialize();
|
||||
|
||||
/**
|
||||
* Creates a new image with the given properties.
|
||||
* Can be called on or off the main thread.
|
||||
*
|
||||
* @param aRequest The associated request.
|
||||
* @param aProgressTracker A status tracker for the image to use.
|
||||
* @param aMimeType The mimetype of the image.
|
||||
* @param aURI The URI of the image.
|
||||
* @param aIsMultiPart Whether the image is part of a multipart request.
|
||||
* @param aInnerWindowId The window this image belongs to.
|
||||
*/
|
||||
static already_AddRefed<Image> CreateImage(nsIRequest* aRequest,
|
||||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
ImageURL* aURI,
|
||||
bool aIsMultiPart,
|
||||
uint32_t aInnerWindowId);
|
||||
/**
|
||||
* Creates a new image which isn't associated with a URI or loaded through
|
||||
* the usual image loading mechanism.
|
||||
*
|
||||
* @param aMimeType The mimetype of the image.
|
||||
*/
|
||||
static already_AddRefed<Image>
|
||||
CreateAnonymousImage(const nsCString& aMimeType);
|
||||
|
||||
/**
|
||||
* Creates a new multipart/x-mixed-replace image wrapper, and initializes it
|
||||
* with the first part. Subsequent parts should be passed to the existing
|
||||
* MultipartImage via MultipartImage::BeginTransitionToPart().
|
||||
*
|
||||
* @param aFirstPart An image containing the first part of the multipart
|
||||
* stream.
|
||||
* @param aProgressTracker A progress tracker for the multipart image.
|
||||
*/
|
||||
static already_AddRefed<MultipartImage>
|
||||
CreateMultipartImage(Image* aFirstPart, ProgressTracker* aProgressTracker);
|
||||
|
||||
private:
|
||||
// Factory functions that create specific types of image containers.
|
||||
static already_AddRefed<Image>
|
||||
CreateRasterImage(nsIRequest* aRequest,
|
||||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
ImageURL* aURI,
|
||||
uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId);
|
||||
|
||||
static already_AddRefed<Image>
|
||||
CreateVectorImage(nsIRequest* aRequest,
|
||||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
ImageURL* aURI,
|
||||
uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId);
|
||||
|
||||
// This is a static factory class, so disallow instantiation.
|
||||
virtual ~ImageFactory() = 0;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ImageFactory_h
|
||||
161
image/ImageLogging.h
Normal file
161
image/ImageLogging.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/* -*- 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_ImageLogging_h
|
||||
#define mozilla_image_ImageLogging_h
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
#include "prinrval.h"
|
||||
|
||||
static mozilla::LazyLogModule gImgLog("imgRequest");
|
||||
|
||||
#define GIVE_ME_MS_NOW() PR_IntervalToMilliseconds(PR_IntervalNow())
|
||||
|
||||
using mozilla::LogLevel;
|
||||
|
||||
class LogScope {
|
||||
public:
|
||||
|
||||
LogScope(mozilla::LogModule* aLog, void* aFrom, const char* aFunc)
|
||||
: mLog(aLog)
|
||||
, mFrom(aFrom)
|
||||
, mFunc(aFunc)
|
||||
{
|
||||
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s {ENTER}\n",
|
||||
GIVE_ME_MS_NOW(), mFrom, mFunc));
|
||||
}
|
||||
|
||||
/* const char * constructor */
|
||||
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, const char* paramValue)
|
||||
: mLog(aLog)
|
||||
, mFrom(from)
|
||||
, mFunc(fn)
|
||||
{
|
||||
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%s\") {ENTER}\n",
|
||||
GIVE_ME_MS_NOW(), mFrom, mFunc,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
/* void ptr constructor */
|
||||
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, const void* paramValue)
|
||||
: mLog(aLog)
|
||||
, mFrom(from)
|
||||
, mFunc(fn)
|
||||
{
|
||||
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=%p) {ENTER}\n",
|
||||
GIVE_ME_MS_NOW(), mFrom, mFunc,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
/* int32_t constructor */
|
||||
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, int32_t paramValue)
|
||||
: mLog(aLog)
|
||||
, mFrom(from)
|
||||
, mFunc(fn)
|
||||
{
|
||||
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\") {ENTER}\n",
|
||||
GIVE_ME_MS_NOW(), mFrom, mFunc,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
/* uint32_t constructor */
|
||||
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, uint32_t paramValue)
|
||||
: mLog(aLog)
|
||||
, mFrom(from)
|
||||
, mFunc(fn)
|
||||
{
|
||||
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\") {ENTER}\n",
|
||||
GIVE_ME_MS_NOW(), mFrom, mFunc,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
~LogScope()
|
||||
{
|
||||
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s {EXIT}\n",
|
||||
GIVE_ME_MS_NOW(), mFrom, mFunc));
|
||||
}
|
||||
|
||||
private:
|
||||
mozilla::LogModule* mLog;
|
||||
void* mFrom;
|
||||
const char* mFunc;
|
||||
};
|
||||
|
||||
class LogFunc {
|
||||
public:
|
||||
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn)
|
||||
{
|
||||
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s\n",
|
||||
GIVE_ME_MS_NOW(), from, fn));
|
||||
}
|
||||
|
||||
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, const char* paramValue)
|
||||
{
|
||||
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%s\")\n",
|
||||
GIVE_ME_MS_NOW(), from, fn,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, const void* paramValue)
|
||||
{
|
||||
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%p\")\n",
|
||||
GIVE_ME_MS_NOW(), from, fn,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
|
||||
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* paramName, uint32_t paramValue)
|
||||
{
|
||||
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\")\n",
|
||||
GIVE_ME_MS_NOW(), from, fn,
|
||||
paramName, paramValue));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class LogMessage {
|
||||
public:
|
||||
LogMessage(mozilla::LogModule* aLog, void* from, const char* fn,
|
||||
const char* msg)
|
||||
{
|
||||
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s -- %s\n",
|
||||
GIVE_ME_MS_NOW(), from, fn, msg));
|
||||
}
|
||||
};
|
||||
|
||||
#define LOG_SCOPE_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
|
||||
#define LOG_SCOPE_APPEND_LINE_NUMBER_EXPAND(id, line) \
|
||||
LOG_SCOPE_APPEND_LINE_NUMBER_PASTE(id, line)
|
||||
#define LOG_SCOPE_APPEND_LINE_NUMBER(id) \
|
||||
LOG_SCOPE_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
|
||||
|
||||
#define LOG_SCOPE(l, s) \
|
||||
LogScope LOG_SCOPE_APPEND_LINE_NUMBER(LOG_SCOPE_TMP_VAR) (l, this, s)
|
||||
|
||||
#define LOG_SCOPE_WITH_PARAM(l, s, pn, pv) \
|
||||
LogScope LOG_SCOPE_APPEND_LINE_NUMBER(LOG_SCOPE_TMP_VAR) (l, this, s, pn, pv)
|
||||
|
||||
#define LOG_FUNC(l, s) LogFunc(l, this, s)
|
||||
|
||||
#define LOG_FUNC_WITH_PARAM(l, s, pn, pv) LogFunc(l, this, s, pn, pv)
|
||||
|
||||
#define LOG_STATIC_FUNC(l, s) LogFunc(l, nullptr, s)
|
||||
|
||||
#define LOG_STATIC_FUNC_WITH_PARAM(l, s, pn, pv) LogFunc(l, nullptr, s, pn, pv)
|
||||
|
||||
#define LOG_MSG(l, s, m) LogMessage(l, this, s, m)
|
||||
|
||||
#define LOG_MSG_WITH_PARAM LOG_FUNC_WITH_PARAM
|
||||
|
||||
#endif // mozilla_image_ImageLogging_h
|
||||
99
image/ImageMetadata.h
Normal file
99
image/ImageMetadata.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* -*- 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_ImageMetadata_h
|
||||
#define mozilla_image_ImageMetadata_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "nsSize.h"
|
||||
#include "Orientation.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class RasterImage;
|
||||
|
||||
// The metadata about an image that decoders accumulate as they decode.
|
||||
class ImageMetadata
|
||||
{
|
||||
public:
|
||||
ImageMetadata()
|
||||
: mLoopCount(-1)
|
||||
, mFirstFrameTimeout(FrameTimeout::Forever())
|
||||
, mHasAnimation(false)
|
||||
{ }
|
||||
|
||||
void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY)
|
||||
{
|
||||
mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY));
|
||||
}
|
||||
gfx::IntPoint GetHotspot() const { return *mHotspot; }
|
||||
bool HasHotspot() const { return mHotspot.isSome(); }
|
||||
|
||||
void SetLoopCount(int32_t loopcount)
|
||||
{
|
||||
mLoopCount = loopcount;
|
||||
}
|
||||
int32_t GetLoopCount() const { return mLoopCount; }
|
||||
|
||||
void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); }
|
||||
FrameTimeout GetLoopLength() const { return *mLoopLength; }
|
||||
bool HasLoopLength() const { return mLoopLength.isSome(); }
|
||||
|
||||
void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; }
|
||||
FrameTimeout GetFirstFrameTimeout() const { return mFirstFrameTimeout; }
|
||||
|
||||
void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea)
|
||||
{
|
||||
mFirstFrameRefreshArea = Some(aRefreshArea);
|
||||
}
|
||||
gfx::IntRect GetFirstFrameRefreshArea() const { return *mFirstFrameRefreshArea; }
|
||||
bool HasFirstFrameRefreshArea() const { return mFirstFrameRefreshArea.isSome(); }
|
||||
|
||||
void SetSize(int32_t width, int32_t height, Orientation orientation)
|
||||
{
|
||||
if (!HasSize()) {
|
||||
mSize.emplace(nsIntSize(width, height));
|
||||
mOrientation.emplace(orientation);
|
||||
}
|
||||
}
|
||||
nsIntSize GetSize() const { return *mSize; }
|
||||
bool HasSize() const { return mSize.isSome(); }
|
||||
|
||||
Orientation GetOrientation() const { return *mOrientation; }
|
||||
bool HasOrientation() const { return mOrientation.isSome(); }
|
||||
|
||||
void SetHasAnimation() { mHasAnimation = true; }
|
||||
bool HasAnimation() const { return mHasAnimation; }
|
||||
|
||||
private:
|
||||
/// The hotspot found on cursors, if present.
|
||||
Maybe<gfx::IntPoint> mHotspot;
|
||||
|
||||
/// The loop count for animated images, or -1 for infinite loop.
|
||||
int32_t mLoopCount;
|
||||
|
||||
// The total length of a single loop through an animated image.
|
||||
Maybe<FrameTimeout> mLoopLength;
|
||||
|
||||
/// The timeout of an animated image's first frame.
|
||||
FrameTimeout mFirstFrameTimeout;
|
||||
|
||||
// The area of the image that needs to be invalidated when the animation
|
||||
// loops.
|
||||
Maybe<gfx::IntRect> mFirstFrameRefreshArea;
|
||||
|
||||
Maybe<nsIntSize> mSize;
|
||||
Maybe<Orientation> mOrientation;
|
||||
|
||||
bool mHasAnimation : 1;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ImageMetadata_h
|
||||
150
image/ImageOps.cpp
Normal file
150
image/ImageOps.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* -*- 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 "ImageOps.h"
|
||||
|
||||
#include "ClippedImage.h"
|
||||
#include "DecodePool.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "DynamicImage.h"
|
||||
#include "FrozenImage.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "Image.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "OrientedImage.h"
|
||||
#include "SourceBuffer.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageOps::Freeze(Image* aImage)
|
||||
{
|
||||
RefPtr<Image> frozenImage = new FrozenImage(aImage);
|
||||
return frozenImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<imgIContainer>
|
||||
ImageOps::Freeze(imgIContainer* aImage)
|
||||
{
|
||||
nsCOMPtr<imgIContainer> frozenImage =
|
||||
new FrozenImage(static_cast<Image*>(aImage));
|
||||
return frozenImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageOps::Clip(Image* aImage, nsIntRect aClip,
|
||||
const Maybe<nsSize>& aSVGViewportSize)
|
||||
{
|
||||
RefPtr<Image> clippedImage = new ClippedImage(aImage, aClip, aSVGViewportSize);
|
||||
return clippedImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<imgIContainer>
|
||||
ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip,
|
||||
const Maybe<nsSize>& aSVGViewportSize)
|
||||
{
|
||||
nsCOMPtr<imgIContainer> clippedImage =
|
||||
new ClippedImage(static_cast<Image*>(aImage), aClip, aSVGViewportSize);
|
||||
return clippedImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Image>
|
||||
ImageOps::Orient(Image* aImage, Orientation aOrientation)
|
||||
{
|
||||
RefPtr<Image> orientedImage = new OrientedImage(aImage, aOrientation);
|
||||
return orientedImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<imgIContainer>
|
||||
ImageOps::Orient(imgIContainer* aImage, Orientation aOrientation)
|
||||
{
|
||||
nsCOMPtr<imgIContainer> orientedImage =
|
||||
new OrientedImage(static_cast<Image*>(aImage), aOrientation);
|
||||
return orientedImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<imgIContainer>
|
||||
ImageOps::CreateFromDrawable(gfxDrawable* aDrawable)
|
||||
{
|
||||
nsCOMPtr<imgIContainer> drawableImage = new DynamicImage(aDrawable);
|
||||
return drawableImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<gfx::SourceSurface>
|
||||
ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(aInputStream);
|
||||
|
||||
nsresult rv;
|
||||
|
||||
// Prepare the input stream.
|
||||
nsCOMPtr<nsIInputStream> inputStream = aInputStream;
|
||||
if (!NS_InputStreamIsBuffered(aInputStream)) {
|
||||
nsCOMPtr<nsIInputStream> bufStream;
|
||||
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
|
||||
aInputStream, 1024);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
inputStream = bufStream;
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out how much data we've been passed.
|
||||
uint64_t length;
|
||||
rv = inputStream->Available(&length);
|
||||
if (NS_FAILED(rv) || length > UINT32_MAX) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Write the data into a SourceBuffer.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
|
||||
Nothing(), ToSurfaceFlags(aFlags));
|
||||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Run the decoder synchronously.
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
task->Run();
|
||||
if (!decoder->GetDecodeDone() || decoder->HasError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pull out the surface.
|
||||
RawAccessFrameRef frame = decoder->GetCurrentFrameRef();
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<SourceSurface> surface = frame->GetSourceSurface();
|
||||
if (!surface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return surface.forget();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
102
image/ImageOps.h
Normal file
102
image/ImageOps.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* -*- 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_ImageOps_h
|
||||
#define mozilla_image_ImageOps_h
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
class gfxDrawable;
|
||||
class imgIContainer;
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace gfx {
|
||||
class SourceSurface;
|
||||
}
|
||||
|
||||
namespace image {
|
||||
|
||||
class Image;
|
||||
struct Orientation;
|
||||
|
||||
class ImageOps
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Creates a version of an existing image which does not animate and is frozen
|
||||
* at the first frame.
|
||||
*
|
||||
* @param aImage The existing image.
|
||||
*/
|
||||
static already_AddRefed<Image> Freeze(Image* aImage);
|
||||
static already_AddRefed<imgIContainer> Freeze(imgIContainer* aImage);
|
||||
|
||||
/**
|
||||
* Creates a clipped version of an existing image. Animation is unaffected.
|
||||
*
|
||||
* @param aImage The existing image.
|
||||
* @param aClip The rectangle to clip the image against.
|
||||
* @param aSVGViewportSize The specific viewort size of aImage. Unless aImage
|
||||
* is a vector image without intrinsic size, this
|
||||
* argument should be pass as Nothing().
|
||||
*/
|
||||
static already_AddRefed<Image> Clip(Image* aImage, nsIntRect aClip,
|
||||
const Maybe<nsSize>& aSVGViewportSize =
|
||||
Nothing());
|
||||
static already_AddRefed<imgIContainer> Clip(imgIContainer* aImage,
|
||||
nsIntRect aClip,
|
||||
const Maybe<nsSize>& aSVGViewportSize =
|
||||
Nothing());
|
||||
|
||||
/**
|
||||
* Creates a version of an existing image which is rotated and/or flipped to
|
||||
* the specified orientation.
|
||||
*
|
||||
* @param aImage The existing image.
|
||||
* @param aOrientation The desired orientation.
|
||||
*/
|
||||
static already_AddRefed<Image> Orient(Image* aImage,
|
||||
Orientation aOrientation);
|
||||
static already_AddRefed<imgIContainer> Orient(imgIContainer* aImage,
|
||||
Orientation aOrientation);
|
||||
|
||||
/**
|
||||
* Creates an image from a gfxDrawable.
|
||||
*
|
||||
* @param aDrawable The gfxDrawable.
|
||||
*/
|
||||
static already_AddRefed<imgIContainer>
|
||||
CreateFromDrawable(gfxDrawable* aDrawable);
|
||||
|
||||
/**
|
||||
* Decodes an image from an nsIInputStream directly into a SourceSurface,
|
||||
* without ever creating an Image or imgIContainer (which are mostly
|
||||
* main-thread-only). That means that this function may be called
|
||||
* off-main-thread.
|
||||
*
|
||||
* @param aInputStream An input stream containing an encoded image.
|
||||
* @param aMimeType The MIME type of the image.
|
||||
* @param aFlags Flags of the imgIContainer::FLAG_DECODE_* variety.
|
||||
* @return A SourceSurface containing the first frame of the image at its
|
||||
* intrinsic size, or nullptr if the image cannot be decoded.
|
||||
*/
|
||||
static already_AddRefed<gfx::SourceSurface>
|
||||
DecodeToSurface(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags);
|
||||
|
||||
private:
|
||||
// This is a static utility class, so disallow instantiation.
|
||||
virtual ~ImageOps() = 0;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ImageOps_h
|
||||
173
image/ImageRegion.h
Normal file
173
image/ImageRegion.h
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/* -*- 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_ImageRegion_h
|
||||
#define mozilla_image_ImageRegion_h
|
||||
|
||||
#include "gfxRect.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An axis-aligned rectangle in tiled image space, with an optional sampling
|
||||
* restriction rect. The drawing code ensures that if a sampling restriction
|
||||
* rect is present, any pixels sampled during the drawing process are found
|
||||
* within that rect.
|
||||
*
|
||||
* The sampling restriction rect exists primarily for callers which perform
|
||||
* pixel snapping. Other callers should generally use one of the Create()
|
||||
* overloads.
|
||||
*/
|
||||
class ImageRegion
|
||||
{
|
||||
typedef mozilla::gfx::ExtendMode ExtendMode;
|
||||
|
||||
public:
|
||||
static ImageRegion Empty()
|
||||
{
|
||||
return ImageRegion(gfxRect(), ExtendMode::CLAMP);
|
||||
}
|
||||
|
||||
static ImageRegion Create(const gfxRect& aRect,
|
||||
ExtendMode aExtendMode = ExtendMode::CLAMP)
|
||||
{
|
||||
return ImageRegion(aRect, aExtendMode);
|
||||
}
|
||||
|
||||
static ImageRegion Create(const gfxSize& aSize,
|
||||
ExtendMode aExtendMode = ExtendMode::CLAMP)
|
||||
{
|
||||
return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode);
|
||||
}
|
||||
|
||||
static ImageRegion Create(const nsIntSize& aSize,
|
||||
ExtendMode aExtendMode = ExtendMode::CLAMP)
|
||||
{
|
||||
return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode);
|
||||
}
|
||||
|
||||
static ImageRegion CreateWithSamplingRestriction(const gfxRect& aRect,
|
||||
const gfxRect& aRestriction,
|
||||
ExtendMode aExtendMode = ExtendMode::CLAMP)
|
||||
{
|
||||
return ImageRegion(aRect, aRestriction, aExtendMode);
|
||||
}
|
||||
|
||||
bool IsRestricted() const { return mIsRestricted; }
|
||||
const gfxRect& Rect() const { return mRect; }
|
||||
|
||||
const gfxRect& Restriction() const
|
||||
{
|
||||
MOZ_ASSERT(mIsRestricted);
|
||||
return mRestriction;
|
||||
}
|
||||
|
||||
bool RestrictionContains(const gfxRect& aRect) const
|
||||
{
|
||||
if (!mIsRestricted) {
|
||||
return true;
|
||||
}
|
||||
return mRestriction.Contains(aRect);
|
||||
}
|
||||
|
||||
ImageRegion Intersect(const gfxRect& aRect) const
|
||||
{
|
||||
if (mIsRestricted) {
|
||||
return CreateWithSamplingRestriction(aRect.Intersect(mRect),
|
||||
aRect.Intersect(mRestriction));
|
||||
}
|
||||
return Create(aRect.Intersect(mRect));
|
||||
}
|
||||
|
||||
gfxRect IntersectAndRestrict(const gfxRect& aRect) const
|
||||
{
|
||||
gfxRect intersection = mRect.Intersect(aRect);
|
||||
if (mIsRestricted) {
|
||||
intersection = mRestriction.Intersect(intersection);
|
||||
}
|
||||
return intersection;
|
||||
}
|
||||
|
||||
void MoveBy(gfxFloat dx, gfxFloat dy)
|
||||
{
|
||||
mRect.MoveBy(dx, dy);
|
||||
if (mIsRestricted) {
|
||||
mRestriction.MoveBy(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
void Scale(gfxFloat sx, gfxFloat sy)
|
||||
{
|
||||
mRect.Scale(sx, sy);
|
||||
if (mIsRestricted) {
|
||||
mRestriction.Scale(sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
void TransformBy(const gfxMatrix& aMatrix)
|
||||
{
|
||||
mRect = aMatrix.Transform(mRect);
|
||||
if (mIsRestricted) {
|
||||
mRestriction = aMatrix.Transform(mRestriction);
|
||||
}
|
||||
}
|
||||
|
||||
void TransformBoundsBy(const gfxMatrix& aMatrix)
|
||||
{
|
||||
mRect = aMatrix.TransformBounds(mRect);
|
||||
if (mIsRestricted) {
|
||||
mRestriction = aMatrix.TransformBounds(mRestriction);
|
||||
}
|
||||
}
|
||||
|
||||
ImageRegion operator-(const gfxPoint& aPt) const
|
||||
{
|
||||
if (mIsRestricted) {
|
||||
return CreateWithSamplingRestriction(mRect - aPt, mRestriction - aPt);
|
||||
}
|
||||
return Create(mRect - aPt);
|
||||
}
|
||||
|
||||
ImageRegion operator+(const gfxPoint& aPt) const
|
||||
{
|
||||
if (mIsRestricted) {
|
||||
return CreateWithSamplingRestriction(mRect + aPt, mRestriction + aPt);
|
||||
}
|
||||
return Create(mRect + aPt);
|
||||
}
|
||||
|
||||
gfx::ExtendMode GetExtendMode() const
|
||||
{
|
||||
return mExtendMode;
|
||||
}
|
||||
|
||||
/* ImageRegion() : mIsRestricted(false) { } */
|
||||
|
||||
private:
|
||||
explicit ImageRegion(const gfxRect& aRect, ExtendMode aExtendMode)
|
||||
: mRect(aRect)
|
||||
, mExtendMode(aExtendMode)
|
||||
, mIsRestricted(false)
|
||||
{ }
|
||||
|
||||
ImageRegion(const gfxRect& aRect, const gfxRect& aRestriction, ExtendMode aExtendMode)
|
||||
: mRect(aRect)
|
||||
, mRestriction(aRestriction)
|
||||
, mExtendMode(aExtendMode)
|
||||
, mIsRestricted(true)
|
||||
{ }
|
||||
|
||||
gfxRect mRect;
|
||||
gfxRect mRestriction;
|
||||
ExtendMode mExtendMode;
|
||||
bool mIsRestricted;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ImageRegion_h
|
||||
151
image/ImageURL.h
Normal file
151
image/ImageURL.h
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/* -*- 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_ImageURL_h
|
||||
#define mozilla_image_ImageURL_h
|
||||
|
||||
#include "nsIURI.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "nsHashKeys.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class ImageCacheKey;
|
||||
|
||||
/** ImageURL
|
||||
*
|
||||
* nsStandardURL is not threadsafe, so this class is created to hold only the
|
||||
* necessary URL data required for image loading and decoding.
|
||||
*
|
||||
* Note: Although several APIs have the same or similar prototypes as those
|
||||
* found in nsIURI/nsStandardURL, the class does not implement nsIURI. This is
|
||||
* intentional; functionality is limited, and is only useful for imagelib code.
|
||||
* By not implementing nsIURI, external code cannot unintentionally be given an
|
||||
* nsIURI pointer with this limited class behind it; instead, conversion to a
|
||||
* fully implemented nsIURI is required (e.g. through NS_NewURI).
|
||||
*/
|
||||
class ImageURL
|
||||
{
|
||||
public:
|
||||
explicit ImageURL(nsIURI* aURI, nsresult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
|
||||
|
||||
aRv = aURI->GetSpec(mSpec);
|
||||
NS_ENSURE_SUCCESS_VOID(aRv);
|
||||
|
||||
aRv = aURI->GetScheme(mScheme);
|
||||
NS_ENSURE_SUCCESS_VOID(aRv);
|
||||
|
||||
aRv = aURI->GetRef(mRef);
|
||||
NS_ENSURE_SUCCESS_VOID(aRv);
|
||||
}
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageURL)
|
||||
|
||||
nsresult GetSpec(nsACString& result)
|
||||
{
|
||||
result = mSpec;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/// A weak pointer to the URI spec for this ImageURL. For logging only.
|
||||
const char* Spec() const { return mSpec.get(); }
|
||||
|
||||
enum TruncatedSpecStatus {
|
||||
FitsInto1k,
|
||||
TruncatedTo1k
|
||||
};
|
||||
TruncatedSpecStatus GetSpecTruncatedTo1k(nsACString& result)
|
||||
{
|
||||
static const size_t sMaxTruncatedLength = 1024;
|
||||
|
||||
if (sMaxTruncatedLength >= mSpec.Length()) {
|
||||
result = mSpec;
|
||||
return FitsInto1k;
|
||||
}
|
||||
|
||||
result = Substring(mSpec, 0, sMaxTruncatedLength);
|
||||
return TruncatedTo1k;
|
||||
}
|
||||
|
||||
nsresult GetScheme(nsACString& result)
|
||||
{
|
||||
result = mScheme;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult SchemeIs(const char* scheme, bool* result)
|
||||
{
|
||||
NS_PRECONDITION(scheme, "scheme is null");
|
||||
NS_PRECONDITION(result, "result is null");
|
||||
|
||||
*result = mScheme.Equals(scheme);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult GetRef(nsACString& result)
|
||||
{
|
||||
result = mRef;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIURI> ToIURI()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"Convert to nsIURI on main thread only; it is not threadsafe.");
|
||||
nsCOMPtr<nsIURI> newURI;
|
||||
NS_NewURI(getter_AddRefs(newURI), mSpec);
|
||||
return newURI.forget();
|
||||
}
|
||||
|
||||
bool operator==(const ImageURL& aOther) const
|
||||
{
|
||||
// Note that we don't need to consider mScheme and mRef, because they're
|
||||
// already represented in mSpec.
|
||||
return mSpec == aOther.mSpec;
|
||||
}
|
||||
|
||||
bool HasSameRef(const ImageURL& aOther) const
|
||||
{
|
||||
return mRef == aOther.mRef;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ImageCacheKey;
|
||||
|
||||
uint32_t ComputeHash(const Maybe<uint64_t>& aBlobSerial) const
|
||||
{
|
||||
if (aBlobSerial) {
|
||||
// For blob URIs, we hash the serial number of the underlying blob, so that
|
||||
// different blob URIs which point to the same blob share a cache entry. We
|
||||
// also include the ref portion of the URI to support media fragments which
|
||||
// requires us to create different Image objects even if the source data is
|
||||
// the same.
|
||||
return HashGeneric(*aBlobSerial, HashString(mRef));
|
||||
}
|
||||
// For non-blob URIs, we hash the URI spec.
|
||||
return HashString(mSpec);
|
||||
}
|
||||
|
||||
// Since this is a basic storage class, no duplication of spec parsing is
|
||||
// included in the functionality. Instead, the class depends upon the
|
||||
// parsing implementation in the nsIURI class used in object construction.
|
||||
// This means each field is stored separately, but since only a few are
|
||||
// required, this small memory tradeoff for threadsafe usage should be ok.
|
||||
nsAutoCString mSpec;
|
||||
nsAutoCString mScheme;
|
||||
nsAutoCString mRef;
|
||||
|
||||
~ImageURL() { }
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ImageURL_h
|
||||
323
image/ImageWrapper.cpp
Normal file
323
image/ImageWrapper.cpp
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/* -*- 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 "ImageWrapper.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "Orientation.h"
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using gfx::DataSourceSurface;
|
||||
using gfx::IntSize;
|
||||
using gfx::SamplingFilter;
|
||||
using gfx::SourceSurface;
|
||||
using layers::LayerManager;
|
||||
using layers::ImageContainer;
|
||||
|
||||
namespace image {
|
||||
|
||||
// Inherited methods from Image.
|
||||
|
||||
already_AddRefed<ProgressTracker>
|
||||
ImageWrapper::GetProgressTracker()
|
||||
{
|
||||
return mInnerImage->GetProgressTracker();
|
||||
}
|
||||
|
||||
size_t
|
||||
ImageWrapper::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return mInnerImage->SizeOfSourceWithComputedFallback(aMallocSizeOf);
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
mInnerImage->CollectSizeOfSurfaces(aCounters, aMallocSizeOf);
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::IncrementAnimationConsumers()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
|
||||
"with DecrementAnimationConsumers");
|
||||
mInnerImage->IncrementAnimationConsumers();
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::DecrementAnimationConsumers()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
|
||||
"with IncrementAnimationConsumers");
|
||||
mInnerImage->DecrementAnimationConsumers();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t
|
||||
ImageWrapper::GetAnimationConsumers()
|
||||
{
|
||||
return mInnerImage->GetAnimationConsumers();
|
||||
}
|
||||
#endif
|
||||
|
||||
nsresult
|
||||
ImageWrapper::OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
return mInnerImage->OnImageDataAvailable(aRequest, aContext, aInStr,
|
||||
aSourceOffset, aCount);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ImageWrapper::OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart)
|
||||
{
|
||||
return mInnerImage->OnImageDataComplete(aRequest, aContext, aStatus,
|
||||
aLastPart);
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::OnSurfaceDiscarded()
|
||||
{
|
||||
return mInnerImage->OnSurfaceDiscarded();
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::SetInnerWindowID(uint64_t aInnerWindowId)
|
||||
{
|
||||
mInnerImage->SetInnerWindowID(aInnerWindowId);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
ImageWrapper::InnerWindowID() const
|
||||
{
|
||||
return mInnerImage->InnerWindowID();
|
||||
}
|
||||
|
||||
bool
|
||||
ImageWrapper::HasError()
|
||||
{
|
||||
return mInnerImage->HasError();
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::SetHasError()
|
||||
{
|
||||
mInnerImage->SetHasError();
|
||||
}
|
||||
|
||||
ImageURL*
|
||||
ImageWrapper::GetURI()
|
||||
{
|
||||
return mInnerImage->GetURI();
|
||||
}
|
||||
|
||||
// Methods inherited from XPCOM interfaces.
|
||||
|
||||
NS_IMPL_ISUPPORTS(ImageWrapper, imgIContainer)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetWidth(int32_t* aWidth)
|
||||
{
|
||||
return mInnerImage->GetWidth(aWidth);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetHeight(int32_t* aHeight)
|
||||
{
|
||||
return mInnerImage->GetHeight(aHeight);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
return mInnerImage->GetIntrinsicSize(aSize);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetIntrinsicRatio(nsSize* aSize)
|
||||
{
|
||||
return mInnerImage->GetIntrinsicRatio(aSize);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(Orientation)
|
||||
ImageWrapper::GetOrientation()
|
||||
{
|
||||
return mInnerImage->GetOrientation();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetType(uint16_t* aType)
|
||||
{
|
||||
return mInnerImage->GetType(aType);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetAnimated(bool* aAnimated)
|
||||
{
|
||||
return mInnerImage->GetAnimated(aAnimated);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
ImageWrapper::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
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::WillDrawOpaqueNow()
|
||||
{
|
||||
return mInnerImage->WillDrawOpaqueNow();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
ImageWrapper::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
return mInnerImage->IsImageContainerAvailable(aManager, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
|
||||
ImageWrapper::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
return mInnerImage->GetImageContainer(aManager, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(DrawResult)
|
||||
ImageWrapper::Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return mInnerImage->Draw(aContext, aSize, aRegion, aWhichFrame,
|
||||
aSamplingFilter, aSVGContext, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::StartDecoding()
|
||||
{
|
||||
return mInnerImage->StartDecoding();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
|
||||
{
|
||||
return mInnerImage->RequestDecodeForSize(aSize, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::LockImage()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"Main thread to encourage serialization with UnlockImage");
|
||||
return mInnerImage->LockImage();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::UnlockImage()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"Main thread to encourage serialization with LockImage");
|
||||
return mInnerImage->UnlockImage();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::RequestDiscard()
|
||||
{
|
||||
return mInnerImage->RequestDiscard();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
ImageWrapper::RequestRefresh(const TimeStamp& aTime)
|
||||
{
|
||||
return mInnerImage->RequestRefresh(aTime);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetAnimationMode(uint16_t* aAnimationMode)
|
||||
{
|
||||
return mInnerImage->GetAnimationMode(aAnimationMode);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
return mInnerImage->SetAnimationMode(aAnimationMode);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::ResetAnimation()
|
||||
{
|
||||
return mInnerImage->ResetAnimation();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(float)
|
||||
ImageWrapper::GetFrameIndex(uint32_t aWhichFrame)
|
||||
{
|
||||
return mInnerImage->GetFrameIndex(aWhichFrame);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(int32_t)
|
||||
ImageWrapper::GetFirstFrameDelay()
|
||||
{
|
||||
return mInnerImage->GetFirstFrameDelay();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
ImageWrapper::SetAnimationStartTime(const TimeStamp& aTime)
|
||||
{
|
||||
mInnerImage->SetAnimationStartTime(aTime);
|
||||
}
|
||||
|
||||
void
|
||||
ImageWrapper::PropagateUseCounters(nsIDocument* aParentDocument)
|
||||
{
|
||||
mInnerImage->PropagateUseCounters(aParentDocument);
|
||||
}
|
||||
|
||||
nsIntSize
|
||||
ImageWrapper::OptimalImageSizeForDest(const gfxSize& aDest,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return mInnerImage->OptimalImageSizeForDest(aDest, aWhichFrame,
|
||||
aSamplingFilter, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(nsIntRect)
|
||||
ImageWrapper::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
{
|
||||
return mInnerImage->GetImageSpaceInvalidationRect(aRect);
|
||||
}
|
||||
|
||||
already_AddRefed<imgIContainer>
|
||||
ImageWrapper::Unwrap()
|
||||
{
|
||||
return mInnerImage->Unwrap();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
85
image/ImageWrapper.h
Normal file
85
image/ImageWrapper.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* -*- 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_ImageWrapper_h
|
||||
#define mozilla_image_ImageWrapper_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "Image.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* Abstract superclass for Images that wrap other Images.
|
||||
*/
|
||||
class ImageWrapper : public Image
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_IMGICONTAINER
|
||||
|
||||
// Inherited methods from Image.
|
||||
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
|
||||
|
||||
virtual size_t
|
||||
SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const override;
|
||||
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
virtual void IncrementAnimationConsumers() override;
|
||||
virtual void DecrementAnimationConsumers() override;
|
||||
#ifdef DEBUG
|
||||
virtual uint32_t GetAnimationConsumers() override;
|
||||
#endif
|
||||
|
||||
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount) override;
|
||||
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart) override;
|
||||
|
||||
virtual void OnSurfaceDiscarded() override;
|
||||
|
||||
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
|
||||
virtual uint64_t InnerWindowID() const override;
|
||||
|
||||
virtual bool HasError() override;
|
||||
virtual void SetHasError() override;
|
||||
|
||||
virtual ImageURL* GetURI() override;
|
||||
|
||||
protected:
|
||||
explicit ImageWrapper(Image* aInnerImage)
|
||||
: mInnerImage(aInnerImage)
|
||||
{
|
||||
MOZ_ASSERT(aInnerImage, "Need an image to wrap");
|
||||
}
|
||||
|
||||
virtual ~ImageWrapper() { }
|
||||
|
||||
/**
|
||||
* Returns a weak reference to the inner image wrapped by this ImageWrapper.
|
||||
*/
|
||||
Image* InnerImage() const { return mInnerImage.get(); }
|
||||
|
||||
void SetInnerImage(Image* aInnerImage)
|
||||
{
|
||||
MOZ_ASSERT(aInnerImage, "Need an image to wrap");
|
||||
mInnerImage = aInnerImage;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Image> mInnerImage;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ImageWrapper_h
|
||||
90
image/LookupResult.h
Normal file
90
image/LookupResult.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* LookupResult is the return type of SurfaceCache's Lookup*() functions. It
|
||||
* combines a surface with relevant metadata tracked by SurfaceCache.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_LookupResult_h
|
||||
#define mozilla_image_LookupResult_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "ISurfaceProvider.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
enum class MatchType : uint8_t
|
||||
{
|
||||
NOT_FOUND, // No matching surface and no placeholder.
|
||||
PENDING, // Found a matching placeholder, but no surface.
|
||||
EXACT, // Found a surface that matches exactly.
|
||||
SUBSTITUTE_BECAUSE_NOT_FOUND, // No exact match, but found a similar one.
|
||||
SUBSTITUTE_BECAUSE_PENDING // Found a similar surface and a placeholder
|
||||
// for an exact match.
|
||||
};
|
||||
|
||||
/**
|
||||
* LookupResult is the return type of SurfaceCache's Lookup*() functions. It
|
||||
* combines a surface with relevant metadata tracked by SurfaceCache.
|
||||
*/
|
||||
class MOZ_STACK_CLASS LookupResult
|
||||
{
|
||||
public:
|
||||
explicit LookupResult(MatchType aMatchType)
|
||||
: mMatchType(aMatchType)
|
||||
{
|
||||
MOZ_ASSERT(mMatchType == MatchType::NOT_FOUND ||
|
||||
mMatchType == MatchType::PENDING,
|
||||
"Only NOT_FOUND or PENDING make sense with no surface");
|
||||
}
|
||||
|
||||
LookupResult(LookupResult&& aOther)
|
||||
: mSurface(Move(aOther.mSurface))
|
||||
, mMatchType(aOther.mMatchType)
|
||||
{ }
|
||||
|
||||
LookupResult(DrawableSurface&& aSurface, MatchType aMatchType)
|
||||
: mSurface(Move(aSurface))
|
||||
, mMatchType(aMatchType)
|
||||
{
|
||||
MOZ_ASSERT(!mSurface || !(mMatchType == MatchType::NOT_FOUND ||
|
||||
mMatchType == MatchType::PENDING),
|
||||
"Only NOT_FOUND or PENDING make sense with no surface");
|
||||
MOZ_ASSERT(mSurface || mMatchType == MatchType::NOT_FOUND ||
|
||||
mMatchType == MatchType::PENDING,
|
||||
"NOT_FOUND or PENDING do not make sense with a surface");
|
||||
}
|
||||
|
||||
LookupResult& operator=(LookupResult&& aOther)
|
||||
{
|
||||
MOZ_ASSERT(&aOther != this, "Self-move-assignment is not supported");
|
||||
mSurface = Move(aOther.mSurface);
|
||||
mMatchType = aOther.mMatchType;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DrawableSurface& Surface() { return mSurface; }
|
||||
const DrawableSurface& Surface() const { return mSurface; }
|
||||
|
||||
/// @return true if this LookupResult contains a surface.
|
||||
explicit operator bool() const { return bool(mSurface); }
|
||||
|
||||
/// @return what kind of match this is (exact, substitute, etc.)
|
||||
MatchType Type() const { return mMatchType; }
|
||||
|
||||
private:
|
||||
LookupResult(const LookupResult&) = delete;
|
||||
|
||||
DrawableSurface mSurface;
|
||||
MatchType mMatchType;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_LookupResult_h
|
||||
346
image/MultipartImage.cpp
Normal file
346
image/MultipartImage.cpp
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/* -*- 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 "MultipartImage.h"
|
||||
|
||||
#include "imgINotificationObserver.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using gfx::IntSize;
|
||||
using gfx::SourceSurface;
|
||||
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class NextPartObserver : public IProgressObserver
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(NextPartObserver)
|
||||
NS_INLINE_DECL_REFCOUNTING(NextPartObserver, override)
|
||||
|
||||
explicit NextPartObserver(MultipartImage* aOwner)
|
||||
: mOwner(aOwner)
|
||||
{
|
||||
MOZ_ASSERT(mOwner);
|
||||
}
|
||||
|
||||
void BeginObserving(Image* aImage)
|
||||
{
|
||||
MOZ_ASSERT(aImage);
|
||||
mImage = aImage;
|
||||
|
||||
RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
|
||||
tracker->AddObserver(this);
|
||||
}
|
||||
|
||||
void BlockUntilDecodedAndFinishObserving()
|
||||
{
|
||||
// Use GetFrame() to block until our image finishes decoding.
|
||||
RefPtr<SourceSurface> surface =
|
||||
mImage->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// GetFrame() should've sent synchronous notifications that would have
|
||||
// caused us to call FinishObserving() (and null out mImage) already. If for
|
||||
// some reason it didn't, we should do so here.
|
||||
if (mImage) {
|
||||
FinishObserving();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Notify(int32_t aType,
|
||||
const nsIntRect* aRect = nullptr) override
|
||||
{
|
||||
if (!mImage) {
|
||||
// We've already finished observing the last image we were given.
|
||||
return;
|
||||
}
|
||||
|
||||
if (aType == imgINotificationObserver::FRAME_COMPLETE) {
|
||||
FinishObserving();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnLoadComplete(bool aLastPart) override
|
||||
{
|
||||
if (!mImage) {
|
||||
// We've already finished observing the last image we were given.
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the image's intrinsic size.
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
mImage->GetWidth(&width);
|
||||
mImage->GetHeight(&height);
|
||||
|
||||
// Request decoding at the intrinsic size.
|
||||
mImage->RequestDecodeForSize(IntSize(width, height),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
|
||||
// If there's already an error, we may never get a FRAME_COMPLETE
|
||||
// notification, so go ahead and notify our owner right away.
|
||||
RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
|
||||
if (tracker->GetProgress() & FLAG_HAS_ERROR) {
|
||||
FinishObserving();
|
||||
}
|
||||
}
|
||||
|
||||
// Other notifications are ignored.
|
||||
virtual void BlockOnload() override { }
|
||||
virtual void UnblockOnload() override { }
|
||||
virtual void SetHasImage() override { }
|
||||
virtual bool NotificationsDeferred() const override { return false; }
|
||||
virtual void SetNotificationsDeferred(bool) override { }
|
||||
|
||||
private:
|
||||
virtual ~NextPartObserver() { }
|
||||
|
||||
void FinishObserving()
|
||||
{
|
||||
MOZ_ASSERT(mImage);
|
||||
|
||||
RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
|
||||
tracker->RemoveObserver(this);
|
||||
mImage = nullptr;
|
||||
|
||||
mOwner->FinishTransition();
|
||||
}
|
||||
|
||||
MultipartImage* mOwner;
|
||||
RefPtr<Image> mImage;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MultipartImage::MultipartImage(Image* aFirstPart)
|
||||
: ImageWrapper(aFirstPart)
|
||||
, mDeferNotifications(false)
|
||||
{
|
||||
mNextPartObserver = new NextPartObserver(this);
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::Init()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now");
|
||||
|
||||
// Start observing the first part.
|
||||
RefPtr<ProgressTracker> firstPartTracker =
|
||||
InnerImage()->GetProgressTracker();
|
||||
firstPartTracker->AddObserver(this);
|
||||
InnerImage()->IncrementAnimationConsumers();
|
||||
}
|
||||
|
||||
MultipartImage::~MultipartImage()
|
||||
{
|
||||
// Ask our ProgressTracker to drop its weak reference to us.
|
||||
mTracker->ResetImage();
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage, ImageWrapper)
|
||||
|
||||
void
|
||||
MultipartImage::BeginTransitionToPart(Image* aNextPart)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aNextPart);
|
||||
|
||||
if (mNextPart) {
|
||||
// Let the decoder catch up so we don't drop frames.
|
||||
mNextPartObserver->BlockUntilDecodedAndFinishObserving();
|
||||
MOZ_ASSERT(!mNextPart);
|
||||
}
|
||||
|
||||
mNextPart = aNextPart;
|
||||
|
||||
// Start observing the next part; we'll complete the transition when
|
||||
// NextPartObserver calls FinishTransition.
|
||||
mNextPartObserver->BeginObserving(mNextPart);
|
||||
mNextPart->IncrementAnimationConsumers();
|
||||
}
|
||||
|
||||
static Progress
|
||||
FilterProgress(Progress aProgress)
|
||||
{
|
||||
// Filter out onload blocking notifications, since we don't want to block
|
||||
// onload for multipart images.
|
||||
return aProgress & ~(FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED);
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::FinishTransition()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mNextPart, "Should have a next part here");
|
||||
|
||||
RefPtr<ProgressTracker> newCurrentPartTracker =
|
||||
mNextPart->GetProgressTracker();
|
||||
if (newCurrentPartTracker->GetProgress() & FLAG_HAS_ERROR) {
|
||||
// This frame has an error; drop it.
|
||||
mNextPart = nullptr;
|
||||
|
||||
// We still need to notify, though.
|
||||
mTracker->ResetForNewRequest();
|
||||
RefPtr<ProgressTracker> currentPartTracker =
|
||||
InnerImage()->GetProgressTracker();
|
||||
mTracker
|
||||
->SyncNotifyProgress(FilterProgress(currentPartTracker->GetProgress()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop observing the current part.
|
||||
{
|
||||
RefPtr<ProgressTracker> currentPartTracker =
|
||||
InnerImage()->GetProgressTracker();
|
||||
currentPartTracker->RemoveObserver(this);
|
||||
}
|
||||
|
||||
// Make the next part become the current part.
|
||||
mTracker->ResetForNewRequest();
|
||||
SetInnerImage(mNextPart);
|
||||
mNextPart = nullptr;
|
||||
newCurrentPartTracker->AddObserver(this);
|
||||
|
||||
// Finally, send all the notifications for the new current part and send a
|
||||
// FRAME_UPDATE notification so that observers know to redraw.
|
||||
mTracker
|
||||
->SyncNotifyProgress(FilterProgress(newCurrentPartTracker->GetProgress()),
|
||||
GetMaxSizedIntRect());
|
||||
}
|
||||
|
||||
already_AddRefed<imgIContainer>
|
||||
MultipartImage::Unwrap()
|
||||
{
|
||||
// Although we wrap another image, we don't allow callers to unwrap as. As far
|
||||
// as external code is concerned, MultipartImage is atomic.
|
||||
nsCOMPtr<imgIContainer> image = this;
|
||||
return image.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<ProgressTracker>
|
||||
MultipartImage::GetProgressTracker()
|
||||
{
|
||||
MOZ_ASSERT(mTracker);
|
||||
RefPtr<ProgressTracker> tracker = mTracker;
|
||||
return tracker.forget();
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::SetProgressTracker(ProgressTracker* aTracker)
|
||||
{
|
||||
MOZ_ASSERT(aTracker);
|
||||
MOZ_ASSERT(!mTracker);
|
||||
mTracker = aTracker;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MultipartImage::OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
// Note that this method is special in that we forward it to the next part if
|
||||
// one exists, and *not* the current part.
|
||||
|
||||
// We may trigger notifications that will free mNextPart, so keep it alive.
|
||||
RefPtr<Image> nextPart = mNextPart;
|
||||
if (nextPart) {
|
||||
nextPart->OnImageDataAvailable(aRequest, aContext, aInStr,
|
||||
aSourceOffset, aCount);
|
||||
} else {
|
||||
InnerImage()->OnImageDataAvailable(aRequest, aContext, aInStr,
|
||||
aSourceOffset, aCount);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MultipartImage::OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart)
|
||||
{
|
||||
// Note that this method is special in that we forward it to the next part if
|
||||
// one exists, and *not* the current part.
|
||||
|
||||
// We may trigger notifications that will free mNextPart, so keep it alive.
|
||||
RefPtr<Image> nextPart = mNextPart;
|
||||
if (nextPart) {
|
||||
nextPart->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart);
|
||||
} else {
|
||||
InnerImage()->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::Notify(int32_t aType, const nsIntRect* aRect /* = nullptr*/)
|
||||
{
|
||||
if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
|
||||
mTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE);
|
||||
} else if (aType == imgINotificationObserver::FRAME_UPDATE) {
|
||||
mTracker->SyncNotifyProgress(NoProgress, *aRect);
|
||||
} else if (aType == imgINotificationObserver::FRAME_COMPLETE) {
|
||||
mTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE);
|
||||
} else if (aType == imgINotificationObserver::LOAD_COMPLETE) {
|
||||
mTracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
} else if (aType == imgINotificationObserver::DECODE_COMPLETE) {
|
||||
mTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE);
|
||||
} else if (aType == imgINotificationObserver::DISCARD) {
|
||||
mTracker->OnDiscard();
|
||||
} else if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
|
||||
mTracker->OnUnlockedDraw();
|
||||
} else if (aType == imgINotificationObserver::IS_ANIMATED) {
|
||||
mTracker->SyncNotifyProgress(FLAG_IS_ANIMATED);
|
||||
} else if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
|
||||
mTracker->SyncNotifyProgress(FLAG_HAS_TRANSPARENCY);
|
||||
} else {
|
||||
NS_NOTREACHED("Notification list should be exhaustive");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::OnLoadComplete(bool aLastPart)
|
||||
{
|
||||
Progress progress = FLAG_LOAD_COMPLETE;
|
||||
if (aLastPart) {
|
||||
progress |= FLAG_LAST_PART_COMPLETE;
|
||||
}
|
||||
mTracker->SyncNotifyProgress(progress);
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::SetHasImage()
|
||||
{
|
||||
mTracker->OnImageAvailable();
|
||||
}
|
||||
|
||||
bool
|
||||
MultipartImage::NotificationsDeferred() const
|
||||
{
|
||||
return mDeferNotifications;
|
||||
}
|
||||
|
||||
void
|
||||
MultipartImage::SetNotificationsDeferred(bool aDeferNotifications)
|
||||
{
|
||||
mDeferNotifications = aDeferNotifications;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
90
image/MultipartImage.h
Normal file
90
image/MultipartImage.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* -*- 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_MultipartImage_h
|
||||
#define mozilla_image_MultipartImage_h
|
||||
|
||||
#include "ImageWrapper.h"
|
||||
#include "IProgressObserver.h"
|
||||
#include "ProgressTracker.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class NextPartObserver;
|
||||
|
||||
/**
|
||||
* An Image wrapper that implements support for multipart/x-mixed-replace
|
||||
* images.
|
||||
*/
|
||||
class MultipartImage
|
||||
: public ImageWrapper
|
||||
, public IProgressObserver
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(MultipartImage)
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
void BeginTransitionToPart(Image* aNextPart);
|
||||
|
||||
// Overridden ImageWrapper methods:
|
||||
virtual already_AddRefed<imgIContainer> Unwrap() override;
|
||||
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
|
||||
virtual void SetProgressTracker(ProgressTracker* aTracker) override;
|
||||
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount) override;
|
||||
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart) override;
|
||||
|
||||
// We don't support locking or track animation consumers for individual parts,
|
||||
// so we override these methods to do nothing.
|
||||
NS_IMETHOD LockImage() override { return NS_OK; }
|
||||
NS_IMETHOD UnlockImage() override { return NS_OK; }
|
||||
virtual void IncrementAnimationConsumers() override { }
|
||||
virtual void DecrementAnimationConsumers() override { }
|
||||
#ifdef DEBUG
|
||||
virtual uint32_t GetAnimationConsumers() override { return 1; }
|
||||
#endif
|
||||
|
||||
// Overridden IProgressObserver methods:
|
||||
virtual void Notify(int32_t aType,
|
||||
const nsIntRect* aRect = nullptr) override;
|
||||
virtual void OnLoadComplete(bool aLastPart) override;
|
||||
virtual void SetHasImage() override;
|
||||
virtual bool NotificationsDeferred() const override;
|
||||
virtual void SetNotificationsDeferred(bool aDeferNotifications) override;
|
||||
|
||||
// We don't allow multipart images to block onload, so we override these
|
||||
// methods to do nothing.
|
||||
virtual void BlockOnload() override { }
|
||||
virtual void UnblockOnload() override { }
|
||||
|
||||
protected:
|
||||
virtual ~MultipartImage();
|
||||
|
||||
private:
|
||||
friend class ImageFactory;
|
||||
friend class NextPartObserver;
|
||||
|
||||
explicit MultipartImage(Image* aFirstPart);
|
||||
void Init();
|
||||
|
||||
void FinishTransition();
|
||||
|
||||
RefPtr<ProgressTracker> mTracker;
|
||||
RefPtr<NextPartObserver> mNextPartObserver;
|
||||
RefPtr<Image> mNextPart;
|
||||
bool mDeferNotifications : 1;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_MultipartImage_h
|
||||
62
image/Orientation.h
Normal file
62
image/Orientation.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- 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_Orientation_h
|
||||
#define mozilla_image_Orientation_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
enum class Angle : uint8_t {
|
||||
D0,
|
||||
D90,
|
||||
D180,
|
||||
D270
|
||||
};
|
||||
|
||||
enum class Flip : uint8_t {
|
||||
Unflipped,
|
||||
Horizontal
|
||||
};
|
||||
|
||||
/**
|
||||
* A struct that describes an image's orientation as a rotation optionally
|
||||
* followed by a reflection. This may be used to be indicate an image's inherent
|
||||
* orientation or a desired orientation for the image.
|
||||
*/
|
||||
struct Orientation
|
||||
{
|
||||
explicit Orientation(Angle aRotation = Angle::D0,
|
||||
Flip mFlip = Flip::Unflipped)
|
||||
: rotation(aRotation)
|
||||
, flip(mFlip)
|
||||
{ }
|
||||
|
||||
bool IsIdentity() const {
|
||||
return (rotation == Angle::D0) && (flip == Flip::Unflipped);
|
||||
}
|
||||
|
||||
bool SwapsWidthAndHeight() const {
|
||||
return (rotation == Angle::D90) || (rotation == Angle::D270);
|
||||
}
|
||||
|
||||
bool operator==(const Orientation& aOther) const {
|
||||
return (rotation == aOther.rotation) && (flip == aOther.flip);
|
||||
}
|
||||
|
||||
bool operator!=(const Orientation& aOther) const {
|
||||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
Angle rotation;
|
||||
Flip flip;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_Orientation_h
|
||||
354
image/OrientedImage.cpp
Normal file
354
image/OrientedImage.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/* -*- 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 "OrientedImage.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxDrawable.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "ImageRegion.h"
|
||||
#include "SVGImageContext.h"
|
||||
|
||||
using std::swap;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
using layers::LayerManager;
|
||||
using layers::ImageContainer;
|
||||
|
||||
namespace image {
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(OrientedImage, ImageWrapper)
|
||||
|
||||
NS_IMETHODIMP
|
||||
OrientedImage::GetWidth(int32_t* aWidth)
|
||||
{
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
return InnerImage()->GetHeight(aWidth);
|
||||
} else {
|
||||
return InnerImage()->GetWidth(aWidth);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OrientedImage::GetHeight(int32_t* aHeight)
|
||||
{
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
return InnerImage()->GetWidth(aHeight);
|
||||
} else {
|
||||
return InnerImage()->GetHeight(aHeight);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OrientedImage::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
nsresult rv = InnerImage()->GetIntrinsicSize(aSize);
|
||||
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
swap(aSize->width, aSize->height);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OrientedImage::GetIntrinsicRatio(nsSize* aRatio)
|
||||
{
|
||||
nsresult rv = InnerImage()->GetIntrinsicRatio(aRatio);
|
||||
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
swap(aRatio->width, aRatio->height);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
OrientedImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
if (mOrientation.IsIdentity()) {
|
||||
return InnerImage()->GetFrame(aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
// Get the underlying dimensions.
|
||||
IntSize size;
|
||||
rv = InnerImage()->GetWidth(&size.width);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
rv = InnerImage()->GetHeight(&size.height);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
// Determine an appropriate format for the surface.
|
||||
gfx::SurfaceFormat surfaceFormat;
|
||||
if (InnerImage()->WillDrawOpaqueNow()) {
|
||||
surfaceFormat = gfx::SurfaceFormat::B8G8R8X8;
|
||||
} else {
|
||||
surfaceFormat = gfx::SurfaceFormat::B8G8R8A8;
|
||||
}
|
||||
|
||||
// Create a surface to draw into.
|
||||
RefPtr<DrawTarget> target =
|
||||
gfxPlatform::GetPlatform()->
|
||||
CreateOffscreenContentDrawTarget(size, surfaceFormat);
|
||||
if (!target || !target->IsValid()) {
|
||||
NS_ERROR("Could not create a DrawTarget");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// Create our drawable.
|
||||
RefPtr<SourceSurface> innerSurface =
|
||||
InnerImage()->GetFrame(aWhichFrame, aFlags);
|
||||
NS_ENSURE_TRUE(innerSurface, nullptr);
|
||||
RefPtr<gfxDrawable> drawable =
|
||||
new gfxSurfaceDrawable(innerSurface, size);
|
||||
|
||||
// Draw.
|
||||
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
|
||||
MOZ_ASSERT(ctx); // already checked the draw target above
|
||||
ctx->Multiply(OrientationMatrix(size));
|
||||
gfxUtils::DrawPixelSnapped(ctx, drawable, size, ImageRegion::Create(size),
|
||||
surfaceFormat, SamplingFilter::LINEAR);
|
||||
|
||||
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)
|
||||
{
|
||||
if (mOrientation.IsIdentity()) {
|
||||
return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
|
||||
OrientedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
// XXX(seth): We currently don't have a way of orienting the result of
|
||||
// GetImageContainer. We work around this by always returning null, but if it
|
||||
// ever turns out that OrientedImage is widely used on codepaths that can
|
||||
// actually benefit from GetImageContainer, it would be a good idea to fix
|
||||
// that method for performance reasons.
|
||||
|
||||
if (mOrientation.IsIdentity()) {
|
||||
return InnerImage()->GetImageContainer(aManager, aFlags);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct MatrixBuilder
|
||||
{
|
||||
explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) { }
|
||||
|
||||
gfxMatrix Build() { return mMatrix; }
|
||||
|
||||
void Scale(gfxFloat aX, gfxFloat aY)
|
||||
{
|
||||
if (mInvert) {
|
||||
mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY);
|
||||
} else {
|
||||
mMatrix.Scale(aX, aY);
|
||||
}
|
||||
}
|
||||
|
||||
void Rotate(gfxFloat aPhi)
|
||||
{
|
||||
if (mInvert) {
|
||||
mMatrix *= gfxMatrix::Rotation(-aPhi);
|
||||
} else {
|
||||
mMatrix.Rotate(aPhi);
|
||||
}
|
||||
}
|
||||
|
||||
void Translate(gfxPoint aDelta)
|
||||
{
|
||||
if (mInvert) {
|
||||
mMatrix *= gfxMatrix::Translation(-aDelta);
|
||||
} else {
|
||||
mMatrix.Translate(aDelta);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
gfxMatrix mMatrix;
|
||||
bool mInvert;
|
||||
};
|
||||
|
||||
/*
|
||||
* OrientationMatrix() computes a matrix that applies the rotation and
|
||||
* reflection specified by mOrientation, or that matrix's inverse if aInvert is
|
||||
* true.
|
||||
*
|
||||
* @param aSize The scaled size of the inner image. (When outside code specifies
|
||||
* the scaled size, as with imgIContainer::Draw and its aSize
|
||||
* parameter, it's necessary to swap the width and height if
|
||||
* mOrientation.SwapsWidthAndHeight() is true.)
|
||||
* @param aInvert If true, compute the inverse of the orientation matrix. Prefer
|
||||
* this approach to OrientationMatrix(..).Invert(), because it's
|
||||
* more numerically accurate.
|
||||
*/
|
||||
gfxMatrix
|
||||
OrientedImage::OrientationMatrix(const nsIntSize& aSize,
|
||||
bool aInvert /* = false */)
|
||||
{
|
||||
MatrixBuilder builder(aInvert);
|
||||
|
||||
// Apply reflection, if present. (This logically happens second, but we
|
||||
// apply it first because these transformations are all premultiplied.) A
|
||||
// translation is necessary to place the image back in the first quadrant.
|
||||
switch (mOrientation.flip) {
|
||||
case Flip::Unflipped:
|
||||
break;
|
||||
case Flip::Horizontal:
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
builder.Translate(gfxPoint(aSize.height, 0));
|
||||
} else {
|
||||
builder.Translate(gfxPoint(aSize.width, 0));
|
||||
}
|
||||
builder.Scale(-1.0, 1.0);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(false, "Invalid flip value");
|
||||
}
|
||||
|
||||
// Apply rotation, if present. Again, a translation is used to place the
|
||||
// image back in the first quadrant.
|
||||
switch (mOrientation.rotation) {
|
||||
case Angle::D0:
|
||||
break;
|
||||
case Angle::D90:
|
||||
builder.Translate(gfxPoint(aSize.height, 0));
|
||||
builder.Rotate(-1.5 * M_PI);
|
||||
break;
|
||||
case Angle::D180:
|
||||
builder.Translate(gfxPoint(aSize.width, aSize.height));
|
||||
builder.Rotate(-1.0 * M_PI);
|
||||
break;
|
||||
case Angle::D270:
|
||||
builder.Translate(gfxPoint(0, aSize.width));
|
||||
builder.Rotate(-0.5 * M_PI);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(false, "Invalid rotation value");
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(DrawResult)
|
||||
OrientedImage::Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (mOrientation.IsIdentity()) {
|
||||
return InnerImage()->Draw(aContext, aSize, aRegion,
|
||||
aWhichFrame, aSamplingFilter,
|
||||
aSVGContext, aFlags);
|
||||
}
|
||||
|
||||
// Update the image size to match the image's coordinate system. (This could
|
||||
// be done using TransformBounds but since it's only a size a swap is enough.)
|
||||
nsIntSize size(aSize);
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
swap(size.width, size.height);
|
||||
}
|
||||
|
||||
// Update the matrix so that we transform the image into the orientation
|
||||
// expected by the caller before drawing.
|
||||
gfxMatrix matrix(OrientationMatrix(size));
|
||||
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
|
||||
aContext->Multiply(matrix);
|
||||
|
||||
// The region is already in the orientation expected by the caller, but we
|
||||
// need it to be in the image's coordinate system, so we transform it using
|
||||
// the inverse of the orientation matrix.
|
||||
gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true));
|
||||
ImageRegion region(aRegion);
|
||||
region.TransformBoundsBy(inverseMatrix);
|
||||
|
||||
auto orientViewport = [&](const SVGImageContext& aOldContext) {
|
||||
CSSIntSize viewportSize(aOldContext.GetViewportSize());
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
swap(viewportSize.width, viewportSize.height);
|
||||
}
|
||||
return SVGImageContext(viewportSize,
|
||||
aOldContext.GetPreserveAspectRatio());
|
||||
};
|
||||
|
||||
return InnerImage()->Draw(aContext, size, region, aWhichFrame, aSamplingFilter,
|
||||
aSVGContext.map(orientViewport), aFlags);
|
||||
}
|
||||
|
||||
nsIntSize
|
||||
OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest,
|
||||
uint32_t aWhichFrame,
|
||||
SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (!mOrientation.SwapsWidthAndHeight()) {
|
||||
return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
|
||||
aSamplingFilter, aFlags);
|
||||
}
|
||||
|
||||
// Swap the size for the calculation, then swap it back for the caller.
|
||||
gfxSize destSize(aDest.height, aDest.width);
|
||||
nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(destSize,
|
||||
aWhichFrame,
|
||||
aSamplingFilter,
|
||||
aFlags));
|
||||
return nsIntSize(innerImageSize.height, innerImageSize.width);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(nsIntRect)
|
||||
OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
{
|
||||
nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
|
||||
|
||||
if (mOrientation.IsIdentity()) {
|
||||
return rect;
|
||||
}
|
||||
|
||||
nsIntSize innerSize;
|
||||
nsresult rv = InnerImage()->GetWidth(&innerSize.width);
|
||||
rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Fall back to identity if the width and height aren't available.
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Transform the invalidation rect into the correct orientation.
|
||||
gfxMatrix matrix(OrientationMatrix(innerSize));
|
||||
gfxRect invalidRect(matrix.TransformBounds(gfxRect(rect.x, rect.y,
|
||||
rect.width, rect.height)));
|
||||
|
||||
return IntRect::RoundOut(invalidRect.x, invalidRect.y,
|
||||
invalidRect.width, invalidRect.height);
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
79
image/OrientedImage.h
Normal file
79
image/OrientedImage.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/* -*- 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_OrientedImage_h
|
||||
#define mozilla_image_OrientedImage_h
|
||||
|
||||
#include "ImageWrapper.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "Orientation.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An Image wrapper that rotates and/or flips an image according to a specified
|
||||
* Orientation.
|
||||
*
|
||||
* XXX(seth): There a known (performance, not correctness) issue with
|
||||
* GetImageContainer. See the comments for that method for more information.
|
||||
*/
|
||||
class OrientedImage : public ImageWrapper
|
||||
{
|
||||
typedef gfx::SourceSurface SourceSurface;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_IMETHOD GetWidth(int32_t* aWidth) override;
|
||||
NS_IMETHOD GetHeight(int32_t* aHeight) override;
|
||||
NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
|
||||
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>)
|
||||
GetImageContainer(layers::LayerManager* aManager,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
uint32_t aWhichFrame,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(
|
||||
const nsIntRect& aRect) override;
|
||||
nsIntSize OptimalImageSizeForDest(const gfxSize& aDest,
|
||||
uint32_t aWhichFrame,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags) override;
|
||||
|
||||
protected:
|
||||
OrientedImage(Image* aImage, Orientation aOrientation)
|
||||
: ImageWrapper(aImage)
|
||||
, mOrientation(aOrientation)
|
||||
{ }
|
||||
|
||||
virtual ~OrientedImage() { }
|
||||
|
||||
gfxMatrix OrientationMatrix(const nsIntSize& aSize, bool aInvert = false);
|
||||
|
||||
private:
|
||||
Orientation mOrientation;
|
||||
|
||||
friend class ImageOps;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_OrientedImage_h
|
||||
44
image/PlaybackType.h
Normal file
44
image/PlaybackType.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- 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_PlaybackType_h
|
||||
#define mozilla_image_PlaybackType_h
|
||||
|
||||
#include "imgIContainer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* PlaybackType identifies a surface cache entry as either a static surface or
|
||||
* an animation. Note that a specific cache entry is one or the other, but
|
||||
* images may be associated with both types of cache entries, since in some
|
||||
* circumstances we may want to treat an animated image as if it were static.
|
||||
*/
|
||||
enum class PlaybackType : uint8_t
|
||||
{
|
||||
eStatic, // Calls to DrawableRef() will always return the same surface.
|
||||
eAnimated // An animation; calls to DrawableRef() may return different
|
||||
// surfaces at different times.
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an imgIContainer FRAME_* value, returns the corresponding PlaybackType
|
||||
* for use in surface cache lookups.
|
||||
*/
|
||||
inline PlaybackType
|
||||
ToPlaybackType(uint32_t aWhichFrame)
|
||||
{
|
||||
MOZ_ASSERT(aWhichFrame == imgIContainer::FRAME_FIRST ||
|
||||
aWhichFrame == imgIContainer::FRAME_CURRENT);
|
||||
return aWhichFrame == imgIContainer::FRAME_CURRENT
|
||||
? PlaybackType::eAnimated
|
||||
: PlaybackType::eStatic;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_PlaybackType_h
|
||||
570
image/ProgressTracker.cpp
Normal file
570
image/ProgressTracker.cpp
Normal file
|
|
@ -0,0 +1,570 @@
|
|||
/* -*- 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 "ImageLogging.h"
|
||||
#include "ProgressTracker.h"
|
||||
|
||||
#include "imgIContainer.h"
|
||||
#include "imgINotificationObserver.h"
|
||||
#include "imgIRequest.h"
|
||||
#include "Image.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsIObserverService.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Services.h"
|
||||
|
||||
using mozilla::WeakPtr;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
static void
|
||||
CheckProgressConsistency(Progress aOldProgress, Progress aNewProgress)
|
||||
{
|
||||
// Check preconditions for every progress bit.
|
||||
|
||||
if (aNewProgress & FLAG_SIZE_AVAILABLE) {
|
||||
// No preconditions.
|
||||
}
|
||||
if (aNewProgress & FLAG_DECODE_COMPLETE) {
|
||||
MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
|
||||
MOZ_ASSERT(aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR));
|
||||
}
|
||||
if (aNewProgress & FLAG_FRAME_COMPLETE) {
|
||||
MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
|
||||
}
|
||||
if (aNewProgress & FLAG_LOAD_COMPLETE) {
|
||||
MOZ_ASSERT(aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
|
||||
}
|
||||
if (aNewProgress & FLAG_ONLOAD_BLOCKED) {
|
||||
// No preconditions.
|
||||
}
|
||||
if (aNewProgress & FLAG_ONLOAD_UNBLOCKED) {
|
||||
MOZ_ASSERT(aNewProgress & FLAG_ONLOAD_BLOCKED);
|
||||
MOZ_ASSERT(aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
|
||||
}
|
||||
if (aNewProgress & FLAG_IS_ANIMATED) {
|
||||
// No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
|
||||
// discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
|
||||
// GIFs may fool us.
|
||||
}
|
||||
if (aNewProgress & FLAG_HAS_TRANSPARENCY) {
|
||||
// XXX We'd like to assert that transparency is only set during metadata
|
||||
// decode but we don't have any way to assert that until bug 1254892 is fixed.
|
||||
}
|
||||
if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
|
||||
MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
|
||||
}
|
||||
if (aNewProgress & FLAG_HAS_ERROR) {
|
||||
// No preconditions.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::SetImage(Image* aImage)
|
||||
{
|
||||
MutexAutoLock lock(mImageMutex);
|
||||
MOZ_ASSERT(aImage, "Setting null image");
|
||||
MOZ_ASSERT(!mImage, "Setting image when we already have one");
|
||||
mImage = aImage;
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::ResetImage()
|
||||
{
|
||||
MutexAutoLock lock(mImageMutex);
|
||||
MOZ_ASSERT(mImage, "Resetting image when it's already null!");
|
||||
mImage = nullptr;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ProgressTracker::GetImageStatus() const
|
||||
{
|
||||
uint32_t status = imgIRequest::STATUS_NONE;
|
||||
|
||||
// Translate our current state to a set of imgIRequest::STATE_* flags.
|
||||
if (mProgress & FLAG_SIZE_AVAILABLE) {
|
||||
status |= imgIRequest::STATUS_SIZE_AVAILABLE;
|
||||
}
|
||||
if (mProgress & FLAG_DECODE_COMPLETE) {
|
||||
status |= imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
}
|
||||
if (mProgress & FLAG_FRAME_COMPLETE) {
|
||||
status |= imgIRequest::STATUS_FRAME_COMPLETE;
|
||||
}
|
||||
if (mProgress & FLAG_LOAD_COMPLETE) {
|
||||
status |= imgIRequest::STATUS_LOAD_COMPLETE;
|
||||
}
|
||||
if (mProgress & FLAG_IS_ANIMATED) {
|
||||
status |= imgIRequest::STATUS_IS_ANIMATED;
|
||||
}
|
||||
if (mProgress & FLAG_HAS_TRANSPARENCY) {
|
||||
status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
|
||||
}
|
||||
if (mProgress & FLAG_HAS_ERROR) {
|
||||
status |= imgIRequest::STATUS_ERROR;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// A helper class to allow us to call SyncNotify asynchronously.
|
||||
class AsyncNotifyRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
AsyncNotifyRunnable(ProgressTracker* aTracker,
|
||||
IProgressObserver* aObserver)
|
||||
: mTracker(aTracker)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
|
||||
MOZ_ASSERT(aTracker, "aTracker should not be null");
|
||||
MOZ_ASSERT(aObserver, "aObserver should not be null");
|
||||
mObservers.AppendElement(aObserver);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
|
||||
MOZ_ASSERT(mTracker, "mTracker should not be null");
|
||||
for (uint32_t i = 0; i < mObservers.Length(); ++i) {
|
||||
mObservers[i]->SetNotificationsDeferred(false);
|
||||
mTracker->SyncNotify(mObservers[i]);
|
||||
}
|
||||
|
||||
mTracker->mRunnable = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void AddObserver(IProgressObserver* aObserver)
|
||||
{
|
||||
mObservers.AppendElement(aObserver);
|
||||
}
|
||||
|
||||
void RemoveObserver(IProgressObserver* aObserver)
|
||||
{
|
||||
mObservers.RemoveElement(aObserver);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ProgressTracker;
|
||||
|
||||
RefPtr<ProgressTracker> mTracker;
|
||||
nsTArray<RefPtr<IProgressObserver>> mObservers;
|
||||
};
|
||||
|
||||
void
|
||||
ProgressTracker::Notify(IProgressObserver* aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
|
||||
RefPtr<Image> image = GetImage();
|
||||
if (image && image->GetURI()) {
|
||||
RefPtr<ImageURL> uri(image->GetURI());
|
||||
nsAutoCString spec;
|
||||
uri->GetSpec(spec);
|
||||
LOG_FUNC_WITH_PARAM(gImgLog,
|
||||
"ProgressTracker::Notify async", "uri", spec.get());
|
||||
} else {
|
||||
LOG_FUNC_WITH_PARAM(gImgLog,
|
||||
"ProgressTracker::Notify async", "uri", "<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
aObserver->SetNotificationsDeferred(true);
|
||||
|
||||
// If we have an existing runnable that we can use, we just append this
|
||||
// observer to its list of observers to be notified. This ensures we don't
|
||||
// unnecessarily delay onload.
|
||||
AsyncNotifyRunnable* runnable =
|
||||
static_cast<AsyncNotifyRunnable*>(mRunnable.get());
|
||||
|
||||
if (runnable) {
|
||||
runnable->AddObserver(aObserver);
|
||||
} else {
|
||||
mRunnable = new AsyncNotifyRunnable(this, aObserver);
|
||||
NS_DispatchToCurrentThread(mRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
// A helper class to allow us to call SyncNotify asynchronously for a given,
|
||||
// fixed, state.
|
||||
class AsyncNotifyCurrentStateRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker,
|
||||
IProgressObserver* aObserver)
|
||||
: mProgressTracker(aProgressTracker)
|
||||
, mObserver(aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
|
||||
MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null");
|
||||
MOZ_ASSERT(mObserver, "mObserver should not be null");
|
||||
mImage = mProgressTracker->GetImage();
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
|
||||
mObserver->SetNotificationsDeferred(false);
|
||||
|
||||
mProgressTracker->SyncNotify(mObserver);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<ProgressTracker> mProgressTracker;
|
||||
RefPtr<IProgressObserver> mObserver;
|
||||
|
||||
// We have to hold on to a reference to the tracker's image, just in case
|
||||
// it goes away while we're in the event queue.
|
||||
RefPtr<Image> mImage;
|
||||
};
|
||||
|
||||
void
|
||||
ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
|
||||
RefPtr<Image> image = GetImage();
|
||||
nsAutoCString spec;
|
||||
if (image && image->GetURI()) {
|
||||
image->GetURI()->GetSpec(spec);
|
||||
}
|
||||
LOG_FUNC_WITH_PARAM(gImgLog,
|
||||
"ProgressTracker::NotifyCurrentState", "uri", spec.get());
|
||||
}
|
||||
|
||||
aObserver->SetNotificationsDeferred(true);
|
||||
|
||||
nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this,
|
||||
aObserver);
|
||||
NS_DispatchToCurrentThread(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageObserverNotifier is a helper type that abstracts over the difference
|
||||
* between sending notifications to all of the observers in an ObserverTable,
|
||||
* and sending them to a single observer. This allows the same notification code
|
||||
* to be used for both cases.
|
||||
*/
|
||||
template <typename T> struct ImageObserverNotifier;
|
||||
|
||||
template <>
|
||||
struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*>
|
||||
{
|
||||
explicit ImageObserverNotifier(const ObserverTable* aObservers,
|
||||
bool aIgnoreDeferral = false)
|
||||
: mObservers(aObservers)
|
||||
, mIgnoreDeferral(aIgnoreDeferral)
|
||||
{ }
|
||||
|
||||
template <typename Lambda>
|
||||
void operator()(Lambda aFunc)
|
||||
{
|
||||
for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) {
|
||||
RefPtr<IProgressObserver> observer = iter.Data().get();
|
||||
if (observer &&
|
||||
(mIgnoreDeferral || !observer->NotificationsDeferred())) {
|
||||
aFunc(observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const ObserverTable* mObservers;
|
||||
const bool mIgnoreDeferral;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*>
|
||||
{
|
||||
explicit ImageObserverNotifier(IProgressObserver* aObserver)
|
||||
: mObserver(aObserver)
|
||||
{ }
|
||||
|
||||
template <typename Lambda>
|
||||
void operator()(Lambda aFunc)
|
||||
{
|
||||
if (mObserver && !mObserver->NotificationsDeferred()) {
|
||||
aFunc(mObserver);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
IProgressObserver* mObserver;
|
||||
};
|
||||
|
||||
template <typename T> void
|
||||
SyncNotifyInternal(const T& aObservers,
|
||||
bool aHasImage,
|
||||
Progress aProgress,
|
||||
const nsIntRect& aDirtyRect)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
typedef imgINotificationObserver I;
|
||||
ImageObserverNotifier<T> notify(aObservers);
|
||||
|
||||
if (aProgress & FLAG_SIZE_AVAILABLE) {
|
||||
notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
|
||||
}
|
||||
|
||||
if (aProgress & FLAG_ONLOAD_BLOCKED) {
|
||||
notify([](IProgressObserver* aObs) { aObs->BlockOnload(); });
|
||||
}
|
||||
|
||||
if (aHasImage) {
|
||||
// OnFrameUpdate
|
||||
// If there's any content in this frame at all (always true for
|
||||
// vector images, true for raster images that have decoded at
|
||||
// least one frame) then send OnFrameUpdate.
|
||||
if (!aDirtyRect.IsEmpty()) {
|
||||
notify([&](IProgressObserver* aObs) {
|
||||
aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
|
||||
});
|
||||
}
|
||||
|
||||
if (aProgress & FLAG_FRAME_COMPLETE) {
|
||||
notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
|
||||
}
|
||||
|
||||
if (aProgress & FLAG_HAS_TRANSPARENCY) {
|
||||
notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
|
||||
}
|
||||
|
||||
if (aProgress & FLAG_IS_ANIMATED) {
|
||||
notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
|
||||
}
|
||||
}
|
||||
|
||||
// Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
|
||||
// observers that can fire events when they receive those notifications to do
|
||||
// so then, instead of being forced to wait for UnblockOnload.
|
||||
if (aProgress & FLAG_ONLOAD_UNBLOCKED) {
|
||||
notify([](IProgressObserver* aObs) { aObs->UnblockOnload(); });
|
||||
}
|
||||
|
||||
if (aProgress & FLAG_DECODE_COMPLETE) {
|
||||
MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
|
||||
notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
|
||||
}
|
||||
|
||||
if (aProgress & FLAG_LOAD_COMPLETE) {
|
||||
notify([=](IProgressObserver* aObs) {
|
||||
aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::SyncNotifyProgress(Progress aProgress,
|
||||
const nsIntRect& aInvalidRect
|
||||
/* = nsIntRect() */)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
|
||||
|
||||
// Don't unblock onload if we're not blocked.
|
||||
Progress progress = Difference(aProgress);
|
||||
if (!((mProgress | progress) & FLAG_ONLOAD_BLOCKED)) {
|
||||
progress &= ~FLAG_ONLOAD_UNBLOCKED;
|
||||
}
|
||||
|
||||
CheckProgressConsistency(mProgress, mProgress | progress);
|
||||
|
||||
// XXX(seth): Hack to work around the fact that some observers have bugs and
|
||||
// need to get onload blocking notifications multiple times. We should fix
|
||||
// those observers and remove this.
|
||||
if ((aProgress & FLAG_DECODE_COMPLETE) &&
|
||||
(mProgress & FLAG_ONLOAD_BLOCKED) &&
|
||||
(mProgress & FLAG_ONLOAD_UNBLOCKED)) {
|
||||
progress |= FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED;
|
||||
}
|
||||
|
||||
// Apply the changes.
|
||||
mProgress |= progress;
|
||||
|
||||
// Send notifications.
|
||||
mObservers.Read([&](const ObserverTable* aTable) {
|
||||
SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
|
||||
});
|
||||
|
||||
if (progress & FLAG_HAS_ERROR) {
|
||||
FireFailureNotification();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::SyncNotify(IProgressObserver* aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
RefPtr<Image> image = GetImage();
|
||||
|
||||
nsAutoCString spec;
|
||||
if (image && image->GetURI()) {
|
||||
image->GetURI()->GetSpec(spec);
|
||||
}
|
||||
LOG_SCOPE_WITH_PARAM(gImgLog,
|
||||
"ProgressTracker::SyncNotify", "uri", spec.get());
|
||||
|
||||
nsIntRect rect;
|
||||
if (image) {
|
||||
if (NS_FAILED(image->GetWidth(&rect.width)) ||
|
||||
NS_FAILED(image->GetHeight(&rect.height))) {
|
||||
// Either the image has no intrinsic size, or it has an error.
|
||||
rect = GetMaxSizedIntRect();
|
||||
}
|
||||
}
|
||||
|
||||
SyncNotifyInternal(aObserver, !!image, mProgress, rect);
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"SyncNotifyState and mObservers are not threadsafe");
|
||||
RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
|
||||
|
||||
if (mProgress & FLAG_ONLOAD_BLOCKED && !(mProgress & FLAG_ONLOAD_UNBLOCKED)) {
|
||||
aObserver->UnblockOnload();
|
||||
}
|
||||
|
||||
if (!(mProgress & FLAG_LOAD_COMPLETE)) {
|
||||
aObserver->OnLoadComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::AddObserver(IProgressObserver* aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
RefPtr<IProgressObserver> observer = aObserver;
|
||||
|
||||
mObservers.Write([=](ObserverTable* aTable) {
|
||||
MOZ_ASSERT(!aTable->Get(observer, nullptr),
|
||||
"Adding duplicate entry for image observer");
|
||||
|
||||
WeakPtr<IProgressObserver> weakPtr = observer.get();
|
||||
aTable->Put(observer, weakPtr);
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
RefPtr<IProgressObserver> observer = aObserver;
|
||||
|
||||
// Remove the observer from the list.
|
||||
bool removed = mObservers.Write([=](ObserverTable* aTable) {
|
||||
bool removed = aTable->Get(observer, nullptr);
|
||||
aTable->Remove(observer);
|
||||
return removed;
|
||||
});
|
||||
|
||||
// Observers can get confused if they don't get all the proper teardown
|
||||
// notifications. Part ways on good terms.
|
||||
if (removed && !aObserver->NotificationsDeferred()) {
|
||||
EmulateRequestFinished(aObserver);
|
||||
}
|
||||
|
||||
// Make sure we don't give callbacks to an observer that isn't interested in
|
||||
// them any more.
|
||||
AsyncNotifyRunnable* runnable =
|
||||
static_cast<AsyncNotifyRunnable*>(mRunnable.get());
|
||||
|
||||
if (aObserver->NotificationsDeferred() && runnable) {
|
||||
runnable->RemoveObserver(aObserver);
|
||||
aObserver->SetNotificationsDeferred(false);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ProgressTracker::ObserverCount() const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mObservers.Read([](const ObserverTable* aTable) {
|
||||
return aTable->Count();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::OnUnlockedDraw()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mObservers.Read([](const ObserverTable* aTable) {
|
||||
ImageObserverNotifier<const ObserverTable*> notify(aTable);
|
||||
notify([](IProgressObserver* aObs) {
|
||||
aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::ResetForNewRequest()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mProgress = NoProgress;
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::OnDiscard()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mObservers.Read([](const ObserverTable* aTable) {
|
||||
ImageObserverNotifier<const ObserverTable*> notify(aTable);
|
||||
notify([](IProgressObserver* aObs) {
|
||||
aObs->Notify(imgINotificationObserver::DISCARD);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::OnImageAvailable()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Notify any imgRequestProxys that are observing us that we have an Image.
|
||||
mObservers.Read([](const ObserverTable* aTable) {
|
||||
ImageObserverNotifier<const ObserverTable*>
|
||||
notify(aTable, /* aIgnoreDeferral = */ true);
|
||||
notify([](IProgressObserver* aObs) {
|
||||
aObs->SetHasImage();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTracker::FireFailureNotification()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Some kind of problem has happened with image decoding.
|
||||
// Report the URI to net:failed-to-process-uri-conent observers.
|
||||
RefPtr<Image> image = GetImage();
|
||||
if (image) {
|
||||
// Should be on main thread, so ok to create a new nsIURI.
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
{
|
||||
RefPtr<ImageURL> threadsafeUriData = image->GetURI();
|
||||
uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr;
|
||||
}
|
||||
if (uri) {
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
234
image/ProgressTracker.h
Normal file
234
image/ProgressTracker.h
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/* -*- 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_ProgressTracker_h
|
||||
#define mozilla_image_ProgressTracker_h
|
||||
|
||||
#include "CopyOnWrite.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTObserverArray.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsRect.h"
|
||||
#include "IProgressObserver.h"
|
||||
|
||||
class nsIRunnable;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class AsyncNotifyRunnable;
|
||||
class AsyncNotifyCurrentStateRunnable;
|
||||
class Image;
|
||||
|
||||
/**
|
||||
* Image progress bitflags.
|
||||
*
|
||||
* See CheckProgressConsistency() for the invariants we enforce about the
|
||||
* ordering dependencies betweeen these flags.
|
||||
*/
|
||||
enum {
|
||||
FLAG_SIZE_AVAILABLE = 1u << 0, // STATUS_SIZE_AVAILABLE
|
||||
FLAG_DECODE_COMPLETE = 1u << 1, // STATUS_DECODE_COMPLETE
|
||||
FLAG_FRAME_COMPLETE = 1u << 2, // STATUS_FRAME_COMPLETE
|
||||
FLAG_LOAD_COMPLETE = 1u << 3, // STATUS_LOAD_COMPLETE
|
||||
FLAG_ONLOAD_BLOCKED = 1u << 4,
|
||||
FLAG_ONLOAD_UNBLOCKED = 1u << 5,
|
||||
FLAG_IS_ANIMATED = 1u << 6, // STATUS_IS_ANIMATED
|
||||
FLAG_HAS_TRANSPARENCY = 1u << 7, // STATUS_HAS_TRANSPARENCY
|
||||
FLAG_LAST_PART_COMPLETE = 1u << 8,
|
||||
FLAG_HAS_ERROR = 1u << 9 // STATUS_ERROR
|
||||
};
|
||||
|
||||
typedef uint32_t Progress;
|
||||
|
||||
const uint32_t NoProgress = 0;
|
||||
|
||||
inline Progress LoadCompleteProgress(bool aLastPart,
|
||||
bool aError,
|
||||
nsresult aStatus)
|
||||
{
|
||||
Progress progress = FLAG_LOAD_COMPLETE;
|
||||
if (aLastPart) {
|
||||
progress |= FLAG_LAST_PART_COMPLETE;
|
||||
}
|
||||
if (NS_FAILED(aStatus) || aError) {
|
||||
progress |= FLAG_HAS_ERROR;
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProgressTracker stores its observers in an ObserverTable, which is a hash
|
||||
* table mapping raw pointers to WeakPtr's to the same objects. This sounds like
|
||||
* unnecessary duplication of information, but it's necessary for stable hash
|
||||
* values since WeakPtr's lose the knowledge of which object they used to point
|
||||
* to when that object is destroyed.
|
||||
*
|
||||
* ObserverTable subclasses nsDataHashtable to add reference counting support
|
||||
* and a copy constructor, both of which are needed for use with CopyOnWrite<T>.
|
||||
*/
|
||||
class ObserverTable
|
||||
: public nsDataHashtable<nsPtrHashKey<IProgressObserver>,
|
||||
WeakPtr<IProgressObserver>>
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(ObserverTable);
|
||||
|
||||
ObserverTable() = default;
|
||||
|
||||
ObserverTable(const ObserverTable& aOther)
|
||||
{
|
||||
NS_WARNING("Forced to copy ObserverTable due to nested notifications");
|
||||
for (auto iter = aOther.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
this->Put(iter.Key(), iter.Data());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
~ObserverTable() { }
|
||||
};
|
||||
|
||||
/**
|
||||
* ProgressTracker is a class that records an Image's progress through the
|
||||
* loading and decoding process, and makes it possible to send notifications to
|
||||
* IProgressObservers, both synchronously and asynchronously.
|
||||
*
|
||||
* When a new observer needs to be notified of the current progress of an image,
|
||||
* call the Notify() method on this class with the relevant observer as its
|
||||
* argument, and the notifications will be replayed to the observer
|
||||
* asynchronously.
|
||||
*/
|
||||
class ProgressTracker : public mozilla::SupportsWeakPtr<ProgressTracker>
|
||||
{
|
||||
virtual ~ProgressTracker() { }
|
||||
|
||||
public:
|
||||
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ProgressTracker)
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProgressTracker)
|
||||
|
||||
ProgressTracker()
|
||||
: mImageMutex("ProgressTracker::mImage")
|
||||
, mImage(nullptr)
|
||||
, mObservers(new ObserverTable)
|
||||
, mProgress(NoProgress)
|
||||
{ }
|
||||
|
||||
bool HasImage() const { MutexAutoLock lock(mImageMutex); return mImage; }
|
||||
already_AddRefed<Image> GetImage() const
|
||||
{
|
||||
MutexAutoLock lock(mImageMutex);
|
||||
RefPtr<Image> image = mImage;
|
||||
return image.forget();
|
||||
}
|
||||
|
||||
// Get the current image status (as in imgIRequest).
|
||||
uint32_t GetImageStatus() const;
|
||||
|
||||
// Get the current Progress.
|
||||
Progress GetProgress() const { return mProgress; }
|
||||
|
||||
// Schedule an asynchronous "replaying" of all the notifications that would
|
||||
// have to happen to put us in the current state.
|
||||
// We will also take note of any notifications that happen between the time
|
||||
// Notify() is called and when we call SyncNotify on |aObserver|, and replay
|
||||
// them as well.
|
||||
// Should be called on the main thread only, since observers and GetURI are
|
||||
// not threadsafe.
|
||||
void Notify(IProgressObserver* aObserver);
|
||||
|
||||
// Schedule an asynchronous "replaying" of all the notifications that would
|
||||
// have to happen to put us in the state we are in right now.
|
||||
// Unlike Notify(), does *not* take into account future notifications.
|
||||
// This is only useful if you do not have an imgRequest, e.g., if you are a
|
||||
// static request returned from imgIRequest::GetStaticRequest().
|
||||
// Should be called on the main thread only, since observers and GetURI are
|
||||
// not threadsafe.
|
||||
void NotifyCurrentState(IProgressObserver* aObserver);
|
||||
|
||||
// "Replay" all of the notifications that would have to happen to put us in
|
||||
// the state we're currently in.
|
||||
// Only use this if you're already servicing an asynchronous call (e.g.
|
||||
// OnStartRequest).
|
||||
// Should be called on the main thread only, since observers and GetURI are
|
||||
// not threadsafe.
|
||||
void SyncNotify(IProgressObserver* aObserver);
|
||||
|
||||
// Get this ProgressTracker ready for a new request. This resets all the
|
||||
// state that doesn't persist between requests.
|
||||
void ResetForNewRequest();
|
||||
|
||||
// Stateless notifications. These are dispatched and immediately forgotten
|
||||
// about. All of these notifications are main thread only.
|
||||
void OnDiscard();
|
||||
void OnUnlockedDraw();
|
||||
void OnImageAvailable();
|
||||
|
||||
// Compute the difference between this our progress and aProgress. This allows
|
||||
// callers to predict whether SyncNotifyProgress will send any notifications.
|
||||
Progress Difference(Progress aProgress) const
|
||||
{
|
||||
return ~mProgress & aProgress;
|
||||
}
|
||||
|
||||
// Update our state to incorporate the changes in aProgress and synchronously
|
||||
// notify our observers.
|
||||
//
|
||||
// Because this may result in recursive notifications, no decoding locks may
|
||||
// be held. Called on the main thread only.
|
||||
void SyncNotifyProgress(Progress aProgress,
|
||||
const nsIntRect& aInvalidRect = nsIntRect());
|
||||
|
||||
// We manage a set of observers that are using an image and thus concerned
|
||||
// with its loading progress. Weak pointers.
|
||||
void AddObserver(IProgressObserver* aObserver);
|
||||
bool RemoveObserver(IProgressObserver* aObserver);
|
||||
uint32_t ObserverCount() const;
|
||||
|
||||
// Resets our weak reference to our image. Image subclasses should call this
|
||||
// in their destructor.
|
||||
void ResetImage();
|
||||
|
||||
private:
|
||||
friend class AsyncNotifyRunnable;
|
||||
friend class AsyncNotifyCurrentStateRunnable;
|
||||
friend class ImageFactory;
|
||||
|
||||
ProgressTracker(const ProgressTracker& aOther) = delete;
|
||||
|
||||
// Sets our weak reference to our image. Only ImageFactory should call this.
|
||||
void SetImage(Image* aImage);
|
||||
|
||||
// Send some notifications that would be necessary to make |aObserver| believe
|
||||
// the request is finished downloading and decoding. We only send
|
||||
// FLAG_LOAD_COMPLETE and FLAG_ONLOAD_UNBLOCKED, and only if necessary.
|
||||
void EmulateRequestFinished(IProgressObserver* aObserver);
|
||||
|
||||
// Main thread only because it deals with the observer service.
|
||||
void FireFailureNotification();
|
||||
|
||||
// The runnable, if any, that we've scheduled to deliver async notifications.
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
|
||||
// mImage is a weak ref; it should be set to null when the image goes out of
|
||||
// scope. mImageMutex protects mImage.
|
||||
mutable Mutex mImageMutex;
|
||||
Image* mImage;
|
||||
|
||||
// Hashtable of observers attached to the image. Each observer represents a
|
||||
// consumer using the image. Main thread only.
|
||||
CopyOnWrite<ObserverTable> mObservers;
|
||||
|
||||
Progress mProgress;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ProgressTracker_h
|
||||
1724
image/RasterImage.cpp
Normal file
1724
image/RasterImage.cpp
Normal file
File diff suppressed because it is too large
Load diff
514
image/RasterImage.h
Normal file
514
image/RasterImage.h
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/** @file
|
||||
* This file declares the RasterImage class, which
|
||||
* handles static and animated rasterized images.
|
||||
*
|
||||
* @author Stuart Parmenter <pavlov@netscape.com>
|
||||
* @author Chris Saari <saari@netscape.com>
|
||||
* @author Arron Mogge <paper@animecity.nu>
|
||||
* @author Andrew Smith <asmith15@learn.senecac.on.ca>
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_RasterImage_h
|
||||
#define mozilla_image_RasterImage_h
|
||||
|
||||
#include "Image.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "nsIProperties.h"
|
||||
#include "nsTArray.h"
|
||||
#include "LookupResult.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "DecodePool.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "FrameAnimator.h"
|
||||
#include "ImageMetadata.h"
|
||||
#include "ISurfaceProvider.h"
|
||||
#include "Orientation.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/Pair.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "PlaybackType.h"
|
||||
#ifdef DEBUG
|
||||
#include "imgIContainerDebug.h"
|
||||
#endif
|
||||
|
||||
class nsIInputStream;
|
||||
class nsIRequest;
|
||||
|
||||
#define NS_RASTERIMAGE_CID \
|
||||
{ /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */ \
|
||||
0x376ff2c1, \
|
||||
0x9bf6, \
|
||||
0x418a, \
|
||||
{0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles static and animated image containers.
|
||||
*
|
||||
*
|
||||
* @par A Quick Walk Through
|
||||
* The decoder initializes this class and calls AppendFrame() to add a frame.
|
||||
* Once RasterImage detects more than one frame, it starts the animation
|
||||
* with StartAnimation(). Note that the invalidation events for RasterImage are
|
||||
* generated automatically using nsRefreshDriver.
|
||||
*
|
||||
* @par
|
||||
* StartAnimation() initializes the animation helper object and sets the time
|
||||
* the first frame was displayed to the current clock time.
|
||||
*
|
||||
* @par
|
||||
* When the refresh driver corresponding to the imgIContainer that this image is
|
||||
* a part of notifies the RasterImage that it's time to invalidate,
|
||||
* RequestRefresh() is called with a given TimeStamp to advance to. As long as
|
||||
* the timeout of the given frame (the frame's "delay") plus the time that frame
|
||||
* was first displayed is less than or equal to the TimeStamp given,
|
||||
* RequestRefresh() calls AdvanceFrame().
|
||||
*
|
||||
* @par
|
||||
* AdvanceFrame() is responsible for advancing a single frame of the animation.
|
||||
* It can return true, meaning that the frame advanced, or false, meaning that
|
||||
* the frame failed to advance (usually because the next frame hasn't been
|
||||
* decoded yet). It is also responsible for performing the final animation stop
|
||||
* procedure if the final frame of a non-looping animation is reached.
|
||||
*
|
||||
* @par
|
||||
* Each frame can have a different method of removing itself. These are
|
||||
* listed as imgIContainer::cDispose... constants. Notify() calls
|
||||
* DoComposite() to handle any special frame destruction.
|
||||
*
|
||||
* @par
|
||||
* The basic path through DoComposite() is:
|
||||
* 1) Calculate Area that needs updating, which is at least the area of
|
||||
* aNextFrame.
|
||||
* 2) Dispose of previous frame.
|
||||
* 3) Draw new image onto compositingFrame.
|
||||
* See comments in DoComposite() for more information and optimizations.
|
||||
*
|
||||
* @par
|
||||
* The rest of the RasterImage specific functions are used by DoComposite to
|
||||
* destroy the old frame and build the new one.
|
||||
*
|
||||
* @note
|
||||
* <li> "Mask", "Alpha", and "Alpha Level" are interchangeable phrases in
|
||||
* respects to RasterImage.
|
||||
*
|
||||
* @par
|
||||
* <li> GIFs never have more than a 1 bit alpha.
|
||||
* <li> APNGs may have a full alpha channel.
|
||||
*
|
||||
* @par
|
||||
* <li> Background color specified in GIF is ignored by web browsers.
|
||||
*
|
||||
* @par
|
||||
* <li> If Frame 3 wants to dispose by restoring previous, what it wants is to
|
||||
* restore the composition up to and including Frame 2, as well as Frame 2s
|
||||
* disposal. So, in the middle of DoComposite when composing Frame 3, right
|
||||
* after destroying Frame 2's area, we copy compositingFrame to
|
||||
* prevCompositingFrame. When DoComposite gets called to do Frame 4, we
|
||||
* copy prevCompositingFrame back, and then draw Frame 4 on top.
|
||||
*
|
||||
* @par
|
||||
* The mAnim structure has members only needed for animated images, so
|
||||
* it's not allocated until the second frame is added.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace layers {
|
||||
class ImageContainer;
|
||||
class Image;
|
||||
} // namespace layers
|
||||
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
struct DecoderFinalStatus;
|
||||
struct DecoderTelemetry;
|
||||
class ImageMetadata;
|
||||
class SourceBuffer;
|
||||
|
||||
class RasterImage final : public ImageResource
|
||||
, public nsIProperties
|
||||
, public SupportsWeakPtr<RasterImage>
|
||||
#ifdef DEBUG
|
||||
, public imgIContainerDebug
|
||||
#endif
|
||||
{
|
||||
// (no public constructor - use ImageFactory)
|
||||
virtual ~RasterImage();
|
||||
|
||||
public:
|
||||
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(RasterImage)
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIPROPERTIES
|
||||
NS_DECL_IMGICONTAINER
|
||||
#ifdef DEBUG
|
||||
NS_DECL_IMGICONTAINERDEBUG
|
||||
#endif
|
||||
|
||||
virtual nsresult StartAnimation() override;
|
||||
virtual nsresult StopAnimation() override;
|
||||
|
||||
// Methods inherited from Image
|
||||
virtual void OnSurfaceDiscarded() override;
|
||||
|
||||
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
|
||||
const override;
|
||||
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
/* Triggers discarding. */
|
||||
void Discard();
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Decoder callbacks.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Sends the provided progress notifications to ProgressTracker.
|
||||
*
|
||||
* Main-thread only.
|
||||
*
|
||||
* @param aProgress The progress notifications to send.
|
||||
* @param aInvalidRect An invalidation rect to send.
|
||||
* @param aFrameCount If Some(), an updated count of the number of frames of
|
||||
* animation the decoder has finished decoding so far. This
|
||||
* is a lower bound for the total number of animation
|
||||
* frames this image has.
|
||||
* @param aDecoderFlags The decoder flags used by the decoder that generated
|
||||
* these notifications, or DefaultDecoderFlags() if the
|
||||
* notifications don't come from a decoder.
|
||||
* @param aSurfaceFlags 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 gfx::IntRect& aInvalidRect = nsIntRect(),
|
||||
const Maybe<uint32_t>& aFrameCount = Nothing(),
|
||||
DecoderFlags aDecoderFlags = DefaultDecoderFlags(),
|
||||
SurfaceFlags aSurfaceFlags = DefaultSurfaceFlags());
|
||||
|
||||
/**
|
||||
* Records decoding results, sends out any final notifications, updates the
|
||||
* state of this image, and records telemetry.
|
||||
*
|
||||
* Main-thread only.
|
||||
*
|
||||
* @param aStatus Final status information about the decoder. (Whether it
|
||||
* encountered an error, etc.)
|
||||
* @param aMetadata Metadata about this image that the decoder gathered.
|
||||
* @param aTelemetry Telemetry data about the decoder.
|
||||
* @param aProgress Any final progress notifications to send.
|
||||
* @param aInvalidRect Any final invalidation rect to send.
|
||||
* @param aFrameCount If Some(), a final updated count of the number of frames
|
||||
* of animation the decoder has finished decoding so far.
|
||||
* This is a lower bound for the total number of animation
|
||||
* frames this image has.
|
||||
* @param aDecoderFlags The decoder flags used by the decoder.
|
||||
* @param aSurfaceFlags The surface flags used by the decoder.
|
||||
*/
|
||||
void NotifyDecodeComplete(const DecoderFinalStatus& aStatus,
|
||||
const ImageMetadata& aMetadata,
|
||||
const DecoderTelemetry& aTelemetry,
|
||||
Progress aProgress,
|
||||
const gfx::IntRect& aInvalidRect,
|
||||
const Maybe<uint32_t>& aFrameCount,
|
||||
DecoderFlags aDecoderFlags,
|
||||
SurfaceFlags aSurfaceFlags);
|
||||
|
||||
// Helper method for NotifyDecodeComplete.
|
||||
void ReportDecoderError();
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Network callbacks.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount) override;
|
||||
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus,
|
||||
bool aLastPart) override;
|
||||
|
||||
void NotifyForLoadEvent(Progress aProgress);
|
||||
|
||||
/**
|
||||
* A hint of the number of bytes of source data that the image contains. If
|
||||
* called early on, this can help reduce copying and reallocations by
|
||||
* appropriately preallocating the source data buffer.
|
||||
*
|
||||
* We take this approach rather than having the source data management code do
|
||||
* something more complicated (like chunklisting) because HTTP is by far the
|
||||
* dominant source of images, and the Content-Length header is quite reliable.
|
||||
* Thus, pre-allocation simplifies code and reduces the total number of
|
||||
* allocations.
|
||||
*/
|
||||
nsresult SetSourceSizeHint(uint32_t aSizeHint);
|
||||
|
||||
/* Provide a hint for the requested dimension of the resulting image. */
|
||||
void SetRequestedSampleSize(int requestedSampleSize) {
|
||||
mRequestedSampleSize = requestedSampleSize;
|
||||
}
|
||||
|
||||
nsCString GetURIString() {
|
||||
nsCString spec;
|
||||
if (GetURI()) {
|
||||
GetURI()->GetSpec(spec);
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
private:
|
||||
nsresult Init(const char* aMimeType, uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* Tries to retrieve a surface for this image with size @aSize, surface flags
|
||||
* matching @aFlags, and a playback type of @aPlaybackType.
|
||||
*
|
||||
* If @aFlags specifies FLAG_SYNC_DECODE and we already have all the image
|
||||
* data, we'll attempt a sync decode if no matching surface is found. If
|
||||
* FLAG_SYNC_DECODE was not specified and no matching surface was found, we'll
|
||||
* kick off an async decode so that the surface is (hopefully) available next
|
||||
* time it's requested.
|
||||
*
|
||||
* @return a drawable surface, which may be empty if the requested surface
|
||||
* could not be found.
|
||||
*/
|
||||
DrawableSurface LookupFrame(const gfx::IntSize& aSize,
|
||||
uint32_t aFlags,
|
||||
PlaybackType aPlaybackType);
|
||||
|
||||
/// Helper method for LookupFrame().
|
||||
LookupResult LookupFrameInternal(const gfx::IntSize& aSize,
|
||||
uint32_t aFlags,
|
||||
PlaybackType aPlaybackType);
|
||||
|
||||
DrawResult DrawInternal(DrawableSurface&& aFrameRef,
|
||||
gfxContext* aContext,
|
||||
const nsIntSize& aSize,
|
||||
const ImageRegion& aRegion,
|
||||
gfx::SamplingFilter aSamplingFilter,
|
||||
uint32_t aFlags);
|
||||
|
||||
Pair<DrawResult, RefPtr<gfx::SourceSurface>>
|
||||
GetFrameInternal(const gfx::IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags);
|
||||
|
||||
Pair<DrawResult, RefPtr<layers::Image>>
|
||||
GetCurrentImage(layers::ImageContainer* aContainer, uint32_t aFlags);
|
||||
|
||||
void UpdateImageContainer();
|
||||
|
||||
// We would like to just check if we have a zero lock count, but we can't do
|
||||
// that for animated images because in EnsureAnimExists we lock the image and
|
||||
// never unlock so that animated images always have their lock count >= 1. In
|
||||
// that case we use our animation consumers count as a proxy for lock count.
|
||||
bool IsUnlocked() {
|
||||
return (mLockCount == 0 || (mAnimationState && mAnimationConsumers == 0));
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Decoding.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Creates and runs a decoder, either synchronously or asynchronously
|
||||
* according to @aFlags. Decodes at the provided target size @aSize, using
|
||||
* decode flags @aFlags. Performs a single-frame decode of this image unless
|
||||
* we know the image is animated *and* @aPlaybackType is
|
||||
* PlaybackType::eAnimated.
|
||||
*
|
||||
* It's an error to call Decode() before this image's intrinsic size is
|
||||
* available. A metadata decode must successfully complete first.
|
||||
*/
|
||||
NS_IMETHOD Decode(const gfx::IntSize& aSize,
|
||||
uint32_t aFlags,
|
||||
PlaybackType aPlaybackType);
|
||||
|
||||
/**
|
||||
* Creates and runs a metadata decoder, either synchronously or
|
||||
* asynchronously according to @aFlags.
|
||||
*/
|
||||
NS_IMETHOD DecodeMetadata(uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* Sets the size, inherent orientation, animation metadata, and other
|
||||
* information about the image gathered during decoding.
|
||||
*
|
||||
* This function may be called multiple times, but will throw an error if
|
||||
* subsequent calls do not match the first.
|
||||
*
|
||||
* @param aMetadata The metadata to set on this image.
|
||||
* @param aFromMetadataDecode True if this metadata came from a metadata
|
||||
* decode; false if it came from a full decode.
|
||||
* @return |true| unless a catastrophic failure was discovered. If |false| is
|
||||
* returned, it indicates that the image is corrupt in a way that requires all
|
||||
* surfaces to be discarded to recover.
|
||||
*/
|
||||
bool SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode);
|
||||
|
||||
/**
|
||||
* 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 RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags);
|
||||
|
||||
private: // data
|
||||
nsIntSize mSize;
|
||||
Orientation mOrientation;
|
||||
|
||||
/// If this has a value, we're waiting for SetSize() to send the load event.
|
||||
Maybe<Progress> mLoadProgress;
|
||||
|
||||
nsCOMPtr<nsIProperties> mProperties;
|
||||
|
||||
/// If this image is animated, a FrameAnimator which manages its animation.
|
||||
UniquePtr<FrameAnimator> mFrameAnimator;
|
||||
|
||||
/// Animation timeline and other state for animation images.
|
||||
Maybe<AnimationState> mAnimationState;
|
||||
|
||||
// Image locking.
|
||||
uint32_t mLockCount;
|
||||
|
||||
// The type of decoder this image needs. Computed from the MIME type in Init().
|
||||
DecoderType mDecoderType;
|
||||
|
||||
// How many times we've decoded this image.
|
||||
// This is currently only used for statistics
|
||||
int32_t mDecodeCount;
|
||||
|
||||
// A hint for image decoder that directly scale the image to smaller buffer
|
||||
int mRequestedSampleSize;
|
||||
|
||||
// A weak pointer to our ImageContainer, which stays alive only as long as
|
||||
// the layer system needs it.
|
||||
WeakPtr<layers::ImageContainer> mImageContainer;
|
||||
|
||||
layers::ImageContainer::ProducerID mImageProducerID;
|
||||
layers::ImageContainer::FrameID mLastFrameID;
|
||||
|
||||
// If mImageContainer is non-null, this contains the DrawResult we obtained
|
||||
// the last time we updated it.
|
||||
DrawResult mLastImageContainerDrawResult;
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t mFramesNotified;
|
||||
#endif
|
||||
|
||||
// The source data for this image.
|
||||
NotNull<RefPtr<SourceBuffer>> mSourceBuffer;
|
||||
|
||||
// Boolean flags (clustered together to conserve space):
|
||||
bool mHasSize:1; // Has SetSize() been called?
|
||||
bool mTransient:1; // Is the image short-lived?
|
||||
bool mSyncLoad:1; // Are we loading synchronously?
|
||||
bool mDiscardable:1; // Is container discardable?
|
||||
bool mHasSourceData:1; // Do we have source data?
|
||||
bool mHasBeenDecoded:1; // Decoded at least once?
|
||||
|
||||
// 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
|
||||
// we know to start animation later if/when we have more frames.
|
||||
bool mPendingAnimation:1;
|
||||
|
||||
// Whether the animation can stop, due to running out
|
||||
// of frames, or no more owning request
|
||||
bool mAnimationFinished:1;
|
||||
|
||||
// Whether, once we are done doing a metadata decode, we should immediately
|
||||
// kick off a full decode.
|
||||
bool mWantFullDecode:1;
|
||||
|
||||
TimeStamp mDrawStartTime;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Scaling.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Determines whether we can downscale during decode with the given
|
||||
// parameters.
|
||||
bool CanDownscaleDuringDecode(const nsIntSize& aSize, uint32_t aFlags);
|
||||
|
||||
|
||||
// Error handling.
|
||||
void DoError();
|
||||
|
||||
class HandleErrorWorker : public Runnable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Called from decoder threads when DoError() is called, since errors can't
|
||||
* be handled safely off-main-thread. Dispatches an event which reinvokes
|
||||
* DoError on the main thread if there isn't one already pending.
|
||||
*/
|
||||
static void DispatchIfNeeded(RasterImage* aImage);
|
||||
|
||||
NS_IMETHOD Run();
|
||||
|
||||
private:
|
||||
explicit HandleErrorWorker(RasterImage* aImage);
|
||||
|
||||
RefPtr<RasterImage> mImage;
|
||||
};
|
||||
|
||||
// Helpers
|
||||
bool CanDiscard();
|
||||
|
||||
bool IsOpaque();
|
||||
|
||||
protected:
|
||||
explicit RasterImage(ImageURL* aURI = nullptr);
|
||||
|
||||
bool ShouldAnimate() override;
|
||||
|
||||
friend class ImageFactory;
|
||||
};
|
||||
|
||||
inline NS_IMETHODIMP
|
||||
RasterImage::GetAnimationMode(uint16_t* aAnimationMode) {
|
||||
return GetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
/**
|
||||
* Casting RasterImage to nsISupports is ambiguous. This method handles that.
|
||||
*/
|
||||
inline nsISupports*
|
||||
ToSupports(mozilla::image::RasterImage* p)
|
||||
{
|
||||
return NS_ISUPPORTS_CAST(mozilla::image::ImageResource*, p);
|
||||
}
|
||||
|
||||
#endif /* mozilla_image_RasterImage_h */
|
||||
460
image/SVGDocumentWrapper.cpp
Normal file
460
image/SVGDocumentWrapper.cpp
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
/* -*- 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 "SVGDocumentWrapper.h"
|
||||
|
||||
#include "mozilla/dom/DocumentTimeline.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsICategoryManager.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIContentViewer.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIDocumentLoaderFactory.h"
|
||||
#include "nsIDOMSVGLength.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIParser.h"
|
||||
#include "nsIPresShell.h"
|
||||
#include "nsIRequest.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIXMLContentSink.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsSMILAnimationController.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "mozilla/dom/SVGSVGElement.h"
|
||||
#include "nsSVGEffects.h"
|
||||
#include "mozilla/dom/SVGAnimatedLength.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "DOMSVGLength.h"
|
||||
#include "nsDocument.h"
|
||||
#include "mozilla/dom/ImageTracker.h"
|
||||
|
||||
// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
|
||||
#undef GetCurrentTime
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
using namespace gfx;
|
||||
|
||||
namespace image {
|
||||
|
||||
NS_IMPL_ISUPPORTS(SVGDocumentWrapper,
|
||||
nsIStreamListener,
|
||||
nsIRequestObserver,
|
||||
nsIObserver,
|
||||
nsISupportsWeakReference)
|
||||
|
||||
SVGDocumentWrapper::SVGDocumentWrapper()
|
||||
: mIgnoreInvalidation(false),
|
||||
mRegisteredForXPCOMShutdown(false)
|
||||
{ }
|
||||
|
||||
SVGDocumentWrapper::~SVGDocumentWrapper()
|
||||
{
|
||||
DestroyViewer();
|
||||
if (mRegisteredForXPCOMShutdown) {
|
||||
UnregisterForXPCOMShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::DestroyViewer()
|
||||
{
|
||||
if (mViewer) {
|
||||
mViewer->GetDocument()->OnPageHide(false, nullptr);
|
||||
mViewer->Close(nullptr);
|
||||
mViewer->Destroy();
|
||||
mViewer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsIFrame*
|
||||
SVGDocumentWrapper::GetRootLayoutFrame()
|
||||
{
|
||||
Element* rootElem = GetRootSVGElem();
|
||||
return rootElem ? rootElem->GetPrimaryFrame() : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize)
|
||||
{
|
||||
MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
|
||||
mIgnoreInvalidation = true;
|
||||
|
||||
nsIntRect currentBounds;
|
||||
mViewer->GetBounds(currentBounds);
|
||||
|
||||
// If the bounds have changed, we need to do a layout flush.
|
||||
if (currentBounds.Size() != aViewportSize) {
|
||||
mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize));
|
||||
FlushLayout();
|
||||
}
|
||||
|
||||
mIgnoreInvalidation = false;
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::FlushImageTransformInvalidation()
|
||||
{
|
||||
MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
|
||||
|
||||
SVGSVGElement* svgElem = GetRootSVGElem();
|
||||
if (!svgElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
mIgnoreInvalidation = true;
|
||||
svgElem->FlushImageTransformInvalidation();
|
||||
FlushLayout();
|
||||
mIgnoreInvalidation = false;
|
||||
}
|
||||
|
||||
bool
|
||||
SVGDocumentWrapper::IsAnimated()
|
||||
{
|
||||
// Can be called for animated images during shutdown, after we've
|
||||
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
|
||||
if (!mViewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsIDocument* doc = mViewer->GetDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
if (doc->Timeline()->HasAnimations()) {
|
||||
// CSS animations (technically HasAnimations() also checks for CSS
|
||||
// transitions and Web animations but since SVG-as-an-image doesn't run
|
||||
// script they will never run in the document that we wrap).
|
||||
return true;
|
||||
}
|
||||
if (doc->HasAnimationController() &&
|
||||
doc->GetAnimationController()->HasRegisteredAnimations()) {
|
||||
// SMIL animations
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::StartAnimation()
|
||||
{
|
||||
// Can be called for animated images during shutdown, after we've
|
||||
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
|
||||
if (!mViewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* doc = mViewer->GetDocument();
|
||||
if (doc) {
|
||||
nsSMILAnimationController* controller = doc->GetAnimationController();
|
||||
if (controller) {
|
||||
controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
|
||||
}
|
||||
doc->ImageTracker()->SetAnimatingState(true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::StopAnimation()
|
||||
{
|
||||
// Can be called for animated images during shutdown, after we've
|
||||
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
|
||||
if (!mViewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* doc = mViewer->GetDocument();
|
||||
if (doc) {
|
||||
nsSMILAnimationController* controller = doc->GetAnimationController();
|
||||
if (controller) {
|
||||
controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE);
|
||||
}
|
||||
doc->ImageTracker()->SetAnimatingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::ResetAnimation()
|
||||
{
|
||||
SVGSVGElement* svgElem = GetRootSVGElem();
|
||||
if (!svgElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
svgElem->SetCurrentTime(0.0f);
|
||||
}
|
||||
|
||||
float
|
||||
SVGDocumentWrapper::GetCurrentTime()
|
||||
{
|
||||
SVGSVGElement* svgElem = GetRootSVGElem();
|
||||
return svgElem ? svgElem->GetCurrentTime()
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::SetCurrentTime(float aTime)
|
||||
{
|
||||
SVGSVGElement* svgElem = GetRootSVGElem();
|
||||
if (svgElem && svgElem->GetCurrentTime() != aTime) {
|
||||
svgElem->SetCurrentTime(aTime);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::TickRefreshDriver()
|
||||
{
|
||||
nsCOMPtr<nsIPresShell> presShell;
|
||||
mViewer->GetPresShell(getter_AddRefs(presShell));
|
||||
if (presShell) {
|
||||
nsPresContext* presContext = presShell->GetPresContext();
|
||||
if (presContext) {
|
||||
presContext->RefreshDriver()->DoTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** nsIStreamListener methods **/
|
||||
|
||||
NS_IMETHODIMP
|
||||
SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt,
|
||||
nsIInputStream* inStr,
|
||||
uint64_t sourceOffset,
|
||||
uint32_t count)
|
||||
{
|
||||
return mListener->OnDataAvailable(aRequest, ctxt, inStr,
|
||||
sourceOffset, count);
|
||||
}
|
||||
|
||||
/** nsIRequestObserver methods **/
|
||||
|
||||
NS_IMETHODIMP
|
||||
SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
|
||||
{
|
||||
nsresult rv = SetupViewer(aRequest,
|
||||
getter_AddRefs(mViewer),
|
||||
getter_AddRefs(mLoadGroup));
|
||||
|
||||
if (NS_SUCCEEDED(rv) &&
|
||||
NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) {
|
||||
mViewer->GetDocument()->SetIsBeingUsedAsImage();
|
||||
StopAnimation(); // otherwise animations start automatically in helper doc
|
||||
|
||||
rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = mViewer->Open(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt,
|
||||
nsresult status)
|
||||
{
|
||||
if (mListener) {
|
||||
mListener->OnStopRequest(aRequest, ctxt, status);
|
||||
mListener = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/** nsIObserver Methods **/
|
||||
NS_IMETHODIMP
|
||||
SVGDocumentWrapper::Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
||||
// Sever ties from rendering observers to helper-doc's root SVG node
|
||||
SVGSVGElement* svgElem = GetRootSVGElem();
|
||||
if (svgElem) {
|
||||
nsSVGEffects::RemoveAllRenderingObservers(svgElem);
|
||||
}
|
||||
|
||||
// Clean up at XPCOM shutdown time.
|
||||
DestroyViewer();
|
||||
if (mListener) {
|
||||
mListener = nullptr;
|
||||
}
|
||||
if (mLoadGroup) {
|
||||
mLoadGroup = nullptr;
|
||||
}
|
||||
|
||||
// Turn off "registered" flag, or else we'll try to unregister when we die.
|
||||
// (No need for that now, and the try would fail anyway -- it's too late.)
|
||||
mRegisteredForXPCOMShutdown = false;
|
||||
} else {
|
||||
NS_ERROR("Unexpected observer topic.");
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/** Private helper methods **/
|
||||
|
||||
// This method is largely cribbed from
|
||||
// nsExternalResourceMap::PendingLoad::SetupViewer.
|
||||
nsresult
|
||||
SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest,
|
||||
nsIContentViewer** aViewer,
|
||||
nsILoadGroup** aLoadGroup)
|
||||
{
|
||||
nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
|
||||
NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
|
||||
|
||||
// Check for HTTP error page
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
|
||||
if (httpChannel) {
|
||||
bool requestSucceeded;
|
||||
if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
|
||||
!requestSucceeded) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Give this document its own loadgroup
|
||||
nsCOMPtr<nsILoadGroup> loadGroup;
|
||||
chan->GetLoadGroup(getter_AddRefs(loadGroup));
|
||||
|
||||
nsCOMPtr<nsILoadGroup> newLoadGroup =
|
||||
do_CreateInstance(NS_LOADGROUP_CONTRACTID);
|
||||
NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
|
||||
newLoadGroup->SetLoadGroup(loadGroup);
|
||||
|
||||
nsCOMPtr<nsICategoryManager> catMan =
|
||||
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
|
||||
NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
|
||||
nsXPIDLCString contractId;
|
||||
nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML,
|
||||
getter_Copies(contractId));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
|
||||
do_GetService(contractId);
|
||||
NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
nsCOMPtr<nsIContentViewer> viewer;
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
rv = docLoaderFactory->CreateInstance("external-resource", chan,
|
||||
newLoadGroup,
|
||||
NS_LITERAL_CSTRING(IMAGE_SVG_XML),
|
||||
nullptr, nullptr,
|
||||
getter_AddRefs(listener),
|
||||
getter_AddRefs(viewer));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
|
||||
|
||||
// Create a navigation time object and pass it to the SVG document through
|
||||
// the viewer.
|
||||
// The timeline(DocumentTimeline, used in CSS animation) of this SVG
|
||||
// document needs this navigation timing object for time computation, such
|
||||
// as to calculate current time stamp based on the start time of navigation
|
||||
// time object.
|
||||
//
|
||||
// For a root document, DocShell would do these sort of things
|
||||
// automatically. Since there is no DocShell for this wrapped SVG document,
|
||||
// we must set it up manually.
|
||||
RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming();
|
||||
timing->NotifyNavigationStart(nsDOMNavigationTiming::DocShellState::eInactive);
|
||||
viewer->SetNavigationTiming(timing);
|
||||
|
||||
nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
|
||||
NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
|
||||
|
||||
// XML-only, because this is for SVG content
|
||||
nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
|
||||
NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED);
|
||||
|
||||
listener.swap(mListener);
|
||||
viewer.forget(aViewer);
|
||||
newLoadGroup.forget(aLoadGroup);
|
||||
|
||||
RegisterForXPCOMShutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::RegisterForXPCOMShutdown()
|
||||
{
|
||||
MOZ_ASSERT(!mRegisteredForXPCOMShutdown,
|
||||
"re-registering for XPCOM shutdown");
|
||||
// Listen for xpcom-shutdown so that we can drop references to our
|
||||
// helper-document at that point. (Otherwise, we won't get cleaned up
|
||||
// until imgLoader::Shutdown, which can happen after the JAR service
|
||||
// and RDF service have been unregistered.)
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
|
||||
if (NS_FAILED(rv) ||
|
||||
NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
|
||||
true))) {
|
||||
NS_WARNING("Failed to register as observer of XPCOM shutdown");
|
||||
} else {
|
||||
mRegisteredForXPCOMShutdown = true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::UnregisterForXPCOMShutdown()
|
||||
{
|
||||
MOZ_ASSERT(mRegisteredForXPCOMShutdown,
|
||||
"unregistering for XPCOM shutdown w/out being registered");
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
|
||||
if (NS_FAILED(rv) ||
|
||||
NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
|
||||
NS_WARNING("Failed to unregister as observer of XPCOM shutdown");
|
||||
} else {
|
||||
mRegisteredForXPCOMShutdown = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SVGDocumentWrapper::FlushLayout()
|
||||
{
|
||||
nsCOMPtr<nsIPresShell> presShell;
|
||||
mViewer->GetPresShell(getter_AddRefs(presShell));
|
||||
if (presShell) {
|
||||
presShell->FlushPendingNotifications(Flush_Layout);
|
||||
}
|
||||
}
|
||||
|
||||
nsIDocument*
|
||||
SVGDocumentWrapper::GetDocument()
|
||||
{
|
||||
if (!mViewer) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mViewer->GetDocument(); // May be nullptr.
|
||||
}
|
||||
|
||||
SVGSVGElement*
|
||||
SVGDocumentWrapper::GetRootSVGElem()
|
||||
{
|
||||
if (!mViewer) {
|
||||
return nullptr; // Can happen during destruction
|
||||
}
|
||||
|
||||
nsIDocument* doc = mViewer->GetDocument();
|
||||
if (!doc) {
|
||||
return nullptr; // Can happen during destruction
|
||||
}
|
||||
|
||||
Element* rootElem = mViewer->GetDocument()->GetRootElement();
|
||||
if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<SVGSVGElement*>(rootElem);
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
151
image/SVGDocumentWrapper.h
Normal file
151
image/SVGDocumentWrapper.h
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/* This class wraps an SVG document, for use by VectorImage objects. */
|
||||
|
||||
#ifndef mozilla_image_SVGDocumentWrapper_h
|
||||
#define mozilla_image_SVGDocumentWrapper_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIContentViewer.h"
|
||||
#include "nsWeakReference.h"
|
||||
#include "nsSize.h"
|
||||
|
||||
class nsIPresShell;
|
||||
class nsIRequest;
|
||||
class nsILoadGroup;
|
||||
class nsIFrame;
|
||||
|
||||
#define OBSERVER_SVC_CID "@mozilla.org/observer-service;1"
|
||||
|
||||
// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
|
||||
#undef GetCurrentTime
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class SVGSVGElement;
|
||||
} // namespace dom
|
||||
|
||||
namespace image {
|
||||
|
||||
class SVGDocumentWrapper final : public nsIStreamListener,
|
||||
public nsIObserver,
|
||||
nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
SVGDocumentWrapper();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
enum Dimension {
|
||||
eWidth,
|
||||
eHeight
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the wrapped document, or nullptr on failure. (No AddRef.)
|
||||
*/
|
||||
nsIDocument* GetDocument();
|
||||
|
||||
/**
|
||||
* Returns the root <svg> element for the wrapped document, or nullptr on
|
||||
* failure.
|
||||
*/
|
||||
mozilla::dom::SVGSVGElement* GetRootSVGElem();
|
||||
|
||||
/**
|
||||
* Returns the root nsIFrame* for the wrapped document, or nullptr on failure.
|
||||
*
|
||||
* @return the root nsIFrame* for the wrapped document, or nullptr on failure.
|
||||
*/
|
||||
nsIFrame* GetRootLayoutFrame();
|
||||
|
||||
/**
|
||||
* Returns (by reference) the nsIPresShell for the wrapped document.
|
||||
*
|
||||
* @param[out] aPresShell On success, this will be populated with a pointer
|
||||
* to the wrapped document's nsIPresShell.
|
||||
*
|
||||
* @return NS_OK on success, or an error code on failure.
|
||||
*/
|
||||
inline nsresult GetPresShell(nsIPresShell** aPresShell)
|
||||
{ return mViewer->GetPresShell(aPresShell); }
|
||||
|
||||
/**
|
||||
* Modifier to update the viewport dimensions of the wrapped document. This
|
||||
* method performs a synchronous "Flush_Layout" on the wrapped document,
|
||||
* since a viewport-change affects layout.
|
||||
*
|
||||
* @param aViewportSize The new viewport dimensions.
|
||||
*/
|
||||
void UpdateViewportBounds(const nsIntSize& aViewportSize);
|
||||
|
||||
/**
|
||||
* If an SVG image's helper document has a pending notification for an
|
||||
* override on the root node's "preserveAspectRatio" attribute, then this
|
||||
* method will flush that notification so that the image can paint correctly.
|
||||
* (First, though, it sets the mIgnoreInvalidation flag so that we won't
|
||||
* notify the image's observers and trigger unwanted repaint-requests.)
|
||||
*/
|
||||
void FlushImageTransformInvalidation();
|
||||
|
||||
/**
|
||||
* Returns a bool indicating whether the document has any SMIL animations.
|
||||
*
|
||||
* @return true if the document has any SMIL animations. Else, false.
|
||||
*/
|
||||
bool IsAnimated();
|
||||
|
||||
/**
|
||||
* Indicates whether we should currently ignore rendering invalidations sent
|
||||
* from the wrapped SVG doc.
|
||||
*
|
||||
* @return true if we should ignore invalidations sent from this SVG doc.
|
||||
*/
|
||||
bool ShouldIgnoreInvalidation() { return mIgnoreInvalidation; }
|
||||
|
||||
/**
|
||||
* Methods to control animation.
|
||||
*/
|
||||
void StartAnimation();
|
||||
void StopAnimation();
|
||||
void ResetAnimation();
|
||||
float GetCurrentTime();
|
||||
void SetCurrentTime(float aTime);
|
||||
void TickRefreshDriver();
|
||||
|
||||
/**
|
||||
* Force a layout flush of the underlying SVG document.
|
||||
*/
|
||||
void FlushLayout();
|
||||
|
||||
private:
|
||||
~SVGDocumentWrapper();
|
||||
|
||||
nsresult SetupViewer(nsIRequest* aRequest,
|
||||
nsIContentViewer** aViewer,
|
||||
nsILoadGroup** aLoadGroup);
|
||||
void DestroyViewer();
|
||||
void RegisterForXPCOMShutdown();
|
||||
void UnregisterForXPCOMShutdown();
|
||||
|
||||
nsCOMPtr<nsIContentViewer> mViewer;
|
||||
nsCOMPtr<nsILoadGroup> mLoadGroup;
|
||||
nsCOMPtr<nsIStreamListener> mListener;
|
||||
bool mIgnoreInvalidation;
|
||||
bool mRegisteredForXPCOMShutdown;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SVGDocumentWrapper_h
|
||||
62
image/ScriptedNotificationObserver.cpp
Normal file
62
image/ScriptedNotificationObserver.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- 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 "ScriptedNotificationObserver.h"
|
||||
#include "imgIScriptedNotificationObserver.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(ScriptedNotificationObserver, mInner)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptedNotificationObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptedNotificationObserver)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptedNotificationObserver)
|
||||
|
||||
ScriptedNotificationObserver::ScriptedNotificationObserver(
|
||||
imgIScriptedNotificationObserver* aInner)
|
||||
: mInner(aInner)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptedNotificationObserver::Notify(imgIRequest* aRequest,
|
||||
int32_t aType,
|
||||
const nsIntRect* /*aUnused*/)
|
||||
{
|
||||
if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
|
||||
return mInner->SizeAvailable(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::FRAME_UPDATE) {
|
||||
return mInner->FrameUpdate(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::FRAME_COMPLETE) {
|
||||
return mInner->FrameComplete(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::DECODE_COMPLETE) {
|
||||
return mInner->DecodeComplete(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::LOAD_COMPLETE) {
|
||||
return mInner->LoadComplete(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::DISCARD) {
|
||||
return mInner->Discard(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::IS_ANIMATED) {
|
||||
return mInner->IsAnimated(aRequest);
|
||||
}
|
||||
if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
|
||||
return mInner->HasTransparency(aRequest);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
37
image/ScriptedNotificationObserver.h
Normal file
37
image/ScriptedNotificationObserver.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* -*- 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_ScriptedNotificationObserver_h
|
||||
#define mozilla_image_ScriptedNotificationObserver_h
|
||||
|
||||
#include "imgINotificationObserver.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
class imgIScriptedNotificationObserver;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class ScriptedNotificationObserver : public imgINotificationObserver
|
||||
{
|
||||
public:
|
||||
explicit
|
||||
ScriptedNotificationObserver(imgIScriptedNotificationObserver* aInner);
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_IMGINOTIFICATIONOBSERVER
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(ScriptedNotificationObserver)
|
||||
|
||||
private:
|
||||
virtual ~ScriptedNotificationObserver() { }
|
||||
nsCOMPtr<imgIScriptedNotificationObserver> mInner;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ScriptedNotificationObserver_h
|
||||
75
image/ShutdownTracker.cpp
Normal file
75
image/ShutdownTracker.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* -*- 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 "ShutdownTracker.h"
|
||||
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class ShutdownTrackerImpl;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Static Data
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Whether we've observed shutdown starting yet.
|
||||
static bool sShutdownHasStarted = false;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ShutdownObserver : public nsIObserver
|
||||
{
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) override
|
||||
{
|
||||
if (strcmp(aTopic, "xpcom-will-shutdown") != 0) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os) {
|
||||
os->RemoveObserver(this, "xpcom-will-shutdown");
|
||||
}
|
||||
|
||||
sShutdownHasStarted = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ShutdownObserver() { }
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Public API
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* static */ void
|
||||
ShutdownTracker::Initialize()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os) {
|
||||
os->AddObserver(new ShutdownObserver, "xpcom-will-shutdown", false);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
ShutdownTracker::ShutdownHasStarted()
|
||||
{
|
||||
return sShutdownHasStarted;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
46
image/ShutdownTracker.h
Normal file
46
image/ShutdownTracker.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* ShutdownTracker is an imagelib-global service that allows callers to check
|
||||
* whether shutdown has started.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_ShutdownTracker_h
|
||||
#define mozilla_image_ShutdownTracker_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* ShutdownTracker is an imagelib-global service that allows callers to check
|
||||
* whether shutdown has started. It exists to avoid the need for registering
|
||||
* many 'xpcom-will-shutdown' notification observers on short-lived objects,
|
||||
* which would have an unnecessary performance cost.
|
||||
*/
|
||||
struct ShutdownTracker
|
||||
{
|
||||
/**
|
||||
* Initialize static data. Called during imagelib module initialization.
|
||||
*/
|
||||
static void Initialize();
|
||||
|
||||
/**
|
||||
* Check whether shutdown has started. Callers can use this to check whether
|
||||
* it's safe to access XPCOM services; if shutdown has started, such calls
|
||||
* must be avoided.
|
||||
*
|
||||
* @return true if shutdown has already started.
|
||||
*/
|
||||
static bool ShutdownHasStarted();
|
||||
|
||||
private:
|
||||
virtual ~ShutdownTracker() = 0; // Forbid instantiation.
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_ShutdownTracker_h
|
||||
682
image/SourceBuffer.cpp
Normal file
682
image/SourceBuffer.cpp
Normal file
|
|
@ -0,0 +1,682 @@
|
|||
/* -*- 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 "SourceBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include "mozilla/Likely.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
using std::max;
|
||||
using std::min;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SourceBufferIterator implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SourceBufferIterator::~SourceBufferIterator()
|
||||
{
|
||||
if (mOwner) {
|
||||
mOwner->OnIteratorRelease();
|
||||
}
|
||||
}
|
||||
|
||||
SourceBufferIterator&
|
||||
SourceBufferIterator::operator=(SourceBufferIterator&& aOther)
|
||||
{
|
||||
if (mOwner) {
|
||||
mOwner->OnIteratorRelease();
|
||||
}
|
||||
|
||||
mOwner = Move(aOther.mOwner);
|
||||
mState = aOther.mState;
|
||||
mData = aOther.mData;
|
||||
mChunkCount = aOther.mChunkCount;
|
||||
mByteCount = aOther.mByteCount;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
SourceBufferIterator::State
|
||||
SourceBufferIterator::AdvanceOrScheduleResume(size_t aRequestedBytes,
|
||||
IResumable* aConsumer)
|
||||
{
|
||||
MOZ_ASSERT(mOwner);
|
||||
|
||||
if (MOZ_UNLIKELY(!HasMore())) {
|
||||
MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");
|
||||
return COMPLETE;
|
||||
}
|
||||
|
||||
// The range of data [mOffset, mOffset + mNextReadLength) has just been read
|
||||
// by the caller (or at least they don't have any interest in it), so consume
|
||||
// that data.
|
||||
MOZ_ASSERT(mData.mIterating.mNextReadLength <= mData.mIterating.mAvailableLength);
|
||||
mData.mIterating.mOffset += mData.mIterating.mNextReadLength;
|
||||
mData.mIterating.mAvailableLength -= mData.mIterating.mNextReadLength;
|
||||
mData.mIterating.mNextReadLength = 0;
|
||||
|
||||
if (MOZ_LIKELY(mState == READY)) {
|
||||
// If the caller wants zero bytes of data, that's easy enough; we just
|
||||
// configured ourselves for a zero-byte read above! In theory we could do
|
||||
// this even in the START state, but it's not important for performance and
|
||||
// breaking the ability of callers to assert that the pointer returned by
|
||||
// Data() is non-null doesn't seem worth it.
|
||||
if (aRequestedBytes == 0) {
|
||||
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0);
|
||||
return READY;
|
||||
}
|
||||
|
||||
// Try to satisfy the request out of our local buffer. This is potentially
|
||||
// much faster than requesting data from our owning SourceBuffer because we
|
||||
// don't have to take the lock. Note that if we have anything at all in our
|
||||
// local buffer, we use it to satisfy the request; @aRequestedBytes is just
|
||||
// the *maximum* number of bytes we can return.
|
||||
if (mData.mIterating.mAvailableLength > 0) {
|
||||
return AdvanceFromLocalBuffer(aRequestedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Our local buffer is empty, so we'll have to request data from our owning
|
||||
// SourceBuffer.
|
||||
return mOwner->AdvanceIteratorOrScheduleResume(*this,
|
||||
aRequestedBytes,
|
||||
aConsumer);
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes) const
|
||||
{
|
||||
MOZ_ASSERT(mOwner);
|
||||
return mOwner->RemainingBytesIsNoMoreThan(*this, aBytes);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SourceBuffer implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const size_t SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
|
||||
SourceBuffer::SourceBuffer()
|
||||
: mMutex("image::SourceBuffer")
|
||||
, mConsumerCount(0)
|
||||
{ }
|
||||
|
||||
SourceBuffer::~SourceBuffer()
|
||||
{
|
||||
MOZ_ASSERT(mConsumerCount == 0,
|
||||
"SourceBuffer destroyed with active consumers");
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBuffer::AppendChunk(Maybe<Chunk>&& aChunk)
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
#ifdef DEBUG
|
||||
if (mChunks.Length() > 0) {
|
||||
NS_WARNING("Appending an extra chunk for SourceBuffer");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (MOZ_UNLIKELY(!aChunk)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(aChunk->AllocationFailed())) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(!mChunks.AppendElement(Move(*aChunk), fallible))) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<SourceBuffer::Chunk>
|
||||
SourceBuffer::CreateChunk(size_t aCapacity, bool aRoundUp /* = true */)
|
||||
{
|
||||
if (MOZ_UNLIKELY(aCapacity == 0)) {
|
||||
MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?");
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// Round up if requested.
|
||||
size_t finalCapacity = aRoundUp ? RoundedUpCapacity(aCapacity)
|
||||
: aCapacity;
|
||||
|
||||
// Use the size of the SurfaceCache as an additional heuristic to avoid
|
||||
// allocating huge buffers. Generally images do not get smaller when decoded,
|
||||
// so if we could store the source data in the SurfaceCache, we assume that
|
||||
// there's no way we'll be able to store the decoded version.
|
||||
if (MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity))) {
|
||||
NS_WARNING("SourceBuffer refused to create chunk too large for SurfaceCache");
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(Chunk(finalCapacity));
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBuffer::Compact()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
MOZ_ASSERT(mConsumerCount == 0, "Should have no consumers here");
|
||||
MOZ_ASSERT(mWaitingConsumers.Length() == 0, "Shouldn't have waiters");
|
||||
MOZ_ASSERT(mStatus, "Should be complete here");
|
||||
|
||||
// Compact our waiting consumers list, since we're complete and no future
|
||||
// consumer will ever have to wait.
|
||||
mWaitingConsumers.Compact();
|
||||
|
||||
// If we have no chunks, then there's nothing to compact.
|
||||
if (mChunks.Length() < 1) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If we have one chunk, then we can compact if it has excess capacity.
|
||||
if (mChunks.Length() == 1 && mChunks[0].Length() == mChunks[0].Capacity()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// We can compact our buffer. Determine the total length.
|
||||
size_t length = 0;
|
||||
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
|
||||
length += mChunks[i].Length();
|
||||
}
|
||||
|
||||
// If our total length is zero (which means ExpectLength() got called, but no
|
||||
// data ever actually got written) then just empty our chunk list.
|
||||
if (MOZ_UNLIKELY(length == 0)) {
|
||||
mChunks.Clear();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<Chunk> newChunk = CreateChunk(length, /* aRoundUp = */ false);
|
||||
if (MOZ_UNLIKELY(!newChunk || newChunk->AllocationFailed())) {
|
||||
NS_WARNING("Failed to allocate chunk for SourceBuffer compacting - OOM?");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Copy our old chunks into the new chunk.
|
||||
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
|
||||
size_t offset = newChunk->Length();
|
||||
MOZ_ASSERT(offset < newChunk->Capacity());
|
||||
MOZ_ASSERT(offset + mChunks[i].Length() <= newChunk->Capacity());
|
||||
|
||||
memcpy(newChunk->Data() + offset, mChunks[i].Data(), mChunks[i].Length());
|
||||
newChunk->AddLength(mChunks[i].Length());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(newChunk->Length() == newChunk->Capacity(),
|
||||
"Compacted chunk has slack space");
|
||||
|
||||
// Replace the old chunks with the new, compact chunk.
|
||||
mChunks.Clear();
|
||||
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(newChunk))))) {
|
||||
return HandleError(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
mChunks.Compact();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ size_t
|
||||
SourceBuffer::RoundedUpCapacity(size_t aCapacity)
|
||||
{
|
||||
// Protect against overflow.
|
||||
if (MOZ_UNLIKELY(SIZE_MAX - aCapacity < MIN_CHUNK_CAPACITY)) {
|
||||
return aCapacity;
|
||||
}
|
||||
|
||||
// Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the
|
||||
// size of a page).
|
||||
size_t roundedCapacity =
|
||||
(aCapacity + MIN_CHUNK_CAPACITY - 1) & ~(MIN_CHUNK_CAPACITY - 1);
|
||||
MOZ_ASSERT(roundedCapacity >= aCapacity, "Bad math?");
|
||||
MOZ_ASSERT(roundedCapacity - aCapacity < MIN_CHUNK_CAPACITY, "Bad math?");
|
||||
|
||||
return roundedCapacity;
|
||||
}
|
||||
|
||||
size_t
|
||||
SourceBuffer::FibonacciCapacityWithMinimum(size_t aMinCapacity)
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
// We grow the source buffer using a Fibonacci growth rate.
|
||||
|
||||
size_t length = mChunks.Length();
|
||||
|
||||
if (length == 0) {
|
||||
return aMinCapacity;
|
||||
}
|
||||
|
||||
if (length == 1) {
|
||||
return max(2 * mChunks[0].Capacity(), aMinCapacity);
|
||||
}
|
||||
|
||||
return max(mChunks[length - 1].Capacity() + mChunks[length - 2].Capacity(),
|
||||
aMinCapacity);
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
MOZ_ASSERT(!mStatus, "Waiting when we're complete?");
|
||||
|
||||
if (aConsumer) {
|
||||
mWaitingConsumers.AppendElement(aConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::ResumeWaitingConsumers()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
if (mWaitingConsumers.Length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) {
|
||||
mWaitingConsumers[i]->Resume();
|
||||
}
|
||||
|
||||
mWaitingConsumers.Clear();
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBuffer::ExpectLength(size_t aExpectedLength)
|
||||
{
|
||||
MOZ_ASSERT(aExpectedLength > 0, "Zero expected size?");
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (MOZ_UNLIKELY(mStatus)) {
|
||||
MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(mChunks.Length() > 0)) {
|
||||
MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))) {
|
||||
return HandleError(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBuffer::Append(const char* aData, size_t aLength)
|
||||
{
|
||||
MOZ_ASSERT(aData, "Should have a buffer");
|
||||
MOZ_ASSERT(aLength > 0, "Writing a zero-sized chunk");
|
||||
|
||||
size_t currentChunkCapacity = 0;
|
||||
size_t currentChunkLength = 0;
|
||||
char* currentChunkData = nullptr;
|
||||
size_t currentChunkRemaining = 0;
|
||||
size_t forCurrentChunk = 0;
|
||||
size_t forNextChunk = 0;
|
||||
size_t nextChunkCapacity = 0;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (MOZ_UNLIKELY(mStatus)) {
|
||||
// This SourceBuffer is already complete; ignore further data.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(mChunks.Length() == 0)) {
|
||||
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))) {
|
||||
return HandleError(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy out the current chunk's information so we can release the lock.
|
||||
// Note that this wouldn't be safe if multiple producers were allowed!
|
||||
Chunk& currentChunk = mChunks.LastElement();
|
||||
currentChunkCapacity = currentChunk.Capacity();
|
||||
currentChunkLength = currentChunk.Length();
|
||||
currentChunkData = currentChunk.Data();
|
||||
|
||||
// Partition this data between the current chunk and the next chunk.
|
||||
// (Because we always allocate a chunk big enough to fit everything passed
|
||||
// to Append, we'll never need more than those two chunks to store
|
||||
// everything.)
|
||||
currentChunkRemaining = currentChunkCapacity - currentChunkLength;
|
||||
forCurrentChunk = min(aLength, currentChunkRemaining);
|
||||
forNextChunk = aLength - forCurrentChunk;
|
||||
|
||||
// If we'll need another chunk, determine what its capacity should be while
|
||||
// we still hold the lock.
|
||||
nextChunkCapacity = forNextChunk > 0
|
||||
? FibonacciCapacityWithMinimum(forNextChunk)
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Write everything we can fit into the current chunk.
|
||||
MOZ_ASSERT(currentChunkLength + forCurrentChunk <= currentChunkCapacity);
|
||||
memcpy(currentChunkData + currentChunkLength, aData, forCurrentChunk);
|
||||
|
||||
// If there's something left, create a new chunk and write it there.
|
||||
Maybe<Chunk> nextChunk;
|
||||
if (forNextChunk > 0) {
|
||||
MOZ_ASSERT(nextChunkCapacity >= forNextChunk, "Next chunk too small?");
|
||||
nextChunk = CreateChunk(nextChunkCapacity);
|
||||
if (MOZ_LIKELY(nextChunk && !nextChunk->AllocationFailed())) {
|
||||
memcpy(nextChunk->Data(), aData + forCurrentChunk, forNextChunk);
|
||||
nextChunk->AddLength(forNextChunk);
|
||||
}
|
||||
}
|
||||
|
||||
// Update shared data structures.
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// Update the length of the current chunk.
|
||||
Chunk& currentChunk = mChunks.LastElement();
|
||||
MOZ_ASSERT(currentChunk.Data() == currentChunkData, "Multiple producers?");
|
||||
MOZ_ASSERT(currentChunk.Length() == currentChunkLength,
|
||||
"Multiple producers?");
|
||||
|
||||
currentChunk.AddLength(forCurrentChunk);
|
||||
|
||||
// If we created a new chunk, add it to the series.
|
||||
if (forNextChunk > 0) {
|
||||
if (MOZ_UNLIKELY(!nextChunk)) {
|
||||
return HandleError(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(nextChunk))))) {
|
||||
return HandleError(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume any waiting readers now that there's new data.
|
||||
ResumeWaitingConsumers();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
AppendToSourceBuffer(nsIInputStream*,
|
||||
void* aClosure,
|
||||
const char* aFromRawSegment,
|
||||
uint32_t,
|
||||
uint32_t aCount,
|
||||
uint32_t* aWriteCount)
|
||||
{
|
||||
SourceBuffer* sourceBuffer = static_cast<SourceBuffer*>(aClosure);
|
||||
|
||||
// Copy the source data. Unless we hit OOM, we squelch the return value here,
|
||||
// because returning an error means that ReadSegments stops reading data, and
|
||||
// we want to ensure that we read everything we get. If we hit OOM then we
|
||||
// return a failed status to the caller.
|
||||
nsresult rv = sourceBuffer->Append(aFromRawSegment, aCount);
|
||||
if (rv == NS_ERROR_OUT_OF_MEMORY) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Report that we wrote everything we got.
|
||||
*aWriteCount = aCount;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBuffer::AppendFromInputStream(nsIInputStream* aInputStream,
|
||||
uint32_t aCount)
|
||||
{
|
||||
uint32_t bytesRead;
|
||||
nsresult rv = aInputStream->ReadSegments(AppendToSourceBuffer, this,
|
||||
aCount, &bytesRead);
|
||||
if (!NS_WARN_IF(NS_FAILED(rv))) {
|
||||
MOZ_ASSERT(bytesRead == aCount,
|
||||
"AppendToSourceBuffer should consume everything");
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::Complete(nsresult aStatus)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (MOZ_UNLIKELY(mStatus)) {
|
||||
MOZ_ASSERT_UNREACHABLE("Called Complete more than once");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus) && IsEmpty())) {
|
||||
// It's illegal to succeed without writing anything.
|
||||
aStatus = NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mStatus = Some(aStatus);
|
||||
|
||||
// Resume any waiting consumers now that we're complete.
|
||||
ResumeWaitingConsumers();
|
||||
|
||||
// If we still have active consumers, just return.
|
||||
if (mConsumerCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to compact our buffer down to a single chunk.
|
||||
Compact();
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBuffer::IsComplete()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
return bool(mStatus);
|
||||
}
|
||||
|
||||
size_t
|
||||
SourceBuffer::SizeOfIncludingThisWithComputedFallback(MallocSizeOf
|
||||
aMallocSizeOf) const
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
size_t n = aMallocSizeOf(this);
|
||||
n += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
|
||||
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
|
||||
size_t chunkSize = aMallocSizeOf(mChunks[i].Data());
|
||||
|
||||
if (chunkSize == 0) {
|
||||
// We're on a platform where moz_malloc_size_of always returns 0.
|
||||
chunkSize = mChunks[i].Capacity();
|
||||
}
|
||||
|
||||
n += chunkSize;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
SourceBufferIterator
|
||||
SourceBuffer::Iterator()
|
||||
{
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mConsumerCount++;
|
||||
}
|
||||
|
||||
return SourceBufferIterator(this);
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::OnIteratorRelease()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(mConsumerCount > 0, "Consumer count doesn't add up");
|
||||
mConsumerCount--;
|
||||
|
||||
// If we still have active consumers, or we're not complete yet, then return.
|
||||
if (mConsumerCount > 0 || !mStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to compact our buffer down to a single chunk.
|
||||
Compact();
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
|
||||
size_t aBytes) const
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// If we're not complete, we always say no.
|
||||
if (!mStatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the iterator's at the end, the answer is trivial.
|
||||
if (!aIterator.HasMore()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t iteratorChunk = aIterator.mData.mIterating.mChunk;
|
||||
size_t iteratorOffset = aIterator.mData.mIterating.mOffset;
|
||||
size_t iteratorLength = aIterator.mData.mIterating.mAvailableLength;
|
||||
|
||||
// Include the bytes the iterator is currently pointing to in the limit, so
|
||||
// that the current chunk doesn't have to be a special case.
|
||||
size_t bytes = aBytes + iteratorOffset + iteratorLength;
|
||||
|
||||
// Count the length over all of our chunks, starting with the one that the
|
||||
// iterator is currently pointing to. (This is O(N), but N is expected to be
|
||||
// ~1, so it doesn't seem worth caching the length separately.)
|
||||
size_t lengthSoFar = 0;
|
||||
for (uint32_t i = iteratorChunk ; i < mChunks.Length() ; ++i) {
|
||||
lengthSoFar += mChunks[i].Length();
|
||||
if (lengthSoFar > bytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SourceBufferIterator::State
|
||||
SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
|
||||
size_t aRequestedBytes,
|
||||
IResumable* aConsumer)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(aIterator.HasMore(), "Advancing a completed iterator and "
|
||||
"AdvanceOrScheduleResume didn't catch it");
|
||||
|
||||
if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) {
|
||||
// This SourceBuffer is complete due to an error; all reads fail.
|
||||
return aIterator.SetComplete(*mStatus);
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(mChunks.Length() == 0)) {
|
||||
// We haven't gotten an initial chunk yet.
|
||||
AddWaitingConsumer(aConsumer);
|
||||
return aIterator.SetWaiting();
|
||||
}
|
||||
|
||||
uint32_t iteratorChunkIdx = aIterator.mData.mIterating.mChunk;
|
||||
MOZ_ASSERT(iteratorChunkIdx < mChunks.Length());
|
||||
|
||||
const Chunk& currentChunk = mChunks[iteratorChunkIdx];
|
||||
size_t iteratorEnd = aIterator.mData.mIterating.mOffset +
|
||||
aIterator.mData.mIterating.mAvailableLength;
|
||||
MOZ_ASSERT(iteratorEnd <= currentChunk.Length());
|
||||
MOZ_ASSERT(iteratorEnd <= currentChunk.Capacity());
|
||||
|
||||
if (iteratorEnd < currentChunk.Length()) {
|
||||
// There's more data in the current chunk.
|
||||
return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(),
|
||||
iteratorEnd, currentChunk.Length() - iteratorEnd,
|
||||
aRequestedBytes);
|
||||
}
|
||||
|
||||
if (iteratorEnd == currentChunk.Capacity() &&
|
||||
!IsLastChunk(iteratorChunkIdx)) {
|
||||
// Advance to the next chunk.
|
||||
const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1];
|
||||
return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0,
|
||||
nextChunk.Length(), aRequestedBytes);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(IsLastChunk(iteratorChunkIdx), "Should've advanced");
|
||||
|
||||
if (mStatus) {
|
||||
// There's no more data and this SourceBuffer completed successfully.
|
||||
MOZ_ASSERT(NS_SUCCEEDED(*mStatus), "Handled failures earlier");
|
||||
return aIterator.SetComplete(*mStatus);
|
||||
}
|
||||
|
||||
// We're not complete, but there's no more data right now. Arrange to wake up
|
||||
// the consumer when we get more data.
|
||||
AddWaitingConsumer(aConsumer);
|
||||
return aIterator.SetWaiting();
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBuffer::HandleError(nsresult aError)
|
||||
{
|
||||
MOZ_ASSERT(NS_FAILED(aError), "Should have an error here");
|
||||
MOZ_ASSERT(aError == NS_ERROR_OUT_OF_MEMORY,
|
||||
"Unexpected error; may want to notify waiting readers, which "
|
||||
"HandleError currently doesn't do");
|
||||
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
NS_WARNING("SourceBuffer encountered an unrecoverable error");
|
||||
|
||||
// Record the error.
|
||||
mStatus = Some(aError);
|
||||
|
||||
// Drop our references to waiting readers.
|
||||
mWaitingConsumers.Clear();
|
||||
|
||||
return *mStatus;
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBuffer::IsEmpty()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
return mChunks.Length() == 0 ||
|
||||
mChunks[0].Length() == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBuffer::IsLastChunk(uint32_t aChunk)
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
return aChunk + 1 == mChunks.Length();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
459
image/SourceBuffer.h
Normal file
459
image/SourceBuffer.h
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* SourceBuffer is a single producer, multiple consumer data structure used for
|
||||
* storing image source (compressed) data.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_sourcebuffer_h
|
||||
#define mozilla_image_sourcebuffer_h
|
||||
|
||||
#include <algorithm>
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/RefCounted.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class SourceBuffer;
|
||||
|
||||
/**
|
||||
* IResumable is an interface for classes that can schedule themselves to resume
|
||||
* their work later. An implementation of IResumable generally should post a
|
||||
* runnable to some event target which continues the work of the task.
|
||||
*/
|
||||
struct IResumable
|
||||
{
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(IResumable)
|
||||
|
||||
// Subclasses may or may not be XPCOM classes, so we just require that they
|
||||
// implement AddRef and Release.
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
|
||||
|
||||
virtual void Resume() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~IResumable() { }
|
||||
};
|
||||
|
||||
/**
|
||||
* SourceBufferIterator is a class that allows consumers of image source data to
|
||||
* read the contents of a SourceBuffer sequentially.
|
||||
*
|
||||
* Consumers can advance through the SourceBuffer by calling
|
||||
* AdvanceOrScheduleResume() repeatedly. After every advance, they should call
|
||||
* check the return value, which will tell them the iterator's new state.
|
||||
*
|
||||
* If WAITING is returned, AdvanceOrScheduleResume() has arranged
|
||||
* to call the consumer's Resume() method later, so the consumer should save its
|
||||
* state if needed and stop running.
|
||||
*
|
||||
* If the iterator's new state is READY, then the consumer can call Data() and
|
||||
* Length() to read new data from the SourceBuffer.
|
||||
*
|
||||
* Finally, in the COMPLETE state the consumer can call CompletionStatus() to
|
||||
* get the status passed to SourceBuffer::Complete().
|
||||
*/
|
||||
class SourceBufferIterator final
|
||||
{
|
||||
public:
|
||||
enum State {
|
||||
START, // The iterator is at the beginning of the buffer.
|
||||
READY, // The iterator is pointing to new data.
|
||||
WAITING, // The iterator is blocked and the caller must yield.
|
||||
COMPLETE // The iterator is pointing to the end of the buffer.
|
||||
};
|
||||
|
||||
explicit SourceBufferIterator(SourceBuffer* aOwner)
|
||||
: mOwner(aOwner)
|
||||
, mState(START)
|
||||
, mChunkCount(0)
|
||||
, mByteCount(0)
|
||||
{
|
||||
MOZ_ASSERT(aOwner);
|
||||
mData.mIterating.mChunk = 0;
|
||||
mData.mIterating.mData = nullptr;
|
||||
mData.mIterating.mOffset = 0;
|
||||
mData.mIterating.mAvailableLength = 0;
|
||||
mData.mIterating.mNextReadLength = 0;
|
||||
}
|
||||
|
||||
SourceBufferIterator(SourceBufferIterator&& aOther)
|
||||
: mOwner(Move(aOther.mOwner))
|
||||
, mState(aOther.mState)
|
||||
, mData(aOther.mData)
|
||||
, mChunkCount(aOther.mChunkCount)
|
||||
, mByteCount(aOther.mByteCount)
|
||||
{ }
|
||||
|
||||
~SourceBufferIterator();
|
||||
|
||||
SourceBufferIterator& operator=(SourceBufferIterator&& aOther);
|
||||
|
||||
/**
|
||||
* Returns true if there are no more than @aBytes remaining in the
|
||||
* SourceBuffer. If the SourceBuffer is not yet complete, returns false.
|
||||
*/
|
||||
bool RemainingBytesIsNoMoreThan(size_t aBytes) const;
|
||||
|
||||
/**
|
||||
* Advances the iterator through the SourceBuffer if possible. Advances no
|
||||
* more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
|
||||
* possible.)
|
||||
*
|
||||
* This is a wrapper around AdvanceOrScheduleResume() that makes it clearer at
|
||||
* the callsite when the no resuming is intended.
|
||||
*
|
||||
* @return State::READY if the iterator was successfully advanced.
|
||||
* State::WAITING if the iterator could not be advanced because it's
|
||||
* at the end of the underlying SourceBuffer, but the SourceBuffer
|
||||
* may still receive additional data.
|
||||
* State::COMPLETE if the iterator could not be advanced because it's
|
||||
* at the end of the underlying SourceBuffer and the SourceBuffer is
|
||||
* marked complete (i.e., it will never receive any additional
|
||||
* data).
|
||||
*/
|
||||
State Advance(size_t aRequestedBytes)
|
||||
{
|
||||
return AdvanceOrScheduleResume(aRequestedBytes, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the iterator through the SourceBuffer if possible. Advances no
|
||||
* more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
|
||||
* possible.) If advancing is not possible and @aConsumer is not null,
|
||||
* arranges to call the @aConsumer's Resume() method when more data is
|
||||
* available.
|
||||
*
|
||||
* @return State::READY if the iterator was successfully advanced.
|
||||
* State::WAITING if the iterator could not be advanced because it's
|
||||
* at the end of the underlying SourceBuffer, but the SourceBuffer
|
||||
* may still receive additional data. @aConsumer's Resume() method
|
||||
* will be called when additional data is available.
|
||||
* State::COMPLETE if the iterator could not be advanced because it's
|
||||
* at the end of the underlying SourceBuffer and the SourceBuffer is
|
||||
* marked complete (i.e., it will never receive any additional
|
||||
* data).
|
||||
*/
|
||||
State AdvanceOrScheduleResume(size_t aRequestedBytes, IResumable* aConsumer);
|
||||
|
||||
/// If at the end, returns the status passed to SourceBuffer::Complete().
|
||||
nsresult CompletionStatus() const
|
||||
{
|
||||
MOZ_ASSERT(mState == COMPLETE,
|
||||
"Calling CompletionStatus() in the wrong state");
|
||||
return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK;
|
||||
}
|
||||
|
||||
/// If we're ready to read, returns a pointer to the new data.
|
||||
const char* Data() const
|
||||
{
|
||||
MOZ_ASSERT(mState == READY, "Calling Data() in the wrong state");
|
||||
return mState == READY ? mData.mIterating.mData + mData.mIterating.mOffset
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
/// If we're ready to read, returns the length of the new data.
|
||||
size_t Length() const
|
||||
{
|
||||
MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state");
|
||||
return mState == READY ? mData.mIterating.mNextReadLength : 0;
|
||||
}
|
||||
|
||||
/// @return a count of the chunks we've advanced through.
|
||||
uint32_t ChunkCount() const { return mChunkCount; }
|
||||
|
||||
/// @return a count of the bytes in all chunks we've advanced through.
|
||||
size_t ByteCount() const { return mByteCount; }
|
||||
|
||||
private:
|
||||
friend class SourceBuffer;
|
||||
|
||||
SourceBufferIterator(const SourceBufferIterator&) = delete;
|
||||
SourceBufferIterator& operator=(const SourceBufferIterator&) = delete;
|
||||
|
||||
bool HasMore() const { return mState != COMPLETE; }
|
||||
|
||||
State AdvanceFromLocalBuffer(size_t aRequestedBytes)
|
||||
{
|
||||
MOZ_ASSERT(mState == READY, "Advancing in the wrong state");
|
||||
MOZ_ASSERT(mData.mIterating.mAvailableLength > 0,
|
||||
"The local buffer shouldn't be empty");
|
||||
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0,
|
||||
"Advancing without consuming previous data");
|
||||
|
||||
mData.mIterating.mNextReadLength =
|
||||
std::min(mData.mIterating.mAvailableLength, aRequestedBytes);
|
||||
|
||||
return READY;
|
||||
}
|
||||
|
||||
State SetReady(uint32_t aChunk, const char* aData,
|
||||
size_t aOffset, size_t aAvailableLength,
|
||||
size_t aRequestedBytes)
|
||||
{
|
||||
MOZ_ASSERT(mState != COMPLETE);
|
||||
mState = READY;
|
||||
|
||||
// Update state.
|
||||
mData.mIterating.mChunk = aChunk;
|
||||
mData.mIterating.mData = aData;
|
||||
mData.mIterating.mOffset = aOffset;
|
||||
mData.mIterating.mAvailableLength = aAvailableLength;
|
||||
|
||||
// Update metrics.
|
||||
mChunkCount++;
|
||||
mByteCount += aAvailableLength;
|
||||
|
||||
// Attempt to advance by the requested number of bytes.
|
||||
return AdvanceFromLocalBuffer(aRequestedBytes);
|
||||
}
|
||||
|
||||
State SetWaiting()
|
||||
{
|
||||
MOZ_ASSERT(mState != COMPLETE);
|
||||
MOZ_ASSERT(mState != WAITING, "Did we get a spurious wakeup somehow?");
|
||||
return mState = WAITING;
|
||||
}
|
||||
|
||||
State SetComplete(nsresult aStatus)
|
||||
{
|
||||
mData.mAtEnd.mStatus = aStatus;
|
||||
return mState = COMPLETE;
|
||||
}
|
||||
|
||||
RefPtr<SourceBuffer> mOwner;
|
||||
|
||||
State mState;
|
||||
|
||||
/**
|
||||
* This union contains our iteration state if we're still iterating (for
|
||||
* states START, READY, and WAITING) and the status the SourceBuffer was
|
||||
* completed with if we're in state COMPLETE.
|
||||
*/
|
||||
union {
|
||||
struct {
|
||||
uint32_t mChunk;
|
||||
const char* mData;
|
||||
size_t mOffset;
|
||||
size_t mAvailableLength;
|
||||
size_t mNextReadLength;
|
||||
} mIterating;
|
||||
struct {
|
||||
nsresult mStatus;
|
||||
} mAtEnd;
|
||||
} mData;
|
||||
|
||||
uint32_t mChunkCount; // Count of chunks we've advanced through.
|
||||
size_t mByteCount; // Count of bytes in all chunks we've advanced through.
|
||||
};
|
||||
|
||||
/**
|
||||
* SourceBuffer is a parallel data structure used for storing image source
|
||||
* (compressed) data.
|
||||
*
|
||||
* SourceBuffer is a single producer, multiple consumer data structure. The
|
||||
* single producer calls Append() to append data to the buffer. In parallel,
|
||||
* multiple consumers can call Iterator(), which returns a SourceBufferIterator
|
||||
* that they can use to iterate through the buffer. The SourceBufferIterator
|
||||
* returns a series of pointers which remain stable for lifetime of the
|
||||
* SourceBuffer, and the data they point to is immutable, ensuring that the
|
||||
* producer never interferes with the consumers.
|
||||
*
|
||||
* In order to avoid blocking, SourceBuffer works with SourceBufferIterator to
|
||||
* keep a list of consumers which are waiting for new data, and to resume them
|
||||
* when the producer appends more. All consumers must implement the IResumable
|
||||
* interface to make this possible.
|
||||
*/
|
||||
class SourceBuffer final
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(image::SourceBuffer)
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(image::SourceBuffer)
|
||||
|
||||
SourceBuffer();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Producer methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* If the producer knows how long the source data will be, it should call
|
||||
* ExpectLength, which enables SourceBuffer to preallocate its buffer.
|
||||
*/
|
||||
nsresult ExpectLength(size_t aExpectedLength);
|
||||
|
||||
/// Append the provided data to the buffer.
|
||||
nsresult Append(const char* aData, size_t aLength);
|
||||
|
||||
/// Append the data available on the provided nsIInputStream to the buffer.
|
||||
nsresult AppendFromInputStream(nsIInputStream* aInputStream, uint32_t aCount);
|
||||
|
||||
/**
|
||||
* Mark the buffer complete, with a status that will be available to
|
||||
* consumers. Further calls to Append() are forbidden after Complete().
|
||||
*/
|
||||
void Complete(nsresult aStatus);
|
||||
|
||||
/// Returns true if the buffer is complete.
|
||||
bool IsComplete();
|
||||
|
||||
/// Memory reporting.
|
||||
size_t SizeOfIncludingThisWithComputedFallback(MallocSizeOf) const;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Consumer methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Returns an iterator to this SourceBuffer.
|
||||
SourceBufferIterator Iterator();
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Consumer methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The minimum chunk capacity we'll allocate, if we don't know the correct
|
||||
* capacity (which would happen because ExpectLength() wasn't called or gave
|
||||
* us the wrong value). This is only exposed for use by tests; if normal code
|
||||
* is using this, it's doing something wrong.
|
||||
*/
|
||||
static const size_t MIN_CHUNK_CAPACITY = 4096;
|
||||
|
||||
private:
|
||||
friend class SourceBufferIterator;
|
||||
|
||||
~SourceBuffer();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Chunk type and chunk-related methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Chunk
|
||||
{
|
||||
public:
|
||||
explicit Chunk(size_t aCapacity)
|
||||
: mCapacity(aCapacity)
|
||||
, mLength(0)
|
||||
{
|
||||
MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
|
||||
mData.reset(new (fallible) char[mCapacity]);
|
||||
}
|
||||
|
||||
Chunk(Chunk&& aOther)
|
||||
: mCapacity(aOther.mCapacity)
|
||||
, mLength(aOther.mLength)
|
||||
, mData(Move(aOther.mData))
|
||||
{
|
||||
aOther.mCapacity = aOther.mLength = 0;
|
||||
aOther.mData = nullptr;
|
||||
}
|
||||
|
||||
Chunk& operator=(Chunk&& aOther)
|
||||
{
|
||||
mCapacity = aOther.mCapacity;
|
||||
mLength = aOther.mLength;
|
||||
mData = Move(aOther.mData);
|
||||
aOther.mCapacity = aOther.mLength = 0;
|
||||
aOther.mData = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AllocationFailed() const { return !mData; }
|
||||
size_t Capacity() const { return mCapacity; }
|
||||
size_t Length() const { return mLength; }
|
||||
|
||||
char* Data() const
|
||||
{
|
||||
MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
|
||||
return mData.get();
|
||||
}
|
||||
|
||||
void AddLength(size_t aAdditionalLength)
|
||||
{
|
||||
MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity);
|
||||
mLength += aAdditionalLength;
|
||||
}
|
||||
|
||||
private:
|
||||
Chunk(const Chunk&) = delete;
|
||||
Chunk& operator=(const Chunk&) = delete;
|
||||
|
||||
size_t mCapacity;
|
||||
size_t mLength;
|
||||
UniquePtr<char[]> mData;
|
||||
};
|
||||
|
||||
nsresult AppendChunk(Maybe<Chunk>&& aChunk);
|
||||
Maybe<Chunk> CreateChunk(size_t aCapacity, bool aRoundUp = true);
|
||||
nsresult Compact();
|
||||
static size_t RoundedUpCapacity(size_t aCapacity);
|
||||
size_t FibonacciCapacityWithMinimum(size_t aMinCapacity);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Iterator / consumer methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void AddWaitingConsumer(IResumable* aConsumer);
|
||||
void ResumeWaitingConsumers();
|
||||
|
||||
typedef SourceBufferIterator::State State;
|
||||
|
||||
State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
|
||||
size_t aRequestedBytes,
|
||||
IResumable* aConsumer);
|
||||
bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
|
||||
size_t aBytes) const;
|
||||
|
||||
void OnIteratorRelease();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Helper methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsresult HandleError(nsresult aError);
|
||||
bool IsEmpty();
|
||||
bool IsLastChunk(uint32_t aChunk);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Member variables.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// All private members are protected by mMutex.
|
||||
mutable Mutex mMutex;
|
||||
|
||||
/// The data in this SourceBuffer, stored as a series of Chunks.
|
||||
FallibleTArray<Chunk> mChunks;
|
||||
|
||||
/// Consumers which are waiting to be notified when new data is available.
|
||||
nsTArray<RefPtr<IResumable>> mWaitingConsumers;
|
||||
|
||||
/// If present, marks this SourceBuffer complete with the given final status.
|
||||
Maybe<nsresult> mStatus;
|
||||
|
||||
/// Count of active consumers.
|
||||
uint32_t mConsumerCount;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_sourcebuffer_h
|
||||
750
image/StreamingLexer.h
Normal file
750
image/StreamingLexer.h
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* StreamingLexer is a lexing framework designed to make it simple to write
|
||||
* image decoders without worrying about the details of how the data is arriving
|
||||
* from the network.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_StreamingLexer_h
|
||||
#define mozilla_image_StreamingLexer_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Variant.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/// Buffering behaviors for StreamingLexer transitions.
|
||||
enum class BufferingStrategy
|
||||
{
|
||||
BUFFERED, // Data will be buffered and processed in one chunk.
|
||||
UNBUFFERED // Data will be processed as it arrives, in multiple chunks.
|
||||
};
|
||||
|
||||
/// Control flow behaviors for StreamingLexer transitions.
|
||||
enum class ControlFlowStrategy
|
||||
{
|
||||
CONTINUE, // If there's enough data, proceed to the next state immediately.
|
||||
YIELD // Yield to the caller before proceeding to the next state.
|
||||
};
|
||||
|
||||
/// Possible terminal states for the lexer.
|
||||
enum class TerminalState
|
||||
{
|
||||
SUCCESS,
|
||||
FAILURE
|
||||
};
|
||||
|
||||
/// Possible yield reasons for the lexer.
|
||||
enum class Yield
|
||||
{
|
||||
NEED_MORE_DATA, // The lexer cannot continue without more data.
|
||||
OUTPUT_AVAILABLE // There is output available for the caller to consume.
|
||||
};
|
||||
|
||||
/// The result of a call to StreamingLexer::Lex().
|
||||
typedef Variant<TerminalState, Yield> LexerResult;
|
||||
|
||||
/**
|
||||
* LexerTransition is a type used to give commands to the lexing framework.
|
||||
* Code that uses StreamingLexer can create LexerTransition values using the
|
||||
* static methods on Transition, and then return them to the lexing framework
|
||||
* for execution.
|
||||
*/
|
||||
template <typename State>
|
||||
class LexerTransition
|
||||
{
|
||||
public:
|
||||
// This is implicit so that Terminate{Success,Failure}() can return a
|
||||
// TerminalState and have it implicitly converted to a
|
||||
// LexerTransition<State>, which avoids the need for a "<State>"
|
||||
// qualification to the Terminate{Success,Failure}() callsite.
|
||||
MOZ_IMPLICIT LexerTransition(TerminalState aFinalState)
|
||||
: mNextState(aFinalState)
|
||||
{}
|
||||
|
||||
bool NextStateIsTerminal() const
|
||||
{
|
||||
return mNextState.template is<TerminalState>();
|
||||
}
|
||||
|
||||
TerminalState NextStateAsTerminal() const
|
||||
{
|
||||
return mNextState.template as<TerminalState>();
|
||||
}
|
||||
|
||||
State NextState() const
|
||||
{
|
||||
return mNextState.template as<NonTerminalState>().mState;
|
||||
}
|
||||
|
||||
State UnbufferedState() const
|
||||
{
|
||||
return *mNextState.template as<NonTerminalState>().mUnbufferedState;
|
||||
}
|
||||
|
||||
size_t Size() const
|
||||
{
|
||||
return mNextState.template as<NonTerminalState>().mSize;
|
||||
}
|
||||
|
||||
BufferingStrategy Buffering() const
|
||||
{
|
||||
return mNextState.template as<NonTerminalState>().mBufferingStrategy;
|
||||
}
|
||||
|
||||
ControlFlowStrategy ControlFlow() const
|
||||
{
|
||||
return mNextState.template as<NonTerminalState>().mControlFlowStrategy;
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct Transition;
|
||||
|
||||
LexerTransition(State aNextState,
|
||||
const Maybe<State>& aUnbufferedState,
|
||||
size_t aSize,
|
||||
BufferingStrategy aBufferingStrategy,
|
||||
ControlFlowStrategy aControlFlowStrategy)
|
||||
: mNextState(NonTerminalState(aNextState, aUnbufferedState, aSize,
|
||||
aBufferingStrategy, aControlFlowStrategy))
|
||||
{}
|
||||
|
||||
struct NonTerminalState
|
||||
{
|
||||
State mState;
|
||||
Maybe<State> mUnbufferedState;
|
||||
size_t mSize;
|
||||
BufferingStrategy mBufferingStrategy;
|
||||
ControlFlowStrategy mControlFlowStrategy;
|
||||
|
||||
NonTerminalState(State aState,
|
||||
const Maybe<State>& aUnbufferedState,
|
||||
size_t aSize,
|
||||
BufferingStrategy aBufferingStrategy,
|
||||
ControlFlowStrategy aControlFlowStrategy)
|
||||
: mState(aState)
|
||||
, mUnbufferedState(aUnbufferedState)
|
||||
, mSize(aSize)
|
||||
, mBufferingStrategy(aBufferingStrategy)
|
||||
, mControlFlowStrategy(aControlFlowStrategy)
|
||||
{
|
||||
MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
|
||||
mUnbufferedState);
|
||||
MOZ_ASSERT_IF(mUnbufferedState,
|
||||
mBufferingStrategy == BufferingStrategy::UNBUFFERED);
|
||||
}
|
||||
};
|
||||
|
||||
Variant<NonTerminalState, TerminalState> mNextState;
|
||||
};
|
||||
|
||||
struct Transition
|
||||
{
|
||||
/// Transition to @aNextState, buffering @aSize bytes of data.
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
To(const State& aNextState, size_t aSize)
|
||||
{
|
||||
return LexerTransition<State>(aNextState, Nothing(), aSize,
|
||||
BufferingStrategy::BUFFERED,
|
||||
ControlFlowStrategy::CONTINUE);
|
||||
}
|
||||
|
||||
/// Yield to the caller, transitioning to @aNextState when Lex() is next
|
||||
/// invoked. The same data that was delivered for the current state will be
|
||||
/// delivered again.
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
ToAfterYield(const State& aNextState)
|
||||
{
|
||||
return LexerTransition<State>(aNextState, Nothing(), 0,
|
||||
BufferingStrategy::BUFFERED,
|
||||
ControlFlowStrategy::YIELD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of
|
||||
* data unbuffered.
|
||||
*
|
||||
* The unbuffered data will be delivered in state @aUnbufferedState, which may
|
||||
* be invoked repeatedly until all @aSize bytes have been delivered. Then,
|
||||
* @aNextState will be invoked with no data. No state transitions are allowed
|
||||
* from @aUnbufferedState except for transitions to a terminal state, so
|
||||
* @aNextState will always be reached unless lexing terminates early.
|
||||
*/
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
ToUnbuffered(const State& aNextState,
|
||||
const State& aUnbufferedState,
|
||||
size_t aSize)
|
||||
{
|
||||
return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize,
|
||||
BufferingStrategy::UNBUFFERED,
|
||||
ControlFlowStrategy::CONTINUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue receiving unbuffered data. @aUnbufferedState should be the same
|
||||
* state as the @aUnbufferedState specified in the preceding call to
|
||||
* ToUnbuffered().
|
||||
*
|
||||
* This should be used during an unbuffered read initiated by ToUnbuffered().
|
||||
*/
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
ContinueUnbuffered(const State& aUnbufferedState)
|
||||
{
|
||||
return LexerTransition<State>(aUnbufferedState, Nothing(), 0,
|
||||
BufferingStrategy::BUFFERED,
|
||||
ControlFlowStrategy::CONTINUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue receiving unbuffered data. @aUnbufferedState should be the same
|
||||
* state as the @aUnbufferedState specified in the preceding call to
|
||||
* ToUnbuffered(). @aSize indicates the amount of data that has already been
|
||||
* consumed; the next state will receive the same data that was delivered to
|
||||
* the current state, without the first @aSize bytes.
|
||||
*
|
||||
* This should be used during an unbuffered read initiated by ToUnbuffered().
|
||||
*/
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
ContinueUnbufferedAfterYield(const State& aUnbufferedState, size_t aSize)
|
||||
{
|
||||
return LexerTransition<State>(aUnbufferedState, Nothing(), aSize,
|
||||
BufferingStrategy::BUFFERED,
|
||||
ControlFlowStrategy::YIELD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate lexing, ending up in terminal state SUCCESS. (The implicit
|
||||
* LexerTransition constructor will convert the result to a LexerTransition
|
||||
* as needed.)
|
||||
*
|
||||
* No more data will be delivered after this function is used.
|
||||
*/
|
||||
static TerminalState
|
||||
TerminateSuccess()
|
||||
{
|
||||
return TerminalState::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate lexing, ending up in terminal state FAILURE. (The implicit
|
||||
* LexerTransition constructor will convert the result to a LexerTransition
|
||||
* as needed.)
|
||||
*
|
||||
* No more data will be delivered after this function is used.
|
||||
*/
|
||||
static TerminalState
|
||||
TerminateFailure()
|
||||
{
|
||||
return TerminalState::FAILURE;
|
||||
}
|
||||
|
||||
private:
|
||||
Transition();
|
||||
};
|
||||
|
||||
/**
|
||||
* StreamingLexer is a lexing framework designed to make it simple to write
|
||||
* image decoders without worrying about the details of how the data is arriving
|
||||
* from the network.
|
||||
*
|
||||
* To use StreamingLexer:
|
||||
*
|
||||
* - Create a State type. This should be an |enum class| listing all of the
|
||||
* states that you can be in while lexing the image format you're trying to
|
||||
* read.
|
||||
*
|
||||
* - Add an instance of StreamingLexer<State> to your decoder class. Initialize
|
||||
* it with a Transition::To() the state that you want to start lexing in, and
|
||||
* a Transition::To() the state you'd like to use to handle truncated data.
|
||||
*
|
||||
* - In your decoder's DoDecode() method, call Lex(), passing in the input
|
||||
* data and length that are passed to DoDecode(). You also need to pass
|
||||
* a lambda which dispatches to lexing code for each state based on the State
|
||||
* value that's passed in. The lambda generally should just continue a
|
||||
* |switch| statement that calls different methods for each State value. Each
|
||||
* method should return a LexerTransition<State>, which the lambda should
|
||||
* return in turn.
|
||||
*
|
||||
* - Write the methods that actually implement lexing for your image format.
|
||||
* These methods should return either Transition::To(), to move on to another
|
||||
* state, or Transition::Terminate{Success,Failure}(), if lexing has
|
||||
* terminated in either success or failure. (There are also additional
|
||||
* transitions for unbuffered reads; see below.)
|
||||
*
|
||||
* That's the basics. The StreamingLexer will track your position in the input
|
||||
* and buffer enough data so that your lexing methods can process everything in
|
||||
* one pass. Lex() returns Yield::NEED_MORE_DATA if more data is needed, in
|
||||
* which case you should just return from DoDecode(). If lexing reaches a
|
||||
* terminal state, Lex() returns TerminalState::SUCCESS or
|
||||
* TerminalState::FAILURE, and you can check which one to determine if lexing
|
||||
* succeeded or failed and do any necessary cleanup.
|
||||
*
|
||||
* Sometimes, the input data is truncated. StreamingLexer will notify you when
|
||||
* this happens by invoking the truncated data state you passed to the
|
||||
* constructor. At this point you can attempt to recover and return
|
||||
* TerminalState::SUCCESS or TerminalState::FAILURE, depending on whether you
|
||||
* were successful. Note that you can't return anything other than a terminal
|
||||
* state in this situation, since there's no more data to read. For the same
|
||||
* reason, your truncated data state shouldn't require any data. (That is, the
|
||||
* @aSize argument you pass to Transition::To() must be zero.) Violating these
|
||||
* requirements will trigger assertions and an immediate transition to
|
||||
* TerminalState::FAILURE.
|
||||
*
|
||||
* Some lexers may want to *avoid* buffering in some cases, and just process the
|
||||
* data as it comes in. This is useful if, for example, you just want to skip
|
||||
* over a large section of data; there's no point in buffering data you're just
|
||||
* going to ignore.
|
||||
*
|
||||
* You can begin an unbuffered read with Transition::ToUnbuffered(). This works
|
||||
* a little differently than Transition::To() in that you specify *two* states.
|
||||
* The @aUnbufferedState argument specifies a state that will be called
|
||||
* repeatedly with unbuffered data, as soon as it arrives. The implementation
|
||||
* for that state should return either a transition to a terminal state, or a
|
||||
* Transition::ContinueUnbuffered() to the same @aUnbufferedState. (From a
|
||||
* technical perspective, it's not necessary to specify the state again, but
|
||||
* it's helpful to human readers.) Once the amount of data requested in the
|
||||
* original call to Transition::ToUnbuffered() has been delivered, Lex() will
|
||||
* transition to the @aNextState state specified via Transition::ToUnbuffered().
|
||||
* That state will be invoked with *no* data; it's just called to signal that
|
||||
* the unbuffered read is over.
|
||||
*
|
||||
* It's sometimes useful for a lexer to provide incremental results, rather
|
||||
* than simply running to completion and presenting all its output at once. For
|
||||
* example, when decoding animated images, it may be useful to produce each
|
||||
* frame incrementally. StreamingLexer supports this by allowing a lexer to
|
||||
* yield.
|
||||
*
|
||||
* To yield back to the caller, a state implementation can simply return
|
||||
* Transition::ToAfterYield(). ToAfterYield()'s @aNextState argument specifies
|
||||
* the next state that the lexer should transition to, just like when using
|
||||
* Transition::To(), but there are two differences. One is that Lex() will
|
||||
* return to the caller before processing any more data when it encounters a
|
||||
* yield transition. This provides an opportunity for the caller to interact with the
|
||||
* lexer's intermediate results. The second difference is that @aNextState
|
||||
* will be called with *the same data as the state that you returned
|
||||
* Transition::ToAfterYield() from*. This allows a lexer to partially consume
|
||||
* the data, return intermediate results, and then finish consuming the data
|
||||
* when @aNextState is called.
|
||||
*
|
||||
* It's also possible to yield during an unbuffered read. Just return a
|
||||
* Transition::ContinueUnbufferedAfterYield(). Just like with
|
||||
* Transition::ContinueUnbuffered(), the @aUnbufferedState must be the same as
|
||||
* the one originally passed to Transition::ToUnbuffered(). The second argument,
|
||||
* @aSize, specifies the amount of data that the lexer has already consumed.
|
||||
* When @aUnbufferedState is next invoked, it will get the same data that it
|
||||
* received previously, except that the first @aSize bytes will be excluded.
|
||||
* This makes it easy to consume unbuffered data incrementally.
|
||||
*
|
||||
* XXX(seth): We should be able to get of the |State| stuff totally once bug
|
||||
* 1198451 lands, since we can then just return a function representing the next
|
||||
* state directly.
|
||||
*/
|
||||
template <typename State, size_t InlineBufferSize = 16>
|
||||
class StreamingLexer
|
||||
{
|
||||
public:
|
||||
StreamingLexer(LexerTransition<State> aStartState,
|
||||
LexerTransition<State> aTruncatedState)
|
||||
: mTransition(TerminalState::FAILURE)
|
||||
, mTruncatedTransition(aTruncatedState)
|
||||
{
|
||||
if (!aStartState.NextStateIsTerminal() &&
|
||||
aStartState.ControlFlow() == ControlFlowStrategy::YIELD) {
|
||||
// Allowing a StreamingLexer to start in a yield state doesn't make sense
|
||||
// semantically (since yield states are supposed to deliver the same data
|
||||
// as previous states, and there's no previous state here), but more
|
||||
// importantly, it's necessary to advance a SourceBufferIterator at least
|
||||
// once before you can read from it, and adding the necessary checks to
|
||||
// Lex() to avoid that issue has the potential to mask real bugs. So
|
||||
// instead, it's better to forbid starting in a yield state.
|
||||
MOZ_ASSERT_UNREACHABLE("Starting in a yield state");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aTruncatedState.NextStateIsTerminal() &&
|
||||
(aTruncatedState.ControlFlow() == ControlFlowStrategy::YIELD ||
|
||||
aTruncatedState.Buffering() == BufferingStrategy::UNBUFFERED ||
|
||||
aTruncatedState.Size() != 0)) {
|
||||
// The truncated state can't receive any data because, by definition,
|
||||
// there is no more data to receive. That means that yielding or an
|
||||
// unbuffered read would not make sense, and that the state must require
|
||||
// zero bytes.
|
||||
MOZ_ASSERT_UNREACHABLE("Truncated state makes no sense");
|
||||
return;
|
||||
}
|
||||
|
||||
SetTransition(aStartState);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
LexerResult Lex(SourceBufferIterator& aIterator,
|
||||
IResumable* aOnResume,
|
||||
Func aFunc)
|
||||
{
|
||||
if (mTransition.NextStateIsTerminal()) {
|
||||
// We've already reached a terminal state. We never deliver any more data
|
||||
// in this case; just return the terminal state again immediately.
|
||||
return LexerResult(mTransition.NextStateAsTerminal());
|
||||
}
|
||||
|
||||
Maybe<LexerResult> result;
|
||||
|
||||
// If the lexer requested a yield last time, we deliver the same data again
|
||||
// before we read anything else from |aIterator|. Note that although to the
|
||||
// callers of Lex(), Yield::NEED_MORE_DATA is just another type of yield,
|
||||
// internally they're different in that we don't redeliver the same data in
|
||||
// the Yield::NEED_MORE_DATA case, and |mYieldingToState| is not set. This
|
||||
// means that for Yield::NEED_MORE_DATA, we go directly to the loop below.
|
||||
if (mYieldingToState) {
|
||||
result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
|
||||
? UnbufferedReadAfterYield(aIterator, aFunc)
|
||||
: BufferedReadAfterYield(aIterator, aFunc);
|
||||
}
|
||||
|
||||
while (!result) {
|
||||
MOZ_ASSERT_IF(mTransition.Buffering() == BufferingStrategy::UNBUFFERED,
|
||||
mUnbufferedState);
|
||||
|
||||
// Figure out how much we need to read.
|
||||
const size_t toRead = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
|
||||
? mUnbufferedState->mBytesRemaining
|
||||
: mTransition.Size() - mBuffer.length();
|
||||
|
||||
// Attempt to advance the iterator by |toRead| bytes.
|
||||
switch (aIterator.AdvanceOrScheduleResume(toRead, aOnResume)) {
|
||||
case SourceBufferIterator::WAITING:
|
||||
// We can't continue because the rest of the data hasn't arrived from
|
||||
// the network yet. We don't have to do anything special; the
|
||||
// SourceBufferIterator will ensure that |aOnResume| gets called when
|
||||
// more data is available.
|
||||
result = Some(LexerResult(Yield::NEED_MORE_DATA));
|
||||
break;
|
||||
|
||||
case SourceBufferIterator::COMPLETE:
|
||||
// The data is truncated; if not, the lexer would've reached a
|
||||
// terminal state by now. We only get to
|
||||
// SourceBufferIterator::COMPLETE after every byte of data has been
|
||||
// delivered to the lexer.
|
||||
result = Truncated(aIterator, aFunc);
|
||||
break;
|
||||
|
||||
case SourceBufferIterator::READY:
|
||||
// Process the new data that became available.
|
||||
MOZ_ASSERT(aIterator.Data());
|
||||
|
||||
result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
|
||||
? UnbufferedRead(aIterator, aFunc)
|
||||
: BufferedRead(aIterator, aFunc);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown SourceBufferIterator state");
|
||||
result = SetTransition(Transition::TerminateFailure());
|
||||
}
|
||||
};
|
||||
|
||||
return *result;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Func>
|
||||
Maybe<LexerResult> UnbufferedRead(SourceBufferIterator& aIterator, Func aFunc)
|
||||
{
|
||||
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
|
||||
MOZ_ASSERT(mUnbufferedState);
|
||||
MOZ_ASSERT(!mYieldingToState);
|
||||
MOZ_ASSERT(mBuffer.empty(),
|
||||
"Buffered read at the same time as unbuffered read?");
|
||||
MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining,
|
||||
"Read too much data during unbuffered read?");
|
||||
MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk == 0,
|
||||
"Already consumed data in the current chunk, but not yielding?");
|
||||
|
||||
if (mUnbufferedState->mBytesRemaining == 0) {
|
||||
// We're done with the unbuffered read, so transition to the next state.
|
||||
return SetTransition(aFunc(mTransition.NextState(), nullptr, 0));
|
||||
}
|
||||
|
||||
return ContinueUnbufferedRead(aIterator.Data(), aIterator.Length(),
|
||||
aIterator.Length(), aFunc);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
Maybe<LexerResult> UnbufferedReadAfterYield(SourceBufferIterator& aIterator, Func aFunc)
|
||||
{
|
||||
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
|
||||
MOZ_ASSERT(mUnbufferedState);
|
||||
MOZ_ASSERT(mYieldingToState);
|
||||
MOZ_ASSERT(mBuffer.empty(),
|
||||
"Buffered read at the same time as unbuffered read?");
|
||||
MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining,
|
||||
"Read too much data during unbuffered read?");
|
||||
MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk <= aIterator.Length(),
|
||||
"Consumed more data than the current chunk holds?");
|
||||
MOZ_ASSERT(mTransition.UnbufferedState() == *mYieldingToState);
|
||||
|
||||
mYieldingToState = Nothing();
|
||||
|
||||
if (mUnbufferedState->mBytesRemaining == 0) {
|
||||
// We're done with the unbuffered read, so transition to the next state.
|
||||
return SetTransition(aFunc(mTransition.NextState(), nullptr, 0));
|
||||
}
|
||||
|
||||
// Since we've yielded, we may have already consumed some data in this
|
||||
// chunk. Make the necessary adjustments. (Note that the std::min call is
|
||||
// just belt-and-suspenders to keep this code memory safe even if there's
|
||||
// a bug somewhere.)
|
||||
const size_t toSkip =
|
||||
std::min(mUnbufferedState->mBytesConsumedInCurrentChunk, aIterator.Length());
|
||||
const char* data = aIterator.Data() + toSkip;
|
||||
const size_t length = aIterator.Length() - toSkip;
|
||||
|
||||
// If |length| is zero, we've hit the end of the current chunk. This only
|
||||
// happens if we yield right at the end of a chunk. Rather than call |aFunc|
|
||||
// with a |length| of zero bytes (which seems potentially surprising to
|
||||
// decoder authors), we go ahead and read more data.
|
||||
if (length == 0) {
|
||||
return FinishCurrentChunkOfUnbufferedRead(aIterator.Length());
|
||||
}
|
||||
|
||||
return ContinueUnbufferedRead(data, length, aIterator.Length(), aFunc);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
Maybe<LexerResult> ContinueUnbufferedRead(const char* aData,
|
||||
size_t aLength,
|
||||
size_t aChunkLength,
|
||||
Func aFunc)
|
||||
{
|
||||
// Call aFunc with the unbuffered state to indicate that we're in the
|
||||
// middle of an unbuffered read. We enforce that any state transition
|
||||
// passed back to us is either a terminal state or takes us back to the
|
||||
// unbuffered state.
|
||||
LexerTransition<State> unbufferedTransition =
|
||||
aFunc(mTransition.UnbufferedState(), aData, aLength);
|
||||
|
||||
// If we reached a terminal state, we're done.
|
||||
if (unbufferedTransition.NextStateIsTerminal()) {
|
||||
return SetTransition(unbufferedTransition);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mTransition.UnbufferedState() ==
|
||||
unbufferedTransition.NextState());
|
||||
|
||||
// Perform bookkeeping.
|
||||
if (unbufferedTransition.ControlFlow() == ControlFlowStrategy::YIELD) {
|
||||
mUnbufferedState->mBytesConsumedInCurrentChunk += unbufferedTransition.Size();
|
||||
return SetTransition(unbufferedTransition);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(unbufferedTransition.Size() == 0);
|
||||
return FinishCurrentChunkOfUnbufferedRead(aChunkLength);
|
||||
}
|
||||
|
||||
Maybe<LexerResult> FinishCurrentChunkOfUnbufferedRead(size_t aChunkLength)
|
||||
{
|
||||
// We've finished an unbuffered read of a chunk of length |aChunkLength|, so
|
||||
// update |myBytesRemaining| to reflect that we're |aChunkLength| closer to
|
||||
// the end of the unbuffered read. (The std::min call is just
|
||||
// belt-and-suspenders to keep this code memory safe even if there's a bug
|
||||
// somewhere.)
|
||||
mUnbufferedState->mBytesRemaining -=
|
||||
std::min(mUnbufferedState->mBytesRemaining, aChunkLength);
|
||||
|
||||
// Since we're moving on to a new chunk, we can forget about the count of
|
||||
// bytes consumed by yielding in the current chunk.
|
||||
mUnbufferedState->mBytesConsumedInCurrentChunk = 0;
|
||||
|
||||
return Nothing(); // Keep processing.
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
Maybe<LexerResult> BufferedRead(SourceBufferIterator& aIterator, Func aFunc)
|
||||
{
|
||||
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
|
||||
MOZ_ASSERT(!mYieldingToState);
|
||||
MOZ_ASSERT(!mUnbufferedState,
|
||||
"Buffered read at the same time as unbuffered read?");
|
||||
MOZ_ASSERT(mBuffer.length() < mTransition.Size() ||
|
||||
(mBuffer.length() == 0 && mTransition.Size() == 0),
|
||||
"Buffered more than we needed?");
|
||||
|
||||
// If we have all the data, we don't actually need to buffer anything.
|
||||
if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) {
|
||||
return SetTransition(aFunc(mTransition.NextState(),
|
||||
aIterator.Data(),
|
||||
aIterator.Length()));
|
||||
}
|
||||
|
||||
// We do need to buffer, so make sure the buffer has enough capacity. We
|
||||
// deliberately wait until we know for sure we need to buffer to call
|
||||
// reserve() since it could require memory allocation.
|
||||
if (!mBuffer.reserve(mTransition.Size())) {
|
||||
return SetTransition(Transition::TerminateFailure());
|
||||
}
|
||||
|
||||
// Append the new data we just got to the buffer.
|
||||
if (!mBuffer.append(aIterator.Data(), aIterator.Length())) {
|
||||
return SetTransition(Transition::TerminateFailure());
|
||||
}
|
||||
|
||||
if (mBuffer.length() != mTransition.Size()) {
|
||||
return Nothing(); // Keep processing.
|
||||
}
|
||||
|
||||
// We've buffered everything, so transition to the next state.
|
||||
return SetTransition(aFunc(mTransition.NextState(),
|
||||
mBuffer.begin(),
|
||||
mBuffer.length()));
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
Maybe<LexerResult> BufferedReadAfterYield(SourceBufferIterator& aIterator,
|
||||
Func aFunc)
|
||||
{
|
||||
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
|
||||
MOZ_ASSERT(mYieldingToState);
|
||||
MOZ_ASSERT(!mUnbufferedState,
|
||||
"Buffered read at the same time as unbuffered read?");
|
||||
MOZ_ASSERT(mBuffer.length() <= mTransition.Size(),
|
||||
"Buffered more than we needed?");
|
||||
|
||||
State nextState = Move(*mYieldingToState);
|
||||
|
||||
// After a yield, we need to take the same data that we delivered to the
|
||||
// last state, and deliver it again to the new state. We know that this is
|
||||
// happening right at a state transition, and that the last state was a
|
||||
// buffered read, so there are two cases:
|
||||
|
||||
// 1. We got the data from the SourceBufferIterator directly.
|
||||
if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) {
|
||||
return SetTransition(aFunc(nextState,
|
||||
aIterator.Data(),
|
||||
aIterator.Length()));
|
||||
}
|
||||
|
||||
// 2. We got the data from the buffer.
|
||||
if (mBuffer.length() == mTransition.Size()) {
|
||||
return SetTransition(aFunc(nextState,
|
||||
mBuffer.begin(),
|
||||
mBuffer.length()));
|
||||
}
|
||||
|
||||
// Anything else indicates a bug.
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected state encountered during yield");
|
||||
return SetTransition(Transition::TerminateFailure());
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
Maybe<LexerResult> Truncated(SourceBufferIterator& aIterator,
|
||||
Func aFunc)
|
||||
{
|
||||
// The data is truncated. Let the lexer clean up and decide which terminal
|
||||
// state we should end up in.
|
||||
LexerTransition<State> transition
|
||||
= mTruncatedTransition.NextStateIsTerminal()
|
||||
? mTruncatedTransition
|
||||
: aFunc(mTruncatedTransition.NextState(), nullptr, 0);
|
||||
|
||||
if (!transition.NextStateIsTerminal()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Truncated state didn't lead to terminal state?");
|
||||
return SetTransition(Transition::TerminateFailure());
|
||||
}
|
||||
|
||||
// If the SourceBuffer was completed with a failing state, we end in
|
||||
// TerminalState::FAILURE no matter what. This only happens in exceptional
|
||||
// situations like SourceBuffer itself encountering a failure due to OOM.
|
||||
if (NS_FAILED(aIterator.CompletionStatus())) {
|
||||
return SetTransition(Transition::TerminateFailure());
|
||||
}
|
||||
|
||||
return SetTransition(transition);
|
||||
}
|
||||
|
||||
Maybe<LexerResult> SetTransition(const LexerTransition<State>& aTransition)
|
||||
{
|
||||
// There should be no transitions while we're buffering for a buffered read
|
||||
// unless they're to terminal states. (The terminal state transitions would
|
||||
// generally be triggered by error handling code.)
|
||||
MOZ_ASSERT_IF(!mBuffer.empty(),
|
||||
aTransition.NextStateIsTerminal() ||
|
||||
mBuffer.length() == mTransition.Size());
|
||||
|
||||
// Similarly, the only transitions allowed in the middle of an unbuffered
|
||||
// read are to a terminal state, or a yield to the same state. Otherwise, we
|
||||
// should remain in the same state until the unbuffered read completes.
|
||||
MOZ_ASSERT_IF(mUnbufferedState,
|
||||
aTransition.NextStateIsTerminal() ||
|
||||
(aTransition.ControlFlow() == ControlFlowStrategy::YIELD &&
|
||||
aTransition.NextState() == mTransition.UnbufferedState()) ||
|
||||
mUnbufferedState->mBytesRemaining == 0);
|
||||
|
||||
// If this transition is a yield, save the next state and return. We'll
|
||||
// handle the rest when Lex() gets called again.
|
||||
if (!aTransition.NextStateIsTerminal() &&
|
||||
aTransition.ControlFlow() == ControlFlowStrategy::YIELD) {
|
||||
mYieldingToState = Some(aTransition.NextState());
|
||||
return Some(LexerResult(Yield::OUTPUT_AVAILABLE));
|
||||
}
|
||||
|
||||
// Update our transition.
|
||||
mTransition = aTransition;
|
||||
|
||||
// Get rid of anything left over from the previous state.
|
||||
mBuffer.clear();
|
||||
mYieldingToState = Nothing();
|
||||
mUnbufferedState = Nothing();
|
||||
|
||||
// If we reached a terminal state, let the caller know.
|
||||
if (mTransition.NextStateIsTerminal()) {
|
||||
return Some(LexerResult(mTransition.NextStateAsTerminal()));
|
||||
}
|
||||
|
||||
// If we're entering an unbuffered state, record how long we'll stay in it.
|
||||
if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
|
||||
mUnbufferedState.emplace(mTransition.Size());
|
||||
}
|
||||
|
||||
return Nothing(); // Keep processing.
|
||||
}
|
||||
|
||||
// State that tracks our position within an unbuffered read.
|
||||
struct UnbufferedState
|
||||
{
|
||||
explicit UnbufferedState(size_t aBytesRemaining)
|
||||
: mBytesRemaining(aBytesRemaining)
|
||||
, mBytesConsumedInCurrentChunk(0)
|
||||
{ }
|
||||
|
||||
size_t mBytesRemaining;
|
||||
size_t mBytesConsumedInCurrentChunk;
|
||||
};
|
||||
|
||||
Vector<char, InlineBufferSize> mBuffer;
|
||||
LexerTransition<State> mTransition;
|
||||
const LexerTransition<State> mTruncatedTransition;
|
||||
Maybe<State> mYieldingToState;
|
||||
Maybe<UnbufferedState> mUnbufferedState;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_StreamingLexer_h
|
||||
1164
image/SurfaceCache.cpp
Normal file
1164
image/SurfaceCache.cpp
Normal file
File diff suppressed because it is too large
Load diff
427
image/SurfaceCache.h
Normal file
427
image/SurfaceCache.h
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* SurfaceCache is a service for caching temporary surfaces and decoded image
|
||||
* data in imagelib.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_SurfaceCache_h
|
||||
#define mozilla_image_SurfaceCache_h
|
||||
|
||||
#include "mozilla/Maybe.h" // for Maybe
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/MemoryReporting.h" // for MallocSizeOf
|
||||
#include "mozilla/HashFunctions.h" // for HashGeneric and AddToHash
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxPoint.h" // for gfxSize
|
||||
#include "nsCOMPtr.h" // for already_AddRefed
|
||||
#include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize
|
||||
#include "mozilla/gfx/2D.h" // for SourceSurface
|
||||
#include "PlaybackType.h"
|
||||
#include "SurfaceFlags.h"
|
||||
#include "SVGImageContext.h" // for SVGImageContext
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Image;
|
||||
class ISurfaceProvider;
|
||||
class LookupResult;
|
||||
class SurfaceCacheImpl;
|
||||
struct SurfaceMemoryCounter;
|
||||
|
||||
/*
|
||||
* ImageKey contains the information we need to look up all SurfaceCache entries
|
||||
* for a particular image.
|
||||
*/
|
||||
typedef Image* ImageKey;
|
||||
|
||||
/*
|
||||
* SurfaceKey contains the information we need to look up a specific
|
||||
* SurfaceCache entry. Together with an ImageKey, this uniquely identifies the
|
||||
* surface.
|
||||
*
|
||||
* Callers should construct a SurfaceKey using the appropriate helper function
|
||||
* for their image type - either RasterSurfaceKey or VectorSurfaceKey.
|
||||
*/
|
||||
class SurfaceKey
|
||||
{
|
||||
typedef gfx::IntSize IntSize;
|
||||
|
||||
public:
|
||||
bool operator==(const SurfaceKey& aOther) const
|
||||
{
|
||||
return aOther.mSize == mSize &&
|
||||
aOther.mSVGContext == mSVGContext &&
|
||||
aOther.mPlayback == mPlayback &&
|
||||
aOther.mFlags == mFlags;
|
||||
}
|
||||
|
||||
uint32_t Hash() const
|
||||
{
|
||||
uint32_t hash = HashGeneric(mSize.width, mSize.height);
|
||||
hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0));
|
||||
hash = AddToHash(hash, uint8_t(mPlayback), uint32_t(mFlags));
|
||||
return hash;
|
||||
}
|
||||
|
||||
const IntSize& Size() const { return mSize; }
|
||||
Maybe<SVGImageContext> SVGContext() const { return mSVGContext; }
|
||||
PlaybackType Playback() const { return mPlayback; }
|
||||
SurfaceFlags Flags() const { return mFlags; }
|
||||
|
||||
private:
|
||||
SurfaceKey(const IntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
PlaybackType aPlayback,
|
||||
SurfaceFlags aFlags)
|
||||
: mSize(aSize)
|
||||
, mSVGContext(aSVGContext)
|
||||
, mPlayback(aPlayback)
|
||||
, mFlags(aFlags)
|
||||
{ }
|
||||
|
||||
static uint32_t HashSIC(const SVGImageContext& aSIC) {
|
||||
return aSIC.Hash();
|
||||
}
|
||||
|
||||
friend SurfaceKey RasterSurfaceKey(const IntSize&, SurfaceFlags, PlaybackType);
|
||||
friend SurfaceKey VectorSurfaceKey(const IntSize&,
|
||||
const Maybe<SVGImageContext>&);
|
||||
|
||||
IntSize mSize;
|
||||
Maybe<SVGImageContext> mSVGContext;
|
||||
PlaybackType mPlayback;
|
||||
SurfaceFlags mFlags;
|
||||
};
|
||||
|
||||
inline SurfaceKey
|
||||
RasterSurfaceKey(const gfx::IntSize& aSize,
|
||||
SurfaceFlags aFlags,
|
||||
PlaybackType aPlayback)
|
||||
{
|
||||
return SurfaceKey(aSize, Nothing(), aPlayback, aFlags);
|
||||
}
|
||||
|
||||
inline SurfaceKey
|
||||
VectorSurfaceKey(const gfx::IntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext)
|
||||
{
|
||||
// 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.
|
||||
// Similarly, we don't accept a PlaybackType parameter because we don't
|
||||
// currently cache frames of animated SVG images.
|
||||
return SurfaceKey(aSize, aSVGContext, PlaybackType::eStatic,
|
||||
DefaultSurfaceFlags());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AvailabilityState is used to track whether an ISurfaceProvider has a surface
|
||||
* available or is just a placeholder.
|
||||
*
|
||||
* To ensure that availability changes are atomic (and especially that internal
|
||||
* SurfaceCache code doesn't have to deal with asynchronous availability
|
||||
* changes), an ISurfaceProvider which starts as a placeholder can only reveal
|
||||
* the fact that it now has a surface available via a call to
|
||||
* SurfaceCache::SurfaceAvailable().
|
||||
*/
|
||||
class AvailabilityState
|
||||
{
|
||||
public:
|
||||
static AvailabilityState StartAvailable() { return AvailabilityState(true); }
|
||||
static AvailabilityState StartAsPlaceholder() { return AvailabilityState(false); }
|
||||
|
||||
bool IsAvailable() const { return mIsAvailable; }
|
||||
bool IsPlaceholder() const { return !mIsAvailable; }
|
||||
|
||||
private:
|
||||
friend class SurfaceCacheImpl;
|
||||
|
||||
explicit AvailabilityState(bool aIsAvailable) : mIsAvailable(aIsAvailable) { }
|
||||
|
||||
void SetAvailable() { mIsAvailable = true; }
|
||||
|
||||
bool mIsAvailable;
|
||||
};
|
||||
|
||||
enum class InsertOutcome : uint8_t {
|
||||
SUCCESS, // Success (but see Insert documentation).
|
||||
FAILURE, // Couldn't insert (e.g., for capacity reasons).
|
||||
FAILURE_ALREADY_PRESENT // A surface with the same key is already present.
|
||||
};
|
||||
|
||||
/**
|
||||
* SurfaceCache is an ImageLib-global service that allows caching of decoded
|
||||
* image surfaces, temporary surfaces (e.g. for caching rotated or clipped
|
||||
* versions of images), or dynamically generated surfaces (e.g. for animations).
|
||||
* SurfaceCache entries normally expire from the cache automatically if they go
|
||||
* too long without being accessed.
|
||||
*
|
||||
* Because SurfaceCache must support both normal surfaces and dynamically
|
||||
* generated surfaces, it does not actually hold surfaces directly. Instead, it
|
||||
* holds ISurfaceProvider objects which can provide access to a surface when
|
||||
* requested; SurfaceCache doesn't care about the details of how this is
|
||||
* accomplished.
|
||||
*
|
||||
* Sometime it's useful to temporarily prevent entries from expiring from the
|
||||
* 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; 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
|
||||
* RemoveImage() for more details.
|
||||
*/
|
||||
struct SurfaceCache
|
||||
{
|
||||
typedef gfx::IntSize IntSize;
|
||||
|
||||
/**
|
||||
* Initialize static data. Called during imagelib module initialization.
|
||||
*/
|
||||
static void Initialize();
|
||||
|
||||
/**
|
||||
* Release static data. Called during imagelib module shutdown.
|
||||
*/
|
||||
static void Shutdown();
|
||||
|
||||
/**
|
||||
* Looks up the requested cache entry and returns a drawable reference to its
|
||||
* associated surface.
|
||||
*
|
||||
* If the image associated with the cache entry is locked, then the entry will
|
||||
* be locked before it is returned.
|
||||
*
|
||||
* If a matching ISurfaceProvider was found in the cache, but SurfaceCache
|
||||
* couldn't obtain a surface from it (e.g. because it 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 ISurfaceProviders associated with a
|
||||
* locked image; SurfaceCache tells such ISurfaceProviders to keep a strong
|
||||
* references to their data internally.
|
||||
*
|
||||
* @param aImageKey Key data identifying which image the cache entry
|
||||
* belongs to.
|
||||
* @param aSurfaceKey Key data which uniquely identifies the requested
|
||||
* cache entry.
|
||||
* @return a LookupResult which will contain a DrawableSurface
|
||||
* if the cache entry was found.
|
||||
*/
|
||||
static LookupResult Lookup(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
|
||||
/**
|
||||
* Looks up the best matching cache entry and returns a drawable reference to
|
||||
* its associated surface.
|
||||
*
|
||||
* The result may vary from the requested cache entry only in terms of size.
|
||||
*
|
||||
* @param aImageKey Key data identifying which image the cache entry
|
||||
* belongs to.
|
||||
* @param aSurfaceKey Key data which uniquely identifies the requested
|
||||
* cache entry.
|
||||
* @return a LookupResult which will contain a DrawableSurface
|
||||
* if a cache entry similar to the one the caller
|
||||
* requested could be found. Callers can use
|
||||
* LookupResult::IsExactMatch() to check whether the
|
||||
* returned surface exactly matches @aSurfaceKey.
|
||||
*/
|
||||
static LookupResult LookupBestMatch(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
|
||||
/**
|
||||
* Insert an ISurfaceProvider into the cache. If an entry with the same
|
||||
* ImageKey and SurfaceKey is already in the cache, Insert returns
|
||||
* FAILURE_ALREADY_PRESENT. If a matching placeholder is already present, it
|
||||
* is replaced.
|
||||
*
|
||||
* Cache entries 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 cache entry is locked if its associated image is
|
||||
* locked. When that image is later unlocked, the cache entry becomes
|
||||
* unlocked too. To become locked again at that point, two things must happen:
|
||||
* the image must become locked again (via LockImage()), and the cache entry
|
||||
* must be touched again (via one of the Lookup() functions).
|
||||
*
|
||||
* All of this means that a very particular procedure has to be followed for
|
||||
* cache entries which cannot be rematerialized. First, they must be inserted
|
||||
* *after* the image is locked with LockImage(); if you use the other order,
|
||||
* the cache entry might expire before LockImage() gets called or before the
|
||||
* entry is touched again by Lookup(). Second, the image they are associated
|
||||
* with must never be unlocked.
|
||||
*
|
||||
* If a cache entry 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 cache entry, which could happen because
|
||||
* of capacity reasons, or because it was already freed by the OS. If the
|
||||
* cache entry isn't associated with a locked image, checking for SUCCESS or
|
||||
* FAILURE is useless: the entry 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 aProvider The new cache entry to insert into the cache.
|
||||
* @return SUCCESS if the cache entry was inserted successfully. (But see above
|
||||
* for more information about when you should check this.)
|
||||
* FAILURE if the cache entry could not be inserted, e.g. for capacity
|
||||
* reasons. (But see above for more information about when you
|
||||
* should check this.)
|
||||
* FAILURE_ALREADY_PRESENT if an entry with the same ImageKey and
|
||||
* SurfaceKey already exists in the cache.
|
||||
*/
|
||||
static InsertOutcome Insert(NotNull<ISurfaceProvider*> aProvider);
|
||||
|
||||
/**
|
||||
* Mark the cache entry @aProvider as having an available surface. This turns
|
||||
* a placeholder cache entry into a normal cache entry. The cache entry
|
||||
* becomes locked if the associated image is locked; otherwise, it starts in
|
||||
* the unlocked state.
|
||||
*
|
||||
* If the cache entry containing @aProvider has already been evicted from the
|
||||
* surface cache, this function has no effect.
|
||||
*
|
||||
* It's illegal to call this function if @aProvider is not a placeholder; by
|
||||
* definition, non-placeholder ISurfaceProviders should have a surface
|
||||
* available already.
|
||||
*
|
||||
* @param aProvider The cache entry that now has a surface available.
|
||||
*/
|
||||
static void SurfaceAvailable(NotNull<ISurfaceProvider*> aProvider);
|
||||
|
||||
/**
|
||||
* Checks if a surface of a given size could possibly be stored in the cache.
|
||||
* If CanHold() returns false, Insert() will always fail to insert the
|
||||
* surface, but the inverse is not true: Insert() may take more information
|
||||
* into account than just image size when deciding whether to cache the
|
||||
* surface, so Insert() may still fail even if CanHold() returns true.
|
||||
*
|
||||
* Use CanHold() to avoid the need to create a temporary surface when we know
|
||||
* for sure the cache can't hold it.
|
||||
*
|
||||
* @param aSize The dimensions of a surface in pixels.
|
||||
* @param aBytesPerPixel How many bytes each pixel of the surface requires.
|
||||
* Defaults to 4, which is appropriate for RGBA or RGBX
|
||||
* images.
|
||||
*
|
||||
* @return false if the surface cache can't hold a surface of that size.
|
||||
*/
|
||||
static bool CanHold(const IntSize& aSize, uint32_t aBytesPerPixel = 4);
|
||||
static bool CanHold(size_t aSize);
|
||||
|
||||
/**
|
||||
* Locks an image. Any of the image's cache entries 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 cache
|
||||
* entries. A call to LockImage() guarantees that entries which are inserted
|
||||
* afterward will not expire before the next call to UnlockImage() or
|
||||
* UnlockSurfaces() for that image. Cache entries 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 cache entries owned by the image may expire at any time.
|
||||
*
|
||||
* All of an image's cache entries are removed by RemoveImage(), whether the
|
||||
* image is locked or not.
|
||||
*
|
||||
* It's safe to call LockImage() on an image that's already locked; this has
|
||||
* no effect.
|
||||
*
|
||||
* You must always unlock any image you lock. You may do this explicitly by
|
||||
* calling UnlockImage(), or implicitly by calling RemoveImage(). Since you're
|
||||
* required to call RemoveImage() when you destroy an image, this doesn't
|
||||
* impose any additional requirements, but it's preferable to call
|
||||
* UnlockImage() earlier if it's possible.
|
||||
*
|
||||
* @param aImageKey The image to lock.
|
||||
*/
|
||||
static void LockImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Unlocks an image, allowing any of its cache entries to expire at any time.
|
||||
*
|
||||
* It's OK to call UnlockImage() on an image that's already unlocked; this has
|
||||
* no effect.
|
||||
*
|
||||
* @param aImageKey The image to unlock.
|
||||
*/
|
||||
static void UnlockImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Unlocks the existing cache entries of an image, allowing them to expire at
|
||||
* any time.
|
||||
*
|
||||
* This does not unlock the image itself, so accessing the cache entries via
|
||||
* Lookup() or LookupBestMatch() will lock them again, and prevent them from
|
||||
* expiring.
|
||||
*
|
||||
* This is intended to be used in situations where it's no longer clear that
|
||||
* all of the cache entries owned by an image are needed. Calling
|
||||
* UnlockSurfaces() and then taking some action that will cause Lookup() to
|
||||
* touch any cache entries that are still useful will permit the remaining
|
||||
* entries to expire from the cache.
|
||||
*
|
||||
* If the image is unlocked, this has no effect.
|
||||
*
|
||||
* @param aImageKey The image which should have its existing cache entries
|
||||
* unlocked.
|
||||
*/
|
||||
static void UnlockEntries(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Removes all cache entries (including placeholders) associated with the
|
||||
* given image from the cache. If the image is locked, it is automatically
|
||||
* unlocked.
|
||||
*
|
||||
* This MUST be called, at a minimum, when an Image which could be storing
|
||||
* entries in the surface cache is destroyed. If another image were allocated
|
||||
* at the same address it could result in subtle, difficult-to-reproduce bugs.
|
||||
*
|
||||
* @param aImageKey The image which should be removed from the cache.
|
||||
*/
|
||||
static void RemoveImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Evicts all evictable entries from the cache.
|
||||
*
|
||||
* All entries are evictable except for entries associated with locked images.
|
||||
* Non-evictable entries can only be removed by RemoveImage().
|
||||
*/
|
||||
static void DiscardAll();
|
||||
|
||||
/**
|
||||
* Collects an accounting of the surfaces contained in the SurfaceCache for
|
||||
* the given image, along with their size and various other metadata.
|
||||
*
|
||||
* This is intended for use with memory reporting.
|
||||
*
|
||||
* @param aImageKey The image to report memory usage for.
|
||||
* @param aCounters An array into which the report for each surface will
|
||||
* be written.
|
||||
* @param aMallocSizeOf A fallback malloc memory reporting function.
|
||||
*/
|
||||
static void CollectSizeOfSurfaces(const ImageKey aImageKey,
|
||||
nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf);
|
||||
|
||||
/**
|
||||
* @return maximum capacity of the SurfaceCache in bytes. This is only exposed
|
||||
* for use by tests; normal code should use CanHold() instead.
|
||||
*/
|
||||
static size_t MaximumCapacity();
|
||||
|
||||
private:
|
||||
virtual ~SurfaceCache() = 0; // Forbid instantiation.
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SurfaceCache_h
|
||||
20
image/SurfaceCacheUtils.cpp
Normal file
20
image/SurfaceCacheUtils.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- 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 "SurfaceCacheUtils.h"
|
||||
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/* static */ void
|
||||
SurfaceCacheUtils::DiscardAll()
|
||||
{
|
||||
SurfaceCache::DiscardAll();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
34
image/SurfaceCacheUtils.h
Normal file
34
image/SurfaceCacheUtils.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- 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_SurfaceCacheUtils_h
|
||||
#define mozilla_image_SurfaceCacheUtils_h
|
||||
|
||||
/**
|
||||
* SurfaceCacheUtils provides an ImageLib-external API to interact with
|
||||
* ImageLib's SurfaceCache.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class SurfaceCacheUtils
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Evicts all evictable entries from the surface cache.
|
||||
*
|
||||
* See the documentation for SurfaceCache::DiscardAll() for the details.
|
||||
*/
|
||||
static void DiscardAll();
|
||||
|
||||
private:
|
||||
virtual ~SurfaceCacheUtils() = 0; // Forbid instantiation.
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SurfaceCacheUtils_h
|
||||
893
image/SurfaceFilters.h
Normal file
893
image/SurfaceFilters.h
Normal file
|
|
@ -0,0 +1,893 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* This header contains various SurfaceFilter implementations that apply
|
||||
* transformations to image data, for usage with SurfacePipe.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_SurfaceFilters_h
|
||||
#define mozilla_image_SurfaceFilters_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
#include "DownscalingFilter.h"
|
||||
#include "SurfaceCache.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// DeinterlacingFilter
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename PixelType, typename Next> class DeinterlacingFilter;
|
||||
|
||||
/**
|
||||
* A configuration struct for DeinterlacingFilter.
|
||||
*
|
||||
* The 'PixelType' template parameter should be either uint32_t (for output to a
|
||||
* SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
|
||||
*/
|
||||
template <typename PixelType>
|
||||
struct DeinterlacingConfig
|
||||
{
|
||||
template <typename Next> using Filter = DeinterlacingFilter<PixelType, Next>;
|
||||
bool mProgressiveDisplay; /// If true, duplicate rows during deinterlacing
|
||||
/// to make progressive display look better, at
|
||||
/// the cost of some performance.
|
||||
};
|
||||
|
||||
/**
|
||||
* DeinterlacingFilter performs deinterlacing by reordering the rows that are
|
||||
* written to it.
|
||||
*
|
||||
* The 'PixelType' template parameter should be either uint32_t (for output to a
|
||||
* SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
|
||||
*
|
||||
* The 'Next' template parameter specifies the next filter in the chain.
|
||||
*/
|
||||
template <typename PixelType, typename Next>
|
||||
class DeinterlacingFilter final : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
DeinterlacingFilter()
|
||||
: mInputRow(0)
|
||||
, mOutputRow(0)
|
||||
, mPass(0)
|
||||
, mProgressiveDisplay(true)
|
||||
{ }
|
||||
|
||||
template <typename... Rest>
|
||||
nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig, Rest... aRest)
|
||||
{
|
||||
nsresult rv = mNext.Configure(aRest...);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (sizeof(PixelType) == 1 && !mNext.IsValidPalettedPipe()) {
|
||||
NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (sizeof(PixelType) == 4 && mNext.IsValidPalettedPipe()) {
|
||||
NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
gfx::IntSize outputSize = mNext.InputSize();
|
||||
mProgressiveDisplay = aConfig.mProgressiveDisplay;
|
||||
|
||||
const uint32_t bufferSize = outputSize.width *
|
||||
outputSize.height *
|
||||
sizeof(PixelType);
|
||||
|
||||
// Use the size of the SurfaceCache as a heuristic to avoid gigantic
|
||||
// allocations. Even if DownscalingFilter allowed us to allocate space for
|
||||
// the output image, the deinterlacing buffer may still be too big, and
|
||||
// fallible allocation won't always save us in the presence of overcommit.
|
||||
if (!SurfaceCache::CanHold(bufferSize)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Allocate the buffer, which contains deinterlaced scanlines of the image.
|
||||
// The buffer is necessary so that we can output rows which have already
|
||||
// been deinterlaced again on subsequent passes. Since a later stage in the
|
||||
// pipeline may be transforming the rows it receives (for example, by
|
||||
// downscaling them), the rows may no longer exist in their original form on
|
||||
// the surface itself.
|
||||
mBuffer.reset(new (fallible) uint8_t[bufferSize]);
|
||||
if (MOZ_UNLIKELY(!mBuffer)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Clear the buffer to avoid writing uninitialized memory to the output.
|
||||
memset(mBuffer.get(), 0, bufferSize);
|
||||
|
||||
ConfigureFilter(outputSize, sizeof(PixelType));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool IsValidPalettedPipe() const override
|
||||
{
|
||||
return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe();
|
||||
}
|
||||
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
|
||||
{
|
||||
return mNext.TakeInvalidRect();
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override
|
||||
{
|
||||
mNext.ResetToFirstRow();
|
||||
mPass = 0;
|
||||
mInputRow = 0;
|
||||
mOutputRow = InterlaceOffset(mPass);
|
||||
return GetRowPointer(mOutputRow);
|
||||
}
|
||||
|
||||
uint8_t* DoAdvanceRow() override
|
||||
{
|
||||
if (mPass >= 4) {
|
||||
return nullptr; // We already finished all passes.
|
||||
}
|
||||
if (mInputRow >= InputSize().height) {
|
||||
return nullptr; // We already got all the input rows we expect.
|
||||
}
|
||||
|
||||
// Duplicate from the first Haeberli row to the remaining Haeberli rows
|
||||
// within the buffer.
|
||||
DuplicateRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
|
||||
HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), mOutputRow));
|
||||
|
||||
// Write the current set of Haeberli rows (which contains the current row)
|
||||
// to the next stage in the pipeline.
|
||||
OutputRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
|
||||
HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), mOutputRow));
|
||||
|
||||
// Determine which output row the next input row corresponds to.
|
||||
bool advancedPass = false;
|
||||
uint32_t stride = InterlaceStride(mPass);
|
||||
int32_t nextOutputRow = mOutputRow + stride;
|
||||
while (nextOutputRow >= InputSize().height) {
|
||||
// Copy any remaining rows from the buffer.
|
||||
if (!advancedPass) {
|
||||
OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), mOutputRow),
|
||||
InputSize().height);
|
||||
}
|
||||
|
||||
// We finished the current pass; advance to the next one.
|
||||
mPass++;
|
||||
if (mPass >= 4) {
|
||||
return nullptr; // Finished all passes.
|
||||
}
|
||||
|
||||
// Tell the next pipeline stage that we're starting the next pass.
|
||||
mNext.ResetToFirstRow();
|
||||
|
||||
// Update our state to reflect the pass change.
|
||||
advancedPass = true;
|
||||
stride = InterlaceStride(mPass);
|
||||
nextOutputRow = InterlaceOffset(mPass);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(nextOutputRow >= 0);
|
||||
MOZ_ASSERT(nextOutputRow < InputSize().height);
|
||||
|
||||
MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
|
||||
nextOutputRow) >= 0);
|
||||
MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
|
||||
nextOutputRow) < InputSize().height);
|
||||
MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
|
||||
nextOutputRow) <= nextOutputRow);
|
||||
|
||||
MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), nextOutputRow) >= 0);
|
||||
MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), nextOutputRow)
|
||||
<= InputSize().height);
|
||||
MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), nextOutputRow)
|
||||
> nextOutputRow);
|
||||
|
||||
int32_t nextHaeberliOutputRow =
|
||||
HaeberliOutputStartRow(mPass, mProgressiveDisplay, nextOutputRow);
|
||||
|
||||
// Copy rows from the buffer until we reach the desired output row.
|
||||
if (advancedPass) {
|
||||
OutputRows(0, nextHaeberliOutputRow);
|
||||
} else {
|
||||
OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
|
||||
InputSize(), mOutputRow),
|
||||
nextHaeberliOutputRow);
|
||||
}
|
||||
|
||||
// Update our position within the buffer.
|
||||
mInputRow++;
|
||||
mOutputRow = nextOutputRow;
|
||||
|
||||
// We'll actually write to the first Haeberli output row, then copy it until
|
||||
// we reach the last Haeberli output row. The assertions above make sure
|
||||
// this always includes mOutputRow.
|
||||
return GetRowPointer(nextHaeberliOutputRow);
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t InterlaceOffset(uint32_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(aPass < 4, "Invalid pass");
|
||||
static const uint8_t offset[] = { 0, 4, 2, 1 };
|
||||
return offset[aPass];
|
||||
}
|
||||
|
||||
static uint32_t InterlaceStride(uint32_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(aPass < 4, "Invalid pass");
|
||||
static const uint8_t stride[] = { 8, 8, 4, 2 };
|
||||
return stride[aPass];
|
||||
}
|
||||
|
||||
static int32_t HaeberliOutputStartRow(uint32_t aPass,
|
||||
bool aProgressiveDisplay,
|
||||
int32_t aOutputRow)
|
||||
{
|
||||
MOZ_ASSERT(aPass < 4, "Invalid pass");
|
||||
static const uint8_t firstRowOffset[] = { 3, 1, 0, 0 };
|
||||
|
||||
if (aProgressiveDisplay) {
|
||||
return std::max(aOutputRow - firstRowOffset[aPass], 0);
|
||||
} else {
|
||||
return aOutputRow;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t HaeberliOutputUntilRow(uint32_t aPass,
|
||||
bool aProgressiveDisplay,
|
||||
const gfx::IntSize& aInputSize,
|
||||
int32_t aOutputRow)
|
||||
{
|
||||
MOZ_ASSERT(aPass < 4, "Invalid pass");
|
||||
static const uint8_t lastRowOffset[] = { 4, 2, 1, 0 };
|
||||
|
||||
if (aProgressiveDisplay) {
|
||||
return std::min(aOutputRow + lastRowOffset[aPass],
|
||||
aInputSize.height - 1)
|
||||
+ 1; // Add one because this is an open interval on the right.
|
||||
} else {
|
||||
return aOutputRow + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void DuplicateRows(int32_t aStart, int32_t aUntil)
|
||||
{
|
||||
MOZ_ASSERT(aStart >= 0);
|
||||
MOZ_ASSERT(aUntil >= 0);
|
||||
|
||||
if (aUntil <= aStart || aStart >= InputSize().height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The source row is the first row in the range.
|
||||
const uint8_t* sourceRowPointer = GetRowPointer(aStart);
|
||||
|
||||
// We duplicate the source row into each subsequent row in the range.
|
||||
for (int32_t destRow = aStart + 1 ; destRow < aUntil ; ++destRow) {
|
||||
uint8_t* destRowPointer = GetRowPointer(destRow);
|
||||
memcpy(destRowPointer, sourceRowPointer, InputSize().width * sizeof(PixelType));
|
||||
}
|
||||
}
|
||||
|
||||
void OutputRows(int32_t aStart, int32_t aUntil)
|
||||
{
|
||||
MOZ_ASSERT(aStart >= 0);
|
||||
MOZ_ASSERT(aUntil >= 0);
|
||||
|
||||
if (aUntil <= aStart || aStart >= InputSize().height) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t rowToOutput = aStart; rowToOutput < aUntil; ++rowToOutput) {
|
||||
mNext.WriteBuffer(reinterpret_cast<PixelType*>(GetRowPointer(rowToOutput)));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* GetRowPointer(uint32_t aRow) const
|
||||
{
|
||||
uint32_t offset = aRow * InputSize().width * sizeof(PixelType);
|
||||
MOZ_ASSERT(offset < InputSize().width * InputSize().height * sizeof(PixelType),
|
||||
"Start of row is outside of image");
|
||||
MOZ_ASSERT(offset + InputSize().width * sizeof(PixelType)
|
||||
<= InputSize().width * InputSize().height * sizeof(PixelType),
|
||||
"End of row is outside of image");
|
||||
return mBuffer.get() + offset;
|
||||
}
|
||||
|
||||
Next mNext; /// The next SurfaceFilter in the chain.
|
||||
|
||||
UniquePtr<uint8_t[]> mBuffer; /// The buffer used to store reordered rows.
|
||||
int32_t mInputRow; /// The current row we're reading. (0-indexed)
|
||||
int32_t mOutputRow; /// The current row we're writing. (0-indexed)
|
||||
uint8_t mPass; /// Which pass we're on. (0-indexed)
|
||||
bool mProgressiveDisplay; /// If true, duplicate rows to optimize for
|
||||
/// progressive display.
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// RemoveFrameRectFilter
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename Next> class RemoveFrameRectFilter;
|
||||
|
||||
/**
|
||||
* A configuration struct for RemoveFrameRectFilter.
|
||||
*/
|
||||
struct RemoveFrameRectConfig
|
||||
{
|
||||
template <typename Next> using Filter = RemoveFrameRectFilter<Next>;
|
||||
gfx::IntRect mFrameRect; /// The surface subrect which contains data.
|
||||
};
|
||||
|
||||
/**
|
||||
* RemoveFrameRectFilter turns an image with a frame rect that does not match
|
||||
* its logical size into an image with no frame rect. It does this by writing
|
||||
* transparent pixels into any padding regions and throwing away excess data.
|
||||
*
|
||||
* The 'Next' template parameter specifies the next filter in the chain.
|
||||
*/
|
||||
template <typename Next>
|
||||
class RemoveFrameRectFilter final : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
RemoveFrameRectFilter()
|
||||
: mRow(0)
|
||||
{ }
|
||||
|
||||
template <typename... Rest>
|
||||
nsresult Configure(const RemoveFrameRectConfig& aConfig, Rest... aRest)
|
||||
{
|
||||
nsresult rv = mNext.Configure(aRest...);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mNext.IsValidPalettedPipe()) {
|
||||
NS_WARNING("RemoveFrameRectFilter used with paletted pipe?");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
mFrameRect = mUnclampedFrameRect = aConfig.mFrameRect;
|
||||
gfx::IntSize outputSize = mNext.InputSize();
|
||||
|
||||
// Forbid frame rects with negative size.
|
||||
if (aConfig.mFrameRect.width < 0 || aConfig.mFrameRect.height < 0) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Clamp mFrameRect to the output size.
|
||||
gfx::IntRect outputRect(0, 0, outputSize.width, outputSize.height);
|
||||
mFrameRect = mFrameRect.Intersect(outputRect);
|
||||
|
||||
// If there's no intersection, |mFrameRect| will be an empty rect positioned
|
||||
// at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
|
||||
// not what we want. Force it to (0, 0) in that case.
|
||||
if (mFrameRect.IsEmpty()) {
|
||||
mFrameRect.MoveTo(0, 0);
|
||||
}
|
||||
|
||||
// We don't need an intermediate buffer unless the unclamped frame rect
|
||||
// width is larger than the clamped frame rect width. In that case, the
|
||||
// caller will end up writing data that won't end up in the final image at
|
||||
// all, and we'll need a buffer to give that data a place to go.
|
||||
if (mFrameRect.width < mUnclampedFrameRect.width) {
|
||||
mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width *
|
||||
sizeof(uint32_t)]);
|
||||
if (MOZ_UNLIKELY(!mBuffer)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
|
||||
{
|
||||
return mNext.TakeInvalidRect();
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override
|
||||
{
|
||||
uint8_t* rowPtr = mNext.ResetToFirstRow();
|
||||
if (rowPtr == nullptr) {
|
||||
mRow = mFrameRect.YMost();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mRow = mUnclampedFrameRect.y;
|
||||
|
||||
// Advance the next pipeline stage to the beginning of the frame rect,
|
||||
// outputting blank rows.
|
||||
if (mFrameRect.y > 0) {
|
||||
for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.y ; ++rowToOutput) {
|
||||
mNext.WriteEmptyRow();
|
||||
}
|
||||
}
|
||||
|
||||
// We're at the beginning of the frame rect now, so return if we're either
|
||||
// ready for input or we're already done.
|
||||
rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
|
||||
if (!mFrameRect.IsEmpty() || rowPtr == nullptr) {
|
||||
// Note that the pointer we're returning is for the next row we're
|
||||
// actually going to write to, but we may discard writes before that point
|
||||
// if mRow < mFrameRect.y.
|
||||
return AdjustRowPointer(rowPtr);
|
||||
}
|
||||
|
||||
// We've finished the region specified by the frame rect, but the frame rect
|
||||
// is empty, so we need to output the rest of the image immediately. Advance
|
||||
// to the end of the next pipeline stage's buffer, outputting blank rows.
|
||||
while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
|
||||
|
||||
mRow = mFrameRect.YMost();
|
||||
return nullptr; // We're done.
|
||||
}
|
||||
|
||||
uint8_t* DoAdvanceRow() override
|
||||
{
|
||||
uint8_t* rowPtr = nullptr;
|
||||
|
||||
const int32_t currentRow = mRow;
|
||||
mRow++;
|
||||
|
||||
if (currentRow < mFrameRect.y) {
|
||||
// This row is outside of the frame rect, so just drop it on the floor.
|
||||
rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
|
||||
return AdjustRowPointer(rowPtr);
|
||||
} else if (currentRow >= mFrameRect.YMost()) {
|
||||
NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If we had to buffer, copy the data. Otherwise, just advance the row.
|
||||
if (mBuffer) {
|
||||
// We write from the beginning of the buffer unless |mUnclampedFrameRect.x|
|
||||
// is negative; if that's the case, we have to skip the portion of the
|
||||
// unclamped frame rect that's outside the row.
|
||||
uint32_t* source = reinterpret_cast<uint32_t*>(mBuffer.get()) -
|
||||
std::min(mUnclampedFrameRect.x, 0);
|
||||
|
||||
// We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
|
||||
// already clamped these values to the size of the output, so we don't
|
||||
// have to worry about bounds checking here (though WriteBuffer() will do
|
||||
// it for us in any case).
|
||||
WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.width);
|
||||
|
||||
rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get()
|
||||
: nullptr;
|
||||
} else {
|
||||
rowPtr = mNext.AdvanceRow();
|
||||
}
|
||||
|
||||
// If there's still more data coming or we're already done, just adjust the
|
||||
// pointer and return.
|
||||
if (mRow < mFrameRect.YMost() || rowPtr == nullptr) {
|
||||
return AdjustRowPointer(rowPtr);
|
||||
}
|
||||
|
||||
// We've finished the region specified by the frame rect. Advance to the end
|
||||
// of the next pipeline stage's buffer, outputting blank rows.
|
||||
while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
|
||||
|
||||
mRow = mFrameRect.YMost();
|
||||
return nullptr; // We're done.
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const
|
||||
{
|
||||
if (mBuffer) {
|
||||
MOZ_ASSERT(aNextRowPointer == mBuffer.get() || aNextRowPointer == nullptr);
|
||||
return aNextRowPointer; // No adjustment needed for an intermediate buffer.
|
||||
}
|
||||
|
||||
if (mFrameRect.IsEmpty() ||
|
||||
mRow >= mFrameRect.YMost() ||
|
||||
aNextRowPointer == nullptr) {
|
||||
return nullptr; // Nothing left to write.
|
||||
}
|
||||
|
||||
return aNextRowPointer + mFrameRect.x * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
Next mNext; /// The next SurfaceFilter in the chain.
|
||||
|
||||
gfx::IntRect mFrameRect; /// The surface subrect which contains data,
|
||||
/// clamped to the image size.
|
||||
gfx::IntRect mUnclampedFrameRect; /// The frame rect before clamping.
|
||||
UniquePtr<uint8_t[]> mBuffer; /// The intermediate buffer, if one is
|
||||
/// necessary because the frame rect width
|
||||
/// is larger than the image's logical width.
|
||||
int32_t mRow; /// The row in unclamped frame rect space
|
||||
/// that we're currently writing.
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ADAM7InterpolatingFilter
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename Next> class ADAM7InterpolatingFilter;
|
||||
|
||||
/**
|
||||
* A configuration struct for ADAM7InterpolatingFilter.
|
||||
*/
|
||||
struct ADAM7InterpolatingConfig
|
||||
{
|
||||
template <typename Next> using Filter = ADAM7InterpolatingFilter<Next>;
|
||||
};
|
||||
|
||||
/**
|
||||
* ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
|
||||
* interlaced image.
|
||||
*
|
||||
* ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
|
||||
* of pixels in each block receives their final values, according to the
|
||||
* following pattern:
|
||||
*
|
||||
* 1 6 4 6 2 6 4 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 5 6 5 6 5 6 5 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 3 6 4 6 3 6 4 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 5 6 5 6 5 6 5 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
*
|
||||
* When rendering the pixels that have not yet received their final values, we
|
||||
* can get much better intermediate results if we interpolate between
|
||||
* the pixels we *have* gotten so far. This filter performs bilinear
|
||||
* interpolation by first performing linear interpolation horizontally for each
|
||||
* "important" row (which we'll define as a row that has received any pixels
|
||||
* with final values at all) and then performing linear interpolation vertically
|
||||
* to produce pixel values for rows which aren't important on the current pass.
|
||||
*
|
||||
* Note that this filter totally ignores the data which is written to rows which
|
||||
* aren't important on the current pass! It's fine to write nothing at all for
|
||||
* these rows, although doing so won't cause any harm.
|
||||
*
|
||||
* XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
|
||||
*
|
||||
* The 'Next' template parameter specifies the next filter in the chain.
|
||||
*/
|
||||
template <typename Next>
|
||||
class ADAM7InterpolatingFilter final : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
ADAM7InterpolatingFilter()
|
||||
: mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
|
||||
// DoResetToFirstRow() doesn't have to special case the first pass.
|
||||
, mRow(0)
|
||||
{ }
|
||||
|
||||
template <typename... Rest>
|
||||
nsresult Configure(const ADAM7InterpolatingConfig& aConfig, Rest... aRest)
|
||||
{
|
||||
nsresult rv = mNext.Configure(aRest...);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mNext.IsValidPalettedPipe()) {
|
||||
NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// We have two intermediate buffers, one for the previous row with final
|
||||
// pixel values and one for the row that the previous filter in the chain is
|
||||
// currently writing to.
|
||||
size_t inputWidthInBytes = mNext.InputSize().width * sizeof(uint32_t);
|
||||
mPreviousRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
|
||||
if (MOZ_UNLIKELY(!mPreviousRow)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
mCurrentRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
|
||||
if (MOZ_UNLIKELY(!mCurrentRow)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memset(mPreviousRow.get(), 0, inputWidthInBytes);
|
||||
memset(mCurrentRow.get(), 0, inputWidthInBytes);
|
||||
|
||||
ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
|
||||
{
|
||||
return mNext.TakeInvalidRect();
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override
|
||||
{
|
||||
mRow = 0;
|
||||
mPass = std::min(mPass + 1, 7);
|
||||
|
||||
uint8_t* rowPtr = mNext.ResetToFirstRow();
|
||||
if (mPass == 7) {
|
||||
// Short circuit this filter on the final pass, since all pixels have
|
||||
// their final values at that point.
|
||||
return rowPtr;
|
||||
}
|
||||
|
||||
return mCurrentRow.get();
|
||||
}
|
||||
|
||||
uint8_t* DoAdvanceRow() override
|
||||
{
|
||||
MOZ_ASSERT(0 < mPass && mPass <= 7, "Invalid pass");
|
||||
|
||||
int32_t currentRow = mRow;
|
||||
++mRow;
|
||||
|
||||
if (mPass == 7) {
|
||||
// On the final pass we short circuit this filter totally.
|
||||
return mNext.AdvanceRow();
|
||||
}
|
||||
|
||||
const int32_t lastImportantRow = LastImportantRow(InputSize().height, mPass);
|
||||
if (currentRow > lastImportantRow) {
|
||||
return nullptr; // This pass is already complete.
|
||||
}
|
||||
|
||||
if (!IsImportantRow(currentRow, mPass)) {
|
||||
// We just ignore whatever the caller gives us for these rows. We'll
|
||||
// interpolate them in later.
|
||||
return mCurrentRow.get();
|
||||
}
|
||||
|
||||
// This is an important row. We need to perform horizontal interpolation for
|
||||
// these rows.
|
||||
InterpolateHorizontally(mCurrentRow.get(), InputSize().width, mPass);
|
||||
|
||||
// Interpolate vertically between the previous important row and the current
|
||||
// important row. We skip this if the current row is 0 (which is always an
|
||||
// important row), because in that case there is no previous important row
|
||||
// to interpolate with.
|
||||
if (currentRow != 0) {
|
||||
InterpolateVertically(mPreviousRow.get(), mCurrentRow.get(), mPass, mNext);
|
||||
}
|
||||
|
||||
// Write out the current row itself, which, being an important row, does not
|
||||
// need vertical interpolation.
|
||||
uint32_t* currentRowAsPixels = reinterpret_cast<uint32_t*>(mCurrentRow.get());
|
||||
mNext.WriteBuffer(currentRowAsPixels);
|
||||
|
||||
if (currentRow == lastImportantRow) {
|
||||
// This is the last important row, which completes this pass. Note that
|
||||
// for very small images, this may be the first row! Since there won't be
|
||||
// another important row, there's nothing to interpolate with vertically,
|
||||
// so we just duplicate this row until the end of the image.
|
||||
while (mNext.WriteBuffer(currentRowAsPixels) == WriteState::NEED_MORE_DATA) { }
|
||||
|
||||
// All of the remaining rows in the image were determined above, so we're done.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The current row is now the previous important row; save it.
|
||||
Swap(mPreviousRow, mCurrentRow);
|
||||
|
||||
MOZ_ASSERT(mRow < InputSize().height, "Reached the end of the surface without "
|
||||
"hitting the last important row?");
|
||||
|
||||
return mCurrentRow.get();
|
||||
}
|
||||
|
||||
private:
|
||||
static void InterpolateVertically(uint8_t* aPreviousRow,
|
||||
uint8_t* aCurrentRow,
|
||||
uint8_t aPass,
|
||||
SurfaceFilter& aNext)
|
||||
{
|
||||
const float* weights = InterpolationWeights(ImportantRowStride(aPass));
|
||||
|
||||
// We need to interpolate vertically to generate the rows between the
|
||||
// previous important row and the next one. Recall that important rows are
|
||||
// rows which contain at least some final pixels; see
|
||||
// InterpolateHorizontally() for some additional explanation as to what that
|
||||
// means. Note that we've already written out the previous important row, so
|
||||
// we start the iteration at 1.
|
||||
for (int32_t outRow = 1; outRow < ImportantRowStride(aPass); ++outRow) {
|
||||
const float weight = weights[outRow];
|
||||
|
||||
// We iterate through the previous and current important row every time we
|
||||
// write out an interpolated row, so we need to copy the pointers.
|
||||
uint8_t* prevRowBytes = aPreviousRow;
|
||||
uint8_t* currRowBytes = aCurrentRow;
|
||||
|
||||
// Write out the interpolated pixels. Interpolation is componentwise.
|
||||
aNext.template WritePixelsToRow<uint32_t>([&]{
|
||||
uint32_t pixel = 0;
|
||||
auto* component = reinterpret_cast<uint8_t*>(&pixel);
|
||||
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
|
||||
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
|
||||
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
|
||||
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
|
||||
return AsVariant(pixel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void InterpolateHorizontally(uint8_t* aRow, int32_t aWidth, uint8_t aPass)
|
||||
{
|
||||
// Collect the data we'll need to perform horizontal interpolation. The
|
||||
// terminology here bears some explanation: a "final pixel" is a pixel which
|
||||
// has received its final value. On each pass, a new set of pixels receives
|
||||
// their final value; see the diagram above of the 8x8 pattern that ADAM7
|
||||
// uses. Any pixel which hasn't received its final value on this pass
|
||||
// derives its value from either horizontal or vertical interpolation
|
||||
// instead.
|
||||
const size_t finalPixelStride = FinalPixelStride(aPass);
|
||||
const size_t finalPixelStrideBytes = finalPixelStride * sizeof(uint32_t);
|
||||
const size_t lastFinalPixel = LastFinalPixel(aWidth, aPass);
|
||||
const size_t lastFinalPixelBytes = lastFinalPixel * sizeof(uint32_t);
|
||||
const float* weights = InterpolationWeights(finalPixelStride);
|
||||
|
||||
// Interpolate blocks of pixels which lie between two final pixels.
|
||||
// Horizontal interpolation is done in place, as we'll need the results
|
||||
// later when we vertically interpolate.
|
||||
for (size_t blockBytes = 0;
|
||||
blockBytes < lastFinalPixelBytes;
|
||||
blockBytes += finalPixelStrideBytes) {
|
||||
uint8_t* finalPixelA = aRow + blockBytes;
|
||||
uint8_t* finalPixelB = aRow + blockBytes + finalPixelStrideBytes;
|
||||
|
||||
MOZ_ASSERT(finalPixelA < aRow + aWidth * sizeof(uint32_t),
|
||||
"Running off end of buffer");
|
||||
MOZ_ASSERT(finalPixelB < aRow + aWidth * sizeof(uint32_t),
|
||||
"Running off end of buffer");
|
||||
|
||||
// Interpolate the individual pixels componentwise. Note that we start
|
||||
// iteration at 1 since we don't need to apply any interpolation to the
|
||||
// first pixel in the block, which has its final value.
|
||||
for (size_t pixelIndex = 1; pixelIndex < finalPixelStride; ++pixelIndex) {
|
||||
const float weight = weights[pixelIndex];
|
||||
uint8_t* pixel = aRow + blockBytes + pixelIndex * sizeof(uint32_t);
|
||||
|
||||
MOZ_ASSERT(pixel < aRow + aWidth * sizeof(uint32_t), "Running off end of buffer");
|
||||
|
||||
for (size_t component = 0; component < sizeof(uint32_t); ++component) {
|
||||
pixel[component] =
|
||||
InterpolateByte(finalPixelA[component], finalPixelB[component], weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the pixels after the last final pixel in the row, there isn't a
|
||||
// second final pixel to interpolate with, so just duplicate.
|
||||
uint32_t* rowPixels = reinterpret_cast<uint32_t*>(aRow);
|
||||
uint32_t pixelToDuplicate = rowPixels[lastFinalPixel];
|
||||
for (int32_t pixelIndex = lastFinalPixel + 1;
|
||||
pixelIndex < aWidth;
|
||||
++pixelIndex) {
|
||||
MOZ_ASSERT(pixelIndex < aWidth, "Running off end of buffer");
|
||||
rowPixels[pixelIndex] = pixelToDuplicate;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
|
||||
{
|
||||
return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
|
||||
}
|
||||
|
||||
static int32_t ImportantRowStride(uint8_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
|
||||
|
||||
// The stride between important rows for each pass, with a dummy value for
|
||||
// the nonexistent pass 0.
|
||||
static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1 };
|
||||
|
||||
return strides[aPass];
|
||||
}
|
||||
|
||||
static bool IsImportantRow(int32_t aRow, uint8_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(aRow >= 0);
|
||||
|
||||
// Whether the row is important comes down to divisibility by the stride for
|
||||
// this pass, which is always a power of 2, so we can check using a mask.
|
||||
int32_t mask = ImportantRowStride(aPass) - 1;
|
||||
return (aRow & mask) == 0;
|
||||
}
|
||||
|
||||
static int32_t LastImportantRow(int32_t aHeight, uint8_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(aHeight > 0);
|
||||
|
||||
// We can find the last important row using the same mask trick as above.
|
||||
int32_t lastRow = aHeight - 1;
|
||||
int32_t mask = ImportantRowStride(aPass) - 1;
|
||||
return lastRow - (lastRow & mask);
|
||||
}
|
||||
|
||||
static size_t FinalPixelStride(uint8_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
|
||||
|
||||
// The stride between the final pixels in important rows for each pass, with
|
||||
// a dummy value for the nonexistent pass 0.
|
||||
static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1 };
|
||||
|
||||
return strides[aPass];
|
||||
}
|
||||
|
||||
static size_t LastFinalPixel(int32_t aWidth, uint8_t aPass)
|
||||
{
|
||||
MOZ_ASSERT(aWidth >= 0);
|
||||
|
||||
// Again, we can use the mask trick above to find the last important pixel.
|
||||
int32_t lastColumn = aWidth - 1;
|
||||
size_t mask = FinalPixelStride(aPass) - 1;
|
||||
return lastColumn - (lastColumn & mask);
|
||||
}
|
||||
|
||||
static const float* InterpolationWeights(int32_t aStride)
|
||||
{
|
||||
// Precalculated interpolation weights. These are used to interpolate
|
||||
// between final pixels or between important rows. Although no interpolation
|
||||
// is actually applied to the previous final pixel or important row value,
|
||||
// the arrays still start with 1.0f, which is always skipped, primarily
|
||||
// because otherwise |stride1Weights| would have zero elements.
|
||||
static float stride8Weights[] =
|
||||
{ 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
|
||||
static float stride4Weights[] = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
|
||||
static float stride2Weights[] = { 1.0f, 1 / 2.0f };
|
||||
static float stride1Weights[] = { 1.0f };
|
||||
|
||||
switch (aStride) {
|
||||
case 8: return stride8Weights;
|
||||
case 4: return stride4Weights;
|
||||
case 2: return stride2Weights;
|
||||
case 1: return stride1Weights;
|
||||
default: MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
Next mNext; /// The next SurfaceFilter in the chain.
|
||||
|
||||
UniquePtr<uint8_t[]> mPreviousRow; /// The last important row (i.e., row with
|
||||
/// final pixel values) that got written to.
|
||||
UniquePtr<uint8_t[]> mCurrentRow; /// The row that's being written to right
|
||||
/// now.
|
||||
uint8_t mPass; /// Which ADAM7 pass we're on. Valid passes
|
||||
/// are 1..7 during processing and 0 prior
|
||||
/// to configuraiton.
|
||||
int32_t mRow; /// The row we're currently reading.
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SurfaceFilters_h
|
||||
73
image/SurfaceFlags.h
Normal file
73
image/SurfaceFlags.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of SurfaceFlags, returns a set of imgIContainer FLAG_* flags with
|
||||
* the corresponding flags set.
|
||||
*/
|
||||
inline uint32_t
|
||||
FromSurfaceFlags(SurfaceFlags aFlags)
|
||||
{
|
||||
uint32_t flags = imgIContainer::DECODE_FLAGS_DEFAULT;
|
||||
if (aFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA) {
|
||||
flags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
|
||||
}
|
||||
if (aFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
|
||||
flags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SurfaceFlags_h
|
||||
199
image/SurfacePipe.cpp
Normal file
199
image/SurfacePipe.cpp
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "SurfacePipe.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "Decoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
using std::min;
|
||||
|
||||
/* static */ UniquePtr<NullSurfaceSink> NullSurfaceSink::sSingleton;
|
||||
|
||||
/* static */ NullSurfaceSink*
|
||||
NullSurfaceSink::Singleton()
|
||||
{
|
||||
if (!sSingleton) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
sSingleton = MakeUnique<NullSurfaceSink>();
|
||||
ClearOnShutdown(&sSingleton);
|
||||
|
||||
DebugOnly<nsresult> rv = sSingleton->Configure(NullSurfaceConfig { });
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv), "Couldn't configure a NullSurfaceSink?");
|
||||
}
|
||||
|
||||
return sSingleton.get();
|
||||
}
|
||||
|
||||
nsresult
|
||||
NullSurfaceSink::Configure(const NullSurfaceConfig& aConfig)
|
||||
{
|
||||
// Note that the choice of uint32_t as the pixel size here is more or less
|
||||
// arbitrary, since you cannot write to a NullSurfaceSink anyway, but uint32_t
|
||||
// is a natural choice since most SurfacePipes will be for BGRA/BGRX surfaces.
|
||||
ConfigureFilter(IntSize(), sizeof(uint32_t));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<SurfaceInvalidRect>
|
||||
AbstractSurfaceSink::TakeInvalidRect()
|
||||
{
|
||||
if (mInvalidRect.IsEmpty()) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
SurfaceInvalidRect invalidRect;
|
||||
invalidRect.mInputSpaceRect = invalidRect.mOutputSpaceRect = mInvalidRect;
|
||||
|
||||
// Forget about the invalid rect we're returning.
|
||||
mInvalidRect = IntRect();
|
||||
|
||||
return Some(invalidRect);
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
AbstractSurfaceSink::DoResetToFirstRow()
|
||||
{
|
||||
mRow = 0;
|
||||
return GetRowPointer();
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
AbstractSurfaceSink::DoAdvanceRow()
|
||||
{
|
||||
if (mRow >= uint32_t(InputSize().height)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If we're vertically flipping the output, we need to flip the invalid rect. Since we're
|
||||
// dealing with an axis-aligned rect, only the y coordinate needs to change.
|
||||
int32_t invalidY = mFlipVertically
|
||||
? InputSize().height - (mRow + 1)
|
||||
: mRow;
|
||||
mInvalidRect.UnionRect(mInvalidRect,
|
||||
IntRect(0, invalidY, InputSize().width, 1));
|
||||
|
||||
mRow = min(uint32_t(InputSize().height), mRow + 1);
|
||||
|
||||
return mRow < uint32_t(InputSize().height) ? GetRowPointer()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SurfaceSink::Configure(const SurfaceConfig& aConfig)
|
||||
{
|
||||
// For non-paletted surfaces, the surface size is just the output size.
|
||||
IntSize surfaceSize = aConfig.mOutputSize;
|
||||
|
||||
// Non-paletted surfaces should not have frame rects, so we just pass
|
||||
// AllocateFrame() a frame rect which covers the entire surface.
|
||||
IntRect frameRect(0, 0, surfaceSize.width, surfaceSize.height);
|
||||
|
||||
// Allocate the frame.
|
||||
// XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
|
||||
// to allocate the frame directly here and get rid of Decoder::AllocateFrame
|
||||
// altogether.
|
||||
nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
|
||||
surfaceSize,
|
||||
frameRect,
|
||||
aConfig.mFormat);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mImageData = aConfig.mDecoder->mImageData;
|
||||
mImageDataLength = aConfig.mDecoder->mImageDataLength;
|
||||
mFlipVertically = aConfig.mFlipVertically;
|
||||
|
||||
MOZ_ASSERT(mImageData);
|
||||
MOZ_ASSERT(mImageDataLength ==
|
||||
uint32_t(surfaceSize.width * surfaceSize.height * sizeof(uint32_t)));
|
||||
|
||||
ConfigureFilter(surfaceSize, sizeof(uint32_t));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
SurfaceSink::GetRowPointer() const
|
||||
{
|
||||
// If we're flipping vertically, reverse the order in which we traverse the
|
||||
// rows.
|
||||
uint32_t row = mFlipVertically
|
||||
? InputSize().height - (mRow + 1)
|
||||
: mRow;
|
||||
|
||||
uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint32_t);
|
||||
|
||||
MOZ_ASSERT(rowPtr >= mImageData);
|
||||
MOZ_ASSERT(rowPtr < mImageData + mImageDataLength);
|
||||
MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint32_t) <=
|
||||
mImageData + mImageDataLength);
|
||||
|
||||
return rowPtr;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig)
|
||||
{
|
||||
// For paletted surfaces, the surface size is the size of the frame rect.
|
||||
IntSize surfaceSize = aConfig.mFrameRect.Size();
|
||||
|
||||
// Allocate the frame.
|
||||
// XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
|
||||
// to allocate the frame directly here and get rid of Decoder::AllocateFrame
|
||||
// altogether.
|
||||
nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
|
||||
aConfig.mOutputSize,
|
||||
aConfig.mFrameRect,
|
||||
aConfig.mFormat,
|
||||
aConfig.mPaletteDepth);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mImageData = aConfig.mDecoder->mImageData;
|
||||
mImageDataLength = aConfig.mDecoder->mImageDataLength;
|
||||
mFlipVertically = aConfig.mFlipVertically;
|
||||
mFrameRect = aConfig.mFrameRect;
|
||||
|
||||
MOZ_ASSERT(mImageData);
|
||||
MOZ_ASSERT(mImageDataLength ==
|
||||
uint32_t(mFrameRect.width * mFrameRect.height * sizeof(uint8_t)));
|
||||
|
||||
ConfigureFilter(surfaceSize, sizeof(uint8_t));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
PalettedSurfaceSink::GetRowPointer() const
|
||||
{
|
||||
// If we're flipping vertically, reverse the order in which we traverse the
|
||||
// rows.
|
||||
uint32_t row = mFlipVertically
|
||||
? InputSize().height - (mRow + 1)
|
||||
: mRow;
|
||||
|
||||
uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint8_t);
|
||||
|
||||
MOZ_ASSERT(rowPtr >= mImageData);
|
||||
MOZ_ASSERT(rowPtr < mImageData + mImageDataLength);
|
||||
MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint8_t) <=
|
||||
mImageData + mImageDataLength);
|
||||
|
||||
return rowPtr;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
793
image/SurfacePipe.h
Normal file
793
image/SurfacePipe.h
Normal file
|
|
@ -0,0 +1,793 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* A SurfacePipe is a pipeline that consists of a series of SurfaceFilters
|
||||
* terminating in a SurfaceSink. Each SurfaceFilter transforms the image data in
|
||||
* some way before the SurfaceSink ultimately writes it to the surface. This
|
||||
* design allows for each transformation to be tested independently, for the
|
||||
* transformations to be combined as needed to meet the needs of different
|
||||
* situations, and for all image decoders to share the same code for these
|
||||
* transformations.
|
||||
*
|
||||
* Writing to the SurfacePipe is done using lambdas that act as generator
|
||||
* functions. Because the SurfacePipe machinery controls where the writes take
|
||||
* place, a bug in an image decoder cannot cause a buffer overflow of the
|
||||
* underlying surface.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_SurfacePipe_h
|
||||
#define mozilla_image_SurfacePipe_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nsDebug.h"
|
||||
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/Variant.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
|
||||
/**
|
||||
* An invalid rect for a surface. Results are given both in the space of the
|
||||
* input image (i.e., before any SurfaceFilters are applied) and in the space
|
||||
* of the output surface (after all SurfaceFilters).
|
||||
*/
|
||||
struct SurfaceInvalidRect
|
||||
{
|
||||
gfx::IntRect mInputSpaceRect; /// The invalid rect in pre-SurfacePipe space.
|
||||
gfx::IntRect mOutputSpaceRect; /// The invalid rect in post-SurfacePipe space.
|
||||
};
|
||||
|
||||
/**
|
||||
* An enum used to allow the lambdas passed to WritePixels() to communicate
|
||||
* their state to the caller.
|
||||
*/
|
||||
enum class WriteState : uint8_t
|
||||
{
|
||||
NEED_MORE_DATA, /// The lambda ran out of data.
|
||||
|
||||
FINISHED, /// The lambda is done writing to the surface; future writes
|
||||
/// will fail.
|
||||
|
||||
FAILURE /// The lambda encountered an error. The caller may recover
|
||||
/// if possible and continue to write. (This never indicates
|
||||
/// an error in the SurfacePipe machinery itself; it's only
|
||||
/// generated by the lambdas.)
|
||||
};
|
||||
|
||||
/**
|
||||
* A template alias used to make the return value of WritePixels() lambdas
|
||||
* (which may return either a pixel value or a WriteState) easier to specify.
|
||||
*/
|
||||
template <typename PixelType>
|
||||
using NextPixel = Variant<PixelType, WriteState>;
|
||||
|
||||
/**
|
||||
* SurfaceFilter is the abstract superclass of SurfacePipe pipeline stages. It
|
||||
* implements the the code that actually writes to the surface - WritePixels()
|
||||
* and the other Write*() methods - which are non-virtual for efficiency.
|
||||
*
|
||||
* SurfaceFilter's API is nonpublic; only SurfacePipe and other SurfaceFilters
|
||||
* should use it. Non-SurfacePipe code should use the methods on SurfacePipe.
|
||||
*
|
||||
* To implement a SurfaceFilter, it's necessary to subclass SurfaceFilter and
|
||||
* implement, at a minimum, the pure virtual methods. It's also necessary to
|
||||
* define a Config struct with a Filter typedef member that identifies the
|
||||
* matching SurfaceFilter class, and a Configure() template method. See an
|
||||
* existing SurfaceFilter subclass, such as RemoveFrameRectFilter, for an
|
||||
* example of how the Configure() method must be implemented. It takes a list of
|
||||
* Config structs, passes the tail of the list to the next filter in the chain's
|
||||
* Configure() method, and then uses the head of the list to configure itself. A
|
||||
* SurfaceFilter's Configure() method must also call
|
||||
* SurfaceFilter::ConfigureFilter() to provide the Write*() methods with the
|
||||
* information they need to do their jobs.
|
||||
*/
|
||||
class SurfaceFilter
|
||||
{
|
||||
public:
|
||||
SurfaceFilter()
|
||||
: mRowPointer(nullptr)
|
||||
, mCol(0)
|
||||
, mPixelSize(0)
|
||||
{ }
|
||||
|
||||
virtual ~SurfaceFilter() { }
|
||||
|
||||
/**
|
||||
* Reset this surface to the first row. It's legal for this filter to throw
|
||||
* away any previously written data at this point, as all rows must be written
|
||||
* to on every pass.
|
||||
*
|
||||
* @return a pointer to the buffer for the first row.
|
||||
*/
|
||||
uint8_t* ResetToFirstRow()
|
||||
{
|
||||
mCol = 0;
|
||||
mRowPointer = DoResetToFirstRow();
|
||||
return mRowPointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by WritePixels() to advance this filter to the next row.
|
||||
*
|
||||
* @return a pointer to the buffer for the next row, or nullptr to indicate
|
||||
* that we've finished the entire surface.
|
||||
*/
|
||||
uint8_t* AdvanceRow()
|
||||
{
|
||||
mCol = 0;
|
||||
mRowPointer = DoAdvanceRow();
|
||||
return mRowPointer;
|
||||
}
|
||||
|
||||
/// @return a pointer to the buffer for the current row.
|
||||
uint8_t* CurrentRowPointer() const { return mRowPointer; }
|
||||
|
||||
/// @return true if we've finished writing to the surface.
|
||||
bool IsSurfaceFinished() const { return mRowPointer == nullptr; }
|
||||
|
||||
/// @return the input size this filter expects.
|
||||
gfx::IntSize InputSize() const { return mInputSize; }
|
||||
|
||||
/**
|
||||
* Write pixels to the surface one at a time by repeatedly calling a lambda
|
||||
* that yields pixels. WritePixels() is completely memory safe.
|
||||
*
|
||||
* Writing continues until every pixel in the surface has been written to
|
||||
* (i.e., IsSurfaceFinished() returns true) or the lambda returns a WriteState
|
||||
* which WritePixels() will return to the caller.
|
||||
*
|
||||
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
|
||||
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
|
||||
* size passed to ConfigureFilter().
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove the PixelType template parameter from this
|
||||
* method.
|
||||
*
|
||||
* @param aFunc A lambda that functions as a generator, yielding the next
|
||||
* pixel in the surface each time it's called. The lambda must
|
||||
* return a NextPixel<PixelType> value.
|
||||
*
|
||||
* @return A WriteState value indicating the lambda generator's state.
|
||||
* WritePixels() itself will return WriteState::FINISHED if writing
|
||||
* has finished, regardless of the lambda's internal state.
|
||||
*/
|
||||
template <typename PixelType, typename Func>
|
||||
WriteState WritePixels(Func aFunc)
|
||||
{
|
||||
Maybe<WriteState> result;
|
||||
while (!(result = DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc)))) { }
|
||||
|
||||
return *result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of WritePixels() that writes a single row of pixels to the
|
||||
* surface one at a time by repeatedly calling a lambda that yields pixels.
|
||||
* WritePixelsToRow() is completely memory safe.
|
||||
*
|
||||
* Writing continues until every pixel in the row has been written to. If the
|
||||
* surface is complete at that pointer, WriteState::FINISHED is returned;
|
||||
* otherwise, WritePixelsToRow() returns WriteState::NEED_MORE_DATA. The
|
||||
* lambda can terminate writing early by returning a WriteState itself, which
|
||||
* WritePixelsToRow() will return to the caller.
|
||||
*
|
||||
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
|
||||
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
|
||||
* size passed to ConfigureFilter().
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove the PixelType template parameter from this
|
||||
* method.
|
||||
*
|
||||
* @param aFunc A lambda that functions as a generator, yielding the next
|
||||
* pixel in the surface each time it's called. The lambda must
|
||||
* return a NextPixel<PixelType> value.
|
||||
*
|
||||
* @return A WriteState value indicating the lambda generator's state.
|
||||
* WritePixels() itself will return WriteState::FINISHED if writing
|
||||
* the entire surface has finished, or WriteState::NEED_MORE_DATA if
|
||||
* writing the row has finished, regardless of the lambda's internal
|
||||
* state.
|
||||
*/
|
||||
template <typename PixelType, typename Func>
|
||||
WriteState WritePixelsToRow(Func aFunc)
|
||||
{
|
||||
return DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc))
|
||||
.valueOr(WriteState::NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a row to the surface by copying from a buffer. This is bounds checked
|
||||
* and memory safe with respect to the surface, but care must still be taken
|
||||
* by the caller not to overread the source buffer. This variant of
|
||||
* WriteBuffer() requires a source buffer which contains |mInputSize.width|
|
||||
* pixels.
|
||||
*
|
||||
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
|
||||
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
|
||||
* size passed to ConfigureFilter().
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove the PixelType template parameter from this
|
||||
* method.
|
||||
*
|
||||
* @param aSource A buffer to copy from. This buffer must be
|
||||
* |mInputSize.width| pixels wide, which means
|
||||
* |mInputSize.width * sizeof(PixelType)| bytes. May not be
|
||||
* null.
|
||||
*
|
||||
* @return WriteState::FINISHED if the entire surface has been written to.
|
||||
* Otherwise, returns WriteState::NEED_MORE_DATA. If a null |aSource|
|
||||
* value is passed, returns WriteState::FAILURE.
|
||||
*/
|
||||
template <typename PixelType>
|
||||
WriteState WriteBuffer(const PixelType* aSource)
|
||||
{
|
||||
return WriteBuffer(aSource, 0, mInputSize.width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a row to the surface by copying from a buffer. This is bounds checked
|
||||
* and memory safe with respect to the surface, but care must still be taken
|
||||
* by the caller not to overread the source buffer. This variant of
|
||||
* WriteBuffer() reads at most @aLength pixels from the buffer and writes them
|
||||
* to the row starting at @aStartColumn. Any pixels in columns before
|
||||
* @aStartColumn or after the pixels copied from the buffer are cleared.
|
||||
*
|
||||
* Bounds checking failures produce warnings in debug builds because although
|
||||
* the bounds checking maintains safety, this kind of failure could indicate a
|
||||
* bug in the calling code.
|
||||
*
|
||||
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
|
||||
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
|
||||
* size passed to ConfigureFilter().
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove the PixelType template parameter from this
|
||||
* method.
|
||||
*
|
||||
* @param aSource A buffer to copy from. This buffer must be @aLength pixels
|
||||
* wide, which means |aLength * sizeof(PixelType)| bytes. May
|
||||
* not be null.
|
||||
* @param aStartColumn The column to start writing to in the row. Columns
|
||||
* before this are cleared.
|
||||
* @param aLength The number of bytes, at most, which may be copied from
|
||||
* @aSource. Fewer bytes may be copied in practice due to
|
||||
* bounds checking.
|
||||
*
|
||||
* @return WriteState::FINISHED if the entire surface has been written to.
|
||||
* Otherwise, returns WriteState::NEED_MORE_DATA. If a null |aSource|
|
||||
* value is passed, returns WriteState::FAILURE.
|
||||
*/
|
||||
template <typename PixelType>
|
||||
WriteState WriteBuffer(const PixelType* aSource,
|
||||
const size_t aStartColumn,
|
||||
const size_t aLength)
|
||||
{
|
||||
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
|
||||
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
|
||||
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
|
||||
|
||||
if (IsSurfaceFinished()) {
|
||||
return WriteState::FINISHED; // Already done.
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(!aSource)) {
|
||||
NS_WARNING("Passed a null pointer to WriteBuffer");
|
||||
return WriteState::FAILURE;
|
||||
}
|
||||
|
||||
PixelType* dest = reinterpret_cast<PixelType*>(mRowPointer);
|
||||
|
||||
// Clear the area before |aStartColumn|.
|
||||
const size_t prefixLength = std::min<size_t>(mInputSize.width, aStartColumn);
|
||||
if (MOZ_UNLIKELY(prefixLength != aStartColumn)) {
|
||||
NS_WARNING("Provided starting column is out-of-bounds in WriteBuffer");
|
||||
}
|
||||
|
||||
memset(dest, 0, mInputSize.width * sizeof(PixelType));
|
||||
dest += prefixLength;
|
||||
|
||||
// Write |aLength| pixels from |aSource| into the row, with bounds checking.
|
||||
const size_t bufferLength =
|
||||
std::min<size_t>(mInputSize.width - prefixLength, aLength);
|
||||
if (MOZ_UNLIKELY(bufferLength != aLength)) {
|
||||
NS_WARNING("Provided buffer length is out-of-bounds in WriteBuffer");
|
||||
}
|
||||
|
||||
memcpy(dest, aSource, bufferLength * sizeof(PixelType));
|
||||
dest += bufferLength;
|
||||
|
||||
// Clear the rest of the row.
|
||||
const size_t suffixLength = mInputSize.width - (prefixLength + bufferLength);
|
||||
memset(dest, 0, suffixLength * sizeof(PixelType));
|
||||
|
||||
AdvanceRow();
|
||||
|
||||
return IsSurfaceFinished() ? WriteState::FINISHED
|
||||
: WriteState::NEED_MORE_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an empty row to the surface. If some pixels have already been written
|
||||
* to this row, they'll be discarded.
|
||||
*
|
||||
* @return WriteState::FINISHED if the entire surface has been written to.
|
||||
* Otherwise, returns WriteState::NEED_MORE_DATA.
|
||||
*/
|
||||
WriteState WriteEmptyRow()
|
||||
{
|
||||
if (IsSurfaceFinished()) {
|
||||
return WriteState::FINISHED; // Already done.
|
||||
}
|
||||
|
||||
memset(mRowPointer, 0, mInputSize.width * mPixelSize);
|
||||
AdvanceRow();
|
||||
|
||||
return IsSurfaceFinished() ? WriteState::FINISHED
|
||||
: WriteState::NEED_MORE_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a row to the surface by calling a lambda that uses a pointer to
|
||||
* directly write to the row. This is unsafe because SurfaceFilter can't
|
||||
* provide any bounds checking; that's up to the lambda itself. For this
|
||||
* reason, the other Write*() methods should be preferred whenever it's
|
||||
* possible to use them; WriteUnsafeComputedRow() should be used only when
|
||||
* it's absolutely necessary to avoid extra copies or other performance
|
||||
* penalties.
|
||||
*
|
||||
* This method should never be exposed to SurfacePipe consumers; it's strictly
|
||||
* for use in SurfaceFilters. If external code needs this method, it should
|
||||
* probably be turned into a SurfaceFilter.
|
||||
*
|
||||
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
|
||||
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
|
||||
* size passed to ConfigureFilter().
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove the PixelType template parameter from this
|
||||
* method.
|
||||
*
|
||||
* @param aFunc A lambda that writes directly to the row.
|
||||
*
|
||||
* @return WriteState::FINISHED if the entire surface has been written to.
|
||||
* Otherwise, returns WriteState::NEED_MORE_DATA.
|
||||
*/
|
||||
template <typename PixelType, typename Func>
|
||||
WriteState WriteUnsafeComputedRow(Func aFunc)
|
||||
{
|
||||
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
|
||||
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
|
||||
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
|
||||
|
||||
if (IsSurfaceFinished()) {
|
||||
return WriteState::FINISHED; // Already done.
|
||||
}
|
||||
|
||||
// Call the provided lambda with a pointer to the buffer for the current
|
||||
// row. This is unsafe because we can't do any bounds checking; the lambda
|
||||
// itself has to be responsible for that.
|
||||
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
|
||||
aFunc(rowPtr, mInputSize.width);
|
||||
AdvanceRow();
|
||||
|
||||
return IsSurfaceFinished() ? WriteState::FINISHED
|
||||
: WriteState::NEED_MORE_DATA;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Methods Subclasses Should Override
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// @return true if this SurfaceFilter can be used with paletted surfaces.
|
||||
virtual bool IsValidPalettedPipe() const { return false; }
|
||||
|
||||
/**
|
||||
* @return a SurfaceInvalidRect representing the region of the surface that
|
||||
* has been written to since the last time TakeInvalidRect() was
|
||||
* called, or Nothing() if the region is empty (i.e. nothing has been
|
||||
* written).
|
||||
*/
|
||||
virtual Maybe<SurfaceInvalidRect> TakeInvalidRect() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Called by ResetToFirstRow() to actually perform the reset. It's legal to
|
||||
* throw away any previously written data at this point, as all rows must be
|
||||
* written to on every pass.
|
||||
*/
|
||||
virtual uint8_t* DoResetToFirstRow() = 0;
|
||||
|
||||
/**
|
||||
* Called by AdvanceRow() to actually advance this filter to the next row.
|
||||
*
|
||||
* @return a pointer to the buffer for the next row, or nullptr to indicate
|
||||
* that we've finished the entire surface.
|
||||
*/
|
||||
virtual uint8_t* DoAdvanceRow() = 0;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Methods For Internal Use By Subclasses
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Called by subclasses' Configure() methods to initialize the configuration
|
||||
* of this filter. After the filter is configured, calls ResetToFirstRow().
|
||||
*
|
||||
* @param aInputSize The input size of this filter, in pixels. The previous
|
||||
* filter in the chain will expect to write into rows
|
||||
* |aInputSize.width| pixels wide.
|
||||
* @param aPixelSize How large, in bytes, each pixel in the surface is. This
|
||||
* should be either 1 for paletted images or 4 for BGRA/BGRX
|
||||
* images.
|
||||
*/
|
||||
void ConfigureFilter(gfx::IntSize aInputSize, uint8_t aPixelSize)
|
||||
{
|
||||
mInputSize = aInputSize;
|
||||
mPixelSize = aPixelSize;
|
||||
|
||||
ResetToFirstRow();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* An internal method used to implement both WritePixels() and
|
||||
* WritePixelsToRow(). Those methods differ only in their behavior after a row
|
||||
* is successfully written - WritePixels() continues to write another row,
|
||||
* while WritePixelsToRow() returns to the caller. This method writes a single
|
||||
* row and returns Some() if we either finished the entire surface or the
|
||||
* lambda returned a WriteState indicating that we should return to the
|
||||
* caller. If the row was successfully written without either of those things
|
||||
* happening, it returns Nothing(), allowing WritePixels() and
|
||||
* WritePixelsToRow() to implement their respective behaviors.
|
||||
*/
|
||||
template <typename PixelType, typename Func>
|
||||
Maybe<WriteState> DoWritePixelsToRow(Func aFunc)
|
||||
{
|
||||
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
|
||||
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
|
||||
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
|
||||
|
||||
if (IsSurfaceFinished()) {
|
||||
return Some(WriteState::FINISHED); // We're already done.
|
||||
}
|
||||
|
||||
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
|
||||
|
||||
for (; mCol < mInputSize.width; ++mCol) {
|
||||
NextPixel<PixelType> result = aFunc();
|
||||
if (result.template is<PixelType>()) {
|
||||
rowPtr[mCol] = result.template as<PixelType>();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (result.template as<WriteState>()) {
|
||||
case WriteState::NEED_MORE_DATA:
|
||||
return Some(WriteState::NEED_MORE_DATA);
|
||||
|
||||
case WriteState::FINISHED:
|
||||
ZeroOutRestOfSurface<PixelType>();
|
||||
return Some(WriteState::FINISHED);
|
||||
|
||||
case WriteState::FAILURE:
|
||||
// Note that we don't need to record this anywhere, because this
|
||||
// indicates an error in aFunc, and there's nothing wrong with our
|
||||
// machinery. The caller can recover as needed and continue writing to
|
||||
// the row.
|
||||
return Some(WriteState::FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
AdvanceRow(); // We've finished the row.
|
||||
|
||||
return IsSurfaceFinished() ? Some(WriteState::FINISHED)
|
||||
: Nothing();
|
||||
}
|
||||
|
||||
template <typename PixelType>
|
||||
void ZeroOutRestOfSurface()
|
||||
{
|
||||
WritePixels<PixelType>([]{ return AsVariant(PixelType(0)); });
|
||||
}
|
||||
|
||||
gfx::IntSize mInputSize; /// The size of the input this filter expects.
|
||||
uint8_t* mRowPointer; /// Pointer to the current row or null if finished.
|
||||
int32_t mCol; /// The current column we're writing to. (0-indexed)
|
||||
uint8_t mPixelSize; /// How large each pixel in the surface is, in bytes.
|
||||
};
|
||||
|
||||
class NullSurfaceSink;
|
||||
|
||||
/// A trivial configuration struct for NullSurfaceSink.
|
||||
struct NullSurfaceConfig
|
||||
{
|
||||
using Filter = NullSurfaceSink;
|
||||
};
|
||||
|
||||
/**
|
||||
* NullSurfaceSink is a trivial SurfaceFilter implementation that behaves as if
|
||||
* it were a zero-size SurfaceSink. It's used as the default filter chain for an
|
||||
* uninitialized SurfacePipe.
|
||||
*
|
||||
* To avoid unnecessary allocations when creating SurfacePipe objects,
|
||||
* NullSurfaceSink is a singleton. (This implies that the implementation must be
|
||||
* stateless.)
|
||||
*/
|
||||
class NullSurfaceSink final : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
/// Returns the singleton instance of NullSurfaceSink.
|
||||
static NullSurfaceSink* Singleton();
|
||||
|
||||
virtual ~NullSurfaceSink() { }
|
||||
|
||||
nsresult Configure(const NullSurfaceConfig& aConfig);
|
||||
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override { return nullptr; }
|
||||
uint8_t* DoAdvanceRow() override { return nullptr; }
|
||||
|
||||
private:
|
||||
static UniquePtr<NullSurfaceSink> sSingleton; /// The singleton instance.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* SurfacePipe is the public API that decoders should use to interact with a
|
||||
* SurfaceFilter pipeline.
|
||||
*/
|
||||
class SurfacePipe
|
||||
{
|
||||
public:
|
||||
/// Initialize global state used by all SurfacePipes.
|
||||
static void Initialize() { NullSurfaceSink::Singleton(); }
|
||||
|
||||
SurfacePipe()
|
||||
: mHead(NullSurfaceSink::Singleton())
|
||||
{ }
|
||||
|
||||
SurfacePipe(SurfacePipe&& aOther)
|
||||
: mHead(Move(aOther.mHead))
|
||||
{ }
|
||||
|
||||
~SurfacePipe()
|
||||
{
|
||||
// Ensure that we don't free the NullSurfaceSink singleton.
|
||||
if (mHead.get() == NullSurfaceSink::Singleton()) {
|
||||
Unused << mHead.release();
|
||||
}
|
||||
}
|
||||
|
||||
SurfacePipe& operator=(SurfacePipe&& aOther)
|
||||
{
|
||||
MOZ_ASSERT(this != &aOther);
|
||||
|
||||
// Ensure that we don't free the NullSurfaceSink singleton.
|
||||
if (mHead.get() == NullSurfaceSink::Singleton()) {
|
||||
Unused << mHead.release();
|
||||
}
|
||||
|
||||
mHead = Move(aOther.mHead);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Begins a new pass, seeking to the first row of the surface.
|
||||
void ResetToFirstRow() { mHead->ResetToFirstRow(); }
|
||||
|
||||
/**
|
||||
* Write pixels to the surface one at a time by repeatedly calling a lambda
|
||||
* that yields pixels. WritePixels() is completely memory safe.
|
||||
*
|
||||
* @see SurfaceFilter::WritePixels() for the canonical documentation.
|
||||
*/
|
||||
template <typename PixelType, typename Func>
|
||||
WriteState WritePixels(Func aFunc)
|
||||
{
|
||||
return mHead->WritePixels<PixelType>(Forward<Func>(aFunc));
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of WritePixels() that writes a single row of pixels to the
|
||||
* surface one at a time by repeatedly calling a lambda that yields pixels.
|
||||
* WritePixelsToRow() is completely memory safe.
|
||||
*
|
||||
* @see SurfaceFilter::WritePixelsToRow() for the canonical documentation.
|
||||
*/
|
||||
template <typename PixelType, typename Func>
|
||||
WriteState WritePixelsToRow(Func aFunc)
|
||||
{
|
||||
return mHead->WritePixelsToRow<PixelType>(Forward<Func>(aFunc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a row to the surface by copying from a buffer. This is bounds checked
|
||||
* and memory safe with respect to the surface, but care must still be taken
|
||||
* by the caller not to overread the source buffer. This variant of
|
||||
* WriteBuffer() requires a source buffer which contains |mInputSize.width|
|
||||
* pixels.
|
||||
*
|
||||
* @see SurfaceFilter::WriteBuffer() for the canonical documentation.
|
||||
*/
|
||||
template <typename PixelType>
|
||||
WriteState WriteBuffer(const PixelType* aSource)
|
||||
{
|
||||
return mHead->WriteBuffer<PixelType>(aSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a row to the surface by copying from a buffer. This is bounds checked
|
||||
* and memory safe with respect to the surface, but care must still be taken
|
||||
* by the caller not to overread the source buffer. This variant of
|
||||
* WriteBuffer() reads at most @aLength pixels from the buffer and writes them
|
||||
* to the row starting at @aStartColumn. Any pixels in columns before
|
||||
* @aStartColumn or after the pixels copied from the buffer are cleared.
|
||||
*
|
||||
* @see SurfaceFilter::WriteBuffer() for the canonical documentation.
|
||||
*/
|
||||
template <typename PixelType>
|
||||
WriteState WriteBuffer(const PixelType* aSource,
|
||||
const size_t aStartColumn,
|
||||
const size_t aLength)
|
||||
{
|
||||
return mHead->WriteBuffer<PixelType>(aSource, aStartColumn, aLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an empty row to the surface. If some pixels have already been written
|
||||
* to this row, they'll be discarded.
|
||||
*
|
||||
* @see SurfaceFilter::WriteEmptyRow() for the canonical documentation.
|
||||
*/
|
||||
WriteState WriteEmptyRow()
|
||||
{
|
||||
return mHead->WriteEmptyRow();
|
||||
}
|
||||
|
||||
/// @return true if we've finished writing to the surface.
|
||||
bool IsSurfaceFinished() const { return mHead->IsSurfaceFinished(); }
|
||||
|
||||
/// @see SurfaceFilter::TakeInvalidRect() for the canonical documentation.
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() const
|
||||
{
|
||||
return mHead->TakeInvalidRect();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class SurfacePipeFactory;
|
||||
friend class TestSurfacePipeFactory;
|
||||
|
||||
explicit SurfacePipe(UniquePtr<SurfaceFilter>&& aHead)
|
||||
: mHead(Move(aHead))
|
||||
{ }
|
||||
|
||||
SurfacePipe(const SurfacePipe&) = delete;
|
||||
SurfacePipe& operator=(const SurfacePipe&) = delete;
|
||||
|
||||
UniquePtr<SurfaceFilter> mHead; /// The first filter in the chain.
|
||||
};
|
||||
|
||||
/**
|
||||
* AbstractSurfaceSink contains shared implementation for both SurfaceSink and
|
||||
* PalettedSurfaceSink.
|
||||
*/
|
||||
class AbstractSurfaceSink : public SurfaceFilter
|
||||
{
|
||||
public:
|
||||
AbstractSurfaceSink()
|
||||
: mImageData(nullptr)
|
||||
, mImageDataLength(0)
|
||||
, mRow(0)
|
||||
, mFlipVertically(false)
|
||||
{ }
|
||||
|
||||
Maybe<SurfaceInvalidRect> TakeInvalidRect() override final;
|
||||
|
||||
protected:
|
||||
uint8_t* DoResetToFirstRow() override final;
|
||||
uint8_t* DoAdvanceRow() override final;
|
||||
virtual uint8_t* GetRowPointer() const = 0;
|
||||
|
||||
gfx::IntRect mInvalidRect; /// The region of the surface that has been written
|
||||
/// to since the last call to TakeInvalidRect().
|
||||
uint8_t* mImageData; /// A pointer to the beginning of the surface data.
|
||||
uint32_t mImageDataLength; /// The length of the surface data.
|
||||
uint32_t mRow; /// The row to which we're writing. (0-indexed)
|
||||
bool mFlipVertically; /// If true, write the rows from top to bottom.
|
||||
};
|
||||
|
||||
class SurfaceSink;
|
||||
|
||||
/// A configuration struct for SurfaceSink.
|
||||
struct SurfaceConfig
|
||||
{
|
||||
using Filter = SurfaceSink;
|
||||
Decoder* mDecoder; /// Which Decoder to use to allocate the surface.
|
||||
uint32_t mFrameNum; /// Which frame of animation this surface is for.
|
||||
gfx::IntSize mOutputSize; /// The size of the surface.
|
||||
gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX).
|
||||
bool mFlipVertically; /// If true, write the rows from bottom to top.
|
||||
};
|
||||
|
||||
/**
|
||||
* A sink for normal (i.e., non-paletted) surfaces. It handles the allocation of
|
||||
* the surface and protects against buffer overflow. This sink should be used
|
||||
* for all non-animated images and for the first frame of animated images.
|
||||
*
|
||||
* Sinks must always be at the end of the SurfaceFilter chain.
|
||||
*/
|
||||
class SurfaceSink final : public AbstractSurfaceSink
|
||||
{
|
||||
public:
|
||||
nsresult Configure(const SurfaceConfig& aConfig);
|
||||
|
||||
protected:
|
||||
uint8_t* GetRowPointer() const override;
|
||||
};
|
||||
|
||||
class PalettedSurfaceSink;
|
||||
|
||||
struct PalettedSurfaceConfig
|
||||
{
|
||||
using Filter = PalettedSurfaceSink;
|
||||
Decoder* mDecoder; /// Which Decoder to use to allocate the surface.
|
||||
uint32_t mFrameNum; /// Which frame of animation this surface is for.
|
||||
gfx::IntSize mOutputSize; /// The logical size of the surface.
|
||||
gfx::IntRect mFrameRect; /// The surface subrect which contains data.
|
||||
gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX).
|
||||
uint8_t mPaletteDepth; /// The palette depth of this surface.
|
||||
bool mFlipVertically; /// If true, write the rows from bottom to top.
|
||||
};
|
||||
|
||||
/**
|
||||
* A sink for paletted surfaces. It handles the allocation of the surface and
|
||||
* protects against buffer overflow. This sink can be used for frames of
|
||||
* animated images except the first.
|
||||
*
|
||||
* Sinks must always be at the end of the SurfaceFilter chain.
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove PalettedSurfaceSink entirely.
|
||||
*/
|
||||
class PalettedSurfaceSink final : public AbstractSurfaceSink
|
||||
{
|
||||
public:
|
||||
bool IsValidPalettedPipe() const override { return true; }
|
||||
|
||||
nsresult Configure(const PalettedSurfaceConfig& aConfig);
|
||||
|
||||
protected:
|
||||
uint8_t* GetRowPointer() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The surface subrect which contains data. Note that the surface size we
|
||||
* actually allocate is the size of the frame rect, not the logical size of
|
||||
* the surface.
|
||||
*/
|
||||
gfx::IntRect mFrameRect;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SurfacePipe_h
|
||||
249
image/SurfacePipeFactory.h
Normal file
249
image/SurfacePipeFactory.h
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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_SurfacePipeFactory_h
|
||||
#define mozilla_image_SurfacePipeFactory_h
|
||||
|
||||
#include "SurfacePipe.h"
|
||||
#include "SurfaceFilters.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* FilterPipeline is a helper template for SurfacePipeFactory that determines
|
||||
* the full type of the sequence of SurfaceFilters that a sequence of
|
||||
* configuration structs corresponds to. To make this work, all configuration
|
||||
* structs must include a typedef 'Filter' that identifies the SurfaceFilter
|
||||
* they configure.
|
||||
*/
|
||||
template <typename... Configs>
|
||||
struct FilterPipeline;
|
||||
|
||||
template <typename Config, typename... Configs>
|
||||
struct FilterPipeline<Config, Configs...>
|
||||
{
|
||||
typedef typename Config::template Filter<typename FilterPipeline<Configs...>::Type> Type;
|
||||
};
|
||||
|
||||
template <typename Config>
|
||||
struct FilterPipeline<Config>
|
||||
{
|
||||
typedef typename Config::Filter Type;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Flags for SurfacePipeFactory, used in conjuction with the factory functions
|
||||
* in SurfacePipeFactory to enable or disable various SurfacePipe
|
||||
* functionality.
|
||||
*/
|
||||
enum class SurfacePipeFlags
|
||||
{
|
||||
DEINTERLACE = 1 << 0, // If set, deinterlace the image.
|
||||
|
||||
ADAM7_INTERPOLATE = 1 << 1, // If set, the caller is deinterlacing the
|
||||
// image using ADAM7, and we may want to
|
||||
// interpolate it for better intermediate results.
|
||||
|
||||
FLIP_VERTICALLY = 1 << 2, // If set, flip the image vertically.
|
||||
|
||||
PROGRESSIVE_DISPLAY = 1 << 3 // If set, we expect the image to be displayed
|
||||
// progressively. This enables features that
|
||||
// result in a better user experience for
|
||||
// progressive display but which may be more
|
||||
// computationally expensive.
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfacePipeFlags)
|
||||
|
||||
class SurfacePipeFactory
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Creates and initializes a normal (i.e., non-paletted) SurfacePipe.
|
||||
*
|
||||
* @param aDecoder The decoder whose current frame the SurfacePipe will write
|
||||
* to.
|
||||
* @param aFrameNum Which frame the SurfacePipe will write to. This will be 0
|
||||
* for non-animated images.
|
||||
* @param aInputSize The original size of the image.
|
||||
* @param aOutputSize The size the SurfacePipe should output. Must be the same
|
||||
* as @aInputSize or smaller. If smaller, the image will be
|
||||
* downscaled during decoding.
|
||||
* @param aFrameRect The portion of the image that actually contains data.
|
||||
* @param aFormat The surface format of the image; generally B8G8R8A8 or
|
||||
* B8G8R8X8.
|
||||
* @param aFlags Flags enabling or disabling various functionality for the
|
||||
* SurfacePipe; see the SurfacePipeFlags documentation for more
|
||||
* information.
|
||||
*
|
||||
* @return A SurfacePipe if the parameters allowed one to be created
|
||||
* successfully, or Nothing() if the SurfacePipe could not be
|
||||
* initialized.
|
||||
*/
|
||||
static Maybe<SurfacePipe>
|
||||
CreateSurfacePipe(Decoder* aDecoder,
|
||||
uint32_t aFrameNum,
|
||||
const nsIntSize& aInputSize,
|
||||
const nsIntSize& aOutputSize,
|
||||
const nsIntRect& aFrameRect,
|
||||
gfx::SurfaceFormat aFormat,
|
||||
SurfacePipeFlags aFlags)
|
||||
{
|
||||
const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
|
||||
const bool flipVertically = bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY);
|
||||
const bool progressiveDisplay = bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY);
|
||||
const bool downscale = aInputSize != aOutputSize;
|
||||
const bool removeFrameRect =
|
||||
!aFrameRect.IsEqualEdges(nsIntRect(0, 0, aInputSize.width, aInputSize.height));
|
||||
|
||||
// Don't interpolate if we're sure we won't show this surface to the user
|
||||
// until it's completely decoded. The final pass of an ADAM7 image doesn't
|
||||
// need interpolation, so we only need to interpolate if we'll be displaying
|
||||
// the image while it's still being decoded.
|
||||
const bool adam7Interpolate = bool(aFlags & SurfacePipeFlags::ADAM7_INTERPOLATE) &&
|
||||
progressiveDisplay;
|
||||
|
||||
if (deinterlace && adam7Interpolate) {
|
||||
MOZ_ASSERT_UNREACHABLE("ADAM7 deinterlacing is handled by libpng");
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// Construct configurations for the SurfaceFilters. Note that the order of
|
||||
// these filters is significant. We want to deinterlace or interpolate raw
|
||||
// input rows, before any other transformations, and we want to remove the
|
||||
// frame rect (which may involve adding blank rows or columns to the image)
|
||||
// before any downscaling, so that the new rows and columns are taken into
|
||||
// account.
|
||||
DeinterlacingConfig<uint32_t> deinterlacingConfig { progressiveDisplay };
|
||||
ADAM7InterpolatingConfig interpolatingConfig;
|
||||
RemoveFrameRectConfig removeFrameRectConfig { aFrameRect };
|
||||
DownscalingConfig downscalingConfig { aInputSize, aFormat };
|
||||
SurfaceConfig surfaceConfig { aDecoder, aFrameNum, aOutputSize,
|
||||
aFormat, flipVertically };
|
||||
|
||||
Maybe<SurfacePipe> pipe;
|
||||
|
||||
if (downscale) {
|
||||
if (removeFrameRect) {
|
||||
if (deinterlace) {
|
||||
pipe = MakePipe(deinterlacingConfig, removeFrameRectConfig,
|
||||
downscalingConfig, surfaceConfig);
|
||||
} else if (adam7Interpolate) {
|
||||
pipe = MakePipe(interpolatingConfig, removeFrameRectConfig,
|
||||
downscalingConfig, surfaceConfig);
|
||||
} else { // (deinterlace and adam7Interpolate are false)
|
||||
pipe = MakePipe(removeFrameRectConfig, downscalingConfig, surfaceConfig);
|
||||
}
|
||||
} else { // (removeFrameRect is false)
|
||||
if (deinterlace) {
|
||||
pipe = MakePipe(deinterlacingConfig, downscalingConfig, surfaceConfig);
|
||||
} else if (adam7Interpolate) {
|
||||
pipe = MakePipe(interpolatingConfig, downscalingConfig, surfaceConfig);
|
||||
} else { // (deinterlace and adam7Interpolate are false)
|
||||
pipe = MakePipe(downscalingConfig, surfaceConfig);
|
||||
}
|
||||
}
|
||||
} else { // (downscale is false)
|
||||
if (removeFrameRect) {
|
||||
if (deinterlace) {
|
||||
pipe = MakePipe(deinterlacingConfig, removeFrameRectConfig, surfaceConfig);
|
||||
} else if (adam7Interpolate) {
|
||||
pipe = MakePipe(interpolatingConfig, removeFrameRectConfig, surfaceConfig);
|
||||
} else { // (deinterlace and adam7Interpolate are false)
|
||||
pipe = MakePipe(removeFrameRectConfig, surfaceConfig);
|
||||
}
|
||||
} else { // (removeFrameRect is false)
|
||||
if (deinterlace) {
|
||||
pipe = MakePipe(deinterlacingConfig, surfaceConfig);
|
||||
} else if (adam7Interpolate) {
|
||||
pipe = MakePipe(interpolatingConfig, surfaceConfig);
|
||||
} else { // (deinterlace and adam7Interpolate are false)
|
||||
pipe = MakePipe(surfaceConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a paletted SurfacePipe.
|
||||
*
|
||||
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
|
||||
* which means we can remove CreatePalettedSurfacePipe() entirely.
|
||||
*
|
||||
* @param aDecoder The decoder whose current frame the SurfacePipe will write
|
||||
* to.
|
||||
* @param aFrameNum Which frame the SurfacePipe will write to. This will be 0
|
||||
* for non-animated images.
|
||||
* @param aInputSize The original size of the image.
|
||||
* @param aFrameRect The portion of the image that actually contains data.
|
||||
* @param aFormat The surface format of the image; generally B8G8R8A8 or
|
||||
* B8G8R8X8.
|
||||
* @param aPaletteDepth The palette depth of the image.
|
||||
* @param aFlags Flags enabling or disabling various functionality for the
|
||||
* SurfacePipe; see the SurfacePipeFlags documentation for more
|
||||
* information.
|
||||
*
|
||||
* @return A SurfacePipe if the parameters allowed one to be created
|
||||
* successfully, or Nothing() if the SurfacePipe could not be
|
||||
* initialized.
|
||||
*/
|
||||
static Maybe<SurfacePipe>
|
||||
CreatePalettedSurfacePipe(Decoder* aDecoder,
|
||||
uint32_t aFrameNum,
|
||||
const nsIntSize& aInputSize,
|
||||
const nsIntRect& aFrameRect,
|
||||
gfx::SurfaceFormat aFormat,
|
||||
uint8_t aPaletteDepth,
|
||||
SurfacePipeFlags aFlags)
|
||||
{
|
||||
const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
|
||||
const bool flipVertically = bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY);
|
||||
const bool progressiveDisplay = bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY);
|
||||
|
||||
// Construct configurations for the SurfaceFilters.
|
||||
DeinterlacingConfig<uint8_t> deinterlacingConfig { progressiveDisplay };
|
||||
PalettedSurfaceConfig palettedSurfaceConfig { aDecoder, aFrameNum, aInputSize,
|
||||
aFrameRect, aFormat, aPaletteDepth,
|
||||
flipVertically };
|
||||
|
||||
Maybe<SurfacePipe> pipe;
|
||||
|
||||
if (deinterlace) {
|
||||
pipe = MakePipe(deinterlacingConfig, palettedSurfaceConfig);
|
||||
} else {
|
||||
pipe = MakePipe(palettedSurfaceConfig);
|
||||
}
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename... Configs>
|
||||
static Maybe<SurfacePipe>
|
||||
MakePipe(Configs... aConfigs)
|
||||
{
|
||||
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
|
||||
nsresult rv = pipe->Configure(aConfigs...);
|
||||
if (NS_FAILED(rv)) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(SurfacePipe { Move(pipe) } );
|
||||
}
|
||||
|
||||
virtual ~SurfacePipeFactory() = 0;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_SurfacePipeFactory_h
|
||||
1350
image/VectorImage.cpp
Normal file
1350
image/VectorImage.cpp
Normal file
File diff suppressed because it is too large
Load diff
140
image/VectorImage.h
Normal file
140
image/VectorImage.h
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/* -*- 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_VectorImage_h
|
||||
#define mozilla_image_VectorImage_h
|
||||
|
||||
#include "Image.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
class nsIRequest;
|
||||
class gfxDrawable;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
struct SVGDrawingParameters;
|
||||
class SVGDocumentWrapper;
|
||||
class SVGRootRenderingObserver;
|
||||
class SVGLoadEventListener;
|
||||
class SVGParseCompleteListener;
|
||||
|
||||
class VectorImage final : public ImageResource,
|
||||
public nsIStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_IMGICONTAINER
|
||||
|
||||
// (no public constructor - use ImageFactory)
|
||||
|
||||
// Methods inherited from Image
|
||||
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
|
||||
const override;
|
||||
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInStr,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aCount) override;
|
||||
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aResult,
|
||||
bool aLastPart) override;
|
||||
|
||||
virtual void OnSurfaceDiscarded() override;
|
||||
|
||||
/**
|
||||
* Callback for SVGRootRenderingObserver.
|
||||
*
|
||||
* This just sets a dirty flag that we check in VectorImage::RequestRefresh,
|
||||
* which is called under the ticks of the refresh driver of any observing
|
||||
* documents that we may have. Only then (after all animations in this image
|
||||
* have been updated) do we send out "frame changed" notifications,
|
||||
*/
|
||||
void InvalidateObserversOnNextRefreshDriverTick();
|
||||
|
||||
// Callback for SVGParseCompleteListener.
|
||||
void OnSVGDocumentParsed();
|
||||
|
||||
// Callbacks for SVGLoadEventListener.
|
||||
void OnSVGDocumentLoaded();
|
||||
void OnSVGDocumentError();
|
||||
|
||||
virtual void ReportUseCounters() override;
|
||||
|
||||
protected:
|
||||
explicit VectorImage(ImageURL* aURI = nullptr);
|
||||
virtual ~VectorImage();
|
||||
|
||||
virtual nsresult StartAnimation() override;
|
||||
virtual nsresult StopAnimation() override;
|
||||
virtual bool ShouldAnimate() override;
|
||||
|
||||
private:
|
||||
/// Attempt to find a cached surface matching @aParams in the SurfaceCache.
|
||||
already_AddRefed<gfxDrawable>
|
||||
LookupCachedSurface(const SVGDrawingParameters& aParams);
|
||||
|
||||
void CreateSurfaceAndShow(const SVGDrawingParameters& aParams,
|
||||
gfx::BackendType aBackend);
|
||||
void Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams);
|
||||
|
||||
nsresult Init(const char* aMimeType, uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* In catastrophic circumstances like a GPU driver crash, we may lose our
|
||||
* surfaces even if they're locked. RecoverFromLossOfSurfaces discards all
|
||||
* existing surfaces, allowing us to recover.
|
||||
*/
|
||||
void RecoverFromLossOfSurfaces();
|
||||
|
||||
void CancelAllListeners();
|
||||
void SendInvalidationNotifications();
|
||||
|
||||
RefPtr<SVGDocumentWrapper> mSVGDocumentWrapper;
|
||||
RefPtr<SVGRootRenderingObserver> mRenderingObserver;
|
||||
RefPtr<SVGLoadEventListener> mLoadEventListener;
|
||||
RefPtr<SVGParseCompleteListener> mParseCompleteListener;
|
||||
|
||||
/// Count of locks on this image (roughly correlated to visible instances).
|
||||
uint32_t mLockCount;
|
||||
|
||||
// Stored result from the Necko load of the image, which we save in
|
||||
// OnImageDataComplete if the underlying SVG document isn't loaded. If we save
|
||||
// this, we actually notify this progress (and clear this value) in
|
||||
// OnSVGDocumentLoaded or OnSVGDocumentError.
|
||||
Maybe<Progress> mLoadProgress;
|
||||
|
||||
bool mIsInitialized; // Have we been initialized?
|
||||
bool mDiscardable; // Are we discardable?
|
||||
bool mIsFullyLoaded; // Has the SVG document finished
|
||||
// loading?
|
||||
bool mIsDrawing; // Are we currently drawing?
|
||||
bool mHaveAnimations; // Is our SVG content SMIL-animated?
|
||||
// (Only set after mIsFullyLoaded.)
|
||||
bool mHasPendingInvalidation; // Invalidate observers next refresh
|
||||
// driver tick.
|
||||
|
||||
friend class ImageFactory;
|
||||
};
|
||||
|
||||
inline NS_IMETHODIMP VectorImage::GetAnimationMode(uint16_t* aAnimationMode) {
|
||||
return GetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
inline NS_IMETHODIMP VectorImage::SetAnimationMode(uint16_t aAnimationMode) {
|
||||
return SetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_VectorImage_h
|
||||
22
image/build/moz.build
Normal file
22
image/build/moz.build
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXPORTS += [
|
||||
'nsImageModule.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'nsImageModule.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
LOCAL_INCLUDES += [
|
||||
'/image',
|
||||
'/image/encoders/bmp',
|
||||
'/image/encoders/ico',
|
||||
'/image/encoders/jpeg',
|
||||
'/image/encoders/png',
|
||||
]
|
||||
136
image/build/nsImageModule.cpp
Normal file
136
image/build/nsImageModule.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/* -*- 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 "nsImageModule.h"
|
||||
|
||||
#include "mozilla/ModuleUtils.h"
|
||||
#include "nsMimeTypes.h"
|
||||
|
||||
#include "DecodePool.h"
|
||||
#include "ImageFactory.h"
|
||||
#include "ShutdownTracker.h"
|
||||
#include "SurfaceCache.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
#include "gfxPrefs.h"
|
||||
#include "imgLoader.h"
|
||||
#include "imgRequest.h"
|
||||
#include "imgRequestProxy.h"
|
||||
#include "imgTools.h"
|
||||
|
||||
#include "nsICOEncoder.h"
|
||||
#include "nsPNGEncoder.h"
|
||||
#include "nsJPEGEncoder.h"
|
||||
#include "nsBMPEncoder.h"
|
||||
|
||||
// objects that just require generic constructors
|
||||
using namespace mozilla::image;
|
||||
|
||||
// XXX We would like to get rid of the imgLoader factory constructor. See the
|
||||
// comment documenting the imgLoader constructor.
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(imgLoader, Init)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(imgRequestProxy)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(imgTools)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsICOEncoder)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsJPEGEncoder)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsPNGEncoder)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsBMPEncoder)
|
||||
NS_DEFINE_NAMED_CID(NS_IMGLOADER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_IMGREQUESTPROXY_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_IMGTOOLS_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_ICOENCODER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_JPEGENCODER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PNGENCODER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_BMPENCODER_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kImageCIDs[] = {
|
||||
{ &kNS_IMGLOADER_CID, false, nullptr, imgLoaderConstructor, },
|
||||
{ &kNS_IMGREQUESTPROXY_CID, false, nullptr, imgRequestProxyConstructor, },
|
||||
{ &kNS_IMGTOOLS_CID, false, nullptr, imgToolsConstructor, },
|
||||
{ &kNS_ICOENCODER_CID, false, nullptr, nsICOEncoderConstructor, },
|
||||
{ &kNS_JPEGENCODER_CID, false, nullptr, nsJPEGEncoderConstructor, },
|
||||
{ &kNS_PNGENCODER_CID, false, nullptr, nsPNGEncoderConstructor, },
|
||||
{ &kNS_BMPENCODER_CID, false, nullptr, nsBMPEncoderConstructor, },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kImageContracts[] = {
|
||||
{ "@mozilla.org/image/cache;1", &kNS_IMGLOADER_CID },
|
||||
{ "@mozilla.org/image/loader;1", &kNS_IMGLOADER_CID },
|
||||
{ "@mozilla.org/image/request;1", &kNS_IMGREQUESTPROXY_CID },
|
||||
{ "@mozilla.org/image/tools;1", &kNS_IMGTOOLS_CID },
|
||||
{ "@mozilla.org/image/encoder;2?type=" IMAGE_ICO_MS, &kNS_ICOENCODER_CID },
|
||||
{ "@mozilla.org/image/encoder;2?type=" IMAGE_JPEG, &kNS_JPEGENCODER_CID },
|
||||
{ "@mozilla.org/image/encoder;2?type=" IMAGE_PNG, &kNS_PNGENCODER_CID },
|
||||
{ "@mozilla.org/image/encoder;2?type=" IMAGE_BMP, &kNS_BMPENCODER_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::CategoryEntry kImageCategories[] = {
|
||||
{ "Gecko-Content-Viewers", IMAGE_GIF, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_JPEG, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_PJPEG, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_JPG, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_ICO, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_ICO_MS, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_BMP, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_BMP_MS, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_ICON_MS, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_PNG, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_APNG, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" },
|
||||
{ "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static bool sInitialized = false;
|
||||
nsresult
|
||||
mozilla::image::EnsureModuleInitialized()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (sInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Make sure the preferences are initialized
|
||||
gfxPrefs::GetSingleton();
|
||||
|
||||
mozilla::image::ShutdownTracker::Initialize();
|
||||
mozilla::image::ImageFactory::Initialize();
|
||||
mozilla::image::DecodePool::Initialize();
|
||||
mozilla::image::SurfaceCache::Initialize();
|
||||
mozilla::image::SurfacePipe::Initialize();
|
||||
imgLoader::GlobalInit();
|
||||
sInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
mozilla::image::ShutdownModule()
|
||||
{
|
||||
if (!sInitialized) {
|
||||
return;
|
||||
}
|
||||
imgLoader::Shutdown();
|
||||
mozilla::image::SurfaceCache::Shutdown();
|
||||
sInitialized = false;
|
||||
}
|
||||
|
||||
static const mozilla::Module kImageModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kImageCIDs,
|
||||
kImageContracts,
|
||||
kImageCategories,
|
||||
nullptr,
|
||||
mozilla::image::EnsureModuleInitialized,
|
||||
// We need to be careful about shutdown ordering to avoid intermittent crashes
|
||||
// when hashtable enumeration decides to destroy modules in an unfortunate
|
||||
// order. So our shutdown is invoked explicitly during layout module shutdown.
|
||||
nullptr
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsImageLib2Module) = &kImageModule;
|
||||
20
image/build/nsImageModule.h
Normal file
20
image/build/nsImageModule.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- 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_build_nsImageModule_h
|
||||
#define mozilla_image_build_nsImageModule_h
|
||||
|
||||
#include "nsError.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
nsresult EnsureModuleInitialized();
|
||||
void ShutdownModule();
|
||||
|
||||
} /* namespace image */
|
||||
} /* namespace mozilla */
|
||||
|
||||
#endif // mozilla_image_build_nsImageModule_h
|
||||
331
image/decoders/EXIF.cpp
Normal file
331
image/decoders/EXIF.cpp
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
/* -*- 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 "EXIF.h"
|
||||
|
||||
#include "mozilla/EndianUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
// Section references in this file refer to the EXIF v2.3 standard, also known
|
||||
// as CIPA DC-008-Translation-2010.
|
||||
|
||||
// See Section 4.6.4, Table 4.
|
||||
// Typesafe enums are intentionally not used here since we're comparing to raw
|
||||
// integers produced by parsing.
|
||||
enum EXIFTag
|
||||
{
|
||||
OrientationTag = 0x112,
|
||||
};
|
||||
|
||||
// See Section 4.6.2.
|
||||
enum EXIFType
|
||||
{
|
||||
ByteType = 1,
|
||||
ASCIIType = 2,
|
||||
ShortType = 3,
|
||||
LongType = 4,
|
||||
RationalType = 5,
|
||||
UndefinedType = 7,
|
||||
SignedLongType = 9,
|
||||
SignedRational = 10,
|
||||
};
|
||||
|
||||
static const char* EXIFHeader = "Exif\0\0";
|
||||
static const uint32_t EXIFHeaderLength = 6;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Parse EXIF data, typically found in a JPEG's APP1 segment.
|
||||
/////////////////////////////////////////////////////////////
|
||||
EXIFData
|
||||
EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength)
|
||||
{
|
||||
if (!Initialize(aData, aLength)) {
|
||||
return EXIFData();
|
||||
}
|
||||
|
||||
if (!ParseEXIFHeader()) {
|
||||
return EXIFData();
|
||||
}
|
||||
|
||||
uint32_t offsetIFD;
|
||||
if (!ParseTIFFHeader(offsetIFD)) {
|
||||
return EXIFData();
|
||||
}
|
||||
|
||||
JumpTo(offsetIFD);
|
||||
|
||||
Orientation orientation;
|
||||
if (!ParseIFD0(orientation)) {
|
||||
return EXIFData();
|
||||
}
|
||||
|
||||
// We only care about orientation at this point, so we don't bother with the
|
||||
// other IFDs. If we got this far we're done.
|
||||
return EXIFData(orientation);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Parse the EXIF header. (Section 4.7.2, Figure 30)
|
||||
/////////////////////////////////////////////////////////
|
||||
bool
|
||||
EXIFParser::ParseEXIFHeader()
|
||||
{
|
||||
return MatchString(EXIFHeader, EXIFHeaderLength);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Parse the TIFF header. (Section 4.5.2, Table 1)
|
||||
/////////////////////////////////////////////////////////
|
||||
bool
|
||||
EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut)
|
||||
{
|
||||
// Determine byte order.
|
||||
if (MatchString("MM\0*", 4)) {
|
||||
mByteOrder = ByteOrder::BigEndian;
|
||||
} else if (MatchString("II*\0", 4)) {
|
||||
mByteOrder = ByteOrder::LittleEndian;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which
|
||||
// is the maximum size of the entry APP1 segment.)
|
||||
uint32_t ifd0Offset;
|
||||
if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The IFD offset is relative to the beginning of the TIFF header, which
|
||||
// begins after the EXIF header, so we need to increase the offset
|
||||
// appropriately.
|
||||
aIFD0OffsetOut = ifd0Offset + EXIFHeaderLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Parse the entries in IFD0. (Section 4.6.2)
|
||||
/////////////////////////////////////////////////////////
|
||||
bool
|
||||
EXIFParser::ParseIFD0(Orientation& aOrientationOut)
|
||||
{
|
||||
uint16_t entryCount;
|
||||
if (!ReadUInt16(entryCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint16_t entry = 0 ; entry < entryCount ; ++entry) {
|
||||
// Read the fields of the entry.
|
||||
uint16_t tag;
|
||||
if (!ReadUInt16(tag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Right now, we only care about orientation, so we immediately skip to the
|
||||
// next entry if we find anything else.
|
||||
if (tag != OrientationTag) {
|
||||
Advance(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t type;
|
||||
if (!ReadUInt16(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t count;
|
||||
if (!ReadUInt32(count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We should have an orientation value here; go ahead and parse it.
|
||||
if (!ParseOrientation(type, count, aOrientationOut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since the orientation is all we care about, we're done.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We didn't find an orientation field in the IFD. That's OK; we assume the
|
||||
// default orientation in that case.
|
||||
aOrientationOut = Orientation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
EXIFParser::ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut)
|
||||
{
|
||||
// Sanity check the type and count.
|
||||
if (aType != ShortType || aCount != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t value;
|
||||
if (!ReadUInt16(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 1: aOut = Orientation(Angle::D0, Flip::Unflipped); break;
|
||||
case 2: aOut = Orientation(Angle::D0, Flip::Horizontal); break;
|
||||
case 3: aOut = Orientation(Angle::D180, Flip::Unflipped); break;
|
||||
case 4: aOut = Orientation(Angle::D180, Flip::Horizontal); break;
|
||||
case 5: aOut = Orientation(Angle::D90, Flip::Horizontal); break;
|
||||
case 6: aOut = Orientation(Angle::D90, Flip::Unflipped); break;
|
||||
case 7: aOut = Orientation(Angle::D270, Flip::Horizontal); break;
|
||||
case 8: aOut = Orientation(Angle::D270, Flip::Unflipped); break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
// This is a 32-bit field, but the orientation value only occupies the first
|
||||
// 16 bits. We need to advance another 16 bits to consume the entire field.
|
||||
Advance(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
EXIFParser::Initialize(const uint8_t* aData, const uint32_t aLength)
|
||||
{
|
||||
if (aData == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// An APP1 segment larger than 64k violates the JPEG standard.
|
||||
if (aLength > 64 * 1024) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mStart = mCurrent = aData;
|
||||
mLength = mRemainingLength = aLength;
|
||||
mByteOrder = ByteOrder::Unknown;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
EXIFParser::Advance(const uint32_t aDistance)
|
||||
{
|
||||
if (mRemainingLength >= aDistance) {
|
||||
mCurrent += aDistance;
|
||||
mRemainingLength -= aDistance;
|
||||
} else {
|
||||
mCurrent = mStart;
|
||||
mRemainingLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
EXIFParser::JumpTo(const uint32_t aOffset)
|
||||
{
|
||||
if (mLength >= aOffset) {
|
||||
mCurrent = mStart + aOffset;
|
||||
mRemainingLength = mLength - aOffset;
|
||||
} else {
|
||||
mCurrent = mStart;
|
||||
mRemainingLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
EXIFParser::MatchString(const char* aString, const uint32_t aLength)
|
||||
{
|
||||
if (mRemainingLength < aLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0 ; i < aLength ; ++i) {
|
||||
if (mCurrent[i] != aString[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Advance(aLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
EXIFParser::MatchUInt16(const uint16_t aValue)
|
||||
{
|
||||
if (mRemainingLength < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matched;
|
||||
switch (mByteOrder) {
|
||||
case ByteOrder::LittleEndian:
|
||||
matched = LittleEndian::readUint16(mCurrent) == aValue;
|
||||
break;
|
||||
case ByteOrder::BigEndian:
|
||||
matched = BigEndian::readUint16(mCurrent) == aValue;
|
||||
break;
|
||||
default:
|
||||
NS_NOTREACHED("Should know the byte order by now");
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
Advance(2);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
bool
|
||||
EXIFParser::ReadUInt16(uint16_t& aValue)
|
||||
{
|
||||
if (mRemainingLength < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matched = true;
|
||||
switch (mByteOrder) {
|
||||
case ByteOrder::LittleEndian:
|
||||
aValue = LittleEndian::readUint16(mCurrent);
|
||||
break;
|
||||
case ByteOrder::BigEndian:
|
||||
aValue = BigEndian::readUint16(mCurrent);
|
||||
break;
|
||||
default:
|
||||
NS_NOTREACHED("Should know the byte order by now");
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
Advance(2);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
bool
|
||||
EXIFParser::ReadUInt32(uint32_t& aValue)
|
||||
{
|
||||
if (mRemainingLength < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matched = true;
|
||||
switch (mByteOrder) {
|
||||
case ByteOrder::LittleEndian:
|
||||
aValue = LittleEndian::readUint32(mCurrent);
|
||||
break;
|
||||
case ByteOrder::BigEndian:
|
||||
aValue = BigEndian::readUint32(mCurrent);
|
||||
break;
|
||||
default:
|
||||
NS_NOTREACHED("Should know the byte order by now");
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
Advance(4);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
75
image/decoders/EXIF.h
Normal file
75
image/decoders/EXIF.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* -*- 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_decoders_EXIF_h
|
||||
#define mozilla_image_decoders_EXIF_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include "nsDebug.h"
|
||||
|
||||
#include "Orientation.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
enum class ByteOrder : uint8_t {
|
||||
Unknown,
|
||||
LittleEndian,
|
||||
BigEndian
|
||||
};
|
||||
|
||||
struct EXIFData
|
||||
{
|
||||
EXIFData() { }
|
||||
explicit EXIFData(Orientation aOrientation) : orientation(aOrientation) { }
|
||||
|
||||
const Orientation orientation;
|
||||
};
|
||||
|
||||
class EXIFParser
|
||||
{
|
||||
public:
|
||||
static EXIFData
|
||||
Parse(const uint8_t* aData, const uint32_t aLength)
|
||||
{
|
||||
EXIFParser parser;
|
||||
return parser.ParseEXIF(aData, aLength);
|
||||
}
|
||||
|
||||
private:
|
||||
EXIFParser()
|
||||
: mStart(nullptr)
|
||||
, mCurrent(nullptr)
|
||||
, mLength(0)
|
||||
, mRemainingLength(0)
|
||||
, mByteOrder(ByteOrder::Unknown)
|
||||
{ }
|
||||
|
||||
EXIFData ParseEXIF(const uint8_t* aData, const uint32_t aLength);
|
||||
bool ParseEXIFHeader();
|
||||
bool ParseTIFFHeader(uint32_t& aIFD0OffsetOut);
|
||||
bool ParseIFD0(Orientation& aOrientationOut);
|
||||
bool ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut);
|
||||
|
||||
bool Initialize(const uint8_t* aData, const uint32_t aLength);
|
||||
void Advance(const uint32_t aDistance);
|
||||
void JumpTo(const uint32_t aOffset);
|
||||
|
||||
bool MatchString(const char* aString, const uint32_t aLength);
|
||||
bool MatchUInt16(const uint16_t aValue);
|
||||
bool ReadUInt16(uint16_t& aOut);
|
||||
bool ReadUInt32(uint32_t& aOut);
|
||||
|
||||
const uint8_t* mStart;
|
||||
const uint8_t* mCurrent;
|
||||
uint32_t mLength;
|
||||
uint32_t mRemainingLength;
|
||||
ByteOrder mByteOrder;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_decoders_EXIF_h
|
||||
65
image/decoders/GIF2.h
Normal file
65
image/decoders/GIF2.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* -*- Mode: C; tab-width: 4; 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_decoders_GIF2_H
|
||||
#define mozilla_image_decoders_GIF2_H
|
||||
|
||||
#define MAX_LZW_BITS 12
|
||||
#define MAX_BITS 4097 // 2^MAX_LZW_BITS+1
|
||||
#define MAX_COLORS 256
|
||||
#define MIN_HOLD_SIZE 256
|
||||
|
||||
enum { GIF_TRAILER = 0x3B }; // ';'
|
||||
enum { GIF_IMAGE_SEPARATOR = 0x2C }; // ','
|
||||
enum { GIF_EXTENSION_INTRODUCER = 0x21 }; // '!'
|
||||
enum { GIF_GRAPHIC_CONTROL_LABEL = 0xF9 };
|
||||
enum { GIF_APPLICATION_EXTENSION_LABEL = 0xFF };
|
||||
|
||||
// A GIF decoder's state
|
||||
typedef struct gif_struct {
|
||||
// LZW decoder state machine
|
||||
uint8_t* stackp; // Current stack pointer
|
||||
int datasize;
|
||||
int codesize;
|
||||
int codemask;
|
||||
int avail; // Index of next available slot in dictionary
|
||||
int oldcode;
|
||||
uint8_t firstchar;
|
||||
int bits; // Number of unread bits in "datum"
|
||||
int32_t datum; // 32-bit input buffer
|
||||
|
||||
// Output state machine
|
||||
int64_t pixels_remaining; // Pixels remaining to be output.
|
||||
|
||||
// Parameters for image frame currently being decoded
|
||||
int tpixel; // Index of transparent pixel
|
||||
int32_t disposal_method; // Restore to background, leave in place, etc.
|
||||
uint32_t* local_colormap; // Per-image colormap
|
||||
int local_colormap_size; // Size of local colormap array.
|
||||
uint32_t delay_time; // Display time, in milliseconds,
|
||||
// for this image in a multi-image GIF
|
||||
|
||||
// Global (multi-image) state
|
||||
int version; // Either 89 for GIF89 or 87 for GIF87
|
||||
int32_t screen_width; // Logical screen width & height
|
||||
int32_t screen_height;
|
||||
uint8_t global_colormap_depth; // Depth of global colormap array
|
||||
uint16_t global_colormap_count; // Number of colors in global colormap
|
||||
int images_decoded; // Counts images for multi-part GIFs
|
||||
int loop_count; // Netscape specific extension block to control
|
||||
// the number of animation loops a GIF
|
||||
// renders.
|
||||
|
||||
bool is_transparent; // TRUE, if tpixel is valid
|
||||
|
||||
uint16_t prefix[MAX_BITS]; // LZW decoding tables
|
||||
uint32_t global_colormap[MAX_COLORS]; // Default colormap if local not
|
||||
// supplied
|
||||
uint8_t suffix[MAX_BITS]; // LZW decoding tables
|
||||
uint8_t stack[MAX_BITS]; // Base of LZW decoder stack
|
||||
|
||||
} gif_struct;
|
||||
|
||||
#endif // mozilla_image_decoders_GIF2_H
|
||||
195
image/decoders/iccjpeg.c
Normal file
195
image/decoders/iccjpeg.c
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/* 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/. */
|
||||
|
||||
/*
|
||||
* iccjpeg.c
|
||||
*
|
||||
* This file provides code to read and write International Color Consortium
|
||||
* (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
|
||||
* defined a standard format for including such data in JPEG "APP2" markers.
|
||||
* The code given here does not know anything about the internal structure
|
||||
* of the ICC profile data; it just knows how to put the profile data into
|
||||
* a JPEG file being written, or get it back out when reading.
|
||||
*
|
||||
* This code depends on new features added to the IJG JPEG library as of
|
||||
* IJG release 6b; it will not compile or work with older IJG versions.
|
||||
*
|
||||
* NOTE: this code would need surgery to work on 16-bit-int machines
|
||||
* with ICC profiles exceeding 64K bytes in size. If you need to do that,
|
||||
* change all the "unsigned int" variables to "INT32". You'll also need
|
||||
* to find a malloc() replacement that can allocate more than 64K.
|
||||
*/
|
||||
|
||||
#include "iccjpeg.h"
|
||||
#include <stdlib.h> /* define malloc() */
|
||||
|
||||
|
||||
/*
|
||||
* Since an ICC profile can be larger than the maximum size of a JPEG marker
|
||||
* (64K), we need provisions to split it into multiple markers. The format
|
||||
* defined by the ICC specifies one or more APP2 markers containing the
|
||||
* following data:
|
||||
* Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
|
||||
* Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
|
||||
* Number of markers Total number of APP2's used (1 byte)
|
||||
* Profile data (remainder of APP2 data)
|
||||
* Decoders should use the marker sequence numbers to reassemble the profile,
|
||||
* rather than assuming that the APP2 markers appear in the correct sequence.
|
||||
*/
|
||||
|
||||
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
|
||||
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
|
||||
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
|
||||
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
|
||||
|
||||
/*
|
||||
* Prepare for reading an ICC profile
|
||||
*/
|
||||
|
||||
void
|
||||
setup_read_icc_profile (j_decompress_ptr cinfo)
|
||||
{
|
||||
/* Tell the library to keep any APP2 data it may find */
|
||||
jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handy subroutine to test whether a saved marker is an ICC profile marker.
|
||||
*/
|
||||
|
||||
static boolean
|
||||
marker_is_icc (jpeg_saved_marker_ptr marker)
|
||||
{
|
||||
return
|
||||
marker->marker == ICC_MARKER &&
|
||||
marker->data_length >= ICC_OVERHEAD_LEN &&
|
||||
/* verify the identifying string */
|
||||
GETJOCTET(marker->data[0]) == 0x49 &&
|
||||
GETJOCTET(marker->data[1]) == 0x43 &&
|
||||
GETJOCTET(marker->data[2]) == 0x43 &&
|
||||
GETJOCTET(marker->data[3]) == 0x5F &&
|
||||
GETJOCTET(marker->data[4]) == 0x50 &&
|
||||
GETJOCTET(marker->data[5]) == 0x52 &&
|
||||
GETJOCTET(marker->data[6]) == 0x4F &&
|
||||
GETJOCTET(marker->data[7]) == 0x46 &&
|
||||
GETJOCTET(marker->data[8]) == 0x49 &&
|
||||
GETJOCTET(marker->data[9]) == 0x4C &&
|
||||
GETJOCTET(marker->data[10]) == 0x45 &&
|
||||
GETJOCTET(marker->data[11]) == 0x0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* See if there was an ICC profile in the JPEG file being read;
|
||||
* if so, reassemble and return the profile data.
|
||||
*
|
||||
* TRUE is returned if an ICC profile was found, FALSE if not.
|
||||
* If TRUE is returned, *icc_data_ptr is set to point to the
|
||||
* returned data, and *icc_data_len is set to its length.
|
||||
*
|
||||
* IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
|
||||
* and must be freed by the caller with free() when the caller no longer
|
||||
* needs it. (Alternatively, we could write this routine to use the
|
||||
* IJG library's memory allocator, so that the data would be freed implicitly
|
||||
* at jpeg_finish_decompress() time. But it seems likely that many apps
|
||||
* will prefer to have the data stick around after decompression finishes.)
|
||||
*
|
||||
* NOTE: if the file contains invalid ICC APP2 markers, we just silently
|
||||
* return FALSE. You might want to issue an error message instead.
|
||||
*/
|
||||
|
||||
boolean
|
||||
read_icc_profile (j_decompress_ptr cinfo,
|
||||
JOCTET** icc_data_ptr,
|
||||
unsigned int* icc_data_len)
|
||||
{
|
||||
jpeg_saved_marker_ptr marker;
|
||||
int num_markers = 0;
|
||||
int seq_no;
|
||||
JOCTET* icc_data;
|
||||
unsigned int total_length;
|
||||
#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */
|
||||
char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */
|
||||
unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
|
||||
unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */
|
||||
|
||||
*icc_data_ptr = NULL; /* avoid confusion if FALSE return */
|
||||
*icc_data_len = 0;
|
||||
|
||||
/* This first pass over the saved markers discovers whether there are
|
||||
* any ICC markers and verifies the consistency of the marker numbering.
|
||||
*/
|
||||
|
||||
for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) {
|
||||
marker_present[seq_no] = 0;
|
||||
}
|
||||
|
||||
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
|
||||
if (marker_is_icc(marker)) {
|
||||
if (num_markers == 0) {
|
||||
num_markers = GETJOCTET(marker->data[13]);
|
||||
} else if (num_markers != GETJOCTET(marker->data[13])) {
|
||||
return FALSE; /* inconsistent num_markers fields */
|
||||
}
|
||||
seq_no = GETJOCTET(marker->data[12]);
|
||||
if (seq_no <= 0 || seq_no > num_markers) {
|
||||
return FALSE; /* bogus sequence number */
|
||||
}
|
||||
if (marker_present[seq_no]) {
|
||||
return FALSE; /* duplicate sequence numbers */
|
||||
}
|
||||
marker_present[seq_no] = 1;
|
||||
data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_markers == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Check for missing markers, count total space needed,
|
||||
* compute offset of each marker's part of the data.
|
||||
*/
|
||||
|
||||
total_length = 0;
|
||||
for (seq_no = 1; seq_no <= num_markers; seq_no++) {
|
||||
if (marker_present[seq_no] == 0) {
|
||||
return FALSE; /* missing sequence number */
|
||||
}
|
||||
data_offset[seq_no] = total_length;
|
||||
total_length += data_length[seq_no];
|
||||
}
|
||||
|
||||
if (total_length <= 0) {
|
||||
return FALSE; /* found only empty markers? */
|
||||
}
|
||||
|
||||
/* Allocate space for assembled data */
|
||||
icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET));
|
||||
if (icc_data == NULL) {
|
||||
return FALSE; /* oops, out of memory */
|
||||
}
|
||||
|
||||
/* and fill it in */
|
||||
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
|
||||
if (marker_is_icc(marker)) {
|
||||
JOCTET FAR* src_ptr;
|
||||
JOCTET* dst_ptr;
|
||||
unsigned int length;
|
||||
seq_no = GETJOCTET(marker->data[12]);
|
||||
dst_ptr = icc_data + data_offset[seq_no];
|
||||
src_ptr = marker->data + ICC_OVERHEAD_LEN;
|
||||
length = data_length[seq_no];
|
||||
while (length--) {
|
||||
*dst_ptr++ = *src_ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*icc_data_ptr = icc_data;
|
||||
*icc_data_len = total_length;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
67
image/decoders/iccjpeg.h
Normal file
67
image/decoders/iccjpeg.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* 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/. */
|
||||
|
||||
/*
|
||||
* iccjpeg.h
|
||||
*
|
||||
* This file provides code to read and write International Color Consortium
|
||||
* (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
|
||||
* defined a standard format for including such data in JPEG "APP2" markers.
|
||||
* The code given here does not know anything about the internal structure
|
||||
* of the ICC profile data; it just knows how to put the profile data into
|
||||
* a JPEG file being written, or get it back out when reading.
|
||||
*
|
||||
* This code depends on new features added to the IJG JPEG library as of
|
||||
* IJG release 6b; it will not compile or work with older IJG versions.
|
||||
*
|
||||
* NOTE: this code would need surgery to work on 16-bit-int machines
|
||||
* with ICC profiles exceeding 64K bytes in size. See iccprofile.c
|
||||
* for details.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_decoders_iccjpeg_h
|
||||
#define mozilla_image_decoders_iccjpeg_h
|
||||
|
||||
#include <stdio.h> /* needed to define "FILE", "NULL" */
|
||||
#include "jpeglib.h"
|
||||
|
||||
/*
|
||||
* Reading a JPEG file that may contain an ICC profile requires two steps:
|
||||
*
|
||||
* 1. After jpeg_create_decompress() but before jpeg_read_header(),
|
||||
* call setup_read_icc_profile(). This routine tells the IJG library
|
||||
* to save in memory any APP2 markers it may find in the file.
|
||||
*
|
||||
* 2. After jpeg_read_header(), call read_icc_profile() to find out
|
||||
* whether there was a profile and obtain it if so.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Prepare for reading an ICC profile
|
||||
*/
|
||||
|
||||
extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo));
|
||||
|
||||
|
||||
/*
|
||||
* See if there was an ICC profile in the JPEG file being read;
|
||||
* if so, reassemble and return the profile data.
|
||||
*
|
||||
* TRUE is returned if an ICC profile was found, FALSE if not.
|
||||
* If TRUE is returned, *icc_data_ptr is set to point to the
|
||||
* returned data, and *icc_data_len is set to its length.
|
||||
*
|
||||
* IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
|
||||
* and must be freed by the caller with free() when the caller no longer
|
||||
* needs it. (Alternatively, we could write this routine to use the
|
||||
* IJG library's memory allocator, so that the data would be freed implicitly
|
||||
* at jpeg_finish_decompress() time. But it seems likely that many apps
|
||||
* will prefer to have the data stick around after decompression finishes.)
|
||||
*/
|
||||
|
||||
extern boolean read_icc_profile JPP((j_decompress_ptr cinfo,
|
||||
JOCTET** icc_data_ptr,
|
||||
unsigned int* icc_data_len));
|
||||
#endif // mozilla_image_decoders_iccjpeg_h
|
||||
13
image/decoders/icon/android/moz.build
Normal file
13
image/decoders/icon/android/moz.build
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsIconChannel.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
145
image/decoders/icon/android/nsIconChannel.cpp
Normal file
145
image/decoders/icon/android/nsIconChannel.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 <stdlib.h>
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsIURL.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "AndroidBridge.h"
|
||||
#include "nsIconChannel.h"
|
||||
#include "nsIStringStream.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsNullPrincipal.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsIconChannel,
|
||||
nsIRequest,
|
||||
nsIChannel)
|
||||
|
||||
using namespace mozilla;
|
||||
using mozilla::dom::ContentChild;
|
||||
|
||||
static nsresult
|
||||
GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
|
||||
uint8_t* const aBuf)
|
||||
{
|
||||
if (!AndroidBridge::Bridge()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, aBuf);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
CallRemoteGetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
|
||||
uint8_t* const aBuf)
|
||||
{
|
||||
NS_ENSURE_TRUE(aBuf != nullptr, NS_ERROR_NULL_POINTER);
|
||||
|
||||
// An array has to be used to get data from remote process
|
||||
InfallibleTArray<uint8_t> bits;
|
||||
uint32_t bufSize = aIconSize * aIconSize * 4;
|
||||
|
||||
if (!ContentChild::GetSingleton()->SendGetIconForExtension(
|
||||
PromiseFlatCString(aFileExt), aIconSize, &bits)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_ASSERTION(bits.Length() == bufSize, "Pixels array is incomplete");
|
||||
if (bits.Length() != bufSize) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
memcpy(aBuf, bits.Elements(), bufSize);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt,
|
||||
uint32_t aIconSize, nsIChannel** aChannel)
|
||||
{
|
||||
NS_ENSURE_TRUE(aIconSize < 256 && aIconSize > 0, NS_ERROR_UNEXPECTED);
|
||||
|
||||
int width = aIconSize;
|
||||
int height = aIconSize;
|
||||
|
||||
// moz-icon data should have two bytes for the size,
|
||||
// then the ARGB pixel values with pre-multiplied Alpha
|
||||
const int channels = 4;
|
||||
long int buf_size = 2 + channels * height * width;
|
||||
uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
|
||||
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
|
||||
uint8_t* out = buf;
|
||||
|
||||
*(out++) = width;
|
||||
*(out++) = height;
|
||||
|
||||
nsresult rv;
|
||||
if (XRE_IsParentProcess()) {
|
||||
rv = GetIconForExtension(aFileExt, aIconSize, out);
|
||||
} else {
|
||||
rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Encode the RGBA data
|
||||
const uint8_t* in = out;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
uint8_t r = *(in++);
|
||||
uint8_t g = *(in++);
|
||||
uint8_t b = *(in++);
|
||||
uint8_t a = *(in++);
|
||||
#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
|
||||
*(out++) = DO_PREMULTIPLY(b);
|
||||
*(out++) = DO_PREMULTIPLY(g);
|
||||
*(out++) = DO_PREMULTIPLY(r);
|
||||
*(out++) = a;
|
||||
#undef DO_PREMULTIPLY
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIStringInputStream> stream =
|
||||
do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = stream->AdoptData((char*)buf, buf_size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
|
||||
// this iconChannel. Use the most restrictive security settings for the
|
||||
// temporary loadInfo to make sure the channel can not be openend.
|
||||
nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
|
||||
return NS_NewInputStreamChannel(aChannel,
|
||||
aURI,
|
||||
stream,
|
||||
nullPrincipal,
|
||||
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
|
||||
nsIContentPolicy::TYPE_INTERNAL_IMAGE,
|
||||
NS_LITERAL_CSTRING(IMAGE_ICON_MS));
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::Init(nsIURI* aURI)
|
||||
{
|
||||
nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
|
||||
NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
|
||||
|
||||
nsAutoCString stockIcon;
|
||||
iconURI->GetStockIcon(stockIcon);
|
||||
|
||||
uint32_t desiredImageSize;
|
||||
iconURI->GetImageSize(&desiredImageSize);
|
||||
|
||||
nsAutoCString iconFileExt;
|
||||
iconURI->GetFileExtension(iconFileExt);
|
||||
|
||||
return moz_icon_to_channel(iconURI, iconFileExt, desiredImageSize,
|
||||
getter_AddRefs(mRealChannel));
|
||||
}
|
||||
46
image/decoders/icon/android/nsIconChannel.h
Normal file
46
image/decoders/icon/android/nsIconChannel.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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_decoders_icon_android_nsIconChannel_h
|
||||
#define mozilla_image_decoders_icon_android_nsIconChannel_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIIconURI.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
/**
|
||||
* This class is the Android implementation of nsIconChannel.
|
||||
* It asks Android for an icon, and creates a new channel for
|
||||
* that file to which all calls will be proxied.
|
||||
*/
|
||||
class nsIconChannel final : public nsIChannel {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_FORWARD_NSIREQUEST(mRealChannel->)
|
||||
NS_FORWARD_NSICHANNEL(mRealChannel->)
|
||||
|
||||
nsIconChannel() { }
|
||||
|
||||
/**
|
||||
* Called by nsIconProtocolHandler after it creates this channel.
|
||||
* Must be called before calling any other function on this object.
|
||||
* If this method fails, no other function must be called on this object.
|
||||
*/
|
||||
nsresult Init(nsIURI* aURI);
|
||||
|
||||
private:
|
||||
~nsIconChannel() { }
|
||||
|
||||
/**
|
||||
* The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
|
||||
* Will always be non-null after a successful Init.
|
||||
*/
|
||||
nsCOMPtr<nsIChannel> mRealChannel;
|
||||
};
|
||||
|
||||
#endif // mozilla_image_decoders_icon_android_nsIconChannel_h
|
||||
16
image/decoders/icon/gtk/moz.build
Normal file
16
image/decoders/icon/gtk/moz.build
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsIconChannel.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
if CONFIG['MOZ_ENABLE_GNOMEUI']:
|
||||
CXXFLAGS += CONFIG['MOZ_GNOMEUI_CFLAGS']
|
||||
else:
|
||||
CXXFLAGS += CONFIG['TK_CFLAGS']
|
||||
427
image/decoders/icon/gtk/nsIconChannel.cpp
Normal file
427
image/decoders/icon/gtk/nsIconChannel.cpp
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 cin et: */
|
||||
/* 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 "nsIconChannel.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef MOZ_ENABLE_GIO
|
||||
#include <gio/gio.h>
|
||||
#endif
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsIMIMEService.h"
|
||||
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsIStringStream.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsNullPrincipal.h"
|
||||
#include "nsIURL.h"
|
||||
#include "prlink.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsIconChannel,
|
||||
nsIRequest,
|
||||
nsIChannel)
|
||||
|
||||
static nsresult
|
||||
moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
|
||||
nsIChannel** aChannel)
|
||||
{
|
||||
int width = gdk_pixbuf_get_width(aPixbuf);
|
||||
int height = gdk_pixbuf_get_height(aPixbuf);
|
||||
NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 &&
|
||||
gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB &&
|
||||
gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 &&
|
||||
gdk_pixbuf_get_has_alpha(aPixbuf) &&
|
||||
gdk_pixbuf_get_n_channels(aPixbuf) == 4,
|
||||
NS_ERROR_UNEXPECTED);
|
||||
|
||||
const int n_channels = 4;
|
||||
gsize buf_size = 2 + n_channels * height * width;
|
||||
uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
|
||||
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
|
||||
uint8_t* out = buf;
|
||||
|
||||
*(out++) = width;
|
||||
*(out++) = height;
|
||||
|
||||
const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf);
|
||||
int rowextra = gdk_pixbuf_get_rowstride(aPixbuf) - width * n_channels;
|
||||
|
||||
// encode the RGB data and the A data
|
||||
const guchar* in = pixels;
|
||||
for (int y = 0; y < height; ++y, in += rowextra) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
uint8_t r = *(in++);
|
||||
uint8_t g = *(in++);
|
||||
uint8_t b = *(in++);
|
||||
uint8_t a = *(in++);
|
||||
#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
*(out++) = DO_PREMULTIPLY(b);
|
||||
*(out++) = DO_PREMULTIPLY(g);
|
||||
*(out++) = DO_PREMULTIPLY(r);
|
||||
*(out++) = a;
|
||||
#else
|
||||
*(out++) = a;
|
||||
*(out++) = DO_PREMULTIPLY(r);
|
||||
*(out++) = DO_PREMULTIPLY(g);
|
||||
*(out++) = DO_PREMULTIPLY(b);
|
||||
#endif
|
||||
#undef DO_PREMULTIPLY
|
||||
}
|
||||
}
|
||||
|
||||
NS_ASSERTION(out == buf + buf_size, "size miscalculation");
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIStringInputStream> stream =
|
||||
do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
|
||||
|
||||
// Prevent the leaking of buf
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
free(buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
// stream takes ownership of buf and will free it on destruction.
|
||||
// This function cannot fail.
|
||||
rv = stream->AdoptData((char*)buf, buf_size);
|
||||
|
||||
// If this no longer holds then re-examine buf's lifetime.
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
|
||||
// this iconChannel. Use the most restrictive security settings for the
|
||||
// temporary loadInfo to make sure the channel can not be openend.
|
||||
nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
|
||||
return NS_NewInputStreamChannel(aChannel,
|
||||
aURI,
|
||||
stream,
|
||||
nullPrincipal,
|
||||
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
|
||||
nsIContentPolicy::TYPE_INTERNAL_IMAGE,
|
||||
NS_LITERAL_CSTRING(IMAGE_ICON_MS));
|
||||
}
|
||||
|
||||
static GtkWidget* gProtoWindow = nullptr;
|
||||
static GtkWidget* gStockImageWidget = nullptr;
|
||||
|
||||
static void
|
||||
ensure_stock_image_widget()
|
||||
{
|
||||
// Only the style of the GtkImage needs to be used, but the widget is kept
|
||||
// to track dynamic style changes.
|
||||
if (!gProtoWindow) {
|
||||
gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
|
||||
GtkWidget* protoLayout = gtk_fixed_new();
|
||||
gtk_container_add(GTK_CONTAINER(gProtoWindow), protoLayout);
|
||||
|
||||
gStockImageWidget = gtk_image_new();
|
||||
gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget);
|
||||
|
||||
gtk_widget_ensure_style(gStockImageWidget);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkIconSize
|
||||
moz_gtk_icon_size(const char* name)
|
||||
{
|
||||
if (strcmp(name, "button") == 0) {
|
||||
return GTK_ICON_SIZE_BUTTON;
|
||||
}
|
||||
|
||||
if (strcmp(name, "menu") == 0) {
|
||||
return GTK_ICON_SIZE_MENU;
|
||||
}
|
||||
|
||||
if (strcmp(name, "toolbar") == 0) {
|
||||
return GTK_ICON_SIZE_LARGE_TOOLBAR;
|
||||
}
|
||||
|
||||
if (strcmp(name, "toolbarsmall") == 0) {
|
||||
return GTK_ICON_SIZE_SMALL_TOOLBAR;
|
||||
}
|
||||
|
||||
if (strcmp(name, "dnd") == 0) {
|
||||
return GTK_ICON_SIZE_DND;
|
||||
}
|
||||
|
||||
if (strcmp(name, "dialog") == 0) {
|
||||
return GTK_ICON_SIZE_DIALOG;
|
||||
}
|
||||
|
||||
return GTK_ICON_SIZE_MENU;
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_GIO
|
||||
static int32_t
|
||||
GetIconSize(nsIMozIconURI* aIconURI)
|
||||
{
|
||||
nsAutoCString iconSizeString;
|
||||
|
||||
aIconURI->GetIconSize(iconSizeString);
|
||||
if (iconSizeString.IsEmpty()) {
|
||||
uint32_t size;
|
||||
mozilla::DebugOnly<nsresult> rv = aIconURI->GetImageSize(&size);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed");
|
||||
return size;
|
||||
} else {
|
||||
int size;
|
||||
|
||||
GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
|
||||
gtk_icon_size_lookup(icon_size, &size, nullptr);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scale icon buffer to preferred size */
|
||||
static nsresult
|
||||
ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize)
|
||||
{
|
||||
// Scale buffer only if width or height differ from preferred size
|
||||
if (gdk_pixbuf_get_width(*aBuf) != iconSize &&
|
||||
gdk_pixbuf_get_height(*aBuf) != iconSize) {
|
||||
GdkPixbuf* scaled = gdk_pixbuf_scale_simple(*aBuf, iconSize, iconSize,
|
||||
GDK_INTERP_BILINEAR);
|
||||
// replace original buffer by scaled
|
||||
g_object_unref(*aBuf);
|
||||
*aBuf = scaled;
|
||||
if (!scaled) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI)
|
||||
{
|
||||
GIcon *icon = nullptr;
|
||||
nsCOMPtr<nsIURL> fileURI;
|
||||
|
||||
// Read icon content
|
||||
aIconURI->GetIconURL(getter_AddRefs(fileURI));
|
||||
|
||||
// Get icon for file specified by URI
|
||||
if (fileURI) {
|
||||
bool isFile;
|
||||
nsAutoCString spec;
|
||||
fileURI->GetAsciiSpec(spec);
|
||||
if (NS_SUCCEEDED(fileURI->SchemeIs("file", &isFile)) && isFile) {
|
||||
GFile* file = g_file_new_for_uri(spec.get());
|
||||
GFileInfo* fileInfo = g_file_query_info(file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_ICON,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
nullptr, nullptr);
|
||||
g_object_unref(file);
|
||||
if (fileInfo) {
|
||||
// icon from g_content_type_get_icon doesn't need unref
|
||||
icon = g_file_info_get_icon(fileInfo);
|
||||
if (icon) {
|
||||
g_object_ref(icon);
|
||||
}
|
||||
g_object_unref(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get icon by using MIME type
|
||||
if (!icon) {
|
||||
nsAutoCString type;
|
||||
aIconURI->GetContentType(type);
|
||||
// Try to get MIME type from file extension by using nsIMIMEService
|
||||
if (type.IsEmpty()) {
|
||||
nsCOMPtr<nsIMIMEService> ms(do_GetService("@mozilla.org/mime;1"));
|
||||
if (ms) {
|
||||
nsAutoCString fileExt;
|
||||
aIconURI->GetFileExtension(fileExt);
|
||||
ms->GetTypeFromExtension(fileExt, type);
|
||||
}
|
||||
}
|
||||
char* ctype = nullptr; // character representation of content type
|
||||
if (!type.IsEmpty()) {
|
||||
ctype = g_content_type_from_mime_type(type.get());
|
||||
}
|
||||
if (ctype) {
|
||||
icon = g_content_type_get_icon(ctype);
|
||||
g_free(ctype);
|
||||
}
|
||||
}
|
||||
|
||||
// Get default icon theme
|
||||
GtkIconTheme* iconTheme = gtk_icon_theme_get_default();
|
||||
GtkIconInfo* iconInfo = nullptr;
|
||||
// Get icon size
|
||||
int32_t iconSize = GetIconSize(aIconURI);
|
||||
|
||||
if (icon) {
|
||||
// Use icon and theme to get GtkIconInfo
|
||||
iconInfo = gtk_icon_theme_lookup_by_gicon(iconTheme,
|
||||
icon, iconSize,
|
||||
(GtkIconLookupFlags)0);
|
||||
g_object_unref(icon);
|
||||
}
|
||||
|
||||
if (!iconInfo) {
|
||||
// Mozilla's mimetype lookup failed. Try the "unknown" icon.
|
||||
iconInfo = gtk_icon_theme_lookup_icon(iconTheme,
|
||||
"unknown", iconSize,
|
||||
(GtkIconLookupFlags)0);
|
||||
if (!iconInfo) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a GdkPixbuf buffer containing icon and scale it
|
||||
GdkPixbuf* buf = gtk_icon_info_load_icon(iconInfo, nullptr);
|
||||
gtk_icon_info_free(iconInfo);
|
||||
if (!buf) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsresult rv = ScaleIconBuf(&buf, iconSize);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
|
||||
getter_AddRefs(mRealChannel));
|
||||
g_object_unref(buf);
|
||||
return rv;
|
||||
}
|
||||
#endif // MOZ_ENABLE_GIO
|
||||
|
||||
nsresult
|
||||
nsIconChannel::Init(nsIURI* aURI)
|
||||
{
|
||||
nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
|
||||
NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
|
||||
|
||||
nsAutoCString stockIcon;
|
||||
iconURI->GetStockIcon(stockIcon);
|
||||
if (stockIcon.IsEmpty()) {
|
||||
#ifdef MOZ_ENABLE_GIO
|
||||
return InitWithGIO(iconURI);
|
||||
#else
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Search for stockIcon
|
||||
nsAutoCString iconSizeString;
|
||||
iconURI->GetIconSize(iconSizeString);
|
||||
|
||||
nsAutoCString iconStateString;
|
||||
iconURI->GetIconState(iconStateString);
|
||||
|
||||
GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
|
||||
GtkStateType state = iconStateString.EqualsLiteral("disabled") ?
|
||||
GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
|
||||
|
||||
// First lookup the icon by stock id and text direction.
|
||||
GtkTextDirection direction = GTK_TEXT_DIR_NONE;
|
||||
if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-ltr"))) {
|
||||
direction = GTK_TEXT_DIR_LTR;
|
||||
} else if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-rtl"))) {
|
||||
direction = GTK_TEXT_DIR_RTL;
|
||||
}
|
||||
|
||||
bool forceDirection = direction != GTK_TEXT_DIR_NONE;
|
||||
nsAutoCString stockID;
|
||||
bool useIconName = false;
|
||||
if (!forceDirection) {
|
||||
direction = gtk_widget_get_default_direction();
|
||||
stockID = stockIcon;
|
||||
} else {
|
||||
// GTK versions < 2.22 use icon names from concatenating stock id with
|
||||
// -(rtl|ltr), which is how the moz-icon stock name is interpreted here.
|
||||
stockID = Substring(stockIcon, 0, stockIcon.Length() - 4);
|
||||
// However, if we lookup bidi icons by the stock name, then GTK versions
|
||||
// >= 2.22 will use a bidi lookup convention that most icon themes do not
|
||||
// yet follow. Therefore, we first check to see if the theme supports the
|
||||
// old icon name as this will have bidi support (if found).
|
||||
GtkIconTheme* icon_theme = gtk_icon_theme_get_default();
|
||||
// Micking what gtk_icon_set_render_icon does with sizes, though it's not
|
||||
// critical as icons will be scaled to suit size. It just means we follow
|
||||
// the same pathes and so share caches.
|
||||
gint width, height;
|
||||
if (gtk_icon_size_lookup(icon_size, &width, &height)) {
|
||||
gint size = std::min(width, height);
|
||||
// We use gtk_icon_theme_lookup_icon() without
|
||||
// GTK_ICON_LOOKUP_USE_BUILTIN instead of gtk_icon_theme_has_icon() so
|
||||
// we don't pick up fallback icons added by distributions for backward
|
||||
// compatibility.
|
||||
GtkIconInfo* icon =
|
||||
gtk_icon_theme_lookup_icon(icon_theme, stockIcon.get(),
|
||||
size, (GtkIconLookupFlags)0);
|
||||
if (icon) {
|
||||
useIconName = true;
|
||||
gtk_icon_info_free(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ensure_stock_image_widget();
|
||||
GtkStyle* style = gtk_widget_get_style(gStockImageWidget);
|
||||
GtkIconSet* icon_set = nullptr;
|
||||
if (!useIconName) {
|
||||
icon_set = gtk_style_lookup_icon_set(style, stockID.get());
|
||||
}
|
||||
|
||||
if (!icon_set) {
|
||||
// Either we have choosen icon-name lookup for a bidi icon, or stockIcon is
|
||||
// not a stock id so we assume it is an icon name.
|
||||
useIconName = true;
|
||||
// Creating a GtkIconSet is a convenient way to allow the style to
|
||||
// render the icon, possibly with variations suitable for insensitive
|
||||
// states.
|
||||
icon_set = gtk_icon_set_new();
|
||||
GtkIconSource* icon_source = gtk_icon_source_new();
|
||||
|
||||
gtk_icon_source_set_icon_name(icon_source, stockIcon.get());
|
||||
gtk_icon_set_add_source(icon_set, icon_source);
|
||||
gtk_icon_source_free(icon_source);
|
||||
}
|
||||
|
||||
GdkPixbuf* icon =
|
||||
gtk_icon_set_render_icon(icon_set, style, direction, state,
|
||||
icon_size, gStockImageWidget, nullptr);
|
||||
if (useIconName) {
|
||||
gtk_icon_set_unref(icon_set);
|
||||
}
|
||||
|
||||
// According to documentation, gtk_icon_set_render_icon() never returns
|
||||
// nullptr, but it does return nullptr when we have the problem reported
|
||||
// here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13
|
||||
if (!icon) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsresult rv = moz_gdk_pixbuf_to_channel(icon, iconURI,
|
||||
getter_AddRefs(mRealChannel));
|
||||
|
||||
g_object_unref(icon);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
nsIconChannel::Shutdown() {
|
||||
if (gProtoWindow) {
|
||||
gtk_widget_destroy(gProtoWindow);
|
||||
gProtoWindow = nullptr;
|
||||
gStockImageWidget = nullptr;
|
||||
}
|
||||
}
|
||||
43
image/decoders/icon/gtk/nsIconChannel.h
Normal file
43
image/decoders/icon/gtk/nsIconChannel.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* 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_decoders_icon_gtk_nsIconChannel_h
|
||||
#define mozilla_image_decoders_icon_gtk_nsIconChannel_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIIconURI.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
/// This class is the gnome implementation of nsIconChannel. It basically asks
|
||||
/// gtk/gnome for an icon, saves it as a tmp icon, and creates a new channel for
|
||||
/// that file to which all calls will be proxied.
|
||||
class nsIconChannel final : public nsIChannel
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_FORWARD_NSIREQUEST(mRealChannel->)
|
||||
NS_FORWARD_NSICHANNEL(mRealChannel->)
|
||||
|
||||
nsIconChannel() { }
|
||||
|
||||
static void Shutdown();
|
||||
|
||||
/// Called by nsIconProtocolHandler after it creates this channel.
|
||||
/// Must be called before calling any other function on this object.
|
||||
/// If this method fails, no other function must be called on this object.
|
||||
nsresult Init(nsIURI* aURI);
|
||||
private:
|
||||
~nsIconChannel() { }
|
||||
/// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
|
||||
/// Will always be non-null after a successful Init.
|
||||
nsCOMPtr<nsIChannel> mRealChannel;
|
||||
|
||||
nsresult InitWithGIO(nsIMozIconURI* aIconURI);
|
||||
};
|
||||
|
||||
#endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h
|
||||
11
image/decoders/icon/mac/moz.build
Normal file
11
image/decoders/icon/mac/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsIconChannelCocoa.mm',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
59
image/decoders/icon/mac/nsIconChannel.h
Normal file
59
image/decoders/icon/mac/nsIconChannel.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* 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_encoders_icon_mac_nsIconChannel_h
|
||||
#define mozilla_image_encoders_icon_mac_nsIconChannel_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsXPIDLString.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsILoadGroup.h"
|
||||
#include "nsILoadInfo.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsIInputStreamPump.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIURI.h"
|
||||
|
||||
class nsIFile;
|
||||
|
||||
class nsIconChannel final : public nsIChannel, public nsIStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIREQUEST
|
||||
NS_DECL_NSICHANNEL
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
|
||||
nsIconChannel();
|
||||
|
||||
nsresult Init(nsIURI* uri);
|
||||
|
||||
protected:
|
||||
virtual ~nsIconChannel();
|
||||
|
||||
nsCOMPtr<nsIURI> mUrl;
|
||||
nsCOMPtr<nsIURI> mOriginalURI;
|
||||
nsCOMPtr<nsILoadGroup> mLoadGroup;
|
||||
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
|
||||
nsCOMPtr<nsISupports> mOwner;
|
||||
nsCOMPtr<nsILoadInfo> mLoadInfo;
|
||||
|
||||
nsCOMPtr<nsIInputStreamPump> mPump;
|
||||
nsCOMPtr<nsIStreamListener> mListener;
|
||||
|
||||
nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking);
|
||||
|
||||
nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile,
|
||||
uint32_t* aDesiredImageSize,
|
||||
nsACString& aContentType,
|
||||
nsACString& aFileExtension);
|
||||
};
|
||||
|
||||
#endif // mozilla_image_encoders_icon_mac_nsIconChannel_h
|
||||
565
image/decoders/icon/mac/nsIconChannelCocoa.mm
Normal file
565
image/decoders/icon/mac/nsIconChannelCocoa.mm
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* 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 "nsContentUtils.h"
|
||||
#include "nsIconChannel.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "nsIIconURI.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsXPIDLString.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsIStringStream.h"
|
||||
#include "nsIURL.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsIPipe.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIMIMEService.h"
|
||||
#include "nsCExternalHandlerService.h"
|
||||
#include "nsILocalFileMac.h"
|
||||
#include "nsIFileURL.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsObjCExceptions.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsContentSecurityManager.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
// nsIconChannel methods
|
||||
nsIconChannel::nsIconChannel()
|
||||
{
|
||||
}
|
||||
|
||||
nsIconChannel::~nsIconChannel()
|
||||
{
|
||||
if (mLoadInfo) {
|
||||
NS_ReleaseOnMainThread(mLoadInfo.forget());
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsIconChannel,
|
||||
nsIChannel,
|
||||
nsIRequest,
|
||||
nsIRequestObserver,
|
||||
nsIStreamListener)
|
||||
|
||||
nsresult
|
||||
nsIconChannel::Init(nsIURI* uri)
|
||||
{
|
||||
NS_ASSERTION(uri, "no uri");
|
||||
mUrl = uri;
|
||||
mOriginalURI = uri;
|
||||
nsresult rv;
|
||||
mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIRequest methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetName(nsACString& result)
|
||||
{
|
||||
return mUrl->GetSpec(result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::IsPending(bool* result)
|
||||
{
|
||||
return mPump->IsPending(result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetStatus(nsresult* status)
|
||||
{
|
||||
return mPump->GetStatus(status);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Cancel(nsresult status)
|
||||
{
|
||||
return mPump->Cancel(status);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Suspend(void)
|
||||
{
|
||||
return mPump->Suspend();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Resume(void)
|
||||
{
|
||||
return mPump->Resume();
|
||||
}
|
||||
|
||||
// nsIRequestObserver methods
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::OnStartRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext)
|
||||
{
|
||||
if (mListener) {
|
||||
return mListener->OnStartRequest(this, aContext);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::OnStopRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus)
|
||||
{
|
||||
if (mListener) {
|
||||
mListener->OnStopRequest(this, aContext, aStatus);
|
||||
mListener = nullptr;
|
||||
}
|
||||
|
||||
// Remove from load group
|
||||
if (mLoadGroup) {
|
||||
mLoadGroup->RemoveRequest(this, nullptr, aStatus);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIStreamListener methods
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (mListener) {
|
||||
return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIChannel methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetOriginalURI(nsIURI** aURI)
|
||||
{
|
||||
*aURI = mOriginalURI;
|
||||
NS_ADDREF(*aURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetOriginalURI(nsIURI* aURI)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aURI);
|
||||
mOriginalURI = aURI;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetURI(nsIURI** aURI)
|
||||
{
|
||||
*aURI = mUrl;
|
||||
NS_IF_ADDREF(*aURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Open(nsIInputStream** _retval)
|
||||
{
|
||||
return MakeInputStream(_retval, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Open2(nsIInputStream** aStream)
|
||||
{
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return Open(aStream);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
|
||||
uint32_t* aDesiredImageSize,
|
||||
nsACString& aContentType,
|
||||
nsACString& aFileExtension)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
iconURI->GetImageSize(aDesiredImageSize);
|
||||
iconURI->GetContentType(aContentType);
|
||||
iconURI->GetFileExtension(aFileExtension);
|
||||
|
||||
nsCOMPtr<nsIURL> url;
|
||||
rv = iconURI->GetIconURL(getter_AddRefs(url));
|
||||
if (NS_FAILED(rv) || !url) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
|
||||
if (NS_FAILED(rv) || !fileURL) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = fileURL->GetFile(getter_AddRefs(file));
|
||||
if (NS_FAILED(rv) || !file) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsILocalFileMac> localFileMac (do_QueryInterface(file, &rv));
|
||||
if (NS_FAILED(rv) || !localFileMac) return NS_OK;
|
||||
|
||||
*aLocalFile = file;
|
||||
NS_IF_ADDREF(*aLocalFile);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
|
||||
nsISupports* ctxt)
|
||||
{
|
||||
MOZ_ASSERT(!mLoadInfo ||
|
||||
mLoadInfo->GetSecurityMode() == 0 ||
|
||||
mLoadInfo->GetInitialSecurityCheckDone() ||
|
||||
(mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
|
||||
nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
|
||||
"security flags in loadInfo but asyncOpen2() not called");
|
||||
|
||||
nsCOMPtr<nsIInputStream> inStream;
|
||||
nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Init our stream pump
|
||||
rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mPump->AsyncRead(this, ctxt);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Store our real listener
|
||||
mListener = aListener;
|
||||
// Add ourself to the load group, if available
|
||||
if (mLoadGroup) {
|
||||
mLoadGroup->AddRequest(this, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
|
||||
{
|
||||
nsCOMPtr<nsIStreamListener> listener = aListener;
|
||||
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return AsyncOpen(listener, nullptr);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::MakeInputStream(nsIInputStream** _retval,
|
||||
bool aNonBlocking)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
nsXPIDLCString contentType;
|
||||
nsAutoCString fileExt;
|
||||
nsCOMPtr<nsIFile> fileloc; // file we want an icon for
|
||||
uint32_t desiredImageSize;
|
||||
nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc),
|
||||
&desiredImageSize, contentType, fileExt);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool fileExists = false;
|
||||
if (fileloc) {
|
||||
// ensure that we DO NOT resolve aliases, very important for file views
|
||||
fileloc->SetFollowLinks(false);
|
||||
fileloc->Exists(&fileExists);
|
||||
}
|
||||
|
||||
NSImage* iconImage = nil;
|
||||
|
||||
// first try to get the icon from the file if it exists
|
||||
if (fileExists) {
|
||||
nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
CFURLRef macURL;
|
||||
if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) {
|
||||
iconImage = [[NSWorkspace sharedWorkspace]
|
||||
iconForFile:[(NSURL*)macURL path]];
|
||||
::CFRelease(macURL);
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't have an icon yet try to get one by extension
|
||||
if (!iconImage && !fileExt.IsEmpty()) {
|
||||
NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()];
|
||||
iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension];
|
||||
}
|
||||
|
||||
// If we still don't have an icon, get the generic document icon.
|
||||
if (!iconImage) {
|
||||
iconImage = [[NSWorkspace sharedWorkspace]
|
||||
iconForFileType:NSFileTypeUnknown];
|
||||
}
|
||||
|
||||
if (!iconImage) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// we have an icon now, size it
|
||||
NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize);
|
||||
[iconImage setSize:desiredSizeRect.size];
|
||||
|
||||
[iconImage lockFocus];
|
||||
NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc]
|
||||
initWithFocusedViewRect:desiredSizeRect]
|
||||
autorelease];
|
||||
[iconImage unlockFocus];
|
||||
|
||||
// we expect the following things to be true about our bitmapRep
|
||||
NS_ENSURE_TRUE(![bitmapRep isPlanar] &&
|
||||
// Not necessarily: on a HiDPI-capable system, we'll get
|
||||
// a 2x bitmap
|
||||
// (unsigned int)[bitmapRep bytesPerPlane] ==
|
||||
// desiredImageSize * desiredImageSize * 4 &&
|
||||
[bitmapRep bitsPerPixel] == 32 &&
|
||||
[bitmapRep samplesPerPixel] == 4 &&
|
||||
[bitmapRep hasAlpha] == YES,
|
||||
NS_ERROR_UNEXPECTED);
|
||||
|
||||
// check what size we actually got, and ensure it isn't too big to return
|
||||
uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4;
|
||||
NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED);
|
||||
|
||||
// now we can validate the amount of data
|
||||
NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] ==
|
||||
actualImageSize * actualImageSize * 4,
|
||||
NS_ERROR_UNEXPECTED);
|
||||
|
||||
// rgba, pre-multiplied data
|
||||
uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData];
|
||||
|
||||
// create our buffer
|
||||
int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane];
|
||||
AutoTArray<uint8_t, 3 + 16 * 16 * 5> iconBuffer; // initial size is for
|
||||
// 16x16
|
||||
iconBuffer.SetLength(bufferCapacity);
|
||||
|
||||
uint8_t* iconBufferPtr = iconBuffer.Elements();
|
||||
|
||||
// write header data into buffer
|
||||
*iconBufferPtr++ = actualImageSize;
|
||||
*iconBufferPtr++ = actualImageSize;
|
||||
|
||||
uint32_t dataCount = [bitmapRep bytesPerPlane];
|
||||
uint32_t index = 0;
|
||||
while (index < dataCount) {
|
||||
// get data from the bitmap
|
||||
uint8_t r = bitmapRepData[index++];
|
||||
uint8_t g = bitmapRepData[index++];
|
||||
uint8_t b = bitmapRepData[index++];
|
||||
uint8_t a = bitmapRepData[index++];
|
||||
|
||||
// write data out to our buffer
|
||||
// non-cairo uses native image format, but the A channel is ignored.
|
||||
// cairo uses ARGB (highest to lowest bits)
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
*iconBufferPtr++ = b;
|
||||
*iconBufferPtr++ = g;
|
||||
*iconBufferPtr++ = r;
|
||||
*iconBufferPtr++ = a;
|
||||
#else
|
||||
*iconBufferPtr++ = a;
|
||||
*iconBufferPtr++ = r;
|
||||
*iconBufferPtr++ = g;
|
||||
*iconBufferPtr++ = b;
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity,
|
||||
"buffer size miscalculation");
|
||||
|
||||
// Now, create a pipe and stuff our data into it
|
||||
nsCOMPtr<nsIInputStream> inStream;
|
||||
nsCOMPtr<nsIOutputStream> outStream;
|
||||
rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
|
||||
bufferCapacity, bufferCapacity, aNonBlocking);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
uint32_t written;
|
||||
rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity,
|
||||
&written);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_IF_ADDREF(*_retval = inStream);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop notification callbacks to prevent cycles.
|
||||
mCallbacks = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
|
||||
{
|
||||
return mPump->GetLoadFlags(aLoadAttributes);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
|
||||
{
|
||||
return mPump->SetLoadFlags(aLoadAttributes);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentType(nsACString& aContentType)
|
||||
{
|
||||
aContentType.AssignLiteral(IMAGE_ICON_MS);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentType(const nsACString& aContentType)
|
||||
{
|
||||
//It doesn't make sense to set the content-type on this type
|
||||
// of channel...
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentCharset(nsACString& aContentCharset)
|
||||
{
|
||||
aContentCharset.AssignLiteral(IMAGE_ICON_MS);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
|
||||
{
|
||||
//It doesn't make sense to set the content-type on this type
|
||||
// of channel...
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
GetContentDispositionFilename(nsAString& aContentDispositionFilename)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
GetContentDispositionHeader(nsACString& aContentDispositionHeader)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentLength(int64_t* aContentLength)
|
||||
{
|
||||
*aContentLength = 0;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentLength(int64_t aContentLength)
|
||||
{
|
||||
NS_NOTREACHED("nsIconChannel::SetContentLength");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
|
||||
{
|
||||
*aLoadGroup = mLoadGroup;
|
||||
NS_IF_ADDREF(*aLoadGroup);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
|
||||
{
|
||||
mLoadGroup = aLoadGroup;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetOwner(nsISupports** aOwner)
|
||||
{
|
||||
*aOwner = mOwner.get();
|
||||
NS_IF_ADDREF(*aOwner);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetOwner(nsISupports* aOwner)
|
||||
{
|
||||
mOwner = aOwner;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
|
||||
{
|
||||
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
|
||||
{
|
||||
mLoadInfo = aLoadInfo;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
|
||||
{
|
||||
*aNotificationCallbacks = mCallbacks.get();
|
||||
NS_IF_ADDREF(*aNotificationCallbacks);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
|
||||
{
|
||||
mCallbacks = aNotificationCallbacks;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
|
||||
{
|
||||
*aSecurityInfo = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
32
image/decoders/icon/moz.build
Normal file
32
image/decoders/icon/moz.build
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsIconModule.cpp',
|
||||
'nsIconProtocolHandler.cpp',
|
||||
'nsIconURI.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
platform = None
|
||||
|
||||
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
|
||||
platform = 'gtk'
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
platform = 'win'
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
platform = 'mac'
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Android':
|
||||
platform = 'android'
|
||||
|
||||
if platform:
|
||||
LOCAL_INCLUDES += [platform]
|
||||
60
image/decoders/icon/nsIconModule.cpp
Normal file
60
image/decoders/icon/nsIconModule.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/* -*- 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 "mozilla/ModuleUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
||||
#include "nsIconProtocolHandler.h"
|
||||
#include "nsIconURI.h"
|
||||
#include "nsIconChannel.h"
|
||||
|
||||
// objects that just require generic constructors
|
||||
//*****************************************************************************
|
||||
// Protocol CIDs
|
||||
|
||||
#define NS_ICONPROTOCOL_CID { 0xd0f9db12, 0x249c, 0x11d5, \
|
||||
{ 0x99, 0x5, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIconProtocolHandler)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMozIconURI)
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_ICONPROTOCOL_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_MOZICONURI_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kIconCIDs[] = {
|
||||
{ &kNS_ICONPROTOCOL_CID, false, nullptr, nsIconProtocolHandlerConstructor },
|
||||
{ &kNS_MOZICONURI_CID, false, nullptr, nsMozIconURIConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kIconContracts[] = {
|
||||
{ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-icon", &kNS_ICONPROTOCOL_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::CategoryEntry kIconCategories[] = {
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static void
|
||||
IconDecoderModuleDtor()
|
||||
{
|
||||
#if (MOZ_WIDGET_GTK == 2)
|
||||
nsIconChannel::Shutdown();
|
||||
#endif
|
||||
}
|
||||
|
||||
static const mozilla::Module kIconModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kIconCIDs,
|
||||
kIconContracts,
|
||||
kIconCategories,
|
||||
nullptr,
|
||||
nullptr,
|
||||
IconDecoderModuleDtor
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsIconDecoderModule) = &kIconModule;
|
||||
126
image/decoders/icon/nsIconProtocolHandler.cpp
Normal file
126
image/decoders/icon/nsIconProtocolHandler.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* 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 "nsIconProtocolHandler.h"
|
||||
|
||||
#include "nsIconChannel.h"
|
||||
#include "nsIconURI.h"
|
||||
#include "nsIURL.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIComponentManager.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsNetCID.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsIconProtocolHandler::nsIconProtocolHandler()
|
||||
{ }
|
||||
|
||||
nsIconProtocolHandler::~nsIconProtocolHandler()
|
||||
{ }
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsIconProtocolHandler, nsIProtocolHandler,
|
||||
nsISupportsWeakReference)
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// nsIProtocolHandler methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::GetScheme(nsACString& result)
|
||||
{
|
||||
result = "moz-icon";
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::GetDefaultPort(int32_t* result)
|
||||
{
|
||||
*result = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::AllowPort(int32_t port,
|
||||
const char* scheme,
|
||||
bool* _retval)
|
||||
{
|
||||
// don't override anything.
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::GetProtocolFlags(uint32_t* result)
|
||||
{
|
||||
*result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE |
|
||||
URI_IS_LOCAL_RESOURCE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::NewURI(const nsACString& aSpec,
|
||||
const char* aOriginCharset, // ignored
|
||||
nsIURI* aBaseURI,
|
||||
nsIURI** result)
|
||||
{
|
||||
nsCOMPtr<nsIMozIconURI> uri = new nsMozIconURI();
|
||||
if (!uri) return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
nsresult rv = uri->SetSpec(aSpec);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
nsCOMPtr<nsIURL> iconURL;
|
||||
uri->GetIconURL(getter_AddRefs(iconURL));
|
||||
if (iconURL) {
|
||||
uri = new nsNestedMozIconURI();
|
||||
rv = uri->SetSpec(aSpec);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ADDREF(*result = uri);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::NewChannel2(nsIURI* url,
|
||||
nsILoadInfo* aLoadInfo,
|
||||
nsIChannel** result)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(url);
|
||||
nsIconChannel* channel = new nsIconChannel;
|
||||
if (!channel) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
NS_ADDREF(channel);
|
||||
|
||||
nsresult rv = channel->Init(url);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_RELEASE(channel);
|
||||
return rv;
|
||||
}
|
||||
|
||||
// set the loadInfo on the new channel
|
||||
rv = channel->SetLoadInfo(aLoadInfo);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_RELEASE(channel);
|
||||
return rv;
|
||||
}
|
||||
|
||||
*result = channel;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconProtocolHandler::NewChannel(nsIURI* url, nsIChannel** result)
|
||||
{
|
||||
return NewChannel2(url, nullptr, result);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
26
image/decoders/icon/nsIconProtocolHandler.h
Normal file
26
image/decoders/icon/nsIconProtocolHandler.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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_decoders_icon_nsIconProtocolHandler_h
|
||||
#define mozilla_image_decoders_icon_nsIconProtocolHandler_h
|
||||
|
||||
#include "nsWeakReference.h"
|
||||
#include "nsIProtocolHandler.h"
|
||||
|
||||
class nsIconProtocolHandler : public nsIProtocolHandler,
|
||||
public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPROTOCOLHANDLER
|
||||
|
||||
// nsIconProtocolHandler methods:
|
||||
nsIconProtocolHandler();
|
||||
|
||||
protected:
|
||||
virtual ~nsIconProtocolHandler();
|
||||
};
|
||||
|
||||
#endif // mozilla_image_decoders_icon_nsIconProtocolHandler_h
|
||||
699
image/decoders/icon/nsIconURI.cpp
Normal file
699
image/decoders/icon/nsIconURI.cpp
Normal file
|
|
@ -0,0 +1,699 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set sw=2 sts=2 ts=2 et tw=80:
|
||||
*
|
||||
* 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 "nsIconURI.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/ipc/URIUtils.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "nsIIOService.h"
|
||||
#include "nsIURL.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "plstr.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::ipc;
|
||||
|
||||
#define DEFAULT_IMAGE_SIZE 16
|
||||
|
||||
#if defined(MAX_PATH)
|
||||
#define SANE_FILE_NAME_LEN MAX_PATH
|
||||
#elif defined(PATH_MAX)
|
||||
#define SANE_FILE_NAME_LEN PATH_MAX
|
||||
#else
|
||||
#define SANE_FILE_NAME_LEN 1024
|
||||
#endif
|
||||
|
||||
// helper function for parsing out attributes like size, and contentType
|
||||
// from the icon url.
|
||||
static void extractAttributeValue(const char* aSearchString,
|
||||
const char* aAttributeName,
|
||||
nsCString& aResult);
|
||||
|
||||
static const char* kSizeStrings[] =
|
||||
{
|
||||
"button",
|
||||
"toolbar",
|
||||
"toolbarsmall",
|
||||
"menu",
|
||||
"dnd",
|
||||
"dialog"
|
||||
};
|
||||
|
||||
static const char* kStateStrings[] =
|
||||
{
|
||||
"normal",
|
||||
"disabled"
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsMozIconURI::nsMozIconURI()
|
||||
: mSize(DEFAULT_IMAGE_SIZE),
|
||||
mIconSize(-1),
|
||||
mIconState(-1)
|
||||
{ }
|
||||
|
||||
nsMozIconURI::~nsMozIconURI()
|
||||
{ }
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsMozIconURI, nsIMozIconURI, nsIURI, nsIIPCSerializableURI)
|
||||
|
||||
#define MOZICON_SCHEME "moz-icon:"
|
||||
#define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIURI methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetSpec(nsACString& aSpec)
|
||||
{
|
||||
aSpec = MOZICON_SCHEME;
|
||||
|
||||
if (mIconURL) {
|
||||
nsAutoCString fileIconSpec;
|
||||
nsresult rv = mIconURL->GetSpec(fileIconSpec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
aSpec += fileIconSpec;
|
||||
} else if (!mStockIcon.IsEmpty()) {
|
||||
aSpec += "//stock/";
|
||||
aSpec += mStockIcon;
|
||||
} else {
|
||||
aSpec += "//";
|
||||
aSpec += mFileName;
|
||||
}
|
||||
|
||||
aSpec += "?size=";
|
||||
if (mIconSize >= 0) {
|
||||
aSpec += kSizeStrings[mIconSize];
|
||||
} else {
|
||||
char buf[20];
|
||||
SprintfLiteral(buf, "%d", mSize);
|
||||
aSpec.Append(buf);
|
||||
}
|
||||
|
||||
if (mIconState >= 0) {
|
||||
aSpec += "&state=";
|
||||
aSpec += kStateStrings[mIconState];
|
||||
}
|
||||
|
||||
if (!mContentType.IsEmpty()) {
|
||||
aSpec += "&contentType=";
|
||||
aSpec += mContentType.get();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetSpecIgnoringRef(nsACString& result)
|
||||
{
|
||||
return GetSpec(result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetHasRef(bool* result)
|
||||
{
|
||||
*result = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// takes a string like ?size=32&contentType=text/html and returns a new string
|
||||
// containing just the attribute value. i.e you could pass in this string with
|
||||
// an attribute name of 'size=', this will return 32
|
||||
// Assumption: attribute pairs in the string are separated by '&'.
|
||||
void
|
||||
extractAttributeValue(const char* aSearchString,
|
||||
const char* aAttributeName,
|
||||
nsCString& aResult)
|
||||
{
|
||||
//NS_ENSURE_ARG_POINTER(extractAttributeValue);
|
||||
|
||||
aResult.Truncate();
|
||||
|
||||
if (aSearchString && aAttributeName) {
|
||||
// search the string for attributeName
|
||||
uint32_t attributeNameSize = strlen(aAttributeName);
|
||||
const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName);
|
||||
if (startOfAttribute &&
|
||||
( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') ) {
|
||||
startOfAttribute += attributeNameSize; // skip over the attributeName
|
||||
// is there something after the attribute name
|
||||
if (*startOfAttribute) {
|
||||
const char* endofAttribute = strchr(startOfAttribute, '&');
|
||||
if (endofAttribute) {
|
||||
aResult.Assign(Substring(startOfAttribute, endofAttribute));
|
||||
} else {
|
||||
aResult.Assign(startOfAttribute);
|
||||
}
|
||||
} // if we have a attribute value
|
||||
} // if we have a attribute name
|
||||
} // if we got non-null search string and attribute name values
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetSpec(const nsACString& aSpec)
|
||||
{
|
||||
// Reset everything to default values.
|
||||
mIconURL = nullptr;
|
||||
mSize = DEFAULT_IMAGE_SIZE;
|
||||
mContentType.Truncate();
|
||||
mFileName.Truncate();
|
||||
mStockIcon.Truncate();
|
||||
mIconSize = -1;
|
||||
mIconState = -1;
|
||||
|
||||
nsAutoCString iconSpec(aSpec);
|
||||
if (!Substring(iconSpec, 0,
|
||||
MOZICON_SCHEME_LEN).EqualsLiteral(MOZICON_SCHEME)) {
|
||||
return NS_ERROR_MALFORMED_URI;
|
||||
}
|
||||
|
||||
int32_t questionMarkPos = iconSpec.Find("?");
|
||||
if (questionMarkPos != -1 &&
|
||||
static_cast<int32_t>(iconSpec.Length()) > (questionMarkPos + 1)) {
|
||||
extractAttributeValue(iconSpec.get(), "contentType=", mContentType);
|
||||
|
||||
nsAutoCString sizeString;
|
||||
extractAttributeValue(iconSpec.get(), "size=", sizeString);
|
||||
if (!sizeString.IsEmpty()) {
|
||||
const char* sizeStr = sizeString.get();
|
||||
for (uint32_t i = 0; i < ArrayLength(kSizeStrings); i++) {
|
||||
if (PL_strcasecmp(sizeStr, kSizeStrings[i]) == 0) {
|
||||
mIconSize = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t sizeValue = atoi(sizeString.get());
|
||||
if (sizeValue > 0) {
|
||||
mSize = sizeValue;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoCString stateString;
|
||||
extractAttributeValue(iconSpec.get(), "state=", stateString);
|
||||
if (!stateString.IsEmpty()) {
|
||||
const char* stateStr = stateString.get();
|
||||
for (uint32_t i = 0; i < ArrayLength(kStateStrings); i++) {
|
||||
if (PL_strcasecmp(stateStr, kStateStrings[i]) == 0) {
|
||||
mIconState = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t pathLength = iconSpec.Length() - MOZICON_SCHEME_LEN;
|
||||
if (questionMarkPos != -1) {
|
||||
pathLength = questionMarkPos - MOZICON_SCHEME_LEN;
|
||||
}
|
||||
if (pathLength < 3) {
|
||||
return NS_ERROR_MALFORMED_URI;
|
||||
}
|
||||
|
||||
nsAutoCString iconPath(Substring(iconSpec, MOZICON_SCHEME_LEN, pathLength));
|
||||
|
||||
// Icon URI path can have three forms:
|
||||
// (1) //stock/<icon-identifier>
|
||||
// (2) //<some dummy file with an extension>
|
||||
// (3) a valid URL
|
||||
|
||||
if (!strncmp("//stock/", iconPath.get(), 8)) {
|
||||
mStockIcon.Assign(Substring(iconPath, 8));
|
||||
// An icon identifier must always be specified.
|
||||
if (mStockIcon.IsEmpty()) {
|
||||
return NS_ERROR_MALFORMED_URI;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (StringBeginsWith(iconPath, NS_LITERAL_CSTRING("//"))) {
|
||||
// Sanity check this supposed dummy file name.
|
||||
if (iconPath.Length() > SANE_FILE_NAME_LEN) {
|
||||
return NS_ERROR_MALFORMED_URI;
|
||||
}
|
||||
iconPath.Cut(0, 2);
|
||||
mFileName.Assign(iconPath);
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
ioService->NewURI(iconPath, nullptr, nullptr, getter_AddRefs(uri));
|
||||
mIconURL = do_QueryInterface(uri);
|
||||
if (mIconURL) {
|
||||
mFileName.Truncate();
|
||||
} else if (mFileName.IsEmpty()) {
|
||||
return NS_ERROR_MALFORMED_URI;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetPrePath(nsACString& prePath)
|
||||
{
|
||||
prePath = MOZICON_SCHEME;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetScheme(nsACString& aScheme)
|
||||
{
|
||||
aScheme = "moz-icon";
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetScheme(const nsACString& aScheme)
|
||||
{
|
||||
// doesn't make sense to set the scheme of a moz-icon URL
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetUsername(nsACString& aUsername)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetUsername(const nsACString& aUsername)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetPassword(nsACString& aPassword)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetPassword(const nsACString& aPassword)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetUserPass(nsACString& aUserPass)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetUserPass(const nsACString& aUserPass)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetHostPort(nsACString& aHostPort)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetHostPort(const nsACString& aHostPort)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetHostAndPort(const nsACString& aHostPort)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetHost(nsACString& aHost)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetHost(const nsACString& aHost)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetPort(int32_t* aPort)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetPort(int32_t aPort)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetPath(nsACString& aPath)
|
||||
{
|
||||
aPath.Truncate();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetPath(const nsACString& aPath)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetRef(nsACString& aRef)
|
||||
{
|
||||
aRef.Truncate();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetRef(const nsACString& aRef)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::Equals(nsIURI* other, bool* result)
|
||||
{
|
||||
*result = false;
|
||||
NS_ENSURE_ARG_POINTER(other);
|
||||
NS_PRECONDITION(result, "null pointer");
|
||||
|
||||
nsAutoCString spec1;
|
||||
nsAutoCString spec2;
|
||||
|
||||
nsresult rv = GetSpec(spec1);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = other->GetSpec(spec2);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!PL_strcasecmp(spec1.get(), spec2.get())) {
|
||||
*result = true;
|
||||
} else {
|
||||
*result = false;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::EqualsExceptRef(nsIURI* other, bool* result)
|
||||
{
|
||||
// GetRef/SetRef not supported by nsMozIconURI, so
|
||||
// EqualsExceptRef() is the same as Equals().
|
||||
return Equals(other, result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SchemeIs(const char* aScheme, bool* aEquals)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aEquals);
|
||||
if (!aScheme) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*aEquals = PL_strcasecmp("moz-icon", aScheme) ? false : true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::Clone(nsIURI** result)
|
||||
{
|
||||
nsCOMPtr<nsIURL> newIconURL;
|
||||
if (mIconURL) {
|
||||
nsCOMPtr<nsIURI> newURI;
|
||||
nsresult rv = mIconURL->Clone(getter_AddRefs(newURI));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
newIconURL = do_QueryInterface(newURI, &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
nsMozIconURI* uri = new nsMozIconURI();
|
||||
newIconURL.swap(uri->mIconURL);
|
||||
uri->mSize = mSize;
|
||||
uri->mContentType = mContentType;
|
||||
uri->mFileName = mFileName;
|
||||
uri->mStockIcon = mStockIcon;
|
||||
uri->mIconSize = mIconSize;
|
||||
uri->mIconState = mIconState;
|
||||
NS_ADDREF(*result = uri);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::CloneIgnoringRef(nsIURI** result)
|
||||
{
|
||||
// GetRef/SetRef not supported by nsMozIconURI, so
|
||||
// CloneIgnoringRef() is the same as Clone().
|
||||
return Clone(result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::CloneWithNewRef(const nsACString& newRef, nsIURI** result)
|
||||
{
|
||||
// GetRef/SetRef not supported by nsMozIconURI, so
|
||||
// CloneWithNewRef() is the same as Clone().
|
||||
return Clone(result);
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::Resolve(const nsACString& relativePath, nsACString& result)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetAsciiSpec(nsACString& aSpecA)
|
||||
{
|
||||
return GetSpec(aSpecA);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetAsciiHostPort(nsACString& aHostPortA)
|
||||
{
|
||||
return GetHostPort(aHostPortA);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetAsciiHost(nsACString& aHostA)
|
||||
{
|
||||
return GetHost(aHostA);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetOriginCharset(nsACString& result)
|
||||
{
|
||||
result.Truncate();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIIconUri methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetIconURL(nsIURL** aFileUrl)
|
||||
{
|
||||
*aFileUrl = mIconURL;
|
||||
NS_IF_ADDREF(*aFileUrl);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetIconURL(nsIURL* aFileUrl)
|
||||
{
|
||||
// this isn't called anywhere, needs to go through SetSpec parsing
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetImageSize(uint32_t* aImageSize)
|
||||
// measured by # of pixels in a row. defaults to 16.
|
||||
{
|
||||
*aImageSize = mSize;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetImageSize(uint32_t aImageSize)
|
||||
// measured by # of pixels in a row. defaults to 16.
|
||||
{
|
||||
mSize = aImageSize;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetContentType(nsACString& aContentType)
|
||||
{
|
||||
aContentType = mContentType;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::SetContentType(const nsACString& aContentType)
|
||||
{
|
||||
mContentType = aContentType;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetFileExtension(nsACString& aFileExtension)
|
||||
{
|
||||
// First, try to get the extension from mIconURL if we have one
|
||||
if (mIconURL) {
|
||||
nsAutoCString fileExt;
|
||||
if (NS_SUCCEEDED(mIconURL->GetFileExtension(fileExt))) {
|
||||
if (!fileExt.IsEmpty()) {
|
||||
// unfortunately, this code doesn't give us the required '.' in
|
||||
// front of the extension so we have to do it ourselves.
|
||||
aFileExtension.Assign('.');
|
||||
aFileExtension.Append(fileExt);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mFileName.IsEmpty()) {
|
||||
// truncate the extension out of the file path...
|
||||
const char* chFileName = mFileName.get(); // get the underlying buffer
|
||||
const char* fileExt = strrchr(chFileName, '.');
|
||||
if (!fileExt) {
|
||||
return NS_OK;
|
||||
}
|
||||
aFileExtension = fileExt;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetStockIcon(nsACString& aStockIcon)
|
||||
{
|
||||
aStockIcon = mStockIcon;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetIconSize(nsACString& aSize)
|
||||
{
|
||||
if (mIconSize >= 0) {
|
||||
aSize = kSizeStrings[mIconSize];
|
||||
} else {
|
||||
aSize.Truncate();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMozIconURI::GetIconState(nsACString& aState)
|
||||
{
|
||||
if (mIconState >= 0) {
|
||||
aState = kStateStrings[mIconState];
|
||||
} else {
|
||||
aState.Truncate();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIIPCSerializableURI methods:
|
||||
|
||||
void
|
||||
nsMozIconURI::Serialize(URIParams& aParams)
|
||||
{
|
||||
IconURIParams params;
|
||||
|
||||
if (mIconURL) {
|
||||
URIParams iconURLParams;
|
||||
SerializeURI(mIconURL, iconURLParams);
|
||||
if (iconURLParams.type() == URIParams::T__None) {
|
||||
// Serialization failed, bail.
|
||||
return;
|
||||
}
|
||||
|
||||
params.uri() = iconURLParams;
|
||||
} else {
|
||||
params.uri() = void_t();
|
||||
}
|
||||
|
||||
params.size() = mSize;
|
||||
params.fileName() = mFileName;
|
||||
params.stockIcon() = mStockIcon;
|
||||
params.iconSize() = mIconSize;
|
||||
params.iconState() = mIconState;
|
||||
|
||||
aParams = params;
|
||||
}
|
||||
|
||||
bool
|
||||
nsMozIconURI::Deserialize(const URIParams& aParams)
|
||||
{
|
||||
if (aParams.type() != URIParams::TIconURIParams) {
|
||||
MOZ_ASSERT_UNREACHABLE("Received unknown URI from other process!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const IconURIParams& params = aParams.get_IconURIParams();
|
||||
if (params.uri().type() != OptionalURIParams::Tvoid_t) {
|
||||
nsCOMPtr<nsIURI> uri = DeserializeURI(params.uri().get_URIParams());
|
||||
mIconURL = do_QueryInterface(uri);
|
||||
if (!mIconURL) {
|
||||
MOZ_ASSERT_UNREACHABLE("bad nsIURI passed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mSize = params.size();
|
||||
mContentType = params.contentType();
|
||||
mFileName = params.fileName();
|
||||
mStockIcon = params.stockIcon();
|
||||
mIconSize = params.iconSize();
|
||||
mIconState = params.iconState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Nested version of nsIconURI
|
||||
|
||||
nsNestedMozIconURI::nsNestedMozIconURI()
|
||||
{ }
|
||||
|
||||
nsNestedMozIconURI::~nsNestedMozIconURI()
|
||||
{ }
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(nsNestedMozIconURI, nsMozIconURI, nsINestedURI)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNestedMozIconURI::GetInnerURI(nsIURI** aURI)
|
||||
{
|
||||
nsCOMPtr<nsIURI> iconURL = do_QueryInterface(mIconURL);
|
||||
if (iconURL) {
|
||||
iconURL.forget(aURI);
|
||||
} else {
|
||||
*aURI = nullptr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNestedMozIconURI::GetInnermostURI(nsIURI** aURI)
|
||||
{
|
||||
return NS_ImplGetInnermostURI(this, aURI);
|
||||
}
|
||||
|
||||
63
image/decoders/icon/nsIconURI.h
Normal file
63
image/decoders/icon/nsIconURI.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* 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_decoders_icon_nsIconURI_h
|
||||
#define mozilla_image_decoders_icon_nsIconURI_h
|
||||
|
||||
#include "nsIIconURI.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsIIPCSerializableURI.h"
|
||||
#include "nsINestedURI.h"
|
||||
|
||||
class nsMozIconURI : public nsIMozIconURI
|
||||
, public nsIIPCSerializableURI
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIURI
|
||||
NS_DECL_NSIMOZICONURI
|
||||
NS_DECL_NSIIPCSERIALIZABLEURI
|
||||
|
||||
// nsMozIconURI
|
||||
nsMozIconURI();
|
||||
|
||||
protected:
|
||||
virtual ~nsMozIconURI();
|
||||
nsCOMPtr<nsIURL> mIconURL; // a URL that we want the icon for
|
||||
uint32_t mSize; // the # of pixels in a row that we want for this image.
|
||||
// Typically 16, 32, 128, etc.
|
||||
nsCString mContentType; // optional field explicitly specifying the content
|
||||
// type
|
||||
nsCString mFileName; // for if we don't have an actual file path, we're just
|
||||
// given a filename with an extension
|
||||
nsCString mStockIcon;
|
||||
int32_t mIconSize; // -1 if not specified, otherwise index into
|
||||
// kSizeStrings
|
||||
int32_t mIconState; // -1 if not specified, otherwise index into
|
||||
// kStateStrings
|
||||
};
|
||||
|
||||
// For moz-icon URIs that point to an actual file on disk and are
|
||||
// therefore nested URIs
|
||||
class nsNestedMozIconURI final : public nsMozIconURI
|
||||
, public nsINestedURI
|
||||
{
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_FORWARD_NSIURI(nsMozIconURI::)
|
||||
NS_FORWARD_NSIMOZICONURI(nsMozIconURI::)
|
||||
NS_FORWARD_NSIIPCSERIALIZABLEURI(nsMozIconURI::)
|
||||
|
||||
NS_DECL_NSINESTEDURI
|
||||
|
||||
nsNestedMozIconURI();
|
||||
|
||||
protected:
|
||||
virtual ~nsNestedMozIconURI();
|
||||
|
||||
};
|
||||
|
||||
#endif // mozilla_image_decoders_icon_nsIconURI_h
|
||||
11
image/decoders/icon/win/moz.build
Normal file
11
image/decoders/icon/win/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsIconChannel.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
841
image/decoders/icon/win/nsIconChannel.cpp
Normal file
841
image/decoders/icon/win/nsIconChannel.cpp
Normal file
|
|
@ -0,0 +1,841 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* 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 "mozilla/ArrayUtils.h"
|
||||
|
||||
#include "nsIconChannel.h"
|
||||
#include "nsIIconURI.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsXPIDLString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsIStringStream.h"
|
||||
#include "nsIURL.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIPipe.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIFileURL.h"
|
||||
#include "nsIMIMEService.h"
|
||||
#include "nsCExternalHandlerService.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsContentSecurityManager.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0600
|
||||
|
||||
// we need windows.h to read out registry information...
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj.h>
|
||||
#include <objbase.h>
|
||||
#include <wchar.h>
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
struct ICONFILEHEADER {
|
||||
uint16_t ifhReserved;
|
||||
uint16_t ifhType;
|
||||
uint16_t ifhCount;
|
||||
};
|
||||
|
||||
struct ICONENTRY {
|
||||
int8_t ieWidth;
|
||||
int8_t ieHeight;
|
||||
uint8_t ieColors;
|
||||
uint8_t ieReserved;
|
||||
uint16_t iePlanes;
|
||||
uint16_t ieBitCount;
|
||||
uint32_t ieSizeImage;
|
||||
uint32_t ieFileOffset;
|
||||
};
|
||||
|
||||
// Match stock icons with names
|
||||
static SHSTOCKICONID
|
||||
GetStockIconIDForName(const nsACString& aStockName)
|
||||
{
|
||||
return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD :
|
||||
SIID_INVALID;
|
||||
}
|
||||
|
||||
// nsIconChannel methods
|
||||
nsIconChannel::nsIconChannel()
|
||||
{
|
||||
}
|
||||
|
||||
nsIconChannel::~nsIconChannel()
|
||||
{
|
||||
if (mLoadInfo) {
|
||||
NS_ReleaseOnMainThread(mLoadInfo.forget());
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsIconChannel,
|
||||
nsIChannel,
|
||||
nsIRequest,
|
||||
nsIRequestObserver,
|
||||
nsIStreamListener)
|
||||
|
||||
nsresult
|
||||
nsIconChannel::Init(nsIURI* uri)
|
||||
{
|
||||
NS_ASSERTION(uri, "no uri");
|
||||
mUrl = uri;
|
||||
mOriginalURI = uri;
|
||||
nsresult rv;
|
||||
mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIRequest methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetName(nsACString& result)
|
||||
{
|
||||
return mUrl->GetSpec(result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::IsPending(bool* result)
|
||||
{
|
||||
return mPump->IsPending(result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetStatus(nsresult* status)
|
||||
{
|
||||
return mPump->GetStatus(status);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Cancel(nsresult status)
|
||||
{
|
||||
return mPump->Cancel(status);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Suspend(void)
|
||||
{
|
||||
return mPump->Suspend();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Resume(void)
|
||||
{
|
||||
return mPump->Resume();
|
||||
}
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
|
||||
{
|
||||
*aLoadGroup = mLoadGroup;
|
||||
NS_IF_ADDREF(*aLoadGroup);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
|
||||
{
|
||||
mLoadGroup = aLoadGroup;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
|
||||
{
|
||||
return mPump->GetLoadFlags(aLoadAttributes);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
|
||||
{
|
||||
return mPump->SetLoadFlags(aLoadAttributes);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIChannel methods:
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetOriginalURI(nsIURI** aURI)
|
||||
{
|
||||
*aURI = mOriginalURI;
|
||||
NS_ADDREF(*aURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetOriginalURI(nsIURI* aURI)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aURI);
|
||||
mOriginalURI = aURI;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetURI(nsIURI** aURI)
|
||||
{
|
||||
*aURI = mUrl;
|
||||
NS_IF_ADDREF(*aURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Open(nsIInputStream** _retval)
|
||||
{
|
||||
return MakeInputStream(_retval, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::Open2(nsIInputStream** aStream)
|
||||
{
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return Open(aStream);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
|
||||
uint32_t* aDesiredImageSize, nsCString& aContentType,
|
||||
nsCString& aFileExtension)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
iconURI->GetImageSize(aDesiredImageSize);
|
||||
iconURI->GetContentType(aContentType);
|
||||
iconURI->GetFileExtension(aFileExtension);
|
||||
|
||||
nsCOMPtr<nsIURL> url;
|
||||
rv = iconURI->GetIconURL(getter_AddRefs(url));
|
||||
if (NS_FAILED(rv) || !url) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
|
||||
if (NS_FAILED(rv) || !fileURL) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = fileURL->GetFile(getter_AddRefs(file));
|
||||
if (NS_FAILED(rv) || !file) return NS_OK;
|
||||
|
||||
return file->Clone(aLocalFile);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
|
||||
nsISupports* ctxt)
|
||||
{
|
||||
MOZ_ASSERT(!mLoadInfo ||
|
||||
mLoadInfo->GetSecurityMode() == 0 ||
|
||||
mLoadInfo->GetInitialSecurityCheckDone() ||
|
||||
(mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
|
||||
nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
|
||||
"security flags in loadInfo but asyncOpen2() not called");
|
||||
|
||||
nsCOMPtr<nsIInputStream> inStream;
|
||||
nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Init our streampump
|
||||
rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mPump->AsyncRead(this, ctxt);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Store our real listener
|
||||
mListener = aListener;
|
||||
// Add ourself to the load group, if available
|
||||
if (mLoadGroup) {
|
||||
mLoadGroup->AddRequest(this, nullptr);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
|
||||
{
|
||||
nsCOMPtr<nsIStreamListener> listener = aListener;
|
||||
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return AsyncOpen(listener, nullptr);
|
||||
}
|
||||
|
||||
static DWORD
|
||||
GetSpecialFolderIcon(nsIFile* aFile, int aFolder,
|
||||
SHFILEINFOW* aSFI, UINT aInfoFlags)
|
||||
{
|
||||
DWORD shellResult = 0;
|
||||
|
||||
if (!aFile) {
|
||||
return shellResult;
|
||||
}
|
||||
|
||||
wchar_t fileNativePath[MAX_PATH];
|
||||
nsAutoString fileNativePathStr;
|
||||
aFile->GetPath(fileNativePathStr);
|
||||
::GetShortPathNameW(fileNativePathStr.get(), fileNativePath,
|
||||
ArrayLength(fileNativePath));
|
||||
|
||||
LPITEMIDLIST idList;
|
||||
HRESULT hr = ::SHGetSpecialFolderLocation(nullptr, aFolder, &idList);
|
||||
if (SUCCEEDED(hr)) {
|
||||
wchar_t specialNativePath[MAX_PATH];
|
||||
::SHGetPathFromIDListW(idList, specialNativePath);
|
||||
::GetShortPathNameW(specialNativePath, specialNativePath,
|
||||
ArrayLength(specialNativePath));
|
||||
|
||||
if (!wcsicmp(fileNativePath, specialNativePath)) {
|
||||
aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX);
|
||||
shellResult = ::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList, 0,
|
||||
aSFI,
|
||||
sizeof(*aSFI), aInfoFlags);
|
||||
}
|
||||
}
|
||||
CoTaskMemFree(idList);
|
||||
return shellResult;
|
||||
}
|
||||
|
||||
static UINT
|
||||
GetSizeInfoFlag(uint32_t aDesiredImageSize)
|
||||
{
|
||||
return
|
||||
(UINT) (aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::GetHIconFromFile(HICON* hIcon)
|
||||
{
|
||||
nsXPIDLCString contentType;
|
||||
nsCString fileExt;
|
||||
nsCOMPtr<nsIFile> localFile; // file we want an icon for
|
||||
uint32_t desiredImageSize;
|
||||
nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile),
|
||||
&desiredImageSize, contentType,
|
||||
fileExt);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// if the file exists, we are going to use it's real attributes...
|
||||
// otherwise we only want to use it for it's extension...
|
||||
SHFILEINFOW sfi;
|
||||
UINT infoFlags = SHGFI_ICON;
|
||||
|
||||
bool fileExists = false;
|
||||
|
||||
nsAutoString filePath;
|
||||
CopyASCIItoUTF16(fileExt, filePath);
|
||||
if (localFile) {
|
||||
rv = localFile->Normalize();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
localFile->GetPath(filePath);
|
||||
if (filePath.Length() < 2 || filePath[1] != ':') {
|
||||
return NS_ERROR_MALFORMED_URI; // UNC
|
||||
}
|
||||
|
||||
if (filePath.Last() == ':') {
|
||||
filePath.Append('\\');
|
||||
} else {
|
||||
localFile->Exists(&fileExists);
|
||||
if (!fileExists) {
|
||||
localFile->GetLeafName(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileExists) {
|
||||
infoFlags |= SHGFI_USEFILEATTRIBUTES;
|
||||
}
|
||||
|
||||
infoFlags |= GetSizeInfoFlag(desiredImageSize);
|
||||
|
||||
// if we have a content type... then use it! but for existing files,
|
||||
// we want to show their real icon.
|
||||
if (!fileExists && !contentType.IsEmpty()) {
|
||||
nsCOMPtr<nsIMIMEService> mimeService
|
||||
(do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoCString defFileExt;
|
||||
mimeService->GetPrimaryExtension(contentType, fileExt, defFileExt);
|
||||
// If the mime service does not know about this mime type, we show
|
||||
// the generic icon.
|
||||
// In any case, we need to insert a '.' before the extension.
|
||||
filePath = NS_LITERAL_STRING(".") +
|
||||
NS_ConvertUTF8toUTF16(defFileExt);
|
||||
}
|
||||
|
||||
// Is this the "Desktop" folder?
|
||||
DWORD shellResult = GetSpecialFolderIcon(localFile, CSIDL_DESKTOP,
|
||||
&sfi, infoFlags);
|
||||
if (!shellResult) {
|
||||
// Is this the "My Documents" folder?
|
||||
shellResult = GetSpecialFolderIcon(localFile, CSIDL_PERSONAL,
|
||||
&sfi, infoFlags);
|
||||
}
|
||||
|
||||
// There are other "Special Folders" and Namespace entities that we
|
||||
// are not fetching icons for, see:
|
||||
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
|
||||
// shellcc/platform/shell/reference/enums/csidl.asp
|
||||
// If we ever need to get them, code to do so would be inserted here.
|
||||
|
||||
// Not a special folder, or something else failed above.
|
||||
if (!shellResult) {
|
||||
shellResult = ::SHGetFileInfoW(filePath.get(),
|
||||
FILE_ATTRIBUTE_ARCHIVE,
|
||||
&sfi, sizeof(sfi), infoFlags);
|
||||
}
|
||||
|
||||
if (shellResult && sfi.hIcon) {
|
||||
*hIcon = sfi.hIcon;
|
||||
} else {
|
||||
rv = NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI,
|
||||
HICON* hIcon)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// We can only do this on Vista or above
|
||||
HMODULE hShellDLL = ::LoadLibraryW(L"shell32.dll");
|
||||
decltype(SHGetStockIconInfo)* pSHGetStockIconInfo =
|
||||
(decltype(SHGetStockIconInfo)*) ::GetProcAddress(hShellDLL,
|
||||
"SHGetStockIconInfo");
|
||||
|
||||
if (pSHGetStockIconInfo) {
|
||||
uint32_t desiredImageSize;
|
||||
aIconURI->GetImageSize(&desiredImageSize);
|
||||
nsAutoCString stockIcon;
|
||||
aIconURI->GetStockIcon(stockIcon);
|
||||
|
||||
SHSTOCKICONID stockIconID = GetStockIconIDForName(stockIcon);
|
||||
if (stockIconID == SIID_INVALID) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
UINT infoFlags = SHGSI_ICON;
|
||||
infoFlags |= GetSizeInfoFlag(desiredImageSize);
|
||||
|
||||
SHSTOCKICONINFO sii = {0};
|
||||
sii.cbSize = sizeof(sii);
|
||||
HRESULT hr = pSHGetStockIconInfo(stockIconID, infoFlags, &sii);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
*hIcon = sii.hIcon;
|
||||
} else {
|
||||
rv = NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
rv = NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (hShellDLL) {
|
||||
::FreeLibrary(hShellDLL);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Given a BITMAPINFOHEADER, returns the size of the color table.
|
||||
static int
|
||||
GetColorTableSize(BITMAPINFOHEADER* aHeader)
|
||||
{
|
||||
int colorTableSize = -1;
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx
|
||||
switch (aHeader->biBitCount) {
|
||||
case 0:
|
||||
colorTableSize = 0;
|
||||
break;
|
||||
case 1:
|
||||
colorTableSize = 2 * sizeof(RGBQUAD);
|
||||
break;
|
||||
case 4:
|
||||
case 8: {
|
||||
// The maximum possible size for the color table is 2**bpp, so check for
|
||||
// that and fail if we're not in those bounds
|
||||
unsigned int maxEntries = 1 << (aHeader->biBitCount);
|
||||
if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries) {
|
||||
colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD);
|
||||
} else if (aHeader->biClrUsed == 0) {
|
||||
colorTableSize = maxEntries * sizeof(RGBQUAD);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 16:
|
||||
case 32:
|
||||
// If we have BI_BITFIELDS compression, we would normally need 3 DWORDS for
|
||||
// the bitfields mask which would be stored in the color table; However,
|
||||
// we instead force the bitmap to request data of type BI_RGB so the color
|
||||
// table should be of size 0.
|
||||
// Setting aHeader->biCompression = BI_RGB forces the later call to
|
||||
// GetDIBits to return to us BI_RGB data.
|
||||
if (aHeader->biCompression == BI_BITFIELDS) {
|
||||
aHeader->biCompression = BI_RGB;
|
||||
}
|
||||
colorTableSize = 0;
|
||||
break;
|
||||
case 24:
|
||||
colorTableSize = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (colorTableSize < 0) {
|
||||
NS_WARNING("Unable to figure out the color table size for this bitmap");
|
||||
}
|
||||
|
||||
return colorTableSize;
|
||||
}
|
||||
|
||||
// Given a header and a size, creates a freshly allocated BITMAPINFO structure.
|
||||
// It is the caller's responsibility to null-check and delete the structure.
|
||||
static BITMAPINFO*
|
||||
CreateBitmapInfo(BITMAPINFOHEADER* aHeader, size_t aColorTableSize)
|
||||
{
|
||||
BITMAPINFO* bmi = (BITMAPINFO*) ::operator new(sizeof(BITMAPINFOHEADER) +
|
||||
aColorTableSize,
|
||||
mozilla::fallible);
|
||||
if (bmi) {
|
||||
memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER));
|
||||
memset(bmi->bmiColors, 0, aColorTableSize);
|
||||
}
|
||||
return bmi;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlocking)
|
||||
{
|
||||
// Check whether the icon requested's a file icon or a stock icon
|
||||
nsresult rv = NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
// GetDIBits does not exist on windows mobile.
|
||||
HICON hIcon = nullptr;
|
||||
|
||||
nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoCString stockIcon;
|
||||
iconURI->GetStockIcon(stockIcon);
|
||||
if (!stockIcon.IsEmpty()) {
|
||||
rv = GetStockHIcon(iconURI, &hIcon);
|
||||
} else {
|
||||
rv = GetHIconFromFile(&hIcon);
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (hIcon) {
|
||||
// we got a handle to an icon. Now we want to get a bitmap for the icon
|
||||
// using GetIconInfo....
|
||||
ICONINFO iconInfo;
|
||||
if (GetIconInfo(hIcon, &iconInfo)) {
|
||||
// we got the bitmaps, first find out their size
|
||||
HDC hDC = CreateCompatibleDC(nullptr); // get a device context for
|
||||
// the screen.
|
||||
BITMAPINFOHEADER maskHeader = {sizeof(BITMAPINFOHEADER)};
|
||||
BITMAPINFOHEADER colorHeader = {sizeof(BITMAPINFOHEADER)};
|
||||
int colorTableSize, maskTableSize;
|
||||
if (GetDIBits(hDC, iconInfo.hbmMask, 0, 0, nullptr,
|
||||
(BITMAPINFO*)&maskHeader, DIB_RGB_COLORS) &&
|
||||
GetDIBits(hDC, iconInfo.hbmColor, 0, 0, nullptr,
|
||||
(BITMAPINFO*)&colorHeader, DIB_RGB_COLORS) &&
|
||||
maskHeader.biHeight == colorHeader.biHeight &&
|
||||
maskHeader.biWidth == colorHeader.biWidth &&
|
||||
colorHeader.biBitCount > 8 &&
|
||||
colorHeader.biSizeImage > 0 &&
|
||||
colorHeader.biWidth >= 0 && colorHeader.biWidth <= 255 &&
|
||||
colorHeader.biHeight >= 0 && colorHeader.biHeight <= 255 &&
|
||||
maskHeader.biSizeImage > 0 &&
|
||||
(colorTableSize = GetColorTableSize(&colorHeader)) >= 0 &&
|
||||
(maskTableSize = GetColorTableSize(&maskHeader)) >= 0) {
|
||||
uint32_t iconSize = sizeof(ICONFILEHEADER) +
|
||||
sizeof(ICONENTRY) +
|
||||
sizeof(BITMAPINFOHEADER) +
|
||||
colorHeader.biSizeImage +
|
||||
maskHeader.biSizeImage;
|
||||
|
||||
UniquePtr<char[]> buffer = MakeUnique<char[]>(iconSize);
|
||||
if (!buffer) {
|
||||
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||
} else {
|
||||
char* whereTo = buffer.get();
|
||||
int howMuch;
|
||||
|
||||
// the data starts with an icon file header
|
||||
ICONFILEHEADER iconHeader;
|
||||
iconHeader.ifhReserved = 0;
|
||||
iconHeader.ifhType = 1;
|
||||
iconHeader.ifhCount = 1;
|
||||
howMuch = sizeof(ICONFILEHEADER);
|
||||
memcpy(whereTo, &iconHeader, howMuch);
|
||||
whereTo += howMuch;
|
||||
|
||||
// followed by the single icon entry
|
||||
ICONENTRY iconEntry;
|
||||
iconEntry.ieWidth = static_cast<int8_t>(colorHeader.biWidth);
|
||||
iconEntry.ieHeight = static_cast<int8_t>(colorHeader.biHeight);
|
||||
iconEntry.ieColors = 0;
|
||||
iconEntry.ieReserved = 0;
|
||||
iconEntry.iePlanes = 1;
|
||||
iconEntry.ieBitCount = colorHeader.biBitCount;
|
||||
iconEntry.ieSizeImage = sizeof(BITMAPINFOHEADER) +
|
||||
colorHeader.biSizeImage +
|
||||
maskHeader.biSizeImage;
|
||||
iconEntry.ieFileOffset = sizeof(ICONFILEHEADER) + sizeof(ICONENTRY);
|
||||
howMuch = sizeof(ICONENTRY);
|
||||
memcpy(whereTo, &iconEntry, howMuch);
|
||||
whereTo += howMuch;
|
||||
|
||||
// followed by the bitmap info header
|
||||
// (doubling the height because icons have two bitmaps)
|
||||
colorHeader.biHeight *= 2;
|
||||
colorHeader.biSizeImage += maskHeader.biSizeImage;
|
||||
howMuch = sizeof(BITMAPINFOHEADER);
|
||||
memcpy(whereTo, &colorHeader, howMuch);
|
||||
whereTo += howMuch;
|
||||
colorHeader.biHeight /= 2;
|
||||
colorHeader.biSizeImage -= maskHeader.biSizeImage;
|
||||
|
||||
// followed by the XOR bitmap data (colorHeader)
|
||||
// (you'd expect the color table to come here, but it apparently
|
||||
// doesn't)
|
||||
BITMAPINFO* colorInfo = CreateBitmapInfo(&colorHeader,
|
||||
colorTableSize);
|
||||
if (colorInfo && GetDIBits(hDC, iconInfo.hbmColor, 0,
|
||||
colorHeader.biHeight, whereTo, colorInfo,
|
||||
DIB_RGB_COLORS)) {
|
||||
whereTo += colorHeader.biSizeImage;
|
||||
|
||||
// and finally the AND bitmap data (maskHeader)
|
||||
BITMAPINFO* maskInfo = CreateBitmapInfo(&maskHeader, maskTableSize);
|
||||
if (maskInfo && GetDIBits(hDC, iconInfo.hbmMask, 0,
|
||||
maskHeader.biHeight, whereTo, maskInfo,
|
||||
DIB_RGB_COLORS)) {
|
||||
// Now, create a pipe and stuff our data into it
|
||||
nsCOMPtr<nsIInputStream> inStream;
|
||||
nsCOMPtr<nsIOutputStream> outStream;
|
||||
rv = NS_NewPipe(getter_AddRefs(inStream),
|
||||
getter_AddRefs(outStream),
|
||||
iconSize, iconSize, aNonBlocking);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
uint32_t written;
|
||||
rv = outStream->Write(buffer.get(), iconSize, &written);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ADDREF(*_retval = inStream);
|
||||
}
|
||||
}
|
||||
|
||||
} // if we got bitmap bits
|
||||
delete maskInfo;
|
||||
} // if we got mask bits
|
||||
delete colorInfo;
|
||||
} // if we allocated the buffer
|
||||
} // if we got mask size
|
||||
|
||||
DeleteDC(hDC);
|
||||
DeleteObject(iconInfo.hbmColor);
|
||||
DeleteObject(iconInfo.hbmMask);
|
||||
} // if we got icon info
|
||||
DestroyIcon(hIcon);
|
||||
} // if we got an hIcon
|
||||
|
||||
// If we didn't make a stream, then fail.
|
||||
if (!*_retval && NS_SUCCEEDED(rv)) {
|
||||
rv = NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentType(nsACString& aContentType)
|
||||
{
|
||||
aContentType.AssignLiteral(IMAGE_ICO);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentType(const nsACString& aContentType)
|
||||
{
|
||||
// It doesn't make sense to set the content-type on this type
|
||||
// of channel...
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString& aContentCharset)
|
||||
{
|
||||
aContentCharset.Truncate();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
|
||||
{
|
||||
// It doesn't make sense to set the content-charset on this type
|
||||
// of channel...
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
GetContentDispositionFilename(nsAString& aContentDispositionFilename)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
GetContentDispositionHeader(nsACString& aContentDispositionHeader)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetContentLength(int64_t* aContentLength)
|
||||
{
|
||||
*aContentLength = 0;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetContentLength(int64_t aContentLength)
|
||||
{
|
||||
NS_NOTREACHED("nsIconChannel::SetContentLength");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetOwner(nsISupports** aOwner)
|
||||
{
|
||||
*aOwner = mOwner.get();
|
||||
NS_IF_ADDREF(*aOwner);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetOwner(nsISupports* aOwner)
|
||||
{
|
||||
mOwner = aOwner;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
|
||||
{
|
||||
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
|
||||
{
|
||||
mLoadInfo = aLoadInfo;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
|
||||
{
|
||||
*aNotificationCallbacks = mCallbacks.get();
|
||||
NS_IF_ADDREF(*aNotificationCallbacks);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::
|
||||
SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
|
||||
{
|
||||
mCallbacks = aNotificationCallbacks;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
|
||||
{
|
||||
*aSecurityInfo = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIRequestObserver methods
|
||||
NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext)
|
||||
{
|
||||
if (mListener) {
|
||||
return mListener->OnStartRequest(this, aContext);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::OnStopRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatus)
|
||||
{
|
||||
if (mListener) {
|
||||
mListener->OnStopRequest(this, aContext, aStatus);
|
||||
mListener = nullptr;
|
||||
}
|
||||
|
||||
// Remove from load group
|
||||
if (mLoadGroup) {
|
||||
mLoadGroup->RemoveRequest(this, nullptr, aStatus);
|
||||
}
|
||||
|
||||
// Drop notification callbacks to prevent cycles.
|
||||
mCallbacks = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIStreamListener methods
|
||||
NS_IMETHODIMP
|
||||
nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (mListener) {
|
||||
return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue