зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1343499 - Expose native image sizes to imagelib users. r=tnikkel
This commit is contained in:
Родитель
20fb8455d6
Коммит
a60b290b56
|
@ -127,6 +127,12 @@ DynamicImage::GetHeight(int32_t* aHeight)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DynamicImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DynamicImage::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
}
|
||||
|
||||
// Inherited methods from Image.
|
||||
nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
|
||||
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
|
||||
virtual size_t SizeOfSourceWithComputedFallback(
|
||||
MallocSizeOf aMallocSizeOf) const override;
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_image_FrameTimeout_h
|
||||
#define mozilla_image_FrameTimeout_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* FrameTimeout wraps a frame timeout value (measured in milliseconds) after
|
||||
* first normalizing it. This normalization is necessary because some tools
|
||||
* generate incorrect frame timeout values which we nevertheless have to
|
||||
* support. For this reason, code that deals with frame timeouts should always
|
||||
* use a FrameTimeout value rather than the raw value from the image header.
|
||||
*/
|
||||
struct FrameTimeout
|
||||
{
|
||||
/**
|
||||
* @return a FrameTimeout of zero. This should be used only for math
|
||||
* involving FrameTimeout values. You can't obtain a zero FrameTimeout from
|
||||
* FromRawMilliseconds().
|
||||
*/
|
||||
static FrameTimeout Zero() { return FrameTimeout(0); }
|
||||
|
||||
/// @return an infinite FrameTimeout.
|
||||
static FrameTimeout Forever() { return FrameTimeout(-1); }
|
||||
|
||||
/// @return a FrameTimeout obtained by normalizing a raw timeout value.
|
||||
static FrameTimeout FromRawMilliseconds(int32_t aRawMilliseconds)
|
||||
{
|
||||
// Normalize all infinite timeouts to the same value.
|
||||
if (aRawMilliseconds < 0) {
|
||||
return FrameTimeout::Forever();
|
||||
}
|
||||
|
||||
// Very small timeout values are problematic for two reasons: we don't want
|
||||
// to burn energy redrawing animated images extremely fast, and broken tools
|
||||
// generate these values when they actually want a "default" value, so such
|
||||
// images won't play back right without normalization. For some context,
|
||||
// see bug 890743, bug 125137, bug 139677, and bug 207059. The historical
|
||||
// behavior of IE and Opera was:
|
||||
// IE 6/Win:
|
||||
// 10 - 50ms is normalized to 100ms.
|
||||
// >50ms is used unnormalized.
|
||||
// Opera 7 final/Win:
|
||||
// 10ms is normalized to 100ms.
|
||||
// >10ms is used unnormalized.
|
||||
if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10 ) {
|
||||
return FrameTimeout(100);
|
||||
}
|
||||
|
||||
// The provided timeout value is OK as-is.
|
||||
return FrameTimeout(aRawMilliseconds);
|
||||
}
|
||||
|
||||
bool operator==(const FrameTimeout& aOther) const
|
||||
{
|
||||
return mTimeout == aOther.mTimeout;
|
||||
}
|
||||
|
||||
bool operator!=(const FrameTimeout& aOther) const { return !(*this == aOther); }
|
||||
|
||||
FrameTimeout operator+(const FrameTimeout& aOther)
|
||||
{
|
||||
if (*this == Forever() || aOther == Forever()) {
|
||||
return Forever();
|
||||
}
|
||||
|
||||
return FrameTimeout(mTimeout + aOther.mTimeout);
|
||||
}
|
||||
|
||||
FrameTimeout& operator+=(const FrameTimeout& aOther)
|
||||
{
|
||||
*this = *this + aOther;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this FrameTimeout's value in milliseconds. Illegal to call on a
|
||||
* an infinite FrameTimeout value.
|
||||
*/
|
||||
uint32_t AsMilliseconds() const
|
||||
{
|
||||
if (*this == Forever()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling AsMilliseconds() on an infinite FrameTimeout");
|
||||
return 100; // Fail to something sane.
|
||||
}
|
||||
|
||||
return uint32_t(mTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this FrameTimeout value encoded so that non-negative values
|
||||
* represent a timeout in milliseconds, and -1 represents an infinite
|
||||
* timeout.
|
||||
*
|
||||
* XXX(seth): This is a backwards compatibility hack that should be removed.
|
||||
*/
|
||||
int32_t AsEncodedValueDeprecated() const { return mTimeout; }
|
||||
|
||||
private:
|
||||
explicit FrameTimeout(int32_t aTimeout)
|
||||
: mTimeout(aTimeout)
|
||||
{ }
|
||||
|
||||
int32_t mTimeout;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_FrameTimeout_h
|
|
@ -11,12 +11,11 @@
|
|||
#include "mozilla/Maybe.h"
|
||||
#include "nsSize.h"
|
||||
#include "Orientation.h"
|
||||
#include "FrameTimeout.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class RasterImage;
|
||||
|
||||
// The metadata about an image that decoders accumulate as they decode.
|
||||
class ImageMetadata
|
||||
{
|
||||
|
@ -64,6 +63,13 @@ public:
|
|||
nsIntSize GetSize() const { return *mSize; }
|
||||
bool HasSize() const { return mSize.isSome(); }
|
||||
|
||||
void AddNativeSize(const nsIntSize& aSize)
|
||||
{
|
||||
mNativeSizes.AppendElement(aSize);
|
||||
}
|
||||
|
||||
const nsTArray<nsIntSize>& GetNativeSizes() const { return mNativeSizes; }
|
||||
|
||||
Orientation GetOrientation() const { return *mOrientation; }
|
||||
bool HasOrientation() const { return mOrientation.isSome(); }
|
||||
|
||||
|
@ -90,6 +96,9 @@ private:
|
|||
Maybe<nsIntSize> mSize;
|
||||
Maybe<Orientation> mOrientation;
|
||||
|
||||
// Sizes the image can natively decode to.
|
||||
nsTArray<nsIntSize> mNativeSizes;
|
||||
|
||||
bool mHasAnimation : 1;
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "FrozenImage.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "Image.h"
|
||||
#include "ImageMetadata.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsStreamUtils.h"
|
||||
|
@ -79,10 +80,27 @@ ImageOps::CreateFromDrawable(gfxDrawable* aDrawable)
|
|||
return drawableImage.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<gfx::SourceSurface>
|
||||
ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags)
|
||||
class ImageOps::ImageBufferImpl final : public ImageOps::ImageBuffer {
|
||||
public:
|
||||
ImageBufferImpl(already_AddRefed<SourceBuffer> aSourceBuffer)
|
||||
: mSourceBuffer(aSourceBuffer)
|
||||
{ }
|
||||
|
||||
protected:
|
||||
~ImageBufferImpl() override { }
|
||||
|
||||
virtual already_AddRefed<SourceBuffer> GetSourceBuffer()
|
||||
{
|
||||
RefPtr<SourceBuffer> sourceBuffer = mSourceBuffer;
|
||||
return sourceBuffer.forget();
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<SourceBuffer> mSourceBuffer;
|
||||
};
|
||||
|
||||
/* static */ already_AddRefed<ImageOps::ImageBuffer>
|
||||
ImageOps::CreateImageBuffer(nsIInputStream* aInputStream)
|
||||
{
|
||||
MOZ_ASSERT(aInputStream);
|
||||
|
||||
|
@ -107,7 +125,7 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
|
|||
}
|
||||
|
||||
// Write the data into a SourceBuffer.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
|
||||
sourceBuffer->ExpectLength(length);
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
|
||||
if (NS_FAILED(rv)) {
|
||||
|
@ -122,12 +140,90 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
|
|||
}
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
|
||||
RefPtr<ImageBuffer> imageBuffer = new ImageBufferImpl(sourceBuffer.forget());
|
||||
return imageBuffer.forget();
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
ImageOps::DecodeMetadata(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
ImageMetadata& aMetadata)
|
||||
{
|
||||
RefPtr<ImageBuffer> buffer = CreateImageBuffer(aInputStream);
|
||||
return DecodeMetadata(buffer, aMimeType, aMetadata);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
ImageOps::DecodeMetadata(ImageBuffer* aBuffer,
|
||||
const nsACString& aMimeType,
|
||||
ImageMetadata& aMetadata)
|
||||
{
|
||||
if (!aBuffer) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<SourceBuffer> sourceBuffer = aBuffer->GetSourceBuffer();
|
||||
if (NS_WARN_IF(!sourceBuffer)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Create a decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
|
||||
Nothing(), ToSurfaceFlags(aFlags));
|
||||
DecoderFactory::CreateAnonymousMetadataDecoder(decoderType,
|
||||
WrapNotNull(sourceBuffer));
|
||||
if (!decoder) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Run the decoder synchronously.
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
task->Run();
|
||||
if (!decoder->GetDecodeDone() || decoder->HasError()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aMetadata = decoder->GetImageMetadata();
|
||||
if (aMetadata.GetNativeSizes().IsEmpty() && aMetadata.HasSize()) {
|
||||
aMetadata.AddNativeSize(aMetadata.GetSize());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<gfx::SourceSurface>
|
||||
ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags,
|
||||
Maybe<IntSize> aSize /* = Nothing() */)
|
||||
{
|
||||
RefPtr<ImageBuffer> buffer = CreateImageBuffer(aInputStream);
|
||||
return DecodeToSurface(buffer, aMimeType, aFlags, aSize);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<gfx::SourceSurface>
|
||||
ImageOps::DecodeToSurface(ImageBuffer* aBuffer,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags,
|
||||
Maybe<IntSize> aSize /* = Nothing() */)
|
||||
{
|
||||
if (!aBuffer) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<SourceBuffer> sourceBuffer = aBuffer->GetSourceBuffer();
|
||||
if (NS_WARN_IF(!sourceBuffer)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType,
|
||||
WrapNotNull(sourceBuffer),
|
||||
aSize, ToSurfaceFlags(aFlags));
|
||||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsRect.h"
|
||||
#include "ImageMetadata.h"
|
||||
|
||||
class gfxDrawable;
|
||||
class imgIContainer;
|
||||
|
@ -24,10 +25,23 @@ namespace image {
|
|||
|
||||
class Image;
|
||||
struct Orientation;
|
||||
class SourceBuffer;
|
||||
|
||||
class ImageOps
|
||||
{
|
||||
public:
|
||||
class ImageBuffer {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageOps::ImageBuffer);
|
||||
protected:
|
||||
friend class ImageOps;
|
||||
|
||||
ImageBuffer() { }
|
||||
virtual ~ImageBuffer() { }
|
||||
|
||||
virtual already_AddRefed<SourceBuffer> GetSourceBuffer() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a version of an existing image which does not animate and is frozen
|
||||
* at the first frame.
|
||||
|
@ -74,6 +88,39 @@ public:
|
|||
static already_AddRefed<imgIContainer>
|
||||
CreateFromDrawable(gfxDrawable* aDrawable);
|
||||
|
||||
/**
|
||||
* Create a buffer to be used with DecodeMetadata and DecodeToSurface. Reusing
|
||||
* an ImageBuffer representing the given input stream is more efficient if one
|
||||
* has multiple Decode* calls to make on that stream.
|
||||
*
|
||||
* @param aInputStream An input stream containing an encoded image.
|
||||
* @return An image buffer derived from the input stream.
|
||||
*/
|
||||
static already_AddRefed<ImageBuffer>
|
||||
CreateImageBuffer(nsIInputStream* aInputStream);
|
||||
|
||||
/**
|
||||
* Decodes an image's metadata from an nsIInputStream into the given
|
||||
* structure. 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 aMetadata Where the image metadata is stored upon success.
|
||||
* @return The status of the operation.
|
||||
*/
|
||||
static nsresult
|
||||
DecodeMetadata(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
ImageMetadata& aMetadata);
|
||||
|
||||
/**
|
||||
* Same as above but takes an ImageBuffer instead of nsIInputStream.
|
||||
*/
|
||||
static nsresult
|
||||
DecodeMetadata(ImageBuffer* aBuffer,
|
||||
const nsACString& aMimeType,
|
||||
ImageMetadata& aMetadata);
|
||||
|
||||
/**
|
||||
* Decodes an image from an nsIInputStream directly into a SourceSurface,
|
||||
* without ever creating an Image or imgIContainer (which are mostly
|
||||
|
@ -89,9 +136,21 @@ public:
|
|||
static already_AddRefed<gfx::SourceSurface>
|
||||
DecodeToSurface(nsIInputStream* aInputStream,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags);
|
||||
uint32_t aFlags,
|
||||
Maybe<gfx::IntSize> aSize = Nothing());
|
||||
|
||||
/**
|
||||
* Same as above but takes an ImageBuffer instead of nsIInputStream.
|
||||
*/
|
||||
static already_AddRefed<gfx::SourceSurface>
|
||||
DecodeToSurface(ImageBuffer* aBuffer,
|
||||
const nsACString& aMimeType,
|
||||
uint32_t aFlags,
|
||||
Maybe<gfx::IntSize> aSize = Nothing());
|
||||
|
||||
private:
|
||||
class ImageBufferImpl;
|
||||
|
||||
// This is a static utility class, so disallow instantiation.
|
||||
virtual ~ImageOps() = 0;
|
||||
};
|
||||
|
|
|
@ -139,6 +139,12 @@ ImageWrapper::GetHeight(int32_t* aHeight)
|
|||
return mInnerImage->GetHeight(aHeight);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ImageWrapper::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
|
||||
{
|
||||
return mInnerImage->GetNativeSizes(aNativeSizes);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ImageWrapper::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ public:
|
|||
NS_DECL_IMGICONTAINER
|
||||
|
||||
// Inherited methods from Image.
|
||||
nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
|
||||
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
|
||||
|
||||
virtual size_t
|
||||
|
|
|
@ -46,6 +46,22 @@ OrientedImage::GetHeight(int32_t* aHeight)
|
|||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
OrientedImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
|
||||
{
|
||||
nsresult rv = InnerImage()->GetNativeSizes(aNativeSizes);
|
||||
|
||||
if (mOrientation.SwapsWidthAndHeight()) {
|
||||
auto i = aNativeSizes.Length();
|
||||
while (i > 0) {
|
||||
--i;
|
||||
swap(aNativeSizes[i].width, aNativeSizes[i].height);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OrientedImage::GetIntrinsicSize(nsSize* aSize)
|
||||
{
|
||||
|
|
|
@ -30,6 +30,7 @@ public:
|
|||
|
||||
NS_IMETHOD GetWidth(int32_t* aWidth) override;
|
||||
NS_IMETHOD GetHeight(int32_t* aHeight) override;
|
||||
nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
|
||||
NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
|
||||
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
|
|
|
@ -220,6 +220,24 @@ RasterImage::GetHeight(int32_t* aHeight)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
nsresult
|
||||
RasterImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
|
||||
{
|
||||
if (mError) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (mNativeSizes.IsEmpty()) {
|
||||
aNativeSizes.Clear();
|
||||
aNativeSizes.AppendElement(mSize);
|
||||
} else {
|
||||
aNativeSizes = mNativeSizes;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
NS_IMETHODIMP
|
||||
RasterImage::GetIntrinsicSize(nsSize* aSize)
|
||||
|
@ -703,6 +721,7 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata,
|
|||
// Set the size and flag that we have it.
|
||||
mSize = size;
|
||||
mOrientation = orientation;
|
||||
mNativeSizes = aMetadata.GetNativeSizes();
|
||||
mHasSize = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -160,6 +160,7 @@ public:
|
|||
NS_DECL_IMGICONTAINERDEBUG
|
||||
#endif
|
||||
|
||||
nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
|
||||
virtual nsresult StartAnimation() override;
|
||||
virtual nsresult StopAnimation() override;
|
||||
|
||||
|
@ -380,6 +381,7 @@ private:
|
|||
|
||||
private: // data
|
||||
nsIntSize mSize;
|
||||
nsTArray<nsIntSize> mNativeSizes;
|
||||
Orientation mOrientation;
|
||||
|
||||
/// If this has a value, we're waiting for SetSize() to send the load event.
|
||||
|
|
|
@ -520,6 +520,13 @@ VectorImage::GetWidth(int32_t* aWidth)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
nsresult
|
||||
VectorImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
NS_IMETHODIMP_(void)
|
||||
VectorImage::RequestRefresh(const TimeStamp& aTime)
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
// (no public constructor - use ImageFactory)
|
||||
|
||||
// Methods inherited from Image
|
||||
nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
|
||||
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
|
||||
const override;
|
||||
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
|
|
|
@ -242,6 +242,8 @@ nsICODecoder::ReadDirEntry(const char* aData)
|
|||
}
|
||||
}
|
||||
|
||||
mImageMetadata.AddNativeSize(entrySize);
|
||||
|
||||
if (desiredSize) {
|
||||
// Calculate the delta between this resource's size and the desired size, so
|
||||
// we can see if it is better than our current-best option. In the case of
|
||||
|
|
101
image/imgFrame.h
101
image/imgFrame.h
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "FrameTimeout.h"
|
||||
#include "gfxDrawable.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "MainThreadUtils.h"
|
||||
|
@ -46,106 +47,6 @@ enum class Opacity : uint8_t {
|
|||
SOME_TRANSPARENCY
|
||||
};
|
||||
|
||||
/**
|
||||
* FrameTimeout wraps a frame timeout value (measured in milliseconds) after
|
||||
* first normalizing it. This normalization is necessary because some tools
|
||||
* generate incorrect frame timeout values which we nevertheless have to
|
||||
* support. For this reason, code that deals with frame timeouts should always
|
||||
* use a FrameTimeout value rather than the raw value from the image header.
|
||||
*/
|
||||
struct FrameTimeout
|
||||
{
|
||||
/**
|
||||
* @return a FrameTimeout of zero. This should be used only for math
|
||||
* involving FrameTimeout values. You can't obtain a zero FrameTimeout from
|
||||
* FromRawMilliseconds().
|
||||
*/
|
||||
static FrameTimeout Zero() { return FrameTimeout(0); }
|
||||
|
||||
/// @return an infinite FrameTimeout.
|
||||
static FrameTimeout Forever() { return FrameTimeout(-1); }
|
||||
|
||||
/// @return a FrameTimeout obtained by normalizing a raw timeout value.
|
||||
static FrameTimeout FromRawMilliseconds(int32_t aRawMilliseconds)
|
||||
{
|
||||
// Normalize all infinite timeouts to the same value.
|
||||
if (aRawMilliseconds < 0) {
|
||||
return FrameTimeout::Forever();
|
||||
}
|
||||
|
||||
// Very small timeout values are problematic for two reasons: we don't want
|
||||
// to burn energy redrawing animated images extremely fast, and broken tools
|
||||
// generate these values when they actually want a "default" value, so such
|
||||
// images won't play back right without normalization. For some context,
|
||||
// see bug 890743, bug 125137, bug 139677, and bug 207059. The historical
|
||||
// behavior of IE and Opera was:
|
||||
// IE 6/Win:
|
||||
// 10 - 50ms is normalized to 100ms.
|
||||
// >50ms is used unnormalized.
|
||||
// Opera 7 final/Win:
|
||||
// 10ms is normalized to 100ms.
|
||||
// >10ms is used unnormalized.
|
||||
if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10 ) {
|
||||
return FrameTimeout(100);
|
||||
}
|
||||
|
||||
// The provided timeout value is OK as-is.
|
||||
return FrameTimeout(aRawMilliseconds);
|
||||
}
|
||||
|
||||
bool operator==(const FrameTimeout& aOther) const
|
||||
{
|
||||
return mTimeout == aOther.mTimeout;
|
||||
}
|
||||
|
||||
bool operator!=(const FrameTimeout& aOther) const { return !(*this == aOther); }
|
||||
|
||||
FrameTimeout operator+(const FrameTimeout& aOther)
|
||||
{
|
||||
if (*this == Forever() || aOther == Forever()) {
|
||||
return Forever();
|
||||
}
|
||||
|
||||
return FrameTimeout(mTimeout + aOther.mTimeout);
|
||||
}
|
||||
|
||||
FrameTimeout& operator+=(const FrameTimeout& aOther)
|
||||
{
|
||||
*this = *this + aOther;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this FrameTimeout's value in milliseconds. Illegal to call on a
|
||||
* an infinite FrameTimeout value.
|
||||
*/
|
||||
uint32_t AsMilliseconds() const
|
||||
{
|
||||
if (*this == Forever()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling AsMilliseconds() on an infinite FrameTimeout");
|
||||
return 100; // Fail to something sane.
|
||||
}
|
||||
|
||||
return uint32_t(mTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this FrameTimeout value encoded so that non-negative values
|
||||
* represent a timeout in milliseconds, and -1 represents an infinite
|
||||
* timeout.
|
||||
*
|
||||
* XXX(seth): This is a backwards compatibility hack that should be removed.
|
||||
*/
|
||||
int32_t AsEncodedValueDeprecated() const { return mTimeout; }
|
||||
|
||||
private:
|
||||
explicit FrameTimeout(int32_t aTimeout)
|
||||
: mTimeout(aTimeout)
|
||||
{ }
|
||||
|
||||
int32_t mTimeout;
|
||||
};
|
||||
|
||||
/**
|
||||
* AnimationData contains all of the information necessary for using an imgFrame
|
||||
* as part of an animation.
|
||||
|
|
|
@ -90,6 +90,10 @@ interface imgIContainer : nsISupports
|
|||
*/
|
||||
readonly attribute int32_t height;
|
||||
|
||||
%{C++
|
||||
virtual nsresult GetNativeSizes(nsTArray<nsIntSize>& aNativeSizes) const = 0;
|
||||
%}
|
||||
|
||||
/**
|
||||
* The intrinsic size of this image in appunits. If the image has no intrinsic
|
||||
* size in a dimension, -1 will be returned for that dimension. In the case of
|
||||
|
|
|
@ -37,8 +37,10 @@ XPIDL_MODULE = 'imglib2'
|
|||
|
||||
EXPORTS += [
|
||||
'DrawResult.h',
|
||||
'FrameTimeout.h',
|
||||
'ImageCacheKey.h',
|
||||
'ImageLogging.h',
|
||||
'ImageMetadata.h',
|
||||
'ImageOps.h',
|
||||
'ImageRegion.h',
|
||||
'imgLoader.h',
|
||||
|
|
|
@ -679,5 +679,11 @@ ImageTestCase TruncatedSmallGIFTestCase()
|
|||
return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1));
|
||||
}
|
||||
|
||||
ImageTestCase GreenMultipleSizesICOTestCase()
|
||||
{
|
||||
return ImageTestCase("green-multiple-sizes.ico", "image/x-icon",
|
||||
IntSize(256, 256));
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -414,6 +414,8 @@ ImageTestCase DownscaledTransparentICOWithANDMaskTestCase();
|
|||
|
||||
ImageTestCase TruncatedSmallGIFTestCase();
|
||||
|
||||
ImageTestCase GreenMultipleSizesICOTestCase();
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -27,9 +27,11 @@ class DecodeToSurfaceRunnable : public Runnable
|
|||
public:
|
||||
DecodeToSurfaceRunnable(RefPtr<SourceSurface>& aSurface,
|
||||
nsIInputStream* aInputStream,
|
||||
ImageOps::ImageBuffer* aImageBuffer,
|
||||
const ImageTestCase& aTestCase)
|
||||
: mSurface(aSurface)
|
||||
, mInputStream(aInputStream)
|
||||
, mImageBuffer(aImageBuffer)
|
||||
, mTestCase(aTestCase)
|
||||
{ }
|
||||
|
||||
|
@ -41,16 +43,35 @@ public:
|
|||
|
||||
void Go()
|
||||
{
|
||||
mSurface =
|
||||
ImageOps::DecodeToSurface(mInputStream,
|
||||
nsDependentCString(mTestCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
Maybe<IntSize> outputSize;
|
||||
if (mTestCase.mOutputSize != mTestCase.mSize) {
|
||||
outputSize.emplace(mTestCase.mOutputSize);
|
||||
}
|
||||
|
||||
if (mImageBuffer) {
|
||||
mSurface =
|
||||
ImageOps::DecodeToSurface(mImageBuffer,
|
||||
nsDependentCString(mTestCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT,
|
||||
outputSize);
|
||||
} else {
|
||||
mSurface =
|
||||
ImageOps::DecodeToSurface(mInputStream,
|
||||
nsDependentCString(mTestCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT,
|
||||
outputSize);
|
||||
}
|
||||
ASSERT_TRUE(mSurface != nullptr);
|
||||
|
||||
EXPECT_TRUE(mSurface->IsDataSourceSurface());
|
||||
EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
|
||||
mSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
|
||||
EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
|
||||
|
||||
if (outputSize) {
|
||||
EXPECT_EQ(*outputSize, mSurface->GetSize());
|
||||
} else {
|
||||
EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(),
|
||||
mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
|
||||
|
@ -59,14 +80,19 @@ public:
|
|||
private:
|
||||
RefPtr<SourceSurface>& mSurface;
|
||||
nsCOMPtr<nsIInputStream> mInputStream;
|
||||
RefPtr<ImageOps::ImageBuffer> mImageBuffer;
|
||||
ImageTestCase mTestCase;
|
||||
};
|
||||
|
||||
static void
|
||||
RunDecodeToSurface(const ImageTestCase& aTestCase)
|
||||
RunDecodeToSurface(const ImageTestCase& aTestCase,
|
||||
ImageOps::ImageBuffer* aImageBuffer = nullptr)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
if (!aImageBuffer) {
|
||||
inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
nsresult rv =
|
||||
|
@ -77,7 +103,7 @@ RunDecodeToSurface(const ImageTestCase& aTestCase)
|
|||
// DecodeToSurface doesn't require any main-thread-only code.
|
||||
RefPtr<SourceSurface> surface;
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
new DecodeToSurfaceRunnable(surface, inputStream, aTestCase);
|
||||
new DecodeToSurfaceRunnable(surface, inputStream, aImageBuffer, aTestCase);
|
||||
thread->Dispatch(runnable, nsIThread::DISPATCH_SYNC);
|
||||
|
||||
thread->Shutdown();
|
||||
|
@ -122,3 +148,43 @@ TEST_F(ImageDecodeToSurface, Corrupt)
|
|||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
EXPECT_TRUE(surface == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ImageDecodeToSurface, ICOMultipleSizes)
|
||||
{
|
||||
ImageTestCase testCase = GreenMultipleSizesICOTestCase();
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
RefPtr<ImageOps::ImageBuffer> buffer =
|
||||
ImageOps::CreateImageBuffer(inputStream);
|
||||
ASSERT_TRUE(buffer != nullptr);
|
||||
|
||||
ImageMetadata metadata;
|
||||
nsresult rv = ImageOps::DecodeMetadata(buffer,
|
||||
nsDependentCString(testCase.mMimeType),
|
||||
metadata);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
ASSERT_TRUE(metadata.HasSize());
|
||||
EXPECT_EQ(testCase.mSize, metadata.GetSize());
|
||||
|
||||
const nsTArray<IntSize>& nativeSizes = metadata.GetNativeSizes();
|
||||
ASSERT_EQ(6u, nativeSizes.Length());
|
||||
|
||||
IntSize expectedSizes[] = {
|
||||
IntSize(16, 16),
|
||||
IntSize(32, 32),
|
||||
IntSize(64, 64),
|
||||
IntSize(128, 128),
|
||||
IntSize(256, 256),
|
||||
IntSize(256, 128),
|
||||
};
|
||||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
EXPECT_EQ(expectedSizes[i], nativeSizes[i]);
|
||||
|
||||
// Request decoding at native size
|
||||
testCase.mOutputSize = nativeSizes[i];
|
||||
RunDecodeToSurface(testCase, buffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -672,3 +672,87 @@ TEST_F(ImageDecoders, TruncatedSmallGIFSingleChunk)
|
|||
{
|
||||
CheckDecoderSingleChunk(TruncatedSmallGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, MultipleSizesICOSingleChunk)
|
||||
{
|
||||
ImageTestCase testCase = GreenMultipleSizesICOTestCase();
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
nsTArray<IntSize> nativeSizes;
|
||||
rv = image->GetNativeSizes(nativeSizes);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
ASSERT_EQ(6u, nativeSizes.Length());
|
||||
|
||||
IntSize expectedSizes[] = {
|
||||
IntSize(16, 16),
|
||||
IntSize(32, 32),
|
||||
IntSize(64, 64),
|
||||
IntSize(128, 128),
|
||||
IntSize(256, 256),
|
||||
IntSize(256, 128)
|
||||
};
|
||||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
EXPECT_EQ(expectedSizes[i], nativeSizes[i]);
|
||||
}
|
||||
|
||||
RefPtr<Image> image90 =
|
||||
ImageOps::Orient(image, Orientation(Angle::D90, Flip::Unflipped));
|
||||
rv = image90->GetNativeSizes(nativeSizes);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
ASSERT_EQ(6u, nativeSizes.Length());
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
EXPECT_EQ(expectedSizes[i], nativeSizes[i]);
|
||||
}
|
||||
EXPECT_EQ(IntSize(128, 256), nativeSizes[5]);
|
||||
|
||||
RefPtr<Image> image180 =
|
||||
ImageOps::Orient(image, Orientation(Angle::D180, Flip::Unflipped));
|
||||
rv = image180->GetNativeSizes(nativeSizes);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
ASSERT_EQ(6u, nativeSizes.Length());
|
||||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
EXPECT_EQ(expectedSizes[i], nativeSizes[i]);
|
||||
}
|
||||
}
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 14 KiB |
|
@ -47,6 +47,7 @@ TEST_HARNESS_FILES.gtest += [
|
|||
'first-frame-green.png',
|
||||
'first-frame-padding.gif',
|
||||
'green-1x1-truncated.gif',
|
||||
'green-multiple-sizes.ico',
|
||||
'green.bmp',
|
||||
'green.gif',
|
||||
'green.ico',
|
||||
|
|
Загрузка…
Ссылка в новой задаче