Bug 1201796 (Part 4) - Add downscale-during-decode support for the ICO decoder. r=tn

This commit is contained in:
Seth Fowler 2015-09-19 13:34:14 -07:00
Родитель ba6ea7f0cc
Коммит 93cbaca5e0
8 изменённых файлов: 104 добавлений и 63 удалений

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

@ -38,6 +38,7 @@ ShouldDownscaleDuringDecode(const nsCString& aMimeType)
DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get()); DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get());
return type == DecoderType::JPEG || return type == DecoderType::JPEG ||
type == DecoderType::ICON || type == DecoderType::ICON ||
type == DecoderType::ICO ||
type == DecoderType::PNG || type == DecoderType::PNG ||
type == DecoderType::BMP || type == DecoderType::BMP ||
type == DecoderType::GIF; type == DecoderType::GIF;

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

@ -14,13 +14,14 @@
#include "RasterImage.h" #include "RasterImage.h"
using namespace mozilla::gfx;
namespace mozilla { namespace mozilla {
namespace image { namespace image {
// Constants. // Constants.
static const uint32_t ICOHEADERSIZE = 6; static const uint32_t ICOHEADERSIZE = 6;
static const uint32_t BITMAPINFOSIZE = 40; static const uint32_t BITMAPINFOSIZE = 40;
static const uint32_t PREFICONSIZE = 16;
// ---------------------------------------- // ----------------------------------------
// Actual Data Processing // Actual Data Processing
@ -60,6 +61,7 @@ nsICODecoder::GetNumColors()
nsICODecoder::nsICODecoder(RasterImage* aImage) nsICODecoder::nsICODecoder(RasterImage* aImage)
: Decoder(aImage) : Decoder(aImage)
, mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE)) , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE))
, mBiggestResourceColorDepth(0)
, mBestResourceDelta(INT_MIN) , mBestResourceDelta(INT_MIN)
, mBestResourceColorDepth(0) , mBestResourceColorDepth(0)
, mNumIcons(0) , mNumIcons(0)
@ -69,6 +71,20 @@ nsICODecoder::nsICODecoder(RasterImage* aImage)
, mCurrMaskLine(0) , mCurrMaskLine(0)
{ } { }
nsresult
nsICODecoder::SetTargetSize(const nsIntSize& aSize)
{
// Make sure the size is reasonable.
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
return NS_ERROR_FAILURE;
}
// Create a downscaler that we'll filter our output through.
mDownscaler.emplace(aSize);
return NS_OK;
}
void void
nsICODecoder::FinishInternal() nsICODecoder::FinishInternal()
{ {
@ -222,16 +238,6 @@ nsICODecoder::ReadBIHSize(const char* aBIH)
return headerSize; return headerSize;
} }
void
nsICODecoder::SetHotSpotIfCursor()
{
if (!mIsCursor) {
return;
}
mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
}
LexerTransition<ICOState> LexerTransition<ICOState>
nsICODecoder::ReadHeader(const char* aData) nsICODecoder::ReadHeader(const char* aData)
{ {
@ -248,10 +254,13 @@ nsICODecoder::ReadHeader(const char* aData)
return Transition::Terminate(ICOState::SUCCESS); // Nothing to do. return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
} }
// If we didn't get a #-moz-resolution, default to PREFICONSIZE. // Downscale-during-decode can end up decoding different resources in the ICO
if (mResolution.width == 0 && mResolution.height == 0) { // file depending on the target size. Since the resources are not necessarily
mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE); // scaled versions of the same image, some may be transparent and some may not
} // be. We could be precise about transparency if we decoded the metadata of
// every resource, but for now we don't and it's safest to assume that
// transparency could be present.
PostHasTransparency();
return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
} }
@ -288,35 +297,69 @@ nsICODecoder::ReadDirEntry(const char* aData)
memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset)); memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset));
e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset); e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset);
// Calculate the delta between this image's size and the desired size, so we // Determine if this is the biggest resource we've seen so far. We always use
// can see if it is better than our current-best option. In the case of // the biggest resource for the intrinsic size, and if we're not downscaling,
// several equally-good images, we use the last one. "Better" in this case is // we select it as the best resource as well.
// determined by |delta|, a measure of the difference in size between the IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
// entry we've found and the requested size. We will choose the smallest image if (e.mBitCount >= mBiggestResourceColorDepth &&
// that is >= requested size (i.e. we assume it's better to downscale a larger entrySize.width * entrySize.height >=
// icon than to upscale a smaller one). mBiggestResourceSize.width * mBiggestResourceSize.height) {
int32_t delta = GetRealWidth(e) - mResolution.width + mBiggestResourceSize = entrySize;
GetRealHeight(e) - mResolution.height; mBiggestResourceColorDepth = e.mBitCount;
if (e.mBitCount >= mBestResourceColorDepth && mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot);
((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
(delta >= 0 && delta <= mBestResourceDelta))) {
mBestResourceDelta = delta;
// Ensure mImageOffset is >= size of the direntry headers (bug #245631). if (!mDownscaler) {
if (e.mImageOffset < FirstResourceOffset()) { mDirEntry = e;
return Transition::Terminate(ICOState::FAILURE);
} }
}
mBestResourceColorDepth = e.mBitCount; if (mDownscaler) {
mDirEntry = e; // Calculate the delta between this resource's size and the desired size, so
// we can see if it is better than our current-best option. In the case of
// several equally-good resources, we use the last one. "Better" in this
// case is determined by |delta|, a measure of the difference in size
// between the entry we've found and the downscaler's target size. We will
// choose the smallest resource that is >= the target size (i.e. we assume
// it's better to downscale a larger icon than to upscale a smaller one).
IntSize desiredSize = mDownscaler->TargetSize();
int32_t delta = entrySize.width - desiredSize.width +
entrySize.height - desiredSize.height;
if (e.mBitCount >= mBestResourceColorDepth &&
((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
(delta >= 0 && delta <= mBestResourceDelta))) {
mBestResourceDelta = delta;
mBestResourceColorDepth = e.mBitCount;
mDirEntry = e;
}
} }
if (mCurrIcon == mNumIcons) { if (mCurrIcon == mNumIcons) {
PostSize(GetRealWidth(mDirEntry), GetRealHeight(mDirEntry)); // Ensure the resource we selected has an offset past the ICO headers.
if (mDirEntry.mImageOffset < FirstResourceOffset()) {
return Transition::Terminate(ICOState::FAILURE);
}
// If this is a cursor, set the hotspot. We use the hotspot from the biggest
// resource since we also use that resource for the intrinsic size.
if (mIsCursor) {
mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
mBiggestResourceHotSpot.height);
}
// We always report the biggest resource's size as the intrinsic size; this
// is necessary for downscale-during-decode to work since we won't even
// attempt to *upscale* while decoding.
PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
if (IsMetadataDecode()) { if (IsMetadataDecode()) {
return Transition::Terminate(ICOState::SUCCESS); return Transition::Terminate(ICOState::SUCCESS);
} }
// If the resource we selected matches the downscaler's target size
// perfectly, we don't need to do any downscaling.
if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) {
mDownscaler.reset();
}
size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset(); size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
ICOState::SKIP_TO_RESOURCE, ICOState::SKIP_TO_RESOURCE,
@ -339,6 +382,9 @@ nsICODecoder::SniffResource(const char* aData)
mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetDecoderFlags(GetDecoderFlags()); mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags()); mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
if (mDownscaler) {
mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
}
mContainedDecoder->Init(); mContainedDecoder->Init();
if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) { if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
@ -363,6 +409,9 @@ nsICODecoder::SniffResource(const char* aData)
mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetDecoderFlags(GetDecoderFlags()); mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags()); mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
if (mDownscaler) {
mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
}
mContainedDecoder->Init(); mContainedDecoder->Init();
// Make sure we have a sane size for the bitmap information header. // Make sure we have a sane size for the bitmap information header.
@ -389,8 +438,7 @@ nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
// Raymond Chen says that 32bpp only are valid PNG ICOs // Raymond Chen says that 32bpp only are valid PNG ICOs
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
if (!IsMetadataDecode() && if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
return Transition::Terminate(ICOState::FAILURE); return Transition::Terminate(ICOState::FAILURE);
} }
@ -420,9 +468,6 @@ nsICODecoder::ReadBIH(const char* aData)
return Transition::Terminate(ICOState::FAILURE); return Transition::Terminate(ICOState::FAILURE);
} }
// Set up the cursor hot spot if one is present.
SetHotSpotIfCursor();
// Fix the ICO height from the BIH. It needs to be halved so our BMP decoder // Fix the ICO height from the BIH. It needs to be halved so our BMP decoder
// will understand, because the BMP decoder doesn't expect the alpha mask that // will understand, because the BMP decoder doesn't expect the alpha mask that
// follows the BMP data in an ICO. // follows the BMP data in an ICO.
@ -567,9 +612,8 @@ nsICODecoder::FinishResource()
{ {
// Make sure the actual size of the resource matches the size in the directory // Make sure the actual size of the resource matches the size in the directory
// entry. If not, we consider the image corrupt. // entry. If not, we consider the image corrupt.
IntSize expectedSize(GetRealWidth(mDirEntry), GetRealHeight(mDirEntry));
if (mContainedDecoder->HasSize() && if (mContainedDecoder->HasSize() &&
mContainedDecoder->GetSize() != expectedSize) { mContainedDecoder->GetSize() != GetRealSize()) {
return Transition::Terminate(ICOState::FAILURE); return Transition::Terminate(ICOState::FAILURE);
} }

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

@ -43,6 +43,8 @@ class nsICODecoder : public Decoder
public: public:
virtual ~nsICODecoder() { } virtual ~nsICODecoder() { }
nsresult SetTargetSize(const nsIntSize& aSize) override;
/// @return the width of the icon directory entry @aEntry. /// @return the width of the icon directory entry @aEntry.
static uint32_t GetRealWidth(const IconDirEntry& aEntry) static uint32_t GetRealWidth(const IconDirEntry& aEntry)
{ {
@ -61,9 +63,10 @@ public:
/// @return the height of the selected directory entry (mDirEntry). /// @return the height of the selected directory entry (mDirEntry).
uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); } uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
virtual void SetResolution(const gfx::IntSize& aResolution) override /// @return the size of the selected directory entry (mDirEntry).
gfx::IntSize GetRealSize() const
{ {
mResolution = aResolution; return gfx::IntSize(GetRealWidth(), GetRealHeight());
} }
/// @return The offset from the beginning of the ICO to the first resource. /// @return The offset from the beginning of the ICO to the first resource.
@ -86,8 +89,6 @@ private:
// Gets decoder state from the contained decoder so it's visible externally. // Gets decoder state from the contained decoder so it's visible externally.
void GetFinalStateFromContainedDecoder(); void GetFinalStateFromContainedDecoder();
// Sets the hotspot property of if we have a cursor
void SetHotSpotIfCursor();
// Creates a bitmap file header buffer, returns true if successful // Creates a bitmap file header buffer, returns true if successful
bool FillBitmapFileHeaderBuffer(int8_t* bfh); bool FillBitmapFileHeaderBuffer(int8_t* bfh);
// Fixes the ICO height to match that of the BIH. // Fixes the ICO height to match that of the BIH.
@ -118,10 +119,13 @@ private:
LexerTransition<ICOState> FinishResource(); LexerTransition<ICOState> FinishResource();
StreamingLexer<ICOState, 32> mLexer; // The lexer. StreamingLexer<ICOState, 32> mLexer; // The lexer.
Maybe<Downscaler> mDownscaler; // Our downscaler, if we're downscaling.
nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder. nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
gfx::IntSize mResolution; // The requested -moz-resolution.
char mBIHraw[40]; // The bitmap information header. char mBIHraw[40]; // The bitmap information header.
IconDirEntry mDirEntry; // The dir entry for the selected resource. IconDirEntry mDirEntry; // The dir entry for the selected resource.
IntSize mBiggestResourceSize; // Used to select the intrinsic size.
IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size.
uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
int32_t mBestResourceDelta; // Used to select the best resource. int32_t mBestResourceDelta; // Used to select the best resource.
uint16_t mBestResourceColorDepth; // Used to select the best resource. uint16_t mBestResourceColorDepth; // Used to select the best resource.
uint16_t mNumIcons; // Stores the number of icons in the ICO file. uint16_t mNumIcons; // Stores the number of icons in the ICO file.

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

@ -57,8 +57,9 @@ function testFiles() {
yield ["opaque.bmp", false]; yield ["opaque.bmp", false];
// ICO files which contain BMPs have an additional type of transparency - the // ICO files which contain BMPs have an additional type of transparency - the
// AND mask - that warrants separate testing. // AND mask - that warrants separate testing. (Although, after bug 1201796,
yield ["ico-bmp-opaque.ico", false]; // all ICOs are considered transparent.)
yield ["ico-bmp-opaque.ico", true];
yield ["ico-bmp-transparent.ico", true]; yield ["ico-bmp-transparent.ico", true];
// SVGs are always transparent. // SVGs are always transparent.

Двоичные данные
image/test/reftest/ico/cur/pointer.cur

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

До

Ширина:  |  Высота:  |  Размер: 14 KiB

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

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

@ -1,14 +1,3 @@
# ICO BMP and PNG mixed tests # ICO BMP and PNG mixed tests
== mixed-bmp-png.ico mixed-bmp-png.png == mixed-bmp-png.ico mixed-bmp-png48.png
# Using media fragments to select different resolutions
== mixed-bmp-png.ico#-moz-resolution=8,8 mixed-bmp-png.png
== mixed-bmp-png.ico#test=true&-moz-resolution=8,8&other mixed-bmp-png.png
== mixed-bmp-png.ico#-moz-resolution=32,32 mixed-bmp-png32.png
== mixed-bmp-png.ico#-moz-resolution=39,39 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=40,40 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=48,48 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=64,64 mixed-bmp-png48.png
== mixed-bmp-png.ico#-moz-resolution=64 mixed-bmp-png.png # Bad syntax will fall back to lowest resolution

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

До

Ширина:  |  Высота:  |  Размер: 3.0 KiB

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

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

@ -691,8 +691,10 @@ var errsrc = "none";
try { try {
container = imgTools.decodeImage(istream, inMimeType); container = imgTools.decodeImage(istream, inMimeType);
// We should never hit this - decodeImage throws an assertion because the // We expect to hit an error during encoding because the ICO header of the
// image decoded doesn't have enough frames. // image is fine, but the actual resources are corrupt. Since decodeImage()
// only performs a metadata decode, it doesn't decode far enough to realize
// this, but we'll find out when we do a full decode during encodeImage().
try { try {
istream = imgTools.encodeImage(container, "image/png"); istream = imgTools.encodeImage(container, "image/png");
} catch (e) { } catch (e) {
@ -704,7 +706,7 @@ try {
errsrc = "decode"; errsrc = "decode";
} }
do_check_eq(errsrc, "decode"); do_check_eq(errsrc, "encode");
checkExpectedError(/NS_ERROR_FAILURE/, err); checkExpectedError(/NS_ERROR_FAILURE/, err);