import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1194059 (Part 1) - Ensure that metadata decode progress is always delivered atomically. r=tn (07f0441600)
- Bug 1191090 - Use the normal PNG decoder for PNG metadata decodes. r=tn (ce3fe1be5f)
- Bug 1191114 (Part 1) - Always detect HAS_TRANSPARENCY during the metadata decode. r=tn (3841132932)
- Bug 1191114 (Part 2) - Add support for creating an anonymous metadata decoder, for use in tests. r=tn (2cdcc0c278)
- Bug 1191114 (Part 3) - Add flags to image test cases. r=tn (4a6f5a5230)
- Bug 1191114 (Part 4) - Add tests for metadata decoding, including that we always deliver HAS_TRANSPARENCY during the metadata decode. r=tn (b9c5d1cd4a)
- Bug 1126330 - Remove the check for non-looping animations. r=seth (828dabba24)
- Bug 1194059 (Part 2) - Always detect IS_ANIMATED during the metadatadecode. r=tn (0ba5bf38f1)
- Bug 1194059 (Part 3) - Ensure the nsIInputStream LoadImage() returns is always buffered. r=tn (ed2b02205b)
- Bug 1194059 (Part 4) - Add tests that we detect IS_ANIMATED during the metadata decode. r=tn (298f14a7c9)
- Bug 1188705 (part 1) - Remove gfxASurface::GetMemoryLocation(). r=seth. (1f0da73a08)
- Bug 1188705 (part 2) - Remove unused SizeOfDecodedWithComputedFallbackIfHeap declaration. r=seth. (3356dbed06)
- Bug 1188705 (part 3) - Simplify imgFrame::SizeOfExcludingThis(). r=seth. (563262a834)
- Bug 1155252 - Don't allocate X11TextureClients bigger than xlib's maximum surface size. r=jrmuizel (3f11590667)
- Bug 1143994 - Fix some -Wunreachable-code and -Wswitch warnings in imagelib. r=seth (008becc7e2)
- Bug 1060609 (Part 1) - Disable downscale-during-decode when HQ scaling is disabled. r=tn (6da77e3cad)
- Bug 1187569 - PNGs getting stuck in a pixelated state. r=seth (da305ef99c)
- Bug 1194900 - Stop deciding when to send invalidations in nsPNGDecoder and let Decoder handle it. r=tn (50fa14a984)
- Bug 1151694 - Part 1 - Move CommonAnimationManager::sLayerAnimationInfo into LayerAnimationInfo.(cpp|h). r=bbirtles (9f93e0d569)
-  Bug 1151694 - Part 2 - imgTools should be inside mozilla::image namespace. r=bbirtles (8dfc3f2e4b)
- Bug 1196066 (Part 1) - Fix bad directory entries in two of our ICO reftests. r=tn (9e4c70d2b4)
- Bug 1196065 - Add sanity tests for image decoders. r=tn (557b9131cb)
- Bug 1194912 (Part 1) - Add CopyOnWrite<T> to support automatic copy-on-write for recursive writes to data structures. r=tn (b081a50716)
- Bug 1196066 (Part 2) - Add a streaming lexing framework to ImageLib. r=tn (59eb634ea5)
- Bug 1196476 - Replace ProgressTracker::FirstObserverIs() with a simpler mechanism on imgRequest. r=tn (db9ecc65ef)
- missing part of Bug 1139225 (Part 2) - Dispatch OnImageAvailable to the main thread manually in imgRequest. r=tn (e7b22db614)
- Bug 1194912 (Part 2) - Store ProgressTracker observers in a copy-on-write hash table, and dispatch notifications to them using a template. r=tn (5efd7b38b3)
- Bug 1180225. Make convolver more like upstream. r=seth (18e3c168fc)
- Bug 1149318 - Fix the calling convention on SkGetUserDefaultLocaleNameProc. r=eihrul (7b750d4e4e)
- Bug 1210493 - enlarge stroke bounds by line width when doing a quick-reject in SkDraw::drawRect. r=jmuizelaar (e8b5d0fe2d)
- Bug 1188206 - Fix more constructors in gfx; r=jrmuizel (944ea9938c)
This commit is contained in:
2022-05-16 21:07:53 +08:00
parent 646c78a9d0
commit b75b514a74
75 changed files with 2600 additions and 771 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ struct ReferencePtr
{}
template <typename T>
ReferencePtr(const RefPtr<T>& aPtr)
MOZ_IMPLICIT ReferencePtr(const RefPtr<T>& aPtr)
: mLongPtr(uint64_t(aPtr.get()))
{}
+52 -40
View File
@@ -175,11 +175,11 @@ class CircularRowBuffer {
// |src_data| and continues for the [begin, end) of the filter.
template<bool has_alpha>
void ConvolveHorizontally(const unsigned char* src_data,
int begin, int end,
const ConvolutionFilter1D& filter,
unsigned char* out_row) {
int num_values = filter.num_values();
// Loop over each pixel on this row in the output image.
for (int out_x = begin; out_x < end; out_x++) {
for (int out_x = 0; out_x < num_values; out_x++) {
// Get the filter that determines the current output pixel.
int filter_offset, filter_length;
const ConvolutionFilter1D::Fixed* filter_values =
@@ -220,17 +220,18 @@ void ConvolveHorizontally(const unsigned char* src_data,
// Does vertical convolution to produce one output row. The filter values and
// length are given in the first two parameters. These are applied to each
// of the rows pointed to in the |source_data_rows| array, with each row
// being |end - begin| wide.
// being |pixel_width| wide.
//
// The output must have room for |(end - begin) * 4| bytes.
// The output must have room for |pixel_width * 4| bytes.
template<bool has_alpha>
void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values,
int filter_length,
unsigned char* const* source_data_rows,
int begin, int end, unsigned char* out_row) {
int pixel_width,
unsigned char* out_row) {
// We go through each column in the output and do a vertical convolution,
// generating one output pixel each time.
for (int out_x = begin; out_x < end; out_x++) {
for (int out_x = 0; out_x < pixel_width; out_x++) {
// Compute the number of bytes over in each row that the current column
// we're convolving starts at. The pixel will cover the next 4 bytes.
int byte_offset = out_x * 4;
@@ -288,28 +289,29 @@ void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values,
void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values,
int filter_length,
unsigned char* const* source_data_rows,
int width, unsigned char* out_row,
int pixel_width, unsigned char* out_row,
bool has_alpha, bool use_simd) {
int processed = 0;
#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A)
// If the binary was not built with SSE2 support, we had to fallback to C version.
int simd_width = width & ~3;
if (use_simd && simd_width) {
if (use_simd) {
ConvolveVertically_SIMD(filter_values, filter_length,
source_data_rows, 0, simd_width,
source_data_rows,
pixel_width,
out_row, has_alpha);
processed = simd_width;
}
} else
#endif
if (width > processed) {
{
if (has_alpha) {
ConvolveVertically<true>(filter_values, filter_length, source_data_rows,
processed, width, out_row);
ConvolveVertically<true>(filter_values, filter_length,
source_data_rows,
pixel_width,
out_row);
} else {
ConvolveVertically<false>(filter_values, filter_length, source_data_rows,
processed, width, out_row);
ConvolveVertically<false>(filter_values, filter_length,
source_data_rows,
pixel_width,
out_row);
}
}
}
@@ -326,16 +328,16 @@ void ConvolveHorizontally(const unsigned char* src_data,
// SIMD implementation works with 4 pixels at a time.
// Therefore we process as much as we can using SSE and then use
// C implementation for leftovers
ConvolveHorizontally_SSE2(src_data, 0, simd_width, filter, out_row);
ConvolveHorizontally_SSE2(src_data, filter, out_row);
processed = simd_width;
}
#endif
if (width > processed) {
if (has_alpha) {
ConvolveHorizontally<true>(src_data, processed, width, filter, out_row);
ConvolveHorizontally<true>(src_data, filter, out_row);
} else {
ConvolveHorizontally<false>(src_data, processed, width, filter, out_row);
ConvolveHorizontally<false>(src_data, filter, out_row);
}
}
}
@@ -457,9 +459,23 @@ void BGRAConvolve2D(const unsigned char* source_data,
int num_output_rows = filter_y.num_values();
int pixel_width = filter_x.num_values();
// We need to check which is the last line to convolve before we advance 4
// lines in one iteration.
int last_filter_offset, last_filter_length;
// SSE2 can access up to 3 extra pixels past the end of the
// buffer. At the bottom of the image, we have to be careful
// not to access data past the end of the buffer. Normally
// we fall back to the C++ implementation for the last row.
// If the last row is less than 3 pixels wide, we may have to fall
// back to the C++ version for more rows. Compute how many
// rows we need to avoid the SSE implementation for here.
filter_x.FilterForValue(filter_x.num_values() - 1, &last_filter_offset,
&last_filter_length);
#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A)
int avoid_simd_rows = 1 + 3 /
(last_filter_offset + last_filter_length);
#endif
filter_y.FilterForValue(num_output_rows - 1, &last_filter_offset,
&last_filter_length);
@@ -473,36 +489,32 @@ void BGRAConvolve2D(const unsigned char* source_data,
// We don't want to process too much rows in batches of 4 because
// we can go out-of-bounds at the end
while (next_x_row < filter_offset + filter_length) {
if (next_x_row + 3 < last_filter_offset + last_filter_length - 3) {
if (next_x_row + 3 < last_filter_offset + last_filter_length -
avoid_simd_rows) {
const unsigned char* src[4];
unsigned char* out_row[4];
for (int i = 0; i < 4; ++i) {
src[i] = &source_data[(next_x_row + i) * source_byte_row_stride];
out_row[i] = row_buffer.AdvanceRow();
}
ConvolveHorizontally4_SIMD(src, 0, pixel_width, filter_x, out_row);
ConvolveHorizontally4_SIMD(src, filter_x, out_row);
next_x_row += 4;
} else {
unsigned char* buffer = row_buffer.AdvanceRow();
// For last rows, SSE2 load possibly to access data beyond the
// image area. therefore we use cobined C+SSE version here
int simd_width = pixel_width & ~3;
if (simd_width) {
// Check if we need to avoid SSE2 for this row.
if (next_x_row < last_filter_offset + last_filter_length -
avoid_simd_rows) {
ConvolveHorizontally_SIMD(
&source_data[next_x_row * source_byte_row_stride],
0, simd_width, filter_x, buffer);
}
if (pixel_width > simd_width) {
filter_x, row_buffer.AdvanceRow());
} else {
if (source_has_alpha) {
ConvolveHorizontally<true>(
&source_data[next_x_row * source_byte_row_stride],
simd_width, pixel_width, filter_x, buffer);
filter_x, row_buffer.AdvanceRow());
} else {
ConvolveHorizontally<false>(
&source_data[next_x_row * source_byte_row_stride],
simd_width, pixel_width, filter_x, buffer);
filter_x, row_buffer.AdvanceRow());
}
}
next_x_row++;
@@ -513,12 +525,12 @@ void BGRAConvolve2D(const unsigned char* source_data,
while (next_x_row < filter_offset + filter_length) {
if (source_has_alpha) {
ConvolveHorizontally<true>(
&source_data[next_x_row * source_byte_row_stride],
0, pixel_width, filter_x, row_buffer.AdvanceRow());
&source_data[next_x_row * source_byte_row_stride],
filter_x, row_buffer.AdvanceRow());
} else {
ConvolveHorizontally<false>(
&source_data[next_x_row * source_byte_row_stride],
0, pixel_width, filter_x, row_buffer.AdvanceRow());
&source_data[next_x_row * source_byte_row_stride],
filter_x, row_buffer.AdvanceRow());
}
next_x_row++;
}
+36 -41
View File
@@ -35,26 +35,24 @@
namespace skia {
// Convolves horizontally along a single row. The row data is given in
// |src_data| and continues for the [begin, end) of the filter.
// |src_data| and continues for the num_values() of the filter.
void ConvolveHorizontally_SSE2(const unsigned char* src_data,
int begin, int end,
const ConvolutionFilter1D& filter,
unsigned char* out_row) {
int num_values = filter.num_values();
int filter_offset, filter_length;
__m128i zero = _mm_setzero_si128();
__m128i mask[3];
__m128i mask[4];
// |mask| will be used to decimate all extra filter coefficients that are
// loaded by SIMD when |filter_length| is not divisible by 4.
mask[0] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
// This buffer is used for tails
__m128i buffer;
// mask[0] is not used in following algorithm.
mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
// Output one pixel each iteration, calculating all channels (RGBA) together.
for (int out_x = begin; out_x < end; out_x++) {
for (int out_x = 0; out_x < num_values; out_x++) {
const ConvolutionFilter1D::Fixed* filter_values =
filter.FilterForValue(out_x, &filter_offset, &filter_length);
@@ -117,22 +115,21 @@ void ConvolveHorizontally_SSE2(const unsigned char* src_data,
// When |filter_length| is not divisible by 4, we need to decimate some of
// the filter coefficient that was loaded incorrectly to zero; Other than
// that the algorithm is same with above, except that the 4th pixel will be
// that the algorithm is same with above, exceot that the 4th pixel will be
// always absent.
int r = filter_length & 3;
int r = filter_length&3;
if (r) {
memcpy(&buffer, row_to_filter, r * 4);
// Note: filter_values must be padded to align_up(filter_offset, 8).
__m128i coeff, coeff16;
coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
// Mask out extra filter taps.
coeff = _mm_and_si128(coeff, mask[r-1]);
coeff = _mm_and_si128(coeff, mask[r]);
coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
// Note: line buffer must be padded to align_up(filter_offset, 16).
// We resolve this by temporary buffer
__m128i src8 = _mm_loadu_si128(&buffer);
// We resolve this by use C-version for the last horizontal line.
__m128i src8 = _mm_loadu_si128(row_to_filter);
__m128i src16 = _mm_unpacklo_epi8(src8, zero);
__m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
__m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
@@ -165,24 +162,26 @@ void ConvolveHorizontally_SSE2(const unsigned char* src_data,
}
// Convolves horizontally along four rows. The row data is given in
// |src_data| and continues for the [begin, end) of the filter.
// |src_data| and continues for the num_values() of the filter.
// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please
// refer to that function for detailed comments.
void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4],
int begin, int end,
const ConvolutionFilter1D& filter,
unsigned char* out_row[4]) {
int num_values = filter.num_values();
int filter_offset, filter_length;
__m128i zero = _mm_setzero_si128();
__m128i mask[3];
__m128i mask[4];
// |mask| will be used to decimate all extra filter coefficients that are
// loaded by SIMD when |filter_length| is not divisible by 4.
mask[0] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
// mask[0] is not used in following algorithm.
mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
// Output one pixel each iteration, calculating all channels (RGBA) together.
for (int out_x = begin; out_x < end; out_x++) {
for (int out_x = 0; out_x < num_values; out_x++) {
const ConvolutionFilter1D::Fixed* filter_values =
filter.FilterForValue(out_x, &filter_offset, &filter_length);
@@ -240,7 +239,7 @@ void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4],
__m128i coeff;
coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
// Mask out extra filter taps.
coeff = _mm_and_si128(coeff, mask[r-1]);
coeff = _mm_and_si128(coeff, mask[r]);
__m128i coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
/* c1 c1 c1 c1 c0 c0 c0 c0 */
@@ -284,21 +283,22 @@ void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4],
// Does vertical convolution to produce one output row. The filter values and
// length are given in the first two parameters. These are applied to each
// of the rows pointed to in the |source_data_rows| array, with each row
// being |end - begin| wide.
// being |pixel_width| wide.
//
// The output must have room for |(end - begin) * 4| bytes.
// The output must have room for |pixel_width * 4| bytes.
template<bool has_alpha>
void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_values,
int filter_length,
unsigned char* const* source_data_rows,
int begin, int end,
int pixel_width,
unsigned char* out_row) {
int width = pixel_width & ~3;
__m128i zero = _mm_setzero_si128();
__m128i accum0, accum1, accum2, accum3, coeff16;
const __m128i* src;
int out_x;
// Output four pixels per iteration (16 bytes).
for (out_x = begin; out_x + 3 < end; out_x += 4) {
for (int out_x = 0; out_x < width; out_x += 4) {
// Accumulated result for each pixel. 32 bits per RGBA channel.
accum0 = _mm_setzero_si128();
@@ -391,11 +391,7 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value
// When the width of the output is not divisible by 4, We need to save one
// pixel (4 bytes) each time. And also the fourth pixel is always absent.
int r = end - out_x;
if (r > 0) {
// Since accum3 is never used here, we'll use it as a buffer
__m128i *buffer = &accum3;
if (pixel_width & 3) {
accum0 = _mm_setzero_si128();
accum1 = _mm_setzero_si128();
accum2 = _mm_setzero_si128();
@@ -403,9 +399,8 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value
coeff16 = _mm_set1_epi16(filter_values[filter_y]);
// [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
src = reinterpret_cast<const __m128i*>(
&source_data_rows[filter_y][out_x * 4]);
memcpy(buffer, src, r * 4);
__m128i src8 = _mm_loadu_si128(buffer);
&source_data_rows[filter_y][width<<2]);
__m128i src8 = _mm_loadu_si128(src);
// [16] a1 b1 g1 r1 a0 b0 g0 r0
__m128i src16 = _mm_unpacklo_epi8(src8, zero);
__m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
@@ -451,7 +446,7 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value
accum0 = _mm_or_si128(accum0, mask);
}
for (; out_x < end; out_x++) {
for (int out_x = width; out_x < pixel_width; out_x++) {
*(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum0);
accum0 = _mm_srli_si128(accum0, 4);
out_row += 4;
@@ -462,14 +457,14 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value
void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values,
int filter_length,
unsigned char* const* source_data_rows,
int begin, int end,
int pixel_width,
unsigned char* out_row, bool has_alpha) {
if (has_alpha) {
ConvolveVertically_SSE2_impl<true>(filter_values, filter_length,
source_data_rows, begin, end, out_row);
source_data_rows, pixel_width, out_row);
} else {
ConvolveVertically_SSE2_impl<false>(filter_values, filter_length,
source_data_rows, begin, end, out_row);
source_data_rows, pixel_width, out_row);
}
}
+1 -3
View File
@@ -40,7 +40,6 @@ namespace skia {
// Convolves horizontally along a single row. The row data is given in
// |src_data| and continues for the [begin, end) of the filter.
void ConvolveHorizontally_SSE2(const unsigned char* src_data,
int begin, int end,
const ConvolutionFilter1D& filter,
unsigned char* out_row);
@@ -49,7 +48,6 @@ void ConvolveHorizontally_SSE2(const unsigned char* src_data,
// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please
// refer to that function for detailed comments.
void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4],
int begin, int end,
const ConvolutionFilter1D& filter,
unsigned char* out_row[4]);
@@ -62,7 +60,7 @@ void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4],
void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values,
int filter_length,
unsigned char* const* source_data_rows,
int begin, int end,
int pixel_width,
unsigned char* out_row, bool has_alpha);
} // namespace skia
+2 -2
View File
@@ -23,7 +23,7 @@ class HeapCopyOfStackArray
{
public:
template<size_t N>
HeapCopyOfStackArray(ElemType (&array)[N])
MOZ_IMPLICIT HeapCopyOfStackArray(ElemType (&array)[N])
: mArrayLength(N)
, mArrayData(new ElemType[N])
{
@@ -44,4 +44,4 @@ private:
} // namespace mozilla
#endif // HEAPCOPYOFSTACKARRAY_H_
#endif // HEAPCOPYOFSTACKARRAY_H_
+3 -1
View File
@@ -112,7 +112,9 @@ TextureClientX11::AllocateForSurface(IntSize aSize, TextureAllocationFlags aText
//MOZ_ASSERT(mFormat != gfx::FORMAT_YUV, "This TextureClient cannot use YCbCr data");
MOZ_ASSERT(aSize.width >= 0 && aSize.height >= 0);
if (aSize.width <= 0 || aSize.height <= 0) {
if (aSize.width <= 0 || aSize.height <= 0 ||
aSize.width > XLIB_IMAGE_SIDE_SIZE_LIMIT ||
aSize.height > XLIB_IMAGE_SIDE_SIZE_LIMIT) {
gfxDebug() << "Asking for X11 surface of invalid size " << aSize.width << "x" << aSize.height;
return false;
}
+23 -3
View File
@@ -734,6 +734,16 @@ void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
}
}
static inline SkPoint compute_stroke_size(const SkPaint& paint, const SkMatrix& matrix) {
SkASSERT(matrix.rectStaysRect());
SkASSERT(SkPaint::kFill_Style != paint.getStyle());
SkVector size;
SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() };
matrix.mapVectors(&size, &pt, 1);
return SkPoint::Make(SkScalarAbs(size.fX), SkScalarAbs(size.fY));
}
static bool easy_rect_join(const SkPaint& paint, const SkMatrix& matrix,
SkPoint* strokeSize) {
if (SkPaint::kMiter_Join != paint.getStrokeJoin() ||
@@ -812,12 +822,22 @@ void SkDraw::drawRect(const SkRect& rect, const SkPaint& paint) const {
devRect.sort();
// look for the quick exit, before we build a blitter
SkIRect ir;
devRect.roundOut(&ir);
SkRect bbox = devRect;
if (paint.getStyle() != SkPaint::kFill_Style) {
// extra space for hairlines
ir.inset(-1, -1);
if (paint.getStrokeWidth() == 0) {
bbox.outset(1, 1);
} else {
// For kStroke_RectType, strokeSize is already computed.
const SkPoint& ssize = (kStroke_RectType == rtype)
? strokeSize
: compute_stroke_size(paint, *fMatrix);
bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y()));
}
}
SkIRect ir;
bbox.roundOut(&ir);
if (fRC->quickReject(ir)) {
return;
}
+1 -1
View File
@@ -42,7 +42,7 @@ HRESULT sk_wchar_to_skstring(WCHAR* name, SkString* skname);
void sk_get_locale_string(IDWriteLocalizedStrings* names, const WCHAR* preferedLocale,
SkString* skname);
typedef int (*SkGetUserDefaultLocaleNameProc)(LPWSTR, int);
typedef int (WINAPI *SkGetUserDefaultLocaleNameProc)(LPWSTR, int);
HRESULT SkGetGetUserDefaultLocaleNameProc(SkGetUserDefaultLocaleNameProc* proc);
////////////////////////////////////////////////////////////////////////////////
-6
View File
@@ -499,12 +499,6 @@ gfxASurface::GetSubpixelAntialiasingEnabled()
#endif
}
gfxMemoryLocation
gfxASurface::GetMemoryLocation() const
{
return gfxMemoryLocation::IN_PROCESS_HEAP;
}
int32_t
gfxASurface::BytePerPixelFromFormat(gfxImageFormat format)
{
-6
View File
@@ -159,12 +159,6 @@ public:
// to a sub-class of gfxASurface.)
virtual bool SizeOfIsMeasured() const { return false; }
/**
* Where does this surface's memory live? By default, we say it's in this
* process's heap.
*/
virtual gfxMemoryLocation GetMemoryLocation() const;
static int32_t BytePerPixelFromFormat(gfxImageFormat format);
virtual const mozilla::gfx::IntSize GetSize() const;
-11
View File
@@ -92,15 +92,4 @@ enum class gfxContentType {
SENTINEL = 0xffff
};
/**
* The memory used by a gfxASurface (as reported by KnownMemoryUsed()) can
* either live in this process's heap, in this process but outside the
* heap, or in another process altogether.
*/
enum class gfxMemoryLocation {
IN_PROCESS_HEAP,
IN_PROCESS_NONHEAP,
OUT_OF_PROCESS
};
#endif /* GFX_TYPES_H */
-6
View File
@@ -300,9 +300,3 @@ gfxWindowsSurface::GetSize() const
return mozilla::gfx::IntSize(cairo_win32_surface_get_width(mSurface),
cairo_win32_surface_get_height(mSurface));
}
gfxMemoryLocation
gfxWindowsSurface::GetMemoryLocation() const
{
return gfxMemoryLocation::IN_PROCESS_NONHEAP;
}
-4
View File
@@ -65,10 +65,6 @@ public:
const mozilla::gfx::IntSize GetSize() const;
// The memory used by this surface lives in this process's address space,
// but not in the heap.
virtual gfxMemoryLocation GetMemoryLocation() const;
private:
void MakeInvalid(mozilla::gfx::IntSize& size);
-11
View File
@@ -21,11 +21,6 @@
using namespace mozilla;
// Although the dimension parameters in the xCreatePixmapReq wire protocol are
// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if
// either dimension cannot be represented by a 16-bit *signed* integer.
#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff
gfxXlibSurface::gfxXlibSurface(Display *dpy, Drawable drawable, Visual *visual)
: mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable)
#if defined(GL_PROVIDER_GLX)
@@ -619,9 +614,3 @@ gfxXlibSurface::BindGLXPixmap(GLXPixmap aPixmap)
}
#endif
gfxMemoryLocation
gfxXlibSurface::GetMemoryLocation() const
{
return gfxMemoryLocation::OUT_OF_PROCESS;
}
+6 -4
View File
@@ -17,6 +17,12 @@
#include "nsSize.h"
// Although the dimension parameters in the xCreatePixmapReq wire protocol are
// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if
// either dimension cannot be represented by a 16-bit *signed* integer.
#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff
class gfxXlibSurface final : public gfxASurface {
public:
// construct a wrapper around the specified drawable with dpy/visual.
@@ -79,10 +85,6 @@ public:
// Find a visual and colormap pair suitable for rendering to this surface.
bool GetColormapAndVisual(Colormap* colormap, Visual **visual);
// This surface is a wrapper around X pixmaps, which are stored in the X
// server, not the main application.
virtual gfxMemoryLocation GetMemoryLocation() const override;
#if defined(GL_PROVIDER_GLX)
GLXPixmap GetGLXPixmap();
// Binds a GLXPixmap backed by this context's surface.
+250
View File
@@ -0,0 +1,250 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* CopyOnWrite<T> allows code to safely read from a data structure without
* worrying that reentrant code will modify it.
*/
#ifndef mozilla_image_CopyOnWrite_h
#define mozilla_image_CopyOnWrite_h
#include "mozilla/nsRefPtr.h"
#include "MainThreadUtils.h"
#include "nsISupportsImpl.h"
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Implementation Details
///////////////////////////////////////////////////////////////////////////////
namespace detail {
template <typename T>
class CopyOnWriteValue final
{
public:
NS_INLINE_DECL_REFCOUNTING(CopyOnWriteValue)
explicit CopyOnWriteValue(T* aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(already_AddRefed<T>& aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(already_AddRefed<T>&& aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(const nsRefPtr<T>& aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(nsRefPtr<T>&& aValue) : mValue(aValue) { }
T* get() { return mValue.get(); }
const T* get() const { return mValue.get(); }
bool HasReaders() const { return mReaders > 0; }
bool HasWriter() const { return mWriter; }
bool HasUsers() const { return HasReaders() || HasWriter(); }
void LockForReading() { MOZ_ASSERT(!HasWriter()); mReaders++; }
void UnlockForReading() { MOZ_ASSERT(HasReaders()); mReaders--; }
struct MOZ_STACK_CLASS AutoReadLock
{
explicit AutoReadLock(CopyOnWriteValue* aValue)
: mValue(aValue)
{
mValue->LockForReading();
}
~AutoReadLock() { mValue->UnlockForReading(); }
CopyOnWriteValue<T>* mValue;
};
void LockForWriting() { MOZ_ASSERT(!HasUsers()); mWriter = true; }
void UnlockForWriting() { MOZ_ASSERT(HasWriter()); mWriter = false; }
struct MOZ_STACK_CLASS AutoWriteLock
{
explicit AutoWriteLock(CopyOnWriteValue* aValue)
: mValue(aValue)
{
mValue->LockForWriting();
}
~AutoWriteLock() { mValue->UnlockForWriting(); }
CopyOnWriteValue<T>* mValue;
};
private:
CopyOnWriteValue(const CopyOnWriteValue&) = delete;
CopyOnWriteValue(CopyOnWriteValue&&) = delete;
~CopyOnWriteValue() { }
nsRefPtr<T> mValue;
uint64_t mReaders = 0;
bool mWriter = false;
};
} // namespace detail
///////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////
/**
* CopyOnWrite<T> allows code to safely read from a data structure without
* worrying that reentrant code will modify it. If reentrant code would modify
* the data structure while other code is reading from it, a copy is made so
* that readers can continue to use the old version.
*
* Note that it's legal to nest a writer inside any number of readers, but
* nothing can be nested inside a writer. This is because it's assumed that the
* state of the contained data structure may not be consistent during the write.
*
* This is a main-thread-only data structure.
*
* To work with CopyOnWrite<T>, a type T needs to be reference counted and to
* support copy construction.
*/
template <typename T>
class CopyOnWrite final
{
typedef detail::CopyOnWriteValue<T> CopyOnWriteValue;
public:
explicit CopyOnWrite(T* aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(already_AddRefed<T>& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(already_AddRefed<T>&& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(const nsRefPtr<T>& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(nsRefPtr<T>&& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
/// @return true if it's safe to read at this time.
bool CanRead() const { return !mValue->HasWriter(); }
/**
* Read from the contained data structure using the function @aReader.
* @aReader will be passed a pointer of type |const T*|. It's not legal to
* call this while a writer is active.
*
* @return whatever value @aReader returns, or nothing if @aReader is a void
* function.
*/
template <typename ReadFunc>
auto Read(ReadFunc aReader) const
-> decltype(aReader(static_cast<const T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CanRead());
// Run the provided function while holding a read lock.
nsRefPtr<CopyOnWriteValue> cowValue = mValue;
typename CopyOnWriteValue::AutoReadLock lock(cowValue);
return aReader(cowValue->get());
}
/**
* Read from the contained data structure using the function @aReader.
* @aReader will be passed a pointer of type |const T*|. If it's currently not
* possible to read because a writer is currently active, @aOnError will be
* called instead.
*
* @return whatever value @aReader or @aOnError returns (their return types
* must be consistent), or nothing if the provided functions are void.
*/
template <typename ReadFunc, typename ErrorFunc>
auto Read(ReadFunc aReader, ErrorFunc aOnError) const
-> decltype(aReader(static_cast<const T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
if (!CanRead()) {
return aOnError();
}
return Read(aReader);
}
/// @return true if it's safe to write at this time.
bool CanWrite() const { return !mValue->HasWriter(); }
/**
* Write to the contained data structure using the function @aWriter.
* @aWriter will be passed a pointer of type |T*|. It's not legal to call this
* while another writer is active.
*
* If readers are currently active, they will be able to continue reading from
* a copy of the old version of the data structure. The copy will be destroyed
* when all its readers finish. Later readers and writers will see the
* version of the data structure produced by the most recent call to Write().
*
* @return whatever value @aWriter returns, or nothing if @aWriter is a void
* function.
*/
template <typename WriteFunc>
auto Write(WriteFunc aWriter)
-> decltype(aWriter(static_cast<T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CanWrite());
// If there are readers, we need to copy first.
if (mValue->HasReaders()) {
mValue = new CopyOnWriteValue(new T(*mValue->get()));
}
// Run the provided function while holding a write lock.
nsRefPtr<CopyOnWriteValue> cowValue = mValue;
typename CopyOnWriteValue::AutoWriteLock lock(cowValue);
return aWriter(cowValue->get());
}
/**
* Write to the contained data structure using the function @aWriter.
* @aWriter will be passed a pointer of type |T*|. If it's currently not
* possible to write because a writer is currently active, @aOnError will be
* called instead.
*
* If readers are currently active, they will be able to continue reading from
* a copy of the old version of the data structure. The copy will be destroyed
* when all its readers finish. Later readers and writers will see the
* version of the data structure produced by the most recent call to Write().
*
* @return whatever value @aWriter or @aOnError returns (their return types
* must be consistent), or nothing if the provided functions are void.
*/
template <typename WriteFunc, typename ErrorFunc>
auto Write(WriteFunc aWriter, ErrorFunc aOnError)
-> decltype(aWriter(static_cast<T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
if (!CanWrite()) {
return aOnError();
}
return Write(aWriter);
}
private:
CopyOnWrite(const CopyOnWrite&) = delete;
CopyOnWrite(CopyOnWrite&&) = delete;
nsRefPtr<CopyOnWriteValue> mValue;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_CopyOnWrite_h
+4 -1
View File
@@ -451,7 +451,10 @@ DecodePool::Decode(Decoder* aDecoder)
nsresult rv = aDecoder->Decode();
if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
if (aDecoder->HasProgress()) {
// If this isn't a metadata decode, notify for the progress we've made so
// far. It's important that metadata decode results are delivered
// atomically, so for those decodes we wait until NotifyDecodeComplete.
if (aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()) {
NotifyProgress(aDecoder);
}
// The decoder will ensure that a new worker gets enqueued to continue
+18 -20
View File
@@ -37,10 +37,8 @@ Decoder::Decoder(RasterImage* aImage)
, mMetadataDecode(false)
, mSendPartialInvalidations(false)
, mImageIsTransient(false)
, mImageIsLocked(false)
, mFirstFrameDecode(false)
, mInFrame(false)
, mIsAnimated(false)
, mDataDone(false)
, mDecodeDone(false)
, mDataError(false)
@@ -94,15 +92,18 @@ Decoder::Init()
}
nsresult
Decoder::Decode()
Decoder::Decode(IResumable* aOnResume)
{
MOZ_ASSERT(mInitialized, "Should be initialized here");
MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
// If no IResumable was provided, default to |this|.
IResumable* onResume = aOnResume ? aOnResume : this;
// We keep decoding chunks until the decode completes or there are no more
// chunks available.
while (!GetDecodeDone() && !HasError()) {
auto newState = mIterator->AdvanceOrScheduleResume(this);
auto newState = mIterator->AdvanceOrScheduleResume(onResume);
if (newState == SourceBufferIterator::WAITING) {
// We can't continue because the rest of the data hasn't arrived from the
@@ -237,7 +238,7 @@ Decoder::CompleteDecode()
// If this image wasn't animated and isn't a transient image, mark its frame
// as optimizable. We don't support optimizing animated images and
// optimizing transient images isn't worth it.
if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) {
if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) {
mCurrentFrame->SetOptimizable();
}
}
@@ -260,7 +261,12 @@ Decoder::AllocateFrame(uint32_t aFrameNum,
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
if (aFrameNum + 1 == mFrameCount) {
PostFrameStart();
// If we're past the first frame, PostIsAnimated() should've been called.
MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
// Update our state to reflect the new frame
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
mInFrame = true;
}
} else {
PostDataError();
@@ -406,19 +412,11 @@ Decoder::PostHasTransparency()
}
void
Decoder::PostFrameStart()
Decoder::PostIsAnimated(int32_t aFirstFrameTimeout)
{
// We shouldn't already be mid-frame
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
// Update our state to reflect the new frame
mInFrame = true;
// If we just became animated, record that fact.
if (mFrameCount > 1) {
mIsAnimated = true;
mProgress |= FLAG_IS_ANIMATED;
}
mProgress |= FLAG_IS_ANIMATED;
mImageMetadata.SetHasAnimation();
mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
}
void
@@ -442,7 +440,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
// If we're not sending partial invalidations, then we send an invalidation
// here when the first frame is complete.
if (!mSendPartialInvalidations && !mIsAnimated) {
if (!mSendPartialInvalidations && !HasAnimation()) {
mInvalidRect.UnionRect(mInvalidRect,
gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
}
@@ -459,7 +457,7 @@ Decoder::PostInvalidation(const nsIntRect& aRect,
// Record this invalidation, unless we're not sending partial invalidations
// or we're past the first frame.
if (mSendPartialInvalidations && !mIsAnimated) {
if (mSendPartialInvalidations && !HasAnimation()) {
mInvalidRect.UnionRect(mInvalidRect, aRect);
mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect));
}
+13 -24
View File
@@ -34,13 +34,16 @@ public:
void Init();
/**
* Decodes, reading all data currently available in the SourceBuffer. If more
* data is needed, Decode() automatically ensures that it will be called again
* on a DecodePool thread when the data becomes available.
* Decodes, reading all data currently available in the SourceBuffer.
*
* If more data is needed, Decode() will schedule @aOnResume to be called when
* more data is available. If @aOnResume is null or unspecified, the default
* implementation resumes decoding on a DecodePool thread. Most callers should
* use the default implementation.
*
* Any errors are reported by setting the appropriate state on the decoder.
*/
nsresult Decode();
nsresult Decode(IResumable* aOnResume = nullptr);
/**
* Given a maximum number of bytes we're willing to decode, @aByteLimit,
@@ -181,20 +184,6 @@ public:
mImageIsTransient = aIsTransient;
}
/**
* Set whether the image is locked for the lifetime of this decoder. We lock
* the image during our initial decode to ensure that we don't evict any
* surfaces before we realize that the image is animated.
*/
void SetImageIsLocked()
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mImageIsLocked = true;
}
bool ImageIsLocked() const { return mImageIsLocked; }
/**
* Set whether we should stop decoding after the first frame.
*/
@@ -225,7 +214,7 @@ public:
}
// Did we discover that the image we're decoding is animated?
bool HasAnimation() const { return mIsAnimated; }
bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
// Error tracking
bool HasError() const { return HasDataError() || HasDecoderError(); }
@@ -344,9 +333,11 @@ protected:
// actual contents of the frame and give a more accurate result.
void PostHasTransparency();
// Called by decoders when they begin a frame. Informs the image, sends
// notifications, and does internal book-keeping.
void PostFrameStart();
// Called by decoders if they determine that the image is animated.
//
// @param aTimeout The time for which the first frame should be shown before
// we advance to the next frame.
void PostIsAnimated(int32_t aFirstFrameTimeout);
// Called by decoders when they end a frame. Informs the image, sends
// notifications, and does internal book-keeping.
@@ -451,10 +442,8 @@ private:
bool mMetadataDecode : 1;
bool mSendPartialInvalidations : 1;
bool mImageIsTransient : 1;
bool mImageIsLocked : 1;
bool mFirstFrameDecode : 1;
bool mInFrame : 1;
bool mIsAnimated : 1;
bool mDataDone : 1;
bool mDecodeDone : 1;
bool mDataError : 1;
+60 -6
View File
@@ -138,8 +138,7 @@ DecoderFactory::CreateDecoder(DecoderType aType,
int aSampleSize,
const IntSize& aResolution,
bool aIsRedecode,
bool aImageIsTransient,
bool aImageIsLocked)
bool aImageIsTransient)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
@@ -156,10 +155,7 @@ DecoderFactory::CreateDecoder(DecoderType aType,
decoder->SetResolution(aResolution);
decoder->SetSendPartialInvalidations(!aIsRedecode);
decoder->SetImageIsTransient(aImageIsTransient);
if (aImageIsLocked) {
decoder->SetImageIsLocked();
}
decoder->SetIsFirstFrameDecode();
// Set a target size for downscale-during-decode if applicable.
if (aTargetSize) {
@@ -177,6 +173,39 @@ DecoderFactory::CreateDecoder(DecoderType aType,
return decoder.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateAnimationDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
uint32_t aFlags,
const IntSize& aResolution)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
"Calling CreateAnimationDecoder for non-animating DecoderType");
nsRefPtr<Decoder> decoder =
GetDecoder(aType, aImage, /* aIsRedecode = */ true);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetFlags(aFlags);
decoder->SetResolution(aResolution);
decoder->SetSendPartialInvalidations(false);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
return nullptr;
}
return decoder.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateMetadataDecoder(DecoderType aType,
RasterImage* aImage,
@@ -240,5 +269,30 @@ DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
return decoder.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
SourceBuffer* aSourceBuffer)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
nsRefPtr<Decoder> decoder =
GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(true);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetIsFirstFrameDecode();
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
return nullptr;
}
return decoder.forget();
}
} // namespace image
} // namespace mozilla
+54 -10
View File
@@ -40,12 +40,13 @@ public:
static DecoderType GetDecoderType(const char* aMimeType);
/**
* Creates and initializes a decoder of type @aType. The decoder will send
* notifications to @aImage.
* Creates and initializes a decoder for non-animated images of type @aType.
* (If the image *is* animated, only the first frame will be decoded.) The
* decoder will send notifications to @aImage.
*
* XXX(seth): @aIsRedecode, @aImageIsTransient, and @aImageIsLocked should
* really be part of @aFlags. This requires changes to the way that decoder
* flags work, though. See bug 1185800.
* XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of
* @aFlags. This requires changes to the way that decoder flags work, though.
* See bug 1185800.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
@@ -64,9 +65,6 @@ public:
* empty rect if none).
* @param aIsRedecode Specify 'true' if this image has been decoded before.
* @param aImageIsTransient Specify 'true' if this image is transient.
* @param aImageIsLocked Specify 'true' if this image is locked for the
* lifetime of this decoder, and should be unlocked
* when the decoder finishes.
*/
static already_AddRefed<Decoder>
CreateDecoder(DecoderType aType,
@@ -77,8 +75,28 @@ public:
int aSampleSize,
const gfx::IntSize& aResolution,
bool aIsRedecode,
bool aImageIsTransient,
bool aImageIsLocked);
bool aImageIsTransient);
/**
* Creates and initializes a decoder for animated images of type @aType.
* The decoder will send notifications to @aImage.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
* notifications as decoding progresses.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
* @param aResolution The resolution requested using #-moz-resolution (or an
* empty rect if none).
*/
static already_AddRefed<Decoder>
CreateAnimationDecoder(DecoderType aType,
RasterImage* aImage,
SourceBuffer* aSourceBuffer,
uint32_t aFlags,
const gfx::IntSize& aResolution);
/**
* Creates and initializes a metadata decoder of type @aType. This decoder
@@ -103,11 +121,37 @@ public:
int aSampleSize,
const gfx::IntSize& aResolution);
/**
* Creates and initializes an anonymous decoder (one which isn't associated
* with an Image object). Only the first frame of the image will be decoded.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
*/
static already_AddRefed<Decoder>
CreateAnonymousDecoder(DecoderType aType,
SourceBuffer* aSourceBuffer,
uint32_t aFlags);
/**
* Creates and initializes an anonymous metadata decoder (one which isn't
* associated with an Image object). This decoder will only decode the image's
* header, extracting metadata like the size of the image. No actual image
* data will be decoded and no surfaces will be allocated.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aFlags Flags specifying what type of output the decoder should
* produce; see GetDecodeFlags() in RasterImage.h.
*/
static already_AddRefed<Decoder>
CreateAnonymousMetadataDecoder(DecoderType aType,
SourceBuffer* aSourceBuffer);
private:
virtual ~DecoderFactory() = 0;
+4 -2
View File
@@ -91,8 +91,9 @@ Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
mYFilter.get());
// Allocate the buffer, which contains scanlines of the original image.
// pad by 15 to handle overreads by the simd code
size_t bufferLen = mOriginalSize.width * sizeof(uint32_t);
mRowBuffer = MakeUnique<uint8_t[]>(bufferLen);
mRowBuffer = MakeUnique<uint8_t[]>(mOriginalSize.width * sizeof(uint32_t) + 15);
if (MOZ_UNLIKELY(!mRowBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
@@ -111,7 +112,8 @@ Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
}
bool anyAllocationFailed = false;
const int rowSize = mTargetSize.width * sizeof(uint32_t);
// pad by 15 to handle overreads by the simd code
const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15;
for (int32_t i = 0; i < mWindowCapacity; ++i) {
mWindow[i] = new uint8_t[rowSize];
anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
+16 -10
View File
@@ -291,14 +291,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
int32_t
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
{
int32_t rawTimeout = 0;
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
if (!frame) {
if (frame) {
AnimationData data = frame->GetAnimationData();
rawTimeout = data.mRawTimeout;
} else if (aFrameNum == 0) {
rawTimeout = mFirstFrameTimeout;
} else {
NS_WARNING("No frame; called GetTimeoutForFrame too early?");
return 100;
}
AnimationData data = frame->GetAnimationData();
// Ensure a minimal time between updates so we don't throttle the UI thread.
// consider 0 == unspecified and make it fast but not too fast. Unless we
// have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
@@ -312,11 +317,11 @@ FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
// It seems that there are broken tools out there that set a 0ms or 10ms
// timeout when they really want a "default" one. So munge values in that
// range.
if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10 && mLoopCount != 0) {
if (rawTimeout >= 0 && rawTimeout <= 10) {
return 100;
}
return data.mRawTimeout;
return rawTimeout;
}
static void
@@ -334,12 +339,9 @@ DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
// Extract the surface's memory usage information.
size_t heap = aSurface
->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_HEAP, aMallocSizeOf);
size_t heap = 0, nonHeap = 0;
aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap);
counter.Values().SetDecodedHeap(heap);
size_t nonHeap = aSurface
->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr);
counter.Values().SetDecodedNonHeap(nonHeap);
// Record it.
@@ -436,6 +438,7 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
// Calculate area that needs updating
switch (prevFrameData.mDisposalMethod) {
default:
MOZ_ASSERT_UNREACHABLE("Unexpected DisposalMethod");
case DisposalMethod::NOT_SPECIFIED:
case DisposalMethod::KEEP:
*aDirtyRect = nextFrameData.mRect;
@@ -569,6 +572,9 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
break;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected DisposalMethod");
case DisposalMethod::NOT_SPECIFIED:
case DisposalMethod::KEEP:
// Copy previous frame into compositingFrame before we put the new
// frame on top
// Assumes that the previous frame represents a full frame (it could be
+10
View File
@@ -33,6 +33,7 @@ public:
, mLoopRemainingCount(-1)
, mLastCompositedFrameIndex(-1)
, mLoopCount(-1)
, mFirstFrameTimeout(0)
, mAnimationMode(aAnimationMode)
, mDoneDecoding(false)
{ }
@@ -148,6 +149,12 @@ public:
void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
int32_t LoopCount() const { return mLoopCount; }
/*
* Set the timeout for the first frame. This is used to allow animation
* scheduling even before a full decode runs for this image.
*/
void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; }
/**
* Collect an accounting of the memory occupied by the compositing surfaces we
* use during animation playback. All of the actual animation frames are
@@ -277,6 +284,9 @@ private: // data
//! The total number of loops for the image.
int32_t mLoopCount;
//! The timeout for the first frame of this image.
int32_t mFirstFrameTimeout;
//! The animation mode of this image. Constants defined in imgIContainer.
uint16_t mAnimationMode;
+1 -1
View File
@@ -8,7 +8,7 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/TimeStamp.h"
#include "gfx2DGlue.h" // for gfxMemoryLocation
#include "gfx2DGlue.h"
#include "imgIContainer.h"
#include "ImageURL.h"
#include "nsStringFwd.h"
-44
View File
@@ -1,44 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ImageMetadata.h"
#include "RasterImage.h"
#include "nsComponentManagerUtils.h"
#include "nsISupportsPrimitives.h"
#include "nsXPCOMCID.h"
namespace mozilla {
namespace image {
nsresult
ImageMetadata::SetOnImage(RasterImage* aImage)
{
nsresult rv = NS_OK;
if (mHotspotX != -1 && mHotspotY != -1) {
nsCOMPtr<nsISupportsPRUint32> intwrapx =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
nsCOMPtr<nsISupportsPRUint32> intwrapy =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
intwrapx->SetData(mHotspotX);
intwrapy->SetData(mHotspotY);
aImage->Set("hotspotX", intwrapx);
aImage->Set("hotspotY", intwrapy);
}
aImage->SetLoopCount(mLoopCount);
if (HasSize()) {
MOZ_ASSERT(HasOrientation(), "Should have orientation");
rv = aImage->SetSize(GetWidth(), GetHeight(), GetOrientation());
}
return rv;
}
} // namespace image
} // namespace mozilla
+24 -18
View File
@@ -22,23 +22,26 @@ class ImageMetadata
{
public:
ImageMetadata()
: mHotspotX(-1)
, mHotspotY(-1)
, mLoopCount(-1)
: mLoopCount(-1)
, mFirstFrameTimeout(0)
, mHasAnimation(false)
{ }
// Set the metadata this object represents on an image.
nsresult SetOnImage(RasterImage* aImage);
void SetHotspot(uint16_t hotspotx, uint16_t hotspoty)
void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY)
{
mHotspotX = hotspotx;
mHotspotY = hotspoty;
mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY));
}
gfx::IntPoint GetHotspot() const { return *mHotspot; }
bool HasHotspot() const { return mHotspot.isSome(); }
void SetLoopCount(int32_t loopcount)
{
mLoopCount = loopcount;
}
int32_t GetLoopCount() const { return mLoopCount; }
void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; }
int32_t GetFirstFrameTimeout() const { return mFirstFrameTimeout; }
void SetSize(int32_t width, int32_t height, Orientation orientation)
{
@@ -47,25 +50,28 @@ public:
mOrientation.emplace(orientation);
}
}
nsIntSize GetSize() const { return *mSize; }
Orientation GetOrientation() const { return *mOrientation; }
bool HasSize() const { return mSize.isSome(); }
bool HasOrientation() const { return mOrientation.isSome(); }
int32_t GetWidth() const { return mSize->width; }
int32_t GetHeight() const { return mSize->height; }
nsIntSize GetSize() const { return *mSize; }
Orientation GetOrientation() const { return *mOrientation; }
void SetHasAnimation() { mHasAnimation = true; }
bool HasAnimation() const { return mHasAnimation; }
private:
// The hotspot found on cursors, or -1 if none was found.
int32_t mHotspotX;
int32_t mHotspotY;
/// The hotspot found on cursors, if present.
Maybe<gfx::IntPoint> mHotspot;
// The loop count for animated images, or -1 for infinite loop.
/// The loop count for animated images, or -1 for infinite loop.
int32_t mLoopCount;
/// The timeout of an animated image's first frame.
int32_t mFirstFrameTimeout;
Maybe<nsIntSize> mSize;
Maybe<Orientation> mOrientation;
bool mHasAnimation : 1;
};
} // namespace image
+114 -62
View File
@@ -243,33 +243,76 @@ ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
NS_DispatchToCurrentThread(ev);
}
#define NOTIFY_IMAGE_OBSERVERS(OBSERVERS, FUNC) \
do { \
ObserverArray::ForwardIterator iter(OBSERVERS); \
while (iter.HasMore()) { \
nsRefPtr<IProgressObserver> observer = iter.GetNext().get(); \
if (observer && !observer->NotificationsDeferred()) { \
observer->FUNC; \
} \
} \
} while (false);
/**
* ImageObserverNotifier is a helper type that abstracts over the difference
* between sending notifications to all of the observers in an ObserverTable,
* and sending them to a single observer. This allows the same notification code
* to be used for both cases.
*/
template <typename T> struct ImageObserverNotifier;
/* static */ void
ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers,
bool aHasImage,
Progress aProgress,
const nsIntRect& aDirtyRect)
template <>
struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*>
{
explicit ImageObserverNotifier(const ObserverTable* aObservers,
bool aIgnoreDeferral = false)
: mObservers(aObservers)
, mIgnoreDeferral(aIgnoreDeferral)
{ }
template <typename Lambda>
void operator()(Lambda aFunc)
{
for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) {
nsRefPtr<IProgressObserver> observer = iter.Data().get();
if (observer &&
(mIgnoreDeferral || !observer->NotificationsDeferred())) {
aFunc(observer);
}
}
}
private:
const ObserverTable* mObservers;
const bool mIgnoreDeferral;
};
template <>
struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*>
{
explicit ImageObserverNotifier(IProgressObserver* aObserver)
: mObserver(aObserver)
{ }
template <typename Lambda>
void operator()(Lambda aFunc)
{
if (mObserver && !mObserver->NotificationsDeferred()) {
aFunc(mObserver);
}
}
private:
IProgressObserver* mObserver;
};
template <typename T> void
SyncNotifyInternal(const T& aObservers,
bool aHasImage,
Progress aProgress,
const nsIntRect& aDirtyRect)
{
MOZ_ASSERT(NS_IsMainThread());
typedef imgINotificationObserver I;
ImageObserverNotifier<T> notify(aObservers);
if (aProgress & FLAG_SIZE_AVAILABLE) {
NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::SIZE_AVAILABLE));
notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
}
if (aProgress & FLAG_ONLOAD_BLOCKED) {
NOTIFY_IMAGE_OBSERVERS(aObservers, BlockOnload());
notify([](IProgressObserver* aObs) { aObs->BlockOnload(); });
}
if (aHasImage) {
@@ -278,19 +321,21 @@ ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers,
// vector images, true for raster images that have decoded at
// least one frame) then send OnFrameUpdate.
if (!aDirtyRect.IsEmpty()) {
NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::FRAME_UPDATE, &aDirtyRect));
notify([&](IProgressObserver* aObs) {
aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
});
}
if (aProgress & FLAG_FRAME_COMPLETE) {
NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::FRAME_COMPLETE));
notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
}
if (aProgress & FLAG_HAS_TRANSPARENCY) {
NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::HAS_TRANSPARENCY));
notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
}
if (aProgress & FLAG_IS_ANIMATED) {
NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::IS_ANIMATED));
notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
}
}
@@ -298,17 +343,18 @@ ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers,
// observers that can fire events when they receive those notifications to do
// so then, instead of being forced to wait for UnblockOnload.
if (aProgress & FLAG_ONLOAD_UNBLOCKED) {
NOTIFY_IMAGE_OBSERVERS(aObservers, UnblockOnload());
notify([](IProgressObserver* aObs) { aObs->UnblockOnload(); });
}
if (aProgress & FLAG_DECODE_COMPLETE) {
MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::DECODE_COMPLETE));
notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
}
if (aProgress & FLAG_LOAD_COMPLETE) {
NOTIFY_IMAGE_OBSERVERS(aObservers,
OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE));
notify([=](IProgressObserver* aObs) {
aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
});
}
}
@@ -340,7 +386,9 @@ ProgressTracker::SyncNotifyProgress(Progress aProgress,
CheckProgressConsistency(mProgress);
// Send notifications.
SyncNotifyInternal(mObservers, HasImage(), progress, aInvalidRect);
mObservers.Read([&](const ObserverTable* aTable) {
SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
});
if (progress & FLAG_HAS_ERROR) {
FireFailureNotification();
@@ -370,9 +418,7 @@ ProgressTracker::SyncNotify(IProgressObserver* aObserver)
}
}
ObserverArray array;
array.AppendElement(aObserver);
SyncNotifyInternal(array, !!image, mProgress, rect);
SyncNotifyInternal(aObserver, !!image, mProgress, rect);
}
void
@@ -395,7 +441,14 @@ void
ProgressTracker::AddObserver(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
mObservers.AppendElementUnlessExists(aObserver);
mObservers.Write([=](ObserverTable* aTable) {
MOZ_ASSERT(!aTable->Get(aObserver, nullptr),
"Adding duplicate entry for image observer");
WeakPtr<IProgressObserver> weakPtr = aObserver;
aTable->Put(aObserver, weakPtr);
});
}
bool
@@ -404,7 +457,11 @@ ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
MOZ_ASSERT(NS_IsMainThread());
// Remove the observer from the list.
bool removed = mObservers.RemoveElement(aObserver);
bool removed = mObservers.Write([=](ObserverTable* aTable) {
bool removed = aTable->Get(aObserver, nullptr);
aTable->Remove(aObserver);
return removed;
});
// Observers can get confused if they don't get all the proper teardown
// notifications. Part ways on good terms.
@@ -425,26 +482,25 @@ ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
return removed;
}
bool
ProgressTracker::FirstObserverIs(IProgressObserver* aObserver)
uint32_t
ProgressTracker::ObserverCount() const
{
MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
ObserverArray::ForwardIterator iter(mObservers);
while (iter.HasMore()) {
nsRefPtr<IProgressObserver> observer = iter.GetNext().get();
if (observer) {
return observer.get() == aObserver;
}
}
return false;
MOZ_ASSERT(NS_IsMainThread());
return mObservers.Read([](const ObserverTable* aTable) {
return aTable->Count();
});
}
void
ProgressTracker::OnUnlockedDraw()
{
MOZ_ASSERT(NS_IsMainThread());
NOTIFY_IMAGE_OBSERVERS(mObservers,
Notify(imgINotificationObserver::UNLOCKED_DRAW));
mObservers.Read([](const ObserverTable* aTable) {
ImageObserverNotifier<const ObserverTable*> notify(aTable);
notify([](IProgressObserver* aObs) {
aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
});
});
}
void
@@ -459,30 +515,26 @@ void
ProgressTracker::OnDiscard()
{
MOZ_ASSERT(NS_IsMainThread());
NOTIFY_IMAGE_OBSERVERS(mObservers,
Notify(imgINotificationObserver::DISCARD));
mObservers.Read([](const ObserverTable* aTable) {
ImageObserverNotifier<const ObserverTable*> notify(aTable);
notify([](IProgressObserver* aObs) {
aObs->Notify(imgINotificationObserver::DISCARD);
});
});
}
void
ProgressTracker::OnImageAvailable()
{
if (!NS_IsMainThread()) {
// Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
// so subsequent calls or dispatches which Unlock or Decrement~ should
// be issued after this to avoid race conditions.
NS_DispatchToMainThread(
NS_NewRunnableMethod(this, &ProgressTracker::OnImageAvailable));
return;
}
MOZ_ASSERT(NS_IsMainThread());
// Notify any imgRequestProxys that are observing us that we have an Image.
ObserverArray::ForwardIterator iter(mObservers);
while (iter.HasMore()) {
nsRefPtr<IProgressObserver> observer = iter.GetNext().get();
if (observer) {
observer->SetHasImage();
}
}
mObservers.Read([](const ObserverTable* aTable) {
ImageObserverNotifier<const ObserverTable*>
notify(aTable, /* aIgnoreDeferral = */ true);
notify([](IProgressObserver* aObs) {
aObs->SetHasImage();
});
});
}
void
+40 -21
View File
@@ -7,9 +7,11 @@
#ifndef mozilla_image_ProgressTracker_h
#define mozilla_image_ProgressTracker_h
#include "CopyOnWrite.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/WeakPtr.h"
#include "nsDataHashtable.h"
#include "nsCOMPtr.h"
#include "nsTObserverArray.h"
#include "nsThreadUtils.h"
@@ -57,6 +59,37 @@ inline Progress LoadCompleteProgress(bool aLastPart,
return progress;
}
/**
* ProgressTracker stores its observers in an ObserverTable, which is a hash
* table mapping raw pointers to WeakPtr's to the same objects. This sounds like
* unnecessary duplication of information, but it's necessary for stable hash
* values since WeakPtr's lose the knowledge of which object they used to point
* to when that object is destroyed.
*
* ObserverTable subclasses nsDataHashtable to add reference counting support
* and a copy constructor, both of which are needed for use with CopyOnWrite<T>.
*/
class ObserverTable
: public nsDataHashtable<nsPtrHashKey<IProgressObserver>,
WeakPtr<IProgressObserver>>
{
public:
NS_INLINE_DECL_REFCOUNTING(ObserverTable);
ObserverTable() = default;
ObserverTable(const ObserverTable& aOther)
{
NS_WARNING("Forced to copy ObserverTable due to nested notifications");
for (auto iter = aOther.ConstIter(); !iter.Done(); iter.Next()) {
this->Put(iter.Key(), iter.Data());
}
}
private:
~ObserverTable() { }
};
/**
* ProgressTracker is a class that records an Image's progress through the
* loading and decoding process, and makes it possible to send notifications to
@@ -78,6 +111,7 @@ public:
ProgressTracker()
: mImageMutex("ProgressTracker::mImage")
, mImage(nullptr)
, mObservers(new ObserverTable)
, mProgress(NoProgress)
{ }
@@ -126,7 +160,7 @@ public:
void ResetForNewRequest();
// Stateless notifications. These are dispatched and immediately forgotten
// about. All except OnImageAvailable are main thread only.
// about. All of these notifications are main thread only.
void OnDiscard();
void OnUnlockedDraw();
void OnImageAvailable();
@@ -150,22 +184,13 @@ public:
// with its loading progress. Weak pointers.
void AddObserver(IProgressObserver* aObserver);
bool RemoveObserver(IProgressObserver* aObserver);
size_t ObserverCount() const {
MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
return mObservers.Length();
}
// This is intentionally non-general because its sole purpose is to support
// some obscure network priority logic in imgRequest. That stuff could
// probably be improved, but it's too scary to mess with at the moment.
bool FirstObserverIs(IProgressObserver* aObserver);
uint32_t ObserverCount() const;
// Resets our weak reference to our image. Image subclasses should call this
// in their destructor.
void ResetImage();
private:
typedef nsTObserverArray<mozilla::WeakPtr<IProgressObserver>> ObserverArray;
friend class AsyncNotifyRunnable;
friend class AsyncNotifyCurrentStateRunnable;
friend class ImageFactory;
@@ -183,12 +208,7 @@ private:
// Main thread only because it deals with the observer service.
void FireFailureNotification();
// Main thread only, since notifications are expected on the main thread, and
// mObservers is not threadsafe.
static void SyncNotifyInternal(ObserverArray& aObservers,
bool aHasImage, Progress aProgress,
const nsIntRect& aInvalidRect);
// The runnable, if any, that we've scheduled to deliver async notifications.
nsCOMPtr<nsIRunnable> mRunnable;
// mImage is a weak ref; it should be set to null when the image goes out of
@@ -196,10 +216,9 @@ private:
mutable Mutex mImageMutex;
Image* mImage;
// List of observers attached to the image. Each observer represents a
// consumer using the image. Array and/or individual elements should only be
// accessed on the main thread.
ObserverArray mObservers;
// Hashtable of observers attached to the image. Each observer represents a
// consumer using the image. Main thread only.
CopyOnWrite<ObserverTable> mObservers;
Progress mProgress;
};
+85 -92
View File
@@ -24,6 +24,7 @@
#include "nsIConsoleService.h"
#include "nsIInputStream.h"
#include "nsIScriptError.h"
#include "nsISupportsPrimitives.h"
#include "nsPresContext.h"
#include "SourceBuffer.h"
#include "SurfaceCache.h"
@@ -487,7 +488,7 @@ RasterImage::LookupFrame(uint32_t aFrameNum,
// We don't have a copy of this frame, and there's no decoder working on
// one. (Or we're sync decoding and the existing decoder hasn't even started
// yet.) Trigger decoding so it'll be available next time.
MOZ_ASSERT(!mAnim, "Animated frames should be locked");
MOZ_ASSERT(!mAnim || GetNumFrames() < 1, "Animated frames should be locked");
Decode(requestedSize, aFlags);
@@ -538,7 +539,7 @@ RasterImage::GetRequestedFrameIndex(uint32_t aWhichFrame) const
IntRect
RasterImage::GetFirstFrameRect()
{
if (mAnim) {
if (mAnim && mHasBeenDecoded) {
return mAnim->GetFirstFrameRefreshArea();
}
@@ -591,7 +592,10 @@ RasterImage::GetAnimated(bool* aAnimated)
}
// Otherwise, we need to have been decoded to know for sure, since if we were
// decoded at least once mAnim would have been created for animated images
// decoded at least once mAnim would have been created for animated images.
// This is true even though we check for animation during the metadata decode,
// because we may still discover animation only during the full decode for
// corrupt images.
if (!mHasBeenDecoded) {
return NS_ERROR_NOT_AVAILABLE;
}
@@ -923,21 +927,9 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
mFrameCount = aNewFrameCount;
if (aNewFrameCount == 2) {
// We're becoming animated, so initialize animation stuff.
MOZ_ASSERT(!mAnim, "Already have animation state?");
mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
//
// Note that this is inefficient, since we could get rid of the source
// data too. However, doing this is actually hard, because we're probably
// mid-decode, and thus we're decoding out of the source buffer. Since
// we're going to fix this anyway later, and since we didn't kill the
// source data in the old world either, locking is acceptable for the
// moment.
LockImage();
MOZ_ASSERT(mAnim, "Should already have animation state");
// We may be able to start animating.
if (mPendingAnimation && ShouldAnimate()) {
StartAnimation();
}
@@ -949,7 +941,8 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
}
nsresult
RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
RasterImage::SetMetadata(const ImageMetadata& aMetadata,
bool aFromMetadataDecode)
{
MOZ_ASSERT(NS_IsMainThread());
@@ -957,26 +950,64 @@ RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
return NS_ERROR_FAILURE;
}
// Ensure that we have positive values
// XXX - Why isn't the size unsigned? Should this be changed?
if ((aWidth < 0) || (aHeight < 0)) {
return NS_ERROR_INVALID_ARG;
if (aMetadata.HasSize()) {
IntSize size = aMetadata.GetSize();
if (size.width < 0 || size.height < 0) {
return NS_ERROR_INVALID_ARG;
}
MOZ_ASSERT(aMetadata.HasOrientation());
Orientation orientation = aMetadata.GetOrientation();
// If we already have a size, check the new size against the old one.
if (mHasSize && (size != mSize || orientation != mOrientation)) {
NS_WARNING("Image changed size or orientation on redecode! "
"This should not happen!");
DoError();
return NS_ERROR_UNEXPECTED;
}
// Set the size and flag that we have it.
mSize = size;
mOrientation = orientation;
mHasSize = true;
}
// if we already have a size, check the new size against the old one
if (mHasSize &&
((aWidth != mSize.width) ||
(aHeight != mSize.height) ||
(aOrientation != mOrientation))) {
NS_WARNING("Image changed size on redecode! This should not happen!");
DoError();
return NS_ERROR_UNEXPECTED;
if (mHasSize && aMetadata.HasAnimation() && !mAnim) {
// We're becoming animated, so initialize animation stuff.
mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
LockImage();
if (!aFromMetadataDecode) {
// The metadata decode reported that this image isn't animated, but we
// discovered that it actually was during the full decode. This is a
// rare failure that only occurs for corrupt images. To recover, we need
// to discard all existing surfaces and redecode.
RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT);
}
}
// Set the size and flag that we have it
mSize.SizeTo(aWidth, aHeight);
mOrientation = aOrientation;
mHasSize = true;
if (mAnim) {
mAnim->SetLoopCount(aMetadata.GetLoopCount());
mAnim->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout());
}
if (aMetadata.HasHotspot()) {
IntPoint hotspot = aMetadata.GetHotspot();
nsCOMPtr<nsISupportsPRUint32> intwrapx =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
nsCOMPtr<nsISupportsPRUint32> intwrapy =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
intwrapx->SetData(hotspot.x);
intwrapy->SetData(hotspot.y);
Set("hotspotX", intwrapx);
Set("hotspotY", intwrapy);
}
return NS_OK;
}
@@ -1001,10 +1032,9 @@ RasterImage::StartAnimation()
MOZ_ASSERT(ShouldAnimate(), "Should not animate!");
// If we don't have mAnim yet, then we're not ready to animate. Setting
// mPendingAnimation will cause us to start animating as soon as we have a
// second frame, which causes mAnim to be constructed.
mPendingAnimation = !mAnim;
// If we're not ready to animate, then set mPendingAnimation, which will cause
// us to start animating if and when we do become ready.
mPendingAnimation = !mAnim || GetNumFrames() < 2;
if (mPendingAnimation) {
return NS_OK;
}
@@ -1093,19 +1123,6 @@ RasterImage::GetFrameIndex(uint32_t aWhichFrame)
: mAnim->GetCurrentAnimationFrameIndex();
}
void
RasterImage::SetLoopCount(int32_t aLoopCount)
{
if (mError) {
return;
}
// No need to set this if we're not an animation.
if (mAnim) {
mAnim->SetLoopCount(aLoopCount);
}
}
NS_IMETHODIMP_(IntRect)
RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect)
{
@@ -1393,20 +1410,19 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags)
Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing();
bool imageIsLocked = false;
if (!mHasBeenDecoded) {
// Lock the image while we're decoding, so that it doesn't get evicted from
// the SurfaceCache before we have a chance to realize that it's animated.
// The corresponding unlock happens in FinalizeDecoder.
LockImage();
imageIsLocked = true;
}
// Create a decoder.
nsRefPtr<Decoder> decoder =
DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, targetSize,
aFlags, mRequestedSampleSize, mRequestedResolution,
mHasBeenDecoded, mTransient, imageIsLocked);
nsRefPtr<Decoder> decoder;
if (mAnim) {
decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this,
mSourceBuffer, aFlags,
mRequestedResolution);
} else {
decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer,
targetSize, aFlags,
mRequestedSampleSize,
mRequestedResolution,
mHasBeenDecoded, mTransient);
}
// Make sure DecoderFactory was able to create a decoder successfully.
if (!decoder) {
@@ -1556,6 +1572,7 @@ RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags)
// image, we have all the source data and know our size, the flags allow us to
// do it, and a 'good' filter is being used.
if (!mDownscaleDuringDecode || !mHasSize ||
!gfxPrefs::ImageHQDownscalingEnabled() ||
!(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) {
return false;
}
@@ -1931,7 +1948,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
}
// Record all the metadata the decoder gathered about this image.
nsresult rv = aDecoder->GetImageMetadata().SetOnImage(this);
nsresult rv = SetMetadata(aDecoder->GetImageMetadata(),
aDecoder->IsMetadataDecode());
if (NS_FAILED(rv)) {
aDecoder->PostResizeError();
}
@@ -1942,17 +1960,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
if (aDecoder->GetDecodeTotallyDone() && !mError) {
// Flag that we've been decoded before.
mHasBeenDecoded = true;
if (aDecoder->HasAnimation()) {
if (mAnim) {
mAnim->SetDoneDecoding(true);
} else {
// The OnAddedFrame event that will create mAnim is still in the event
// queue. Wait for it.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded);
NS_DispatchToMainThread(runnable);
}
if (mAnim) {
mAnim->SetDoneDecoding(true);
}
}
@@ -1985,11 +1994,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
}
}
if (aDecoder->ImageIsLocked()) {
// Unlock the image, balancing the LockImage call we made in CreateDecoder.
UnlockImage();
}
// If we were a metadata decode and a full decode was requested, do it.
if (done && wasMetadata && mWantFullDecode) {
mWantFullDecode = false;
@@ -1997,17 +2001,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder)
}
}
void
RasterImage::MarkAnimationDecoded()
{
MOZ_ASSERT(mAnim, "Should have an animation now");
if (!mAnim) {
return;
}
mAnim->SetDoneDecoding(true);
}
void
RasterImage::ReportDecoderError(Decoder* aDecoder)
{
+15 -18
View File
@@ -132,6 +132,7 @@ namespace image {
class Decoder;
class FrameAnimator;
class ImageMetadata;
class SourceBuffer;
/**
@@ -188,18 +189,6 @@ public:
void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea);
/** Sets the size and inherent orientation of the container. This should only
* be called by the decoder. This function may be called multiple times, but
* will throw an error if subsequent calls do not match the first.
*/
nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
/**
* Number of times to loop the image.
* @note -1 means forever.
*/
void SetLoopCount(int32_t aLoopCount);
/**
* Sends the provided progress notifications to ProgressTracker.
*
@@ -222,8 +211,7 @@ public:
*/
void FinalizeDecoder(Decoder* aDecoder);
// Helper methods for FinalizeDecoder.
void MarkAnimationDecoded();
// Helper method for FinalizeDecoder.
void ReportDecoderError(Decoder* aDecoder);
@@ -301,10 +289,6 @@ private:
nsIntRect GetFirstFrameRect();
size_t
SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
MallocSizeOf aMallocSizeOf) const;
Pair<DrawResult, nsRefPtr<layers::Image>>
GetCurrentImage(layers::ImageContainer* aContainer, uint32_t aFlags);
@@ -343,6 +327,19 @@ private:
*/
NS_IMETHOD DecodeMetadata(uint32_t aFlags);
/**
* Sets the size, inherent orientation, animation metadata, and other
* information about the image gathered during decoding.
*
* This function may be called multiple times, but will throw an error if
* subsequent calls do not match the first.
*
* @param aMetadata The metadata to set on this image.
* @param aFromMetadataDecode True if this metadata came from a metadata
* decode; false if it came from a full decode.
*/
nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode);
/**
* In catastrophic circumstances like a GPU driver crash, we may lose our
* frames even if they're locked. RecoverFromLossOfFrames discards all
-4
View File
@@ -213,10 +213,6 @@ SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
MOZ_ASSERT(!mStatus, "Waiting when we're complete?");
if (MOZ_UNLIKELY(NS_IsMainThread())) {
NS_WARNING("SourceBuffer consumer on the main thread needed to wait");
}
mWaitingConsumers.AppendElement(aConsumer);
}
+355
View File
@@ -0,0 +1,355 @@
/* -*- 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/. */
/**
* StreamingLexer is a lexing framework designed to make it simple to write
* image decoders without worrying about the details of how the data is arriving
* from the network.
*/
#ifndef mozilla_image_StreamingLexer_h
#define mozilla_image_StreamingLexer_h
#include <algorithm>
#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"
#include "mozilla/Vector.h"
namespace mozilla {
namespace image {
/// Buffering behaviors for StreamingLexer transitions.
enum class BufferingStrategy
{
BUFFERED, // Data will be buffered and processed in one chunk.
UNBUFFERED // Data will be processed as it arrives, in multiple chunks.
};
/// @return true if @aState is a terminal state.
template <typename State>
bool IsTerminalState(State aState)
{
return aState == State::SUCCESS ||
aState == State::FAILURE;
}
/**
* LexerTransition is a type used to give commands to the lexing framework.
* Code that uses StreamingLexer can create LexerTransition values using the
* static methods on Transition, and then return them to the lexing framework
* for execution.
*/
template <typename State>
class LexerTransition
{
public:
State NextState() const { return mNextState; }
State UnbufferedState() const { return *mUnbufferedState; }
size_t Size() const { return mSize; }
BufferingStrategy Buffering() const { return mBufferingStrategy; }
private:
friend struct Transition;
LexerTransition(const State& aNextState,
const Maybe<State>& aUnbufferedState,
size_t aSize,
BufferingStrategy aBufferingStrategy)
: mNextState(aNextState)
, mUnbufferedState(aUnbufferedState)
, mSize(aSize)
, mBufferingStrategy(aBufferingStrategy)
{
MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
mUnbufferedState);
MOZ_ASSERT_IF(mUnbufferedState,
mBufferingStrategy == BufferingStrategy::UNBUFFERED);
}
State mNextState;
Maybe<State> mUnbufferedState;
size_t mSize;
BufferingStrategy mBufferingStrategy;
};
struct Transition
{
/// Transition to @aNextState, buffering @aSize bytes of data.
template <typename State>
static LexerTransition<State>
To(const State& aNextState, size_t aSize)
{
MOZ_ASSERT(!IsTerminalState(aNextState));
return LexerTransition<State>(aNextState, Nothing(), aSize,
BufferingStrategy::BUFFERED);
}
/**
* Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of
* data unbuffered.
*
* The unbuffered data will be delivered in state @aUnbufferedState, which may
* be invoked repeatedly until all @aSize bytes have been delivered. Then,
* @aNextState will be invoked with no data. No state transitions are allowed
* from @aUnbufferedState except for transitions to a terminal state, so
* @aNextState will always be reached unless lexing terminates early.
*/
template <typename State>
static LexerTransition<State>
ToUnbuffered(const State& aNextState,
const State& aUnbufferedState,
size_t aSize)
{
MOZ_ASSERT(!IsTerminalState(aNextState));
MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize,
BufferingStrategy::UNBUFFERED);
}
/**
* Continue receiving unbuffered data. @aUnbufferedState should be the same
* state as the @aUnbufferedState specified in the preceding call to
* ToUnbuffered().
*
* This should be used during an unbuffered read initiated by ToUnbuffered().
*/
template <typename State>
static LexerTransition<State>
ContinueUnbuffered(const State& aUnbufferedState)
{
MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
return LexerTransition<State>(aUnbufferedState, Nothing(), 0,
BufferingStrategy::BUFFERED);
}
/**
* Terminate lexing, ending up in terminal state @aFinalState.
*
* No more data will be delivered after Terminate() is used.
*/
template <typename State>
static LexerTransition<State>
Terminate(const State& aFinalState)
{
MOZ_ASSERT(IsTerminalState(aFinalState));
return LexerTransition<State>(aFinalState, Nothing(), 0,
BufferingStrategy::BUFFERED);
}
private:
Transition();
};
/**
* StreamingLexer is a lexing framework designed to make it simple to write
* image decoders without worrying about the details of how the data is arriving
* from the network.
*
* To use StreamingLexer:
*
* - Create a State type. This should be an |enum class| listing all of the
* states that you can be in while lexing the image format you're trying to
* read. It must contain the two terminal states SUCCESS and FAILURE.
*
* - Add an instance of StreamingLexer<State> to your decoder class. Initialize
* it with a Transition::To() the state that you want to start lexing in.
*
* - In your decoder's WriteInternal method(), call Lex(), passing in the input
* data and length that are passed to WriteInternal(). You also need to pass
* a lambda which dispatches to lexing code for each state based on the State
* value that's passed in. The lambda generally should just continue a
* |switch| statement that calls different methods for each State value. Each
* method should return a LexerTransition<State>, which the lambda should
* return in turn.
*
* - Write the methods that actually implement lexing for your image format.
* These methods should return either Transition::To(), to move on to another
* state, or Transition::Terminate(), if lexing has terminated in either
* success or failure. (There are also additional transitions for unbuffered
* reads; see below.)
*
* That's all there is to it. The StreamingLexer will track your position in the
* input and buffer enough data so that your lexing methods can process
* everything in one pass. Lex() returns Nothing() if more data is needed, in
* which case you should just return from WriteInternal(). If lexing reaches a
* terminal state, Lex() returns Some(State::SUCCESS) or Some(State::FAILURE),
* and you can check which one to determine if lexing succeeded or failed and do
* any necessary cleanup.
*
* There's one more wrinkle: some lexers may want to *avoid* buffering in some
* cases, and just process the data as it comes in. This is useful if, for
* example, you just want to skip over a large section of data; there's no point
* in buffering data you're just going to ignore.
*
* You can begin an unbuffered read with Transition::ToUnbuffered(). This works
* a little differently than Transition::To() in that you specify *two* states.
* The @aUnbufferedState argument specifies a state that will be called
* repeatedly with unbuffered data, as soon as it arrives. The implementation
* for that state should return either a transition to a terminal state, or
* Transition::ContinueUnbuffered(). Once the amount of data requested in the
* original call to Transition::ToUnbuffered() has been delivered, Lex() will
* transition to the @aNextState state specified via Transition::ToUnbuffered().
* That state will be invoked with *no* data; it's just called to signal that
* the unbuffered read is over.
*
* XXX(seth): We should be able to get of the |State| stuff totally once bug
* 1198451 lands, since we can then just return a function representing the next
* state directly.
*/
template <typename State, size_t InlineBufferSize = 16>
class StreamingLexer
{
public:
explicit StreamingLexer(LexerTransition<State> aStartState)
: mTransition(aStartState)
, mToReadUnbuffered(0)
{ }
template <typename Func>
Maybe<State> Lex(const char* aInput, size_t aLength, Func aFunc)
{
if (IsTerminalState(mTransition.NextState())) {
// We've already reached a terminal state. We never deliver any more data
// in this case; just return the terminal state again immediately.
return Some(mTransition.NextState());
}
if (mToReadUnbuffered > 0) {
// We're continuing an unbuffered read.
MOZ_ASSERT(mBuffer.empty(),
"Shouldn't be continuing an unbuffered read and a buffered "
"read at the same time");
size_t toRead = std::min(mToReadUnbuffered, aLength);
// Call aFunc with the unbuffered state to indicate that we're in the middle
// of an unbuffered read. We enforce that any state transition passed back
// to us is either a terminal states or takes us back to the unbuffered
// state.
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aInput, toRead);
if (IsTerminalState(unbufferedTransition.NextState())) {
mTransition = unbufferedTransition;
return Some(mTransition.NextState()); // Done!
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
aInput += toRead;
aLength -= toRead;
mToReadUnbuffered -= toRead;
if (mToReadUnbuffered != 0) {
return Nothing(); // Need more input.
}
// We're done with the unbuffered read, so transition to the next state.
mTransition = aFunc(mTransition.NextState(), nullptr, 0);
if (IsTerminalState(mTransition.NextState())) {
return Some(mTransition.NextState()); // Done!
}
} else if (0 < mBuffer.length()) {
// We're continuing a buffered read.
MOZ_ASSERT(mToReadUnbuffered == 0,
"Shouldn't be continuing an unbuffered read and a buffered "
"read at the same time");
MOZ_ASSERT(mBuffer.length() < mTransition.Size(),
"Buffered more than we needed?");
size_t toRead = std::min(aLength, mTransition.Size() - mBuffer.length());
mBuffer.append(aInput, toRead);
aInput += toRead;
aLength -= toRead;
if (mBuffer.length() != mTransition.Size()) {
return Nothing(); // Need more input.
}
// We've buffered everything, so transition to the next state.
mTransition =
aFunc(mTransition.NextState(), mBuffer.begin(), mBuffer.length());
mBuffer.clear();
if (IsTerminalState(mTransition.NextState())) {
return Some(mTransition.NextState()); // Done!
}
}
MOZ_ASSERT(mToReadUnbuffered == 0);
MOZ_ASSERT(mBuffer.empty());
// Process states as long as we continue to have enough input to do so.
while (mTransition.Size() <= aLength) {
size_t toRead = mTransition.Size();
if (mTransition.Buffering() == BufferingStrategy::BUFFERED) {
mTransition = aFunc(mTransition.NextState(), aInput, toRead);
} else {
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
// Call aFunc with the unbuffered state to indicate that we're in the
// middle of an unbuffered read. We enforce that any state transition
// passed back to us is either a terminal states or takes us back to the
// unbuffered state.
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aInput, toRead);
if (IsTerminalState(unbufferedTransition.NextState())) {
mTransition = unbufferedTransition;
return Some(mTransition.NextState()); // Done!
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
// We're done with the unbuffered read, so transition to the next state.
mTransition = aFunc(mTransition.NextState(), nullptr, 0);
}
aInput += toRead;
aLength -= toRead;
if (IsTerminalState(mTransition.NextState())) {
return Some(mTransition.NextState()); // Done!
}
}
if (aLength == 0) {
// We finished right at a transition point. Just wait for more data.
return Nothing();
}
// If the next state is unbuffered, deliver what we can and then wait.
if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aInput, aLength);
if (IsTerminalState(unbufferedTransition.NextState())) {
mTransition = unbufferedTransition;
return Some(mTransition.NextState()); // Done!
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
mToReadUnbuffered = mTransition.Size() - aLength;
return Nothing(); // Need more input.
}
// If the next state is buffered, buffer what we can and then wait.
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
if (!mBuffer.reserve(mTransition.Size())) {
return Some(State::FAILURE); // Done due to allocation failure.
}
mBuffer.append(aInput, aLength);
return Nothing(); // Need more input.
}
private:
Vector<char, InlineBufferSize> mBuffer;
LexerTransition<State> mTransition;
size_t mToReadUnbuffered;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_StreamingLexer_h
+3 -6
View File
@@ -206,13 +206,10 @@ public:
if (aCachedSurface->mSurface) {
counter.SubframeSize() = Some(aCachedSurface->mSurface->GetSize());
size_t heap = aCachedSurface->mSurface
->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_HEAP,
mMallocSizeOf);
size_t heap = 0, nonHeap = 0;
aCachedSurface->mSurface->AddSizeOfExcludingThis(mMallocSizeOf,
heap, nonHeap);
counter.Values().SetDecodedHeap(heap);
size_t nonHeap = aCachedSurface->mSurface
->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr);
counter.Values().SetDecodedNonHeap(nonHeap);
}
+1 -1
View File
@@ -14,7 +14,7 @@
#include "mozilla/Maybe.h" // for Maybe
#include "mozilla/MemoryReporting.h" // for MallocSizeOf
#include "mozilla/HashFunctions.h" // for HashGeneric and AddToHash
#include "gfx2DGlue.h" // for gfxMemoryLocation
#include "gfx2DGlue.h"
#include "gfxPoint.h" // for gfxSize
#include "nsCOMPtr.h" // for already_AddRefed
#include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize
-2
View File
@@ -31,7 +31,6 @@ typedef enum {
gif_image_header,
gif_image_header_continue,
gif_image_colormap,
gif_image_body,
gif_lzw_start,
gif_lzw,
gif_sub_block,
@@ -107,4 +106,3 @@ typedef struct gif_struct {
} gif_struct;
#endif // mozilla_image_decoders_GIF2_H
+9
View File
@@ -369,6 +369,15 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
return;
}
// We treat BMPs as transparent if they're 32bpp and alpha is enabled, but
// also if they use RLE encoding, because the 'delta' mode can skip pixels
// and cause implicit transparency.
if ((mBIH.compression == BMPINFOHEADER::RLE8) ||
(mBIH.compression == BMPINFOHEADER::RLE4) ||
(mBIH.bpp == 32 && mUseAlphaData)) {
PostHasTransparency();
}
// We have the size. If we're doing a metadata decode, we're done.
if (IsMetadataDecode()) {
return;
+55 -24
View File
@@ -50,6 +50,8 @@ mailing address.
#include <algorithm>
#include "mozilla/Telemetry.h"
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
@@ -161,6 +163,27 @@ nsGIFDecoder2::BeginGIF()
PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
}
void
nsGIFDecoder2::CheckForTransparency(IntRect aFrameRect)
{
// Check if the image has a transparent color in its palette.
if (mGIFStruct.is_transparent) {
PostHasTransparency();
return;
}
if (mGIFStruct.images_decoded > 0) {
return; // We only care about first frame padding below.
}
// If we need padding on the first frame, that means we don't draw into part
// of the image at all. Report that as transparency.
IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height);
if (!imageRect.IsEqualEdges(aFrameRect)) {
PostHasTransparency();
}
}
//******************************************************************************
nsresult
nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
@@ -170,13 +193,14 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
gfx::SurfaceFormat format;
if (mGIFStruct.is_transparent) {
format = gfx::SurfaceFormat::B8G8R8A8;
PostHasTransparency();
} else {
format = gfx::SurfaceFormat::B8G8R8X8;
}
nsIntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
mGIFStruct.width, mGIFStruct.height);
IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
mGIFStruct.width, mGIFStruct.height);
CheckForTransparency(frameRect);
// Use correct format, RGB for first frame, PAL for following frames
// and include transparency to allow for optimization of opaque images
@@ -186,12 +210,6 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
frameRect, format, aDepth);
} else {
if (!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
// We need padding on the first frame, which means that we don't draw into
// part of the image at all. Report that as transparency.
PostHasTransparency();
}
// Regardless of depth of input, the first frame is decoded into 24bit RGB.
rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
frameRect, format);
@@ -689,12 +707,6 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
mGIFStruct.screen_height = GETINT16(q + 2);
mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1;
if (IsMetadataDecode()) {
MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point");
PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
return;
}
// screen_bgcolor is not used
//mGIFStruct.screen_bgcolor = q[5];
// q[6] = Pixel Aspect Ratio
@@ -731,6 +743,9 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
case gif_image_start:
switch (*q) {
case GIF_TRAILER:
if (IsMetadataDecode()) {
return;
}
mGIFStruct.state = gif_done;
break;
@@ -837,6 +852,11 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
}
mGIFStruct.delay_time = GETINT16(q + 1) * 10;
if (mGIFStruct.delay_time > 0) {
PostIsAnimated(mGIFStruct.delay_time);
}
GETN(1, gif_consume_block);
break;
@@ -901,11 +921,20 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
break;
case gif_image_header: {
if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) {
// We're about to get a second frame, but we only want the first. Stop
// decoding now.
mGIFStruct.state = gif_done;
break;
if (mGIFStruct.images_decoded == 1) {
if (!HasAnimation()) {
// We should've already called PostIsAnimated(); this must be a
// corrupt animated image with a first frame timeout of zero. Signal
// that we're animated now, before the first-frame decode early exit
// below, so that RasterImage can detect that this happened.
PostIsAnimated(/* aFirstFrameTimeout = */ 0);
}
if (IsFirstFrameDecode()) {
// We're about to get a second frame, but we only want the first. Stop
// decoding now.
mGIFStruct.state = gif_done;
break;
}
}
// Get image offsets, with respect to the screen origin
@@ -938,6 +967,9 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// If we were doing a metadata decode, we're done.
if (IsMetadataDecode()) {
IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
mGIFStruct.width, mGIFStruct.height);
CheckForTransparency(frameRect);
return;
}
}
@@ -1108,7 +1140,9 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// We shouldn't ever get here.
default:
break;
MOZ_ASSERT_UNREACHABLE("Unexpected mGIFStruct.state");
PostDecoderError(NS_ERROR_UNEXPECTED);
return;
}
}
@@ -1145,8 +1179,6 @@ done:
mLastFlushedRow = mCurrentRow;
mLastFlushedPass = mCurrentPass;
}
return;
}
bool
@@ -1179,6 +1211,5 @@ nsGIFDecoder2::SpeedHistogram()
return Telemetry::IMAGE_DECODE_SPEED_GIF;
}
} // namespace image
} // namespace mozilla
+1
View File
@@ -47,6 +47,7 @@ private:
bool DoLzw(const uint8_t* q);
bool SetHold(const uint8_t* buf, uint32_t count,
const uint8_t* buf2 = nullptr, uint32_t count2 = 0);
void CheckForTransparency(gfx::IntRect aFrameRect);
inline int ClearCode() const { return 1 << mGIFStruct.datasize; }
+4 -4
View File
@@ -378,8 +378,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
}
if (!HasSize() && mContainedDecoder->HasSize()) {
PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
mContainedDecoder->GetImageMetadata().GetHeight());
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
}
mPos += aCount;
@@ -474,8 +474,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
return;
}
PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
mContainedDecoder->GetImageMetadata().GetHeight());
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
// We have the size. If we're doing a metadata decode, we're done.
if (IsMetadataDecode()) {
-7
View File
@@ -80,7 +80,6 @@ METHODDEF(void) my_error_exit (j_common_ptr cinfo);
// Normal JFIF markers can't have more bytes than this.
#define MAX_JPEG_MARKER_LENGTH (((uint32_t)1 << 16) - 1)
nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage,
Decoder::DecodeStyle aDecodeStyle)
: Decoder(aImage)
@@ -390,7 +389,6 @@ nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
MOZ_LOG(GetJPEGDecoderAccountingLog(), LogLevel::Debug,
("} (unknown colorpsace (3))"));
return;
break;
}
}
@@ -423,7 +421,6 @@ nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
}
}
MOZ_LOG(GetJPEGDecoderAccountingLog(), LogLevel::Debug,
(" JPEGDecoderAccounting: nsJPEGDecoder::"
"Write -- created image frame with %ux%u pixels",
@@ -453,7 +450,6 @@ nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
return; // I/O suspension
}
// If this is a progressive JPEG ...
mState = mInfo.buffered_image ?
JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL;
@@ -737,7 +733,6 @@ nsJPEGDecoder::OutputScanlines(bool* suspend)
}
}
// Override the standard error method in the IJG JPEG decoder code.
METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
@@ -841,7 +836,6 @@ skip_input_data (j_decompress_ptr jd, long num_bytes)
}
}
/******************************************************************************/
/* data source manager method
This is called whenever bytes_in_buffer has reached zero and more
@@ -961,7 +955,6 @@ term_source (j_decompress_ptr jd)
} // namespace image
} // namespace mozilla
///*************** Inverted CMYK -> RGB conversion *************************
/// Input is (Inverted) CMYK stored as 4 bytes per pixel.
/// Output is RGB stored as 3 bytes per pixel.
+85 -124
View File
@@ -19,6 +19,8 @@
#include <algorithm>
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
@@ -54,11 +56,6 @@ GetPNGDecoderAccountingLog()
# define MOZ_PNG_MAX_PIX 268435456 // 256 Mpix = 16Ki x 16Ki
#endif
// For metadata decodes.
#define WIDTH_OFFSET 16
#define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
#define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
: mDispose(DisposalMethod::KEEP)
, mBlend(BlendMethod::OVER)
@@ -66,32 +63,33 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
{ }
#ifdef PNG_APNG_SUPPORTED
int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo)
{
// Delay, in seconds, is delayNum / delayDen.
png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo);
png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo);
if (delayNum == 0) {
return 0; // SetFrameTimeout() will set to a minimum.
}
if (delayDen == 0) {
delayDen = 100; // So says the APNG spec.
}
// Need to cast delay_num to float to have a proper division and
// the result to int to avoid a compiler warning.
return static_cast<int32_t>(static_cast<double>(delayNum) * 1000 / delayDen);
}
nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
: mDispose(DisposalMethod::KEEP)
, mBlend(BlendMethod::OVER)
, mTimeout(0)
{
png_uint_16 delay_num, delay_den;
// delay, in seconds is delay_num/delay_den
png_byte dispose_op;
png_byte blend_op;
delay_num = png_get_next_frame_delay_num(aPNG, aInfo);
delay_den = png_get_next_frame_delay_den(aPNG, aInfo);
dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
if (delay_num == 0) {
mTimeout = 0; // SetFrameTimeout() will set to a minimum
} else {
if (delay_den == 0) {
delay_den = 100; // so says the APNG spec
}
// Need to cast delay_num to float to have a proper division and
// the result to int to avoid compiler warning
mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) *
1000 / delay_den);
}
png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
mDispose = DisposalMethod::RESTORE_PREVIOUS;
@@ -106,6 +104,8 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
} else {
mBlend = BlendMethod::OVER;
}
mTimeout = GetNextFrameDelay(aPNG, aInfo);
}
#endif
@@ -148,6 +148,20 @@ nsPNGDecoder::~nsPNGDecoder()
}
}
void
nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
const IntRect& aFrameRect)
{
// Check if the image has a transparent color in its palette.
if (aFormat == SurfaceFormat::B8G8R8A8) {
PostHasTransparency();
}
// PNGs shouldn't have first-frame padding.
MOZ_ASSERT_IF(mNumFrames == 0,
IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect));
}
// CreateFrame() is used for both simple and animated images
nsresult
nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
@@ -155,18 +169,10 @@ nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
gfx::SurfaceFormat aFormat)
{
MOZ_ASSERT(HasSize());
MOZ_ASSERT(!IsMetadataDecode());
if (aFormat == gfx::SurfaceFormat::B8G8R8A8) {
PostHasTransparency();
}
nsIntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
if (mNumFrames == 0 &&
!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
// We need padding on the first frame, which means that we don't draw into
// part of the image at all. Report that as transparency.
PostHasTransparency();
}
IntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
CheckForTransparency(aFormat, frameRect);
// XXX(seth): Some tests depend on the first frame of PNGs being B8G8R8A8.
// This is something we should fix.
@@ -217,15 +223,6 @@ nsPNGDecoder::EndImageFrame()
opacity = Opacity::OPAQUE;
}
#ifdef PNG_APNG_SUPPORTED
uint32_t numFrames = GetFrameCount();
// We can't use mPNG->num_frames_read as it may be one ahead.
if (numFrames > 1) {
PostInvalidation(mFrameRect);
}
#endif
PostFrameStop(opacity, mAnimInfo.mDispose, mAnimInfo.mTimeout,
mAnimInfo.mBlend);
}
@@ -233,11 +230,6 @@ nsPNGDecoder::EndImageFrame()
void
nsPNGDecoder::InitInternal()
{
// For metadata decodes, we don't need to initialize the PNG decoder.
if (IsMetadataDecode()) {
return;
}
mCMSMode = gfxPlatform::GetCMSMode();
if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
mCMSMode = eCMSMode_Off;
@@ -264,8 +256,6 @@ nsPNGDecoder::InitInternal()
122, 84, 88, 116, '\0'}; // zTXt
#endif
// For full decodes, do png init stuff
// Initialize the container's source image header
// Always decode to 24 bit pixdepth
@@ -286,7 +276,7 @@ nsPNGDecoder::InitInternal()
#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
// Ignore unused chunks
if (mCMSMode == eCMSMode_Off) {
if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) {
png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
}
@@ -337,73 +327,24 @@ nsPNGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
{
MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
// If we only want width/height, we don't need to go through libpng.
if (IsMetadataDecode()) {
// libpng uses setjmp/longjmp for error handling. Set it up.
if (setjmp(png_jmpbuf(mPNG))) {
// Are we done?
if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
return;
// We exited early. If mSuccessfulEarlyFinish isn't true, then we
// encountered an error. We might not really know what caused it, but it
// makes more sense to blame the data.
if (!mSuccessfulEarlyFinish && !HasError()) {
PostDataError();
}
// Scan the header for the width and height bytes
uint32_t pos = 0;
const uint8_t* bptr = (uint8_t*)aBuffer;
while (pos < aCount && mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS) {
// Verify the signature bytes
if (mHeaderBytesRead < sizeof(pngSignatureBytes)) {
if (bptr[pos] != nsPNGDecoder::pngSignatureBytes[mHeaderBytesRead]) {
PostDataError();
return;
}
}
// Get width and height bytes into the buffer
if ((mHeaderBytesRead >= WIDTH_OFFSET) &&
(mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS)) {
mSizeBytes[mHeaderBytesRead - WIDTH_OFFSET] = bptr[pos];
}
pos ++;
mHeaderBytesRead ++;
}
// If we're done now, verify the data and set up the container
if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
// Grab the width and height, accounting for endianness (thanks libpng!)
uint32_t width = png_get_uint_32(mSizeBytes);
uint32_t height = png_get_uint_32(mSizeBytes + 4);
// Check sizes against cap limits
if ((width > MOZ_PNG_MAX_WIDTH) || (height > MOZ_PNG_MAX_HEIGHT)) {
PostDataError();
return;
}
// Post our size to the superclass
PostSize(width, height);
}
// Otherwise, we're doing a standard decode
} else {
// libpng uses setjmp/longjmp for error handling - set the buffer
if (setjmp(png_jmpbuf(mPNG))) {
// We might not really know what caused the error, but it makes more
// sense to blame the data.
if (!mSuccessfulEarlyFinish && !HasError()) {
PostDataError();
}
png_destroy_read_struct(&mPNG, &mInfo, nullptr);
return;
}
// Pass the data off to libpng
png_process_data(mPNG, mInfo, (unsigned char*)aBuffer, aCount);
png_destroy_read_struct(&mPNG, &mInfo, nullptr);
return;
}
// Pass the data off to libpng.
png_process_data(mPNG, mInfo,
reinterpret_cast<unsigned char*>(const_cast<char*>((aBuffer))),
aCount);
}
// Sets up gamma pre-correction in libpng before our callback gets called.
@@ -667,7 +608,24 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
}
#ifdef PNG_APNG_SUPPORTED
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
if (isAnimated) {
decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr));
}
#endif
if (decoder->IsMetadataDecode()) {
decoder->CheckForTransparency(decoder->format,
IntRect(0, 0, width, height));
// We have the metadata we're looking for, so we don't need to decode any
// further.
decoder->mSuccessfulEarlyFinish = true;
png_longjmp(decoder->mPNG, 1);
}
#ifdef PNG_APNG_SUPPORTED
if (isAnimated) {
png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
nullptr);
}
@@ -744,7 +702,7 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
return;
}
if (row_num >= (png_uint_32) decoder->mFrameRect.height) {
if (row_num >= static_cast<png_uint_32>(decoder->mFrameRect.height)) {
return;
}
@@ -823,11 +781,14 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
png_longjmp(decoder->mPNG, 1);
}
if (decoder->mNumFrames <= 1) {
// Only do incremental image display for the first frame
// XXXbholley - this check should be handled in the superclass
nsIntRect r(0, row_num, width, 1);
decoder->PostInvalidation(r);
if (!decoder->interlacebuf) {
// Do line-by-line partial invalidations for non-interlaced images
decoder->PostInvalidation(IntRect(0, row_num, width, 1));
} else if (row_num ==
static_cast<png_uint_32>(decoder->mFrameRect.height - 1)) {
// Do only one full image invalidation for each pass (Bug 1187569)
decoder->PostInvalidation(IntRect(0, 0, width,
decoder->mFrameRect.height));
}
}
}
+3
View File
@@ -35,6 +35,9 @@ public:
gfx::SurfaceFormat aFormat);
void EndImageFrame();
void CheckForTransparency(gfx::SurfaceFormat aFormat,
const gfx::IntRect& aFrameRect);
// Check if PNG is valid ICO (32bpp RGBA)
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
bool IsValidICO() const
+14 -28
View File
@@ -1121,42 +1121,28 @@ imgFrame::SetCompositingFailed(bool val)
mCompositingFailed = val;
}
size_t
imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation,
MallocSizeOf aMallocSizeOf) const
void
imgFrame::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
size_t& aHeapSizeOut,
size_t& aNonHeapSizeOut) const
{
MonitorAutoLock lock(mMonitor);
// aMallocSizeOf is only used if aLocation is
// gfxMemoryLocation::IN_PROCESS_HEAP. It
// should be nullptr otherwise.
MOZ_ASSERT(
(aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && aMallocSizeOf) ||
(aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf),
"mismatch between aLocation and aMallocSizeOf");
size_t n = 0;
if (mPalettedImageData && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
n += aMallocSizeOf(mPalettedImageData);
if (mPalettedImageData) {
aHeapSizeOut += aMallocSizeOf(mPalettedImageData);
}
if (mImageSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
n += aMallocSizeOf(mImageSurface);
if (mImageSurface) {
aHeapSizeOut += aMallocSizeOf(mImageSurface);
}
if (mOptSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
n += aMallocSizeOf(mOptSurface);
if (mOptSurface) {
aHeapSizeOut += aMallocSizeOf(mOptSurface);
}
if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
n += aMallocSizeOf(mVBuf);
n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf);
if (mVBuf) {
aHeapSizeOut += aMallocSizeOf(mVBuf);
aHeapSizeOut += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf);
aNonHeapSizeOut += mVBuf->NonHeapSizeOfExcludingThis();
}
if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_NONHEAP) {
n += mVBuf->NonHeapSizeOfExcludingThis();
}
return n;
}
} // namespace image
+2 -2
View File
@@ -264,8 +264,8 @@ public:
already_AddRefed<SourceSurface> GetSurface();
already_AddRefed<DrawTarget> GetDrawTarget();
size_t SizeOfExcludingThis(gfxMemoryLocation aLocation,
MallocSizeOf aMallocSizeOf) const;
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut,
size_t& aNonHeapSizeOut) const;
private: // methods
+8 -2
View File
@@ -64,6 +64,7 @@ imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey)
: mLoader(aLoader)
, mCacheKey(aCacheKey)
, mLoadId(nullptr)
, mFirstProxy(nullptr)
, mValidator(nullptr)
, mInnerWindowId(0)
, mCORSMode(imgIRequest::CORS_NONE)
@@ -218,6 +219,12 @@ imgRequest::AddProxy(imgRequestProxy* proxy)
NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy);
if (!mFirstProxy) {
// Save a raw pointer to the first proxy we see, for use in the network
// priority logic.
mFirstProxy = proxy;
}
// If we're empty before adding, we have to tell the loader we now have
// proxies.
nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
@@ -535,8 +542,7 @@ imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta)
// concern though is that image loads remain lower priority than other pieces
// of content such as link clicks, CSS, and JS.
//
nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
if (!progressTracker->FirstObserverIs(proxy)) {
if (!mFirstProxy || proxy != mFirstProxy) {
return;
}
+4
View File
@@ -256,6 +256,10 @@ private:
void* mLoadId;
/// Raw pointer to the first proxy that was added to this imgRequest. Use only
/// pointer comparisons; there's no guarantee this will remain valid.
void* mFirstProxy;
imgCacheValidator* mValidator;
nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
nsCOMPtr<nsIChannel> mNewRedirectChannel;
+5 -2
View File
@@ -25,10 +25,10 @@
#include "imgIScriptedNotificationObserver.h"
#include "gfxPlatform.h"
using namespace mozilla;
using namespace mozilla::image;
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
/* ========== imgITools implementation ========== */
@@ -349,3 +349,6 @@ imgTools::GetImgCacheForDocument(nsIDOMDocument* aDoc, imgICache** aCache)
NS_ENSURE_SUCCESS(rv, rv);
return CallQueryInterface(loader, aCache);
}
} // namespace image
} // namespace mozilla
+7
View File
@@ -17,6 +17,9 @@
{0xbd, 0xef, 0x2c, 0x7a, 0xe2, 0x49, 0x96, 0x7a} \
}
namespace mozilla {
namespace image {
class imgTools final : public imgITools
{
public:
@@ -28,4 +31,8 @@ public:
private:
virtual ~imgTools();
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_imgITools_h
-1
View File
@@ -61,7 +61,6 @@ UNIFIED_SOURCES += [
'Image.cpp',
'ImageCacheKey.cpp',
'ImageFactory.cpp',
'ImageMetadata.cpp',
'ImageOps.cpp',
'ImageWrapper.cpp',
'imgFrame.cpp',
+69 -5
View File
@@ -15,6 +15,7 @@
#include "nsIProperties.h"
#include "nsNetUtil.h"
#include "mozilla/nsRefPtr.h"
#include "nsStreamUtils.h"
#include "nsString.h"
namespace mozilla {
@@ -71,6 +72,15 @@ LoadFile(const char* aRelativePath)
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
// Ensure the resulting input stream is buffered.
if (!NS_InputStreamIsBuffered(inputStream)) {
nsCOMPtr<nsIInputStream> bufStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
inputStream, 1024);
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
inputStream = bufStream;
}
return inputStream.forget();
}
@@ -126,7 +136,7 @@ ImageTestCase GreenGIFTestCase()
ImageTestCase GreenJPGTestCase()
{
return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100),
/* aFuzzy = */ true);
TEST_CASE_IS_FUZZY);
}
ImageTestCase GreenBMPTestCase()
@@ -136,22 +146,76 @@ ImageTestCase GreenBMPTestCase()
ImageTestCase GreenICOTestCase()
{
return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100));
// This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
// when the BMP is embedded in an ICO, so it's transparent.
return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase GreenFirstFrameAnimatedGIFTestCase()
{
return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100));
return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100),
TEST_CASE_IS_ANIMATED);
}
ImageTestCase GreenFirstFrameAnimatedPNGTestCase()
{
return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100));
return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
}
ImageTestCase CorruptTestCase()
{
return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100));
return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
TEST_CASE_HAS_ERROR);
}
ImageTestCase TransparentPNGTestCase()
{
return ImageTestCase("transparent.png", "image/png", IntSize(32, 32),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase TransparentGIFTestCase()
{
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase FirstFramePaddingGIFTestCase()
{
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase()
{
// Note that we only decode this test case as transparent when the BMP decoder
// is set to use alpha data. (That's not the default, which is why it's not marked
// TEST_CASE_IS_TRANSPARENT; tests that want to treat this testcase as
// transparent need to handle this case manually.)
return ImageTestCase("transparent.bmp", "image/bmp", IntSize(32, 32));
}
ImageTestCase RLE4BMPTestCase()
{
return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase RLE8BMPTestCase()
{
return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
TEST_CASE_IS_TRANSPARENT);
}
ImageTestCase NoFrameDelayGIFTestCase()
{
// This is an invalid (or at least, questionably valid) GIF that's animated
// even though it specifies a frame delay of zero. It's animated, but it's not
// marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that
// it's animated.
return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100));
}
} // namespace mozilla
+21 -3
View File
@@ -17,22 +17,31 @@ namespace mozilla {
// Types
///////////////////////////////////////////////////////////////////////////////
enum TestCaseFlags
{
TEST_CASE_DEFAULT_FLAGS = 0,
TEST_CASE_IS_FUZZY = 1 << 0,
TEST_CASE_HAS_ERROR = 1 << 1,
TEST_CASE_IS_TRANSPARENT = 1 << 2,
TEST_CASE_IS_ANIMATED = 1 << 3,
};
struct ImageTestCase
{
ImageTestCase(const char* aPath,
const char* aMimeType,
gfx::IntSize aSize,
bool aFuzzy = false)
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
: mPath(aPath)
, mMimeType(aMimeType)
, mSize(aSize)
, mFuzzy(aFuzzy)
, mFlags(aFlags)
{ }
const char* mPath;
const char* mMimeType;
gfx::IntSize mSize;
bool mFuzzy;
uint32_t mFlags;
};
struct BGRAColor
@@ -86,6 +95,15 @@ ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
ImageTestCase CorruptTestCase();
ImageTestCase TransparentPNGTestCase();
ImageTestCase TransparentGIFTestCase();
ImageTestCase FirstFramePaddingGIFTestCase();
ImageTestCase NoFrameDelayGIFTestCase();
ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase();
ImageTestCase RLE4BMPTestCase();
ImageTestCase RLE8BMPTestCase();
} // namespace mozilla
#endif // mozilla_image_test_gtest_Common_h
+235
View File
@@ -0,0 +1,235 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "CopyOnWrite.h"
using namespace mozilla;
using namespace mozilla::image;
struct ValueStats
{
int32_t mCopies = 0;
int32_t mFrees = 0;
int32_t mCalls = 0;
int32_t mConstCalls = 0;
int32_t mSerial = 0;
};
struct Value
{
NS_INLINE_DECL_REFCOUNTING(Value)
explicit Value(ValueStats& aStats)
: mStats(aStats)
, mSerial(mStats.mSerial++)
{ }
Value(const Value& aOther)
: mStats(aOther.mStats)
, mSerial(mStats.mSerial++)
{
mStats.mCopies++;
}
void Go() { mStats.mCalls++; }
void Go() const { mStats.mConstCalls++; }
int32_t Serial() const { return mSerial; }
protected:
~Value() { mStats.mFrees++; }
private:
ValueStats& mStats;
int32_t mSerial;
};
TEST(ImageCopyOnWrite, Read)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
aValue->Go();
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
});
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
}
TEST(ImageCopyOnWrite, RecursiveRead)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
// Make sure that Read() inside a Read() succeeds.
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
aValue->Go();
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}, []() {
// This gets called if we can't read. We shouldn't get here.
EXPECT_TRUE(false);
});
});
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
}
TEST(ImageCopyOnWrite, Write)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
cow.Write([&](Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(!cow.CanRead());
EXPECT_TRUE(!cow.CanWrite());
aValue->Go();
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(0, stats.mConstCalls);
});
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(0, stats.mConstCalls);
}
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
}
TEST(ImageCopyOnWrite, WriteRecursive)
{
ValueStats stats;
{
CopyOnWrite<Value> cow(new Value(stats));
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
cow.Read([&](const Value* aValue) {
EXPECT_EQ(0, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(0, aValue->Serial());
EXPECT_TRUE(cow.CanRead());
EXPECT_TRUE(cow.CanWrite());
// Make sure Write() inside a Read() succeeds.
cow.Write([&](Value* aValue) {
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(1, aValue->Serial());
EXPECT_TRUE(!cow.CanRead());
EXPECT_TRUE(!cow.CanWrite());
aValue->Go();
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(0, stats.mConstCalls);
// Make sure Read() inside a Write() fails.
cow.Read([](const Value* aValue) {
// This gets called if we can read. We shouldn't get here.
EXPECT_TRUE(false);
}, []() {
// This gets called if we can't read. We *should* get here.
EXPECT_TRUE(true);
});
// Make sure Write() inside a Write() fails.
cow.Write([](Value* aValue) {
// This gets called if we can write. We shouldn't get here.
EXPECT_TRUE(false);
}, []() {
// This gets called if we can't write. We *should* get here.
EXPECT_TRUE(true);
});
}, []() {
// This gets called if we can't write. We shouldn't get here.
EXPECT_TRUE(false);
});
aValue->Go();
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(0, stats.mFrees);
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
});
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(1, stats.mFrees);
EXPECT_EQ(1, stats.mCalls);
EXPECT_EQ(1, stats.mConstCalls);
}
EXPECT_EQ(1, stats.mCopies);
EXPECT_EQ(2, stats.mFrees);
}
+2 -1
View File
@@ -61,7 +61,8 @@ public:
surface->GetFormat() == SurfaceFormat::B8G8R8A8);
EXPECT_EQ(mTestCase.mSize, surface->GetSize());
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), mTestCase.mFuzzy));
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
mTestCase.mFlags & TEST_CASE_IS_FUZZY));
}
private:
+249
View File
@@ -0,0 +1,249 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "decoders/nsBMPDecoder.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "ImageFactory.h"
#include "mozilla/gfx/2D.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "mozilla/nsRefPtr.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "ProgressTracker.h"
#include "SourceBuffer.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
TEST(ImageDecoders, ImageModuleAvailable)
{
// We can run into problems if XPCOM modules get initialized in the wrong
// order. It's important that this test run first, both as a sanity check and
// to ensure we get the module initialization order we want.
nsCOMPtr<imgITools> imgTools =
do_CreateInstance("@mozilla.org/image/tools;1");
EXPECT_TRUE(imgTools != nullptr);
}
static void
CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder)
{
EXPECT_TRUE(aDecoder->GetDecodeDone());
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
aDecoder->HasError());
EXPECT_TRUE(!aDecoder->WasAborted());
// Verify that the decoder made the expected progress.
Progress progress = aDecoder->TakeProgress();
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
bool(progress & FLAG_HAS_ERROR));
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
return; // That's all we can check for bad images.
}
EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE));
EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE));
EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE));
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT),
bool(progress & FLAG_HAS_TRANSPARENCY));
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
bool(progress & FLAG_IS_ANIMATED));
// The decoder should get the correct size.
IntSize size = aDecoder->GetSize();
EXPECT_EQ(aTestCase.mSize.width, size.width);
EXPECT_EQ(aTestCase.mSize.height, size.height);
// Get the current frame, which is always the first frame of the image
// because CreateAnonymousDecoder() forces a first-frame-only decode.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
nsRefPtr<SourceSurface> surface = currentFrame->GetSurface();
// Verify that the resulting surfaces matches our expectations.
EXPECT_EQ(SurfaceType::DATA, surface->GetType());
EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
surface->GetFormat() == SurfaceFormat::B8G8R8A8);
EXPECT_EQ(aTestCase.mSize, surface->GetSize());
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
aTestCase.mFlags & TEST_CASE_IS_FUZZY));
}
static void
CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into a SourceBuffer.
nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
sourceBuffer->ExpectLength(length);
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
sourceBuffer->Complete(NS_OK);
// Create a decoder.
DecoderType decoderType =
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
nsRefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
DefaultSurfaceFlags());
ASSERT_TRUE(decoder != nullptr);
// Run the full decoder synchronously.
decoder->Decode();
CheckDecoderResults(aTestCase, decoder);
}
class NoResume : public IResumable
{
public:
NS_INLINE_DECL_REFCOUNTING(NoResume, override)
virtual void Resume() override { }
private:
~NoResume() { }
};
static void
CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Create a SourceBuffer and a decoder.
nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
sourceBuffer->ExpectLength(length);
DecoderType decoderType =
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
nsRefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
DefaultSurfaceFlags());
ASSERT_TRUE(decoder != nullptr);
// Decode synchronously, using a |NoResume| IResumable so the Decoder doesn't
// attempt to schedule itself on a nonexistent DecodePool when we write more
// data into the SourceBuffer.
nsRefPtr<NoResume> noResume = new NoResume();
for (uint64_t read = 0; read < length ; ++read) {
uint64_t available = 0;
rv = inputStream->Available(&available);
ASSERT_TRUE(available > 0);
ASSERT_TRUE(NS_SUCCEEDED(rv));
rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
ASSERT_TRUE(NS_SUCCEEDED(rv));
decoder->Decode(noResume);
}
sourceBuffer->Complete(NS_OK);
decoder->Decode(noResume);
CheckDecoderResults(aTestCase, decoder);
}
TEST(ImageDecoders, PNGSingleChunk)
{
CheckDecoderSingleChunk(GreenPNGTestCase());
}
TEST(ImageDecoders, PNGMultiChunk)
{
CheckDecoderMultiChunk(GreenPNGTestCase());
}
TEST(ImageDecoders, GIFSingleChunk)
{
CheckDecoderSingleChunk(GreenGIFTestCase());
}
TEST(ImageDecoders, GIFMultiChunk)
{
CheckDecoderMultiChunk(GreenGIFTestCase());
}
TEST(ImageDecoders, JPGSingleChunk)
{
CheckDecoderSingleChunk(GreenJPGTestCase());
}
TEST(ImageDecoders, JPGMultiChunk)
{
CheckDecoderMultiChunk(GreenJPGTestCase());
}
TEST(ImageDecoders, BMPSingleChunk)
{
CheckDecoderSingleChunk(GreenBMPTestCase());
}
TEST(ImageDecoders, BMPMultiChunk)
{
CheckDecoderMultiChunk(GreenBMPTestCase());
}
TEST(ImageDecoders, ICOSingleChunk)
{
CheckDecoderSingleChunk(GreenICOTestCase());
}
// XXX(seth): Disabled. We'll fix this in bug 1196066.
TEST(ImageDecoders, DISABLED_ICOMultiChunk)
{
CheckDecoderMultiChunk(GreenICOTestCase());
}
TEST(ImageDecoders, AnimatedGIFSingleChunk)
{
CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase());
}
TEST(ImageDecoders, AnimatedGIFMultiChunk)
{
CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase());
}
TEST(ImageDecoders, AnimatedPNGSingleChunk)
{
CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase());
}
TEST(ImageDecoders, AnimatedPNGMultiChunk)
{
CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase());
}
TEST(ImageDecoders, CorruptSingleChunk)
{
CheckDecoderSingleChunk(CorruptTestCase());
}
TEST(ImageDecoders, CorruptMultiChunk)
{
CheckDecoderMultiChunk(CorruptTestCase());
}
+254
View File
@@ -0,0 +1,254 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "decoders/nsBMPDecoder.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "ImageFactory.h"
#include "mozilla/gfx/2D.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "mozilla/nsRefPtr.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "ProgressTracker.h"
#include "SourceBuffer.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
TEST(ImageMetadata, ImageModuleAvailable)
{
// We can run into problems if XPCOM modules get initialized in the wrong
// order. It's important that this test run first, both as a sanity check and
// to ensure we get the module initialization order we want.
nsCOMPtr<imgITools> imgTools =
do_CreateInstance("@mozilla.org/image/tools;1");
EXPECT_TRUE(imgTools != nullptr);
}
enum class BMPAlpha
{
DISABLED,
ENABLED
};
static void
CheckMetadata(const ImageTestCase& aTestCase,
BMPAlpha aBMPAlpha = BMPAlpha::DISABLED)
{
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// Figure out how much data we have.
uint64_t length;
nsresult rv = inputStream->Available(&length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Write the data into a SourceBuffer.
nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
sourceBuffer->ExpectLength(length);
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
ASSERT_TRUE(NS_SUCCEEDED(rv));
sourceBuffer->Complete(NS_OK);
// Create a metadata decoder.
DecoderType decoderType =
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
nsRefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
ASSERT_TRUE(decoder != nullptr);
if (aBMPAlpha == BMPAlpha::ENABLED) {
static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
}
// Run the metadata decoder synchronously.
decoder->Decode();
// Ensure that the metadata decoder didn't make progress it shouldn't have
// (which would indicate that it decoded past the header of the image).
Progress metadataProgress = decoder->TakeProgress();
EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
FLAG_HAS_TRANSPARENCY |
FLAG_IS_ANIMATED)));
// If the test case is corrupt, assert what we can and return early.
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
EXPECT_TRUE(decoder->GetDecodeDone());
EXPECT_TRUE(decoder->HasError());
return;
}
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
// Check that we got the expected metadata.
EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE);
IntSize metadataSize = decoder->GetSize();
EXPECT_EQ(aTestCase.mSize.width, metadataSize.width);
EXPECT_EQ(aTestCase.mSize.height, metadataSize.height);
bool expectTransparency = aBMPAlpha == BMPAlpha::ENABLED
? true
: bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT);
EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
bool(metadataProgress & FLAG_IS_ANIMATED));
// Create a full decoder, so we can compare the result.
decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
imgIContainer::DECODE_FLAGS_DEFAULT);
ASSERT_TRUE(decoder != nullptr);
if (aBMPAlpha == BMPAlpha::ENABLED) {
static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
}
// Run the full decoder synchronously.
decoder->Decode();
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
Progress fullProgress = decoder->TakeProgress();
// If the metadata decoder set a progress bit, the full decoder should also
// have set the same bit.
EXPECT_EQ(fullProgress, metadataProgress | fullProgress);
// The full decoder and the metadata decoder should agree on the image's size.
IntSize fullSize = decoder->GetSize();
EXPECT_EQ(metadataSize.width, fullSize.width);
EXPECT_EQ(metadataSize.height, fullSize.height);
// We should not discover transparency during the full decode that we didn't
// discover during the metadata decode, unless the image is animated.
EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) ||
(metadataProgress & FLAG_HAS_TRANSPARENCY) ||
(fullProgress & FLAG_IS_ANIMATED));
}
TEST(ImageMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); }
TEST(ImageMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); }
TEST(ImageMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); }
TEST(ImageMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); }
TEST(ImageMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); }
TEST(ImageMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); }
TEST(ImageMetadata, ICO) { CheckMetadata(GreenICOTestCase()); }
TEST(ImageMetadata, AnimatedGIF)
{
CheckMetadata(GreenFirstFrameAnimatedGIFTestCase());
}
TEST(ImageMetadata, AnimatedPNG)
{
CheckMetadata(GreenFirstFrameAnimatedPNGTestCase());
}
TEST(ImageMetadata, FirstFramePaddingGIF)
{
CheckMetadata(FirstFramePaddingGIFTestCase());
}
TEST(ImageMetadata, TransparentBMPWithBMPAlphaOff)
{
CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED);
}
TEST(ImageMetadata, TransparentBMPWithBMPAlphaOn)
{
CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED);
}
TEST(ImageMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); }
TEST(ImageMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); }
TEST(ImageMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); }
TEST(ImageMetadata, NoFrameDelayGIF)
{
CheckMetadata(NoFrameDelayGIFTestCase());
}
TEST(ImageMetadata, NoFrameDelayGIFFullDecode)
{
ImageTestCase testCase = NoFrameDelayGIFTestCase();
// The previous test (NoFrameDelayGIF) verifies that we *don't* detect that
// this test case is animated, because it has a zero frame delay for the first
// frame. This test verifies that when we do a full decode, we detect the
// animation at that point and successfully decode all the frames.
// Create an image.
nsRefPtr<Image> image =
ImageFactory::CreateAnonymousImage(nsAutoCString(testCase.mMimeType));
ASSERT_TRUE(!image->HasError());
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
ASSERT_TRUE(inputStream != nullptr);
// 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));
nsRefPtr<ProgressTracker> tracker = image->GetProgressTracker();
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
// Use GetFrame() to force a sync decode of the image.
nsRefPtr<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);
Progress imageProgress = tracker->GetProgress();
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
// Ensure that we decoded both frames of the image.
LookupResult firstFrameLookupResult =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
imgIContainer::DECODE_FLAGS_DEFAULT,
/* aFrameNum = */ 0));
EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type());
LookupResult secondFrameLookupResult =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize,
imgIContainer::DECODE_FLAGS_DEFAULT,
/* aFrameNum = */ 1));
EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type());
}
+266
View File
@@ -0,0 +1,266 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "mozilla/Vector.h"
#include "StreamingLexer.h"
using namespace mozilla;
using namespace mozilla::image;
enum class TestState
{
ONE,
TWO,
THREE,
UNBUFFERED,
SUCCESS,
FAILURE
};
void
CheckData(const char* aData, size_t aLength)
{
EXPECT_TRUE(aLength == 3);
EXPECT_EQ(1, aData[0]);
EXPECT_EQ(2, aData[1]);
EXPECT_EQ(3, aData[2]);
}
LexerTransition<TestState>
DoLex(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckData(aData, aLength);
return Transition::To(TestState::TWO, 3);
case TestState::TWO:
CheckData(aData, aLength);
return Transition::To(TestState::THREE, 3);
case TestState::THREE:
CheckData(aData, aLength);
return Transition::Terminate(TestState::SUCCESS);
default:
EXPECT_TRUE(false); // Shouldn't get here.
return Transition::Terminate(TestState::FAILURE);
}
}
LexerTransition<TestState>
DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
Vector<char>& aUnbufferedVector)
{
switch (aState) {
case TestState::ONE:
CheckData(aData, aLength);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
case TestState::UNBUFFERED:
EXPECT_TRUE(aLength <= 3);
aUnbufferedVector.append(aData, aLength);
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
case TestState::TWO:
CheckData(aUnbufferedVector.begin(), aUnbufferedVector.length());
return Transition::To(TestState::THREE, 3);
case TestState::THREE:
CheckData(aData, aLength);
return Transition::Terminate(TestState::SUCCESS);
default:
EXPECT_TRUE(false);
return Transition::Terminate(TestState::FAILURE);
}
}
LexerTransition<TestState>
DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
{
switch (aState) {
case TestState::ONE:
CheckData(aData, aLength);
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
case TestState::UNBUFFERED:
return Transition::Terminate(TestState::SUCCESS);
default:
EXPECT_TRUE(false);
return Transition::Terminate(TestState::FAILURE);
}
}
TEST(ImageStreamingLexer, SingleChunk)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// Test delivering all the data at once.
Maybe<TestState> result = lexer.Lex(data, sizeof(data), DoLex);
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
}
TEST(ImageStreamingLexer, SingleChunkWithUnbuffered)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
Vector<char> unbufferedVector;
// Test delivering all the data at once.
Maybe<TestState> result =
lexer.Lex(data, sizeof(data),
[&](TestState aState, const char* aData, size_t aLength) {
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
});
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
}
TEST(ImageStreamingLexer, ChunkPerState)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// Test delivering in perfectly-sized chunks, one per state.
for (unsigned i = 0 ; i < 3 ; ++i) {
Maybe<TestState> result = lexer.Lex(data + 3 * i, 3, DoLex);
if (i == 2) {
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
} else {
EXPECT_TRUE(result.isNothing());
}
}
}
TEST(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
Vector<char> unbufferedVector;
// Test delivering in perfectly-sized chunks, one per state.
for (unsigned i = 0 ; i < 3 ; ++i) {
Maybe<TestState> result =
lexer.Lex(data + 3 * i, 3,
[&](TestState aState, const char* aData, size_t aLength) {
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
});
if (i == 2) {
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
} else {
EXPECT_TRUE(result.isNothing());
}
}
}
TEST(ImageStreamingLexer, OneByteChunks)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// Test delivering in one byte chunks.
for (unsigned i = 0 ; i < 9 ; ++i) {
Maybe<TestState> result = lexer.Lex(data + i, 1, DoLex);
if (i == 8) {
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
} else {
EXPECT_TRUE(result.isNothing());
}
}
}
TEST(ImageStreamingLexer, OneByteChunksWithUnbuffered)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
Vector<char> unbufferedVector;
// Test delivering in one byte chunks.
for (unsigned i = 0 ; i < 9 ; ++i) {
Maybe<TestState> result =
lexer.Lex(data + i, 1,
[&](TestState aState, const char* aData, size_t aLength) {
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
});
if (i == 8) {
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
} else {
EXPECT_TRUE(result.isNothing());
}
}
}
TEST(ImageStreamingLexer, TerminateSuccess)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// Test that Terminate is "sticky".
Maybe<TestState> result =
lexer.Lex(data, sizeof(data),
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(aState == TestState::ONE);
return Transition::Terminate(TestState::SUCCESS);
});
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
result =
lexer.Lex(data, sizeof(data),
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(false); // Shouldn't get here.
return Transition::Terminate(TestState::FAILURE);
});
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
}
TEST(ImageStreamingLexer, TerminateFailure)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// Test that Terminate is "sticky".
Maybe<TestState> result =
lexer.Lex(data, sizeof(data),
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(aState == TestState::ONE);
return Transition::Terminate(TestState::FAILURE);
});
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::FAILURE, *result);
result =
lexer.Lex(data, sizeof(data),
[&](TestState aState, const char* aData, size_t aLength) {
EXPECT_TRUE(false); // Shouldn't get here.
return Transition::Terminate(TestState::FAILURE);
});
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::FAILURE, *result);
}
TEST(ImageStreamingLexer, TerminateUnbuffered)
{
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// Test that Terminate works during an unbuffered read.
for (unsigned i = 0 ; i < 9 ; ++i) {
Maybe<TestState> result =
lexer.Lex(data + i, 1, DoLexWithUnbufferedTerminate);
if (i > 2) {
EXPECT_TRUE(result.isSome());
EXPECT_EQ(TestState::SUCCESS, *result);
} else {
EXPECT_TRUE(result.isNothing());
}
}
}
+11
View File
@@ -8,18 +8,29 @@ Library('imagetest')
UNIFIED_SOURCES = [
'Common.cpp',
'TestCopyOnWrite.cpp',
'TestDecoders.cpp',
'TestDecodeToSurface.cpp',
'TestMetadata.cpp',
'TestStreamingLexer.cpp',
]
TEST_HARNESS_FILES.gtest += [
'corrupt.jpg',
'first-frame-green.gif',
'first-frame-green.png',
'first-frame-padding.gif',
'green.bmp',
'green.gif',
'green.ico',
'green.jpg',
'green.png',
'no-frame-delay.gif',
'rle4.bmp',
'rle8.bmp',
'transparent.bmp',
'transparent.gif',
'transparent.png',
]
LOCAL_INCLUDES += [
Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

@@ -48,8 +48,12 @@ function testFiles() {
// GIFs with padding on the first frame are always transparent.
yield ["first-frame-padding.gif", true];
// JPEGs and BMPs are never transparent.
// JPEGs are never transparent.
yield ["damon.jpg", false];
// Most BMPs are not transparent. (The TestMetadata GTest, which will
// eventually replace this test totally, has coverage for the kinds that can be
// transparent.)
yield ["opaque.bmp", false];
// ICO files which contain BMPs have an additional type of transparency - the
Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 B

After

Width:  |  Height:  |  Size: 92 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

+6 -5
View File
@@ -15,6 +15,7 @@
#include "AnimationCommon.h" // For GetLayerAnimationInfo
#include "FrameLayerBuilder.h"
#include "GeckoProfiler.h"
#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords
#include "nsStyleChangeList.h"
#include "nsRuleProcessorData.h"
#include "nsStyleUtil.h"
@@ -2635,10 +2636,10 @@ ElementRestyler::AddLayerChangesForAnimation()
RestyleManager::GetMaxAnimationGenerationForFrame(mFrame);
nsChangeHint hint = nsChangeHint(0);
const auto& layerInfo = CommonAnimationManager::sLayerAnimationInfo;
for (size_t i = 0; i < ArrayLength(layerInfo); i++) {
for (const LayerAnimationInfo::Record& layerInfo :
LayerAnimationInfo::sRecords) {
Layer* layer =
FrameLayerBuilder::GetDedicatedLayer(mFrame, layerInfo[i].mLayerType);
FrameLayerBuilder::GetDedicatedLayer(mFrame, layerInfo.mLayerType);
if (layer && frameGeneration > layer->GetAnimationGeneration()) {
// If we have a transform layer but don't have any transform style, we
// probably just removed the transform but haven't destroyed the layer
@@ -2647,11 +2648,11 @@ ElementRestyler::AddLayerChangesForAnimation()
// so we can skip adding any change hint here. (If we *were* to add
// nsChangeHint_UpdateTransformLayer, ApplyRenderingChangeToTree would
// complain that we're updating a transform layer without a transform).
if (layerInfo[i].mLayerType == nsDisplayItem::TYPE_TRANSFORM &&
if (layerInfo.mLayerType == nsDisplayItem::TYPE_TRANSFORM &&
!mFrame->StyleDisplay()->HasTransformStyle()) {
continue;
}
NS_UpdateHint(hint, layerInfo[i].mChangeHint);
NS_UpdateHint(hint, layerInfo.mChangeHint);
}
}
if (hint) {
+2 -1
View File
@@ -68,6 +68,7 @@
#include "FrameLayerBuilder.h"
#include "mozilla/dom/RequestSyncWifiService.h"
#include "AnimationCommon.h"
#include "LayerAnimationInfo.h"
#include "AudioChannelService.h"
#include "mozilla/dom/DataStoreService.h"
@@ -308,7 +309,7 @@ nsLayoutStatics::Initialize()
#ifdef DEBUG
nsStyleContext::Initialize();
mozilla::CommonAnimationManager::Initialize();
mozilla::LayerAnimationInfo::Initialize();
#endif
MediaDecoder::InitStatics();
+3 -63
View File
@@ -18,6 +18,7 @@
#include "nsIFrame.h"
#include "nsLayoutUtils.h"
#include "mozilla/LookAndFeel.h"
#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords
#include "Layers.h"
#include "FrameLayerBuilder.h"
#include "nsDisplayList.h"
@@ -409,30 +410,6 @@ CommonAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
return collection->mStyleRule;
}
/* static */ const CommonAnimationManager::LayerAnimationRecord
CommonAnimationManager::sLayerAnimationInfo[] =
{ { eCSSProperty_transform,
nsDisplayItem::TYPE_TRANSFORM,
nsChangeHint_UpdateTransformLayer },
{ eCSSProperty_opacity,
nsDisplayItem::TYPE_OPACITY,
nsChangeHint_UpdateOpacityLayer } };
/* static */ const CommonAnimationManager::LayerAnimationRecord*
CommonAnimationManager::LayerAnimationRecordFor(nsCSSProperty aProperty)
{
MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
"unexpected property");
const auto& info = sLayerAnimationInfo;
for (size_t i = 0; i < ArrayLength(info); ++i) {
if (aProperty == info[i].mProperty) {
return &info[i];
}
}
return nullptr;
}
/* virtual */ void
CommonAnimationManager::WillRefresh(TimeStamp aTime)
{
@@ -458,42 +435,6 @@ CommonAnimationManager::WillRefresh(TimeStamp aTime)
MaybeStartOrStopObservingRefreshDriver();
}
#ifdef DEBUG
/* static */ void
CommonAnimationManager::Initialize()
{
const auto& info = CommonAnimationManager::sLayerAnimationInfo;
for (size_t i = 0; i < ArrayLength(info); i++) {
auto record = info[i];
MOZ_ASSERT(nsCSSProps::PropHasFlags(record.mProperty,
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
"CSS property with entry in sLayerAnimationInfo does not "
"have the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag");
}
// Check that every property with the flag for animating on the
// compositor has an entry in sLayerAnimationInfo.
for (nsCSSProperty prop = nsCSSProperty(0);
prop < eCSSProperty_COUNT;
prop = nsCSSProperty(prop + 1)) {
if (nsCSSProps::PropHasFlags(prop,
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) {
bool found = false;
for (size_t i = 0; i < ArrayLength(info); i++) {
auto record = info[i];
if (record.mProperty == prop) {
found = true;
break;
}
}
MOZ_ASSERT(found,
"CSS property with the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR "
"flag does not have an entry in sLayerAnimationInfo");
}
}
}
#endif
NS_IMPL_ISUPPORTS(AnimValuesStyleRule, nsIStyleRule)
/* virtual */ void
@@ -895,9 +836,8 @@ AnimationCollection::CanThrottleAnimation(TimeStamp aTime)
return false;
}
const auto& info = CommonAnimationManager::sLayerAnimationInfo;
for (size_t i = 0; i < ArrayLength(info); i++) {
auto record = info[i];
for (const LayerAnimationInfo::Record& record :
LayerAnimationInfo::sRecords) {
// We only need to worry about *current* animations here.
// - If we have a newly-finished animation, Animation::CanThrottle will
// detect that and force an unthrottled sample.
-16
View File
@@ -64,10 +64,6 @@ public:
// nsARefreshObserver
void WillRefresh(TimeStamp aTime) override;
#ifdef DEBUG
static void Initialize();
#endif
// NOTE: This can return null after Disconnect().
nsPresContext* PresContext() const { return mPresContext; }
@@ -121,18 +117,6 @@ public:
nsChangeHint mChangeHint;
};
protected:
static const size_t kLayerRecords = 2;
public:
static const LayerAnimationRecord sLayerAnimationInfo[kLayerRecords];
// Will return non-null for any property with the
// CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag; should only be called
// on such properties.
static const LayerAnimationRecord*
LayerAnimationRecordFor(nsCSSProperty aProperty);
protected:
virtual ~CommonAnimationManager();
+53
View File
@@ -0,0 +1,53 @@
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LayerAnimationInfo.h"
#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
namespace mozilla {
/* static */ const LayerAnimationInfo::Record LayerAnimationInfo::sRecords[] =
{ { eCSSProperty_transform,
nsDisplayItem::TYPE_TRANSFORM,
nsChangeHint_UpdateTransformLayer },
{ eCSSProperty_opacity,
nsDisplayItem::TYPE_OPACITY,
nsChangeHint_UpdateOpacityLayer } };
#ifdef DEBUG
/* static */ void
LayerAnimationInfo::Initialize()
{
for (const Record& record : sRecords) {
MOZ_ASSERT(nsCSSProps::PropHasFlags(record.mProperty,
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
"CSS property with entry in LayerAnimation::sRecords does not "
"have the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag");
}
// Check that every property with the flag for animating on the
// compositor has an entry in LayerAnimationInfo::sRecords.
for (nsCSSProperty prop = nsCSSProperty(0);
prop < eCSSProperty_COUNT;
prop = nsCSSProperty(prop + 1)) {
if (nsCSSProps::PropHasFlags(prop,
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) {
bool found = false;
for (const Record& record : sRecords) {
if (record.mProperty == prop) {
found = true;
break;
}
}
MOZ_ASSERT(found,
"CSS property with the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR "
"flag does not have an entry in LayerAnimationInfo::sRecords");
}
}
}
#endif
} // namespace mozilla
+33
View File
@@ -0,0 +1,33 @@
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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_LayerAnimationInfo_h
#define mozilla_LayerAnimationInfo_h
#include "nsChangeHint.h"
#include "nsCSSProperty.h"
#include "nsDisplayList.h" // For nsDisplayItem::Type
namespace mozilla {
struct LayerAnimationInfo {
#ifdef DEBUG
static void Initialize();
#endif
// For CSS properties that may be animated on a separate layer, represents
// a record of the corresponding layer type and change hint.
struct Record {
nsCSSProperty mProperty;
nsDisplayItem::Type mLayerType;
nsChangeHint mChangeHint;
};
static const size_t kRecords = 2;
static const Record sRecords[kRecords];
};
} // namespace mozilla
#endif /* !defined(mozilla_LayerAnimationInfo_h) */
+2
View File
@@ -85,6 +85,7 @@ EXPORTS.mozilla += [
'CSSVariableResolver.h',
'CSSVariableValues.h',
'IncrementalClearCOMRuleArray.h',
'LayerAnimationInfo.h',
'RuleNodeCacheConditions.h',
'RuleProcessorCache.h',
'StyleAnimationValue.h',
@@ -130,6 +131,7 @@ UNIFIED_SOURCES += [
'FontFaceSetIterator.cpp',
'ImageLoader.cpp',
'IncrementalClearCOMRuleArray.cpp',
'LayerAnimationInfo.cpp',
'Loader.cpp',
'MediaQueryList.cpp',
'nsAnimationManager.cpp',