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)
@@ -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()))
|
||||
{}
|
||||
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -499,12 +499,6 @@ gfxASurface::GetSubpixelAntialiasingEnabled()
|
||||
#endif
|
||||
}
|
||||
|
||||
gfxMemoryLocation
|
||||
gfxASurface::GetMemoryLocation() const
|
||||
{
|
||||
return gfxMemoryLocation::IN_PROCESS_HEAP;
|
||||
}
|
||||
|
||||
int32_t
|
||||
gfxASurface::BytePerPixelFromFormat(gfxImageFormat format)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -61,7 +61,6 @@ UNIFIED_SOURCES += [
|
||||
'Image.cpp',
|
||||
'ImageCacheKey.cpp',
|
||||
'ImageFactory.cpp',
|
||||
'ImageMetadata.cpp',
|
||||
'ImageOps.cpp',
|
||||
'ImageWrapper.cpp',
|
||||
'imgFrame.cpp',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 += [
|
||||
|
||||
|
After Width: | Height: | Size: 317 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 355 B |
|
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
|
||||
|
||||
|
Before Width: | Height: | Size: 92 B After Width: | Height: | Size: 92 B |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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) */
|
||||
@@ -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',
|
||||
|
||||