Bug 1343499 - Expose native image sizes to imagelib users. r=tnikkel

This commit is contained in:
Andrew Osmond 2017-03-22 09:05:36 -04:00
Родитель 20fb8455d6
Коммит a60b290b56
24 изменённых файлов: 530 добавлений и 119 удалений

Просмотреть файл

@ -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;

119
image/FrameTimeout.h Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -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]);
}
}

Двоичные данные
image/test/gtest/green-multiple-sizes.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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',