Bug 1680387 - Read and expose EXIF image resolution data. r=tnikkel,aosmond

Differential Revision: https://phabricator.services.mozilla.com/D113264
This commit is contained in:
Emilio Cobos Álvarez 2021-05-05 09:41:23 +00:00
Родитель dc8fb2b8d5
Коммит 6c4266f7f7
21 изменённых файлов: 366 добавлений и 104 удалений

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

@ -449,13 +449,13 @@ nsresult Decoder::FinishWithErrorInternal() {
*/
void Decoder::PostSize(int32_t aWidth, int32_t aHeight,
Orientation aOrientation /* = Orientation()*/) {
Orientation aOrientation, Resolution aResolution) {
// 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);
mImageMetadata.SetSize(aWidth, aHeight, aOrientation, aResolution);
// Verify it is the expected size, if given. Note that this is only used by
// the ICO decoder for embedded image types, so only its subdecoders are

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

@ -15,6 +15,7 @@
#include "DecoderFlags.h"
#include "ImageMetadata.h"
#include "Orientation.h"
#include "Resolution.h"
#include "SourceBuffer.h"
#include "StreamingLexer.h"
#include "SurfaceFlags.h"
@ -473,8 +474,8 @@ class Decoder {
// 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());
void PostSize(int32_t aWidth, int32_t aHeight, Orientation = Orientation(),
Resolution = Resolution());
// Called by decoders if they determine that the image has transparency.
//

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

@ -13,6 +13,7 @@
#include "mozilla/SVGImageContext.h"
#include "ImageRegion.h"
#include "Orientation.h"
#include "mozilla/image/Resolution.h"
#include "mozilla/MemoryReporting.h"
@ -113,6 +114,9 @@ Maybe<AspectRatio> DynamicImage::GetIntrinsicRatio() {
NS_IMETHODIMP_(Orientation)
DynamicImage::GetOrientation() { return Orientation(); }
NS_IMETHODIMP_(Resolution)
DynamicImage::GetResolution() { return {}; }
NS_IMETHODIMP
DynamicImage::GetType(uint16_t* aType) {
*aType = imgIContainer::TYPE_RASTER;

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

@ -14,19 +14,16 @@
#include "mozilla/Maybe.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/image/Resolution.h"
#include "nsSize.h"
#include "nsTArray.h"
namespace mozilla {
namespace image {
namespace mozilla::image {
// The metadata about an image that decoders accumulate as they decode.
class ImageMetadata {
public:
ImageMetadata()
: mLoopCount(-1),
mFirstFrameTimeout(FrameTimeout::Forever()),
mHasAnimation(false) {}
ImageMetadata() = default;
void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY) {
mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY));
@ -56,10 +53,12 @@ class ImageMetadata {
return mFirstFrameRefreshArea.isSome();
}
void SetSize(int32_t width, int32_t height, Orientation orientation) {
void SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation,
Resolution aResolution) {
if (!HasSize()) {
mSize.emplace(nsIntSize(width, height));
mOrientation.emplace(orientation);
mSize.emplace(nsIntSize(aWidth, aHeight));
mOrientation.emplace(aOrientation);
mResolution = aResolution;
}
}
nsIntSize GetSize() const { return *mSize; }
@ -69,6 +68,8 @@ class ImageMetadata {
mNativeSizes.AppendElement(aSize);
}
Resolution GetResolution() const { return mResolution; }
const nsTArray<nsIntSize>& GetNativeSizes() const { return mNativeSizes; }
Orientation GetOrientation() const { return *mOrientation; }
@ -82,13 +83,16 @@ class ImageMetadata {
Maybe<gfx::IntPoint> mHotspot;
/// The loop count for animated images, or -1 for infinite loop.
int32_t mLoopCount;
int32_t mLoopCount = -1;
/// The resolution of the image in dppx.
Resolution mResolution;
// 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;
FrameTimeout mFirstFrameTimeout = FrameTimeout::Forever();
// The area of the image that needs to be invalidated when the animation
// loops.
@ -100,10 +104,9 @@ class ImageMetadata {
// Sizes the image can natively decode to.
CopyableTArray<nsIntSize> mNativeSizes;
bool mHasAnimation : 1;
bool mHasAnimation = false;
};
} // namespace image
} // namespace mozilla
} // namespace mozilla::image
#endif // mozilla_image_ImageMetadata_h

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

@ -7,13 +7,13 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/RefPtr.h"
#include "Orientation.h"
#include "mozilla/image/Resolution.h"
#include "mozilla/MemoryReporting.h"
namespace mozilla {
using dom::Document;
using gfx::DataSourceSurface;
using gfx::IntSize;
using gfx::SamplingFilter;
using gfx::SourceSurface;
@ -135,6 +135,9 @@ nsresult ImageWrapper::GetHotspotY(int32_t* aY) {
NS_IMETHODIMP_(Orientation)
ImageWrapper::GetOrientation() { return mInnerImage->GetOrientation(); }
NS_IMETHODIMP_(Resolution)
ImageWrapper::GetResolution() { return mInnerImage->GetResolution(); }
NS_IMETHODIMP
ImageWrapper::GetType(uint16_t* aType) { return mInnerImage->GetType(aType); }

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

@ -259,6 +259,9 @@ Maybe<AspectRatio> RasterImage::GetIntrinsicRatio() {
NS_IMETHODIMP_(Orientation)
RasterImage::GetOrientation() { return mOrientation; }
NS_IMETHODIMP_(Resolution)
RasterImage::GetResolution() { return mResolution; }
//******************************************************************************
NS_IMETHODIMP
RasterImage::GetType(uint16_t* aType) {
@ -710,6 +713,8 @@ bool RasterImage::SetMetadata(const ImageMetadata& aMetadata,
return true;
}
mResolution = aMetadata.GetResolution();
if (aMetadata.HasSize()) {
auto metadataSize = UnorientedIntSize::FromUnknownSize(aMetadata.GetSize());
if (metadataSize.width < 0 || metadataSize.height < 0) {

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

@ -37,6 +37,7 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/image/Resolution.h"
#include "ImageContainer.h"
#include "PlaybackType.h"
#ifdef DEBUG
@ -412,6 +413,9 @@ class RasterImage final : public ImageResource,
// metadata. RasterImage will handle and apply this orientation itself.
Orientation mOrientation;
// The resolution as specified in the image metadata, in dppx.
Resolution mResolution;
/// If this has a value, we're waiting for SetSize() to send the load event.
Maybe<Progress> mLoadProgress;

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

@ -0,0 +1,74 @@
/* -*- 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_Resolution_h
#define mozilla_image_Resolution_h
#include "mozilla/Assertions.h"
#include <cmath>
namespace mozilla {
namespace image {
/**
* The resolution of an image, in dppx.
*/
struct Resolution {
Resolution() = default;
Resolution(float aX, float aY) : mX(aX), mY(aY) {
MOZ_ASSERT(mX != 0.0f);
MOZ_ASSERT(mY != 0.0f);
}
bool operator==(const Resolution& aOther) const {
return mX == aOther.mX && mY == aOther.mY;
}
bool operator!=(const Resolution& aOther) const { return !(*this == aOther); }
float mX = 1.0f;
float mY = 1.0f;
void ApplyXTo(int32_t& aWidth) const {
if (mX != 1.0f) {
aWidth = std::round(float(aWidth) / mX);
}
}
void ApplyXTo(float& aWidth) const {
if (mX != 1.0f) {
aWidth /= mX;
}
}
void ApplyYTo(int32_t& aHeight) const {
if (mY != 1.0f) {
aHeight = std::round(float(aHeight) / mY);
}
}
void ApplyYTo(float& aHeight) const {
if (mY != 1.0f) {
aHeight /= mY;
}
}
void ApplyTo(int32_t& aWidth, int32_t& aHeight) const {
ApplyXTo(aWidth);
ApplyYTo(aHeight);
}
void ApplyTo(float& aWidth, float& aHeight) const {
ApplyXTo(aWidth);
ApplyYTo(aHeight);
}
};
} // namespace image
using ImageResolution = image::Resolution;
} // namespace mozilla
#endif

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

@ -44,6 +44,7 @@
#include "SurfaceCache.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/image/Resolution.h"
namespace mozilla {
@ -603,6 +604,9 @@ Maybe<AspectRatio> VectorImage::GetIntrinsicRatio() {
NS_IMETHODIMP_(Orientation)
VectorImage::GetOrientation() { return Orientation(); }
NS_IMETHODIMP_(Resolution)
VectorImage::GetResolution() { return {}; }
//******************************************************************************
NS_IMETHODIMP
VectorImage::GetType(uint16_t* aType) {

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

@ -6,9 +6,9 @@
#include "EXIF.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/StaticPrefs_image.h"
namespace mozilla {
namespace image {
namespace mozilla::image {
// Section references in this file refer to the EXIF v2.3 standard, also known
// as CIPA DC-008-Translation-2010.
@ -16,8 +16,11 @@ namespace image {
// 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,
enum class EXIFTag : uint16_t {
Orientation = 0x112,
XResolution = 0x11a,
YResolution = 0x11b,
ResolutionUnit = 0x128,
};
// See Section 4.6.2.
@ -34,6 +37,26 @@ enum EXIFType {
static const char* EXIFHeader = "Exif\0\0";
static const uint32_t EXIFHeaderLength = 6;
static const uint32_t TIFFHeaderStart = EXIFHeaderLength;
struct ParsedEXIFData {
Orientation orientation;
float resolutionX = 72.0f;
float resolutionY = 72.0f;
ResolutionUnit resolutionUnit = ResolutionUnit::Dpi;
};
static float ToDppx(float aResolution, ResolutionUnit aUnit) {
constexpr float kPointsPerInch = 72.0f;
constexpr float kPointsPerCm = 1.0f / 2.54f;
switch (aUnit) {
case ResolutionUnit::Dpi:
return aResolution / kPointsPerInch;
case ResolutionUnit::Dpcm:
return aResolution / kPointsPerCm;
}
MOZ_CRASH("Unknown resolution unit?");
}
/////////////////////////////////////////////////////////////
// Parse EXIF data, typically found in a JPEG's APP1 segment.
@ -54,14 +77,13 @@ EXIFData EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength) {
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);
// We only care about IFD0 at this point, so we don't bother with the other
// IFDs. If we got this far we're done.
ParsedEXIFData data;
ParseIFD0(data);
return EXIFData{data.orientation,
Resolution(ToDppx(data.resolutionX, data.resolutionUnit),
ToDppx(data.resolutionY, data.resolutionUnit))};
}
/////////////////////////////////////////////////////////
@ -94,55 +116,134 @@ bool EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut) {
// 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;
aIFD0OffsetOut = ifd0Offset + TIFFHeaderStart;
return true;
}
/////////////////////////////////////////////////////////
// Parse the entries in IFD0. (Section 4.6.2)
/////////////////////////////////////////////////////////
bool EXIFParser::ParseIFD0(Orientation& aOrientationOut) {
void EXIFParser::ParseIFD0(ParsedEXIFData& aData) {
uint16_t entryCount;
if (!ReadUInt16(entryCount)) {
return false;
return;
}
for (uint16_t entry = 0; entry < entryCount; ++entry) {
// Read the fields of the entry.
// Read the fields of the 12-byte 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;
return;
}
uint16_t type;
if (!ReadUInt16(type)) {
return false;
return;
}
uint32_t count;
if (!ReadUInt32(count)) {
return false;
return;
}
// We should have an orientation value here; go ahead and parse it.
if (!ParseOrientation(type, count, aOrientationOut)) {
return false;
switch (EXIFTag(tag)) {
case EXIFTag::Orientation:
// We should have an orientation value here; go ahead and parse it.
if (!ParseOrientation(type, count, aData.orientation)) {
return;
}
break;
case EXIFTag::ResolutionUnit:
if (!ParseResolutionUnit(type, count, aData.resolutionUnit)) {
return;
}
break;
case EXIFTag::XResolution:
if (!ParseResolution(type, count, aData.resolutionX)) {
return;
}
break;
case EXIFTag::YResolution:
if (!ParseResolution(type, count, aData.resolutionY)) {
return;
}
break;
default:
Advance(4);
break;
}
}
}
// Since the orientation is all we care about, we're done.
bool EXIFParser::ReadRational(float& aOut) {
// Values larger than 4 bytes (like rationals) are specified as an offset into
// the TIFF header.
uint32_t valueOffset;
if (!ReadUInt32(valueOffset)) {
return false;
}
ScopedJump jumpToHeader(*this, valueOffset + TIFFHeaderStart);
uint32_t numerator;
if (!ReadUInt32(numerator)) {
return false;
}
uint32_t denominator;
if (!ReadUInt32(denominator)) {
return false;
}
if (denominator == 0) {
return false;
}
aOut = float(numerator) / float(denominator);
return true;
}
bool EXIFParser::ParseResolution(uint16_t aType, uint32_t aCount, float& aOut) {
if (!StaticPrefs::image_exif_density_correction_enabled()) {
Advance(4);
return true;
}
if (aType != RationalType || aCount != 1) {
return false;
}
float value;
if (!ReadRational(value)) {
return false;
}
if (value == 0.0f) {
return false;
}
aOut = value;
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();
bool EXIFParser::ParseResolutionUnit(uint16_t aType, uint32_t aCount,
ResolutionUnit& aOut) {
if (!StaticPrefs::image_exif_density_correction_enabled()) {
Advance(4);
return true;
}
if (aType != ShortType || aCount != 1) {
return false;
}
uint16_t value;
if (!ReadUInt16(value)) {
return false;
}
switch (value) {
case 2:
aOut = ResolutionUnit::Dpi;
break;
case 3:
aOut = ResolutionUnit::Dpcm;
break;
default:
return false;
}
// This is a 32-bit field, but the unit value only occupies the first 16 bits.
// We need to advance another 16 bits to consume the entire field.
Advance(2);
return true;
}
@ -319,5 +420,4 @@ bool EXIFParser::ReadUInt32(uint32_t& aValue) {
return matched;
}
} // namespace image
} // namespace mozilla
} // namespace mozilla::image

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

@ -10,17 +10,22 @@
#include "nsDebug.h"
#include "Orientation.h"
#include "mozilla/image/Resolution.h"
namespace mozilla {
namespace image {
namespace mozilla::image {
enum class ByteOrder : uint8_t { Unknown, LittleEndian, BigEndian };
struct EXIFData {
EXIFData() {}
explicit EXIFData(Orientation aOrientation) : orientation(aOrientation) {}
const Orientation orientation = Orientation();
const Resolution resolution = Resolution();
};
const Orientation orientation;
struct ParsedEXIFData;
enum class ResolutionUnit : uint8_t {
Dpi,
Dpcm,
};
class EXIFParser {
@ -41,17 +46,36 @@ class EXIFParser {
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);
void ParseIFD0(ParsedEXIFData&);
bool ParseOrientation(uint16_t aType, uint32_t aCount, Orientation&);
bool ParseResolution(uint16_t aType, uint32_t aCount, float&);
bool ParseResolutionUnit(uint16_t aType, uint32_t aCount, ResolutionUnit&);
bool Initialize(const uint8_t* aData, const uint32_t aLength);
void Advance(const uint32_t aDistance);
void JumpTo(const uint32_t aOffset);
uint32_t CurrentOffset() const { return mCurrent - mStart; }
class ScopedJump {
EXIFParser& mParser;
uint32_t mOldOffset;
public:
ScopedJump(EXIFParser& aParser, uint32_t aOffset)
: mParser(aParser), mOldOffset(aParser.CurrentOffset()) {
mParser.JumpTo(aOffset);
}
~ScopedJump() { mParser.JumpTo(mOldOffset); }
};
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);
bool ReadRational(float& aOut);
const uint8_t* mStart;
const uint8_t* mCurrent;
@ -60,7 +84,6 @@ class EXIFParser {
ByteOrder mByteOrder;
};
} // namespace image
} // namespace mozilla
} // namespace mozilla::image
#endif // mozilla_image_decoders_EXIF_h

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

@ -236,8 +236,9 @@ LexerTransition<nsJPEGDecoder::State> nsJPEGDecoder::ReadJPEGData(
}
// Post our size to the superclass
PostSize(mInfo.image_width, mInfo.image_height,
ReadOrientationFromEXIF());
EXIFData exif = ReadExifData();
PostSize(mInfo.image_width, mInfo.image_height, exif.orientation,
exif.resolution);
if (HasError()) {
// Setting the size led to an error.
mState = JPEG_ERROR;
@ -601,7 +602,7 @@ LexerTransition<nsJPEGDecoder::State> nsJPEGDecoder::FinishedJPEGData() {
return Transition::TerminateFailure();
}
Orientation nsJPEGDecoder::ReadOrientationFromEXIF() {
EXIFData nsJPEGDecoder::ReadExifData() const {
jpeg_saved_marker_ptr marker;
// Locate the APP1 marker, where EXIF data is stored, in the marker list.
@ -613,13 +614,11 @@ Orientation nsJPEGDecoder::ReadOrientationFromEXIF() {
// If we're at the end of the list, there's no EXIF data.
if (!marker) {
return Orientation();
return EXIFData();
}
// Extract the orientation information.
EXIFData exif = EXIFParser::Parse(marker->data,
static_cast<uint32_t>(marker->data_length));
return exif.orientation;
return EXIFParser::Parse(marker->data,
static_cast<uint32_t>(marker->data_length));
}
void nsJPEGDecoder::NotifyDone() {

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

@ -9,6 +9,7 @@
#include "RasterImage.h"
#include "SurfacePipe.h"
#include "EXIF.h"
// On Windows systems, RasterImage.h brings in 'windows.h', which defines INT32.
// But the jpeg decoder has its own definition of INT32. To avoid build issues,
@ -23,8 +24,7 @@ extern "C" {
#include <setjmp.h>
namespace mozilla {
namespace image {
namespace mozilla::image {
typedef struct {
struct jpeg_error_mgr pub; // "public" fields for IJG library
@ -62,7 +62,7 @@ class nsJPEGDecoder : public Decoder {
Maybe<Telemetry::HistogramID> SpeedHistogram() const override;
protected:
Orientation ReadOrientationFromEXIF();
EXIFData ReadExifData() const;
WriteState OutputScanlines();
private:
@ -107,7 +107,6 @@ class nsJPEGDecoder : public Decoder {
SurfacePipe mPipe;
};
} // namespace image
} // namespace mozilla
} // namespace mozilla::image
#endif // mozilla_image_decoders_nsJPEGDecoder_h

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

@ -48,6 +48,7 @@ namespace image {
class ImageRegion;
struct Orientation;
struct Resolution;
}
}
@ -71,6 +72,7 @@ native TempRefImageContainer(already_AddRefed<mozilla::layers::ImageContainer>);
[ref] native ImageRegion(mozilla::image::ImageRegion);
[ptr] native LayerManager(mozilla::layers::LayerManager);
native Orientation(mozilla::image::Orientation);
native ImageResolution(mozilla::image::Resolution);
[ref] native TimeStamp(mozilla::TimeStamp);
[ref] native MaybeSVGImageContext(mozilla::Maybe<mozilla::SVGImageContext>);
native TempRefSourceSurface(already_AddRefed<mozilla::gfx::SourceSurface>);
@ -639,6 +641,12 @@ interface imgIContainer : nsISupports
*/
[notxpcom] Orientation getOrientation();
/*
* Returns the intrinsic resolution of the image, or 1.0 if the image doesn't
* declare any.
*/
[notxpcom] ImageResolution getResolution();
/*
* Returns the delay, in ms, between the first and second frame. If this
* returns 0, there is no delay between first and second frame (i.e., this

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

@ -61,6 +61,7 @@ EXPORTS.mozilla.image += [
"encoders/png/nsPNGEncoder.h",
"ICOFileHeaders.h",
"ImageMemoryReporter.h",
"Resolution.h",
]
UNIFIED_SOURCES += [

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

@ -9,6 +9,7 @@
#include "gfxPlatform.h"
#include "ImageFactory.h"
#include "imgITools.h"
#include "mozilla/Preferences.h"
#include "nsComponentManagerUtils.h"
@ -831,5 +832,41 @@ ImageTestCase PerfRgbGIFTestCase() {
return ImageTestCase("perf_srgb.gif", "image/gif", IntSize(1000, 1000));
}
ImageTestCase ExifResolutionTestCase() {
return ImageTestCase("exif_resolution.jpg", "image/jpeg", IntSize(100, 50));
}
RefPtr<Image> TestCaseToDecodedImage(const ImageTestCase& aTestCase) {
RefPtr<Image> image = ImageFactory::CreateAnonymousImage(
nsDependentCString(aTestCase.mMimeType));
MOZ_RELEASE_ASSERT(!image->HasError());
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
MOZ_RELEASE_ASSERT(inputStream);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
// Write the data into the image.
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
static_cast<uint32_t>(length));
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
// Let the image know we've sent all the data.
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
MOZ_RELEASE_ASSERT(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);
Unused << surface;
return image;
}
} // namespace image
} // namespace mozilla

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

@ -552,6 +552,11 @@ ImageTestCase DownscaledAVIFTestCase();
ImageTestCase LargeAVIFTestCase();
ImageTestCase MultiLayerAVIFTestCase();
ImageTestCase TransparentAVIFTestCase();
ImageTestCase ExifResolutionTestCase();
RefPtr<Image> TestCaseToDecodedImage(const ImageTestCase&);
} // namespace image
} // namespace mozilla

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

@ -866,37 +866,11 @@ TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks) {
// extra data shouldn't confuse the decoder or cause the decode to fail.
// 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);
RefPtr<Image> image = TestCaseToDecodedImage(testCase);
// Ensure that the image's metadata meets our expectations.
IntSize imageSize(0, 0);
rv = image->GetWidth(&imageSize.width);
nsresult rv = image->GetWidth(&imageSize.width);
EXPECT_TRUE(NS_SUCCEEDED(rv));
rv = image->GetHeight(&imageSize.height);
EXPECT_TRUE(NS_SUCCEEDED(rv));
@ -904,6 +878,7 @@ TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks) {
EXPECT_EQ(testCase.mSize.width, imageSize.width);
EXPECT_EQ(testCase.mSize.height, imageSize.height);
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
Progress imageProgress = tracker->GetProgress();
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
@ -1028,3 +1003,8 @@ TEST_F(ImageDecoders, MultipleSizesICOSingleChunk) {
EXPECT_EQ(expectedSizes[i], nativeSizes[i]);
}
}
TEST_F(ImageDecoders, ExifResolutionEven) {
RefPtr<Image> image = TestCaseToDecodedImage(ExifResolutionTestCase());
EXPECT_EQ(image->GetResolution(), Resolution(2.0, 2.0));
}

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

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

@ -62,6 +62,7 @@ TEST_HARNESS_FILES.gtest += [
"downscaled.jpg",
"downscaled.png",
"downscaled.webp",
"exif_resolution.jpg",
"first-frame-green.gif",
"first-frame-green.png",
"first-frame-green.webp",

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

@ -5307,6 +5307,17 @@
value: true
mirror: always
# Whether we use EXIF metadata for image density.
#
# NOTE: Before shipping this, make sure that the issue described in the
# following comment is addressed:
#
# https://github.com/whatwg/html/pull/5574#issuecomment-826335244
- name: image.exif-density-correction.enabled
type: RelaxedAtomicBool
value: @IS_NIGHTLY_BUILD@
mirror: always
# The threshold for inferring that changes to an <img> element's |src|
# attribute by JavaScript represent an animation, in milliseconds. If the |src|
# attribute is changing more frequently than this value, then we enter a