/src/mozilla-central/image/test/gtest/Common.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "Common.h" |
7 | | |
8 | | #include <cstdlib> |
9 | | |
10 | | #include "gfxPrefs.h" |
11 | | #include "nsDirectoryServiceDefs.h" |
12 | | #include "nsIDirectoryService.h" |
13 | | #include "nsIFile.h" |
14 | | #include "nsIInputStream.h" |
15 | | #include "nsIProperties.h" |
16 | | #include "nsNetUtil.h" |
17 | | #include "mozilla/RefPtr.h" |
18 | | #include "nsStreamUtils.h" |
19 | | #include "nsString.h" |
20 | | |
21 | | namespace mozilla { |
22 | | namespace image { |
23 | | |
24 | | using namespace gfx; |
25 | | |
26 | | using std::abs; |
27 | | using std::vector; |
28 | | |
29 | | static bool sImageLibInitialized = false; |
30 | | |
31 | | AutoInitializeImageLib::AutoInitializeImageLib() |
32 | 0 | { |
33 | 0 | if (MOZ_LIKELY(sImageLibInitialized)) { |
34 | 0 | return; |
35 | 0 | } |
36 | 0 | |
37 | 0 | EXPECT_TRUE(NS_IsMainThread()); |
38 | 0 | sImageLibInitialized = true; |
39 | 0 |
|
40 | 0 | // Force sRGB to be consistent with reftests. |
41 | 0 | Preferences::SetBool("gfx.color_management.force_srgb", true); |
42 | 0 |
|
43 | 0 | // Ensure that ImageLib services are initialized. |
44 | 0 | nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1"); |
45 | 0 | EXPECT_TRUE(imgTools != nullptr); |
46 | 0 |
|
47 | 0 | // Ensure gfxPlatform is initialized. |
48 | 0 | gfxPlatform::GetPlatform(); |
49 | 0 |
|
50 | 0 | // Depending on initialization order, it is possible that our pref changes |
51 | 0 | // have not taken effect yet because there are pending gfx-related events on |
52 | 0 | // the main thread. |
53 | 0 | nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); |
54 | 0 | EXPECT_TRUE(mainThread != nullptr); |
55 | 0 |
|
56 | 0 | bool processed; |
57 | 0 | do { |
58 | 0 | processed = false; |
59 | 0 | nsresult rv = mainThread->ProcessNextEvent(false, &processed); |
60 | 0 | EXPECT_TRUE(NS_SUCCEEDED(rv)); |
61 | 0 | } while (processed); |
62 | 0 | } |
63 | | |
64 | | /////////////////////////////////////////////////////////////////////////////// |
65 | | // General Helpers |
66 | | /////////////////////////////////////////////////////////////////////////////// |
67 | | |
68 | | // These macros work like gtest's ASSERT_* macros, except that they can be used |
69 | | // in functions that return values. |
70 | | #define ASSERT_TRUE_OR_RETURN(e, rv) \ |
71 | 0 | EXPECT_TRUE(e); \ |
72 | 0 | if (!(e)) { \ |
73 | 0 | return rv; \ |
74 | 0 | } |
75 | | |
76 | | #define ASSERT_EQ_OR_RETURN(a, b, rv) \ |
77 | 0 | EXPECT_EQ(a, b); \ |
78 | 0 | if ((a) != (b)) { \ |
79 | 0 | return rv; \ |
80 | 0 | } |
81 | | |
82 | | #define ASSERT_GE_OR_RETURN(a, b, rv) \ |
83 | 0 | EXPECT_GE(a, b); \ |
84 | 0 | if (!((a) >= (b))) { \ |
85 | 0 | return rv; \ |
86 | 0 | } |
87 | | |
88 | | #define ASSERT_LE_OR_RETURN(a, b, rv) \ |
89 | 0 | EXPECT_LE(a, b); \ |
90 | 0 | if (!((a) <= (b))) { \ |
91 | 0 | return rv; \ |
92 | 0 | } |
93 | | |
94 | | #define ASSERT_LT_OR_RETURN(a, b, rv) \ |
95 | 0 | EXPECT_LT(a, b); \ |
96 | 0 | if (!((a) < (b))) { \ |
97 | 0 | return rv; \ |
98 | 0 | } |
99 | | |
100 | | already_AddRefed<nsIInputStream> |
101 | | LoadFile(const char* aRelativePath) |
102 | 0 | { |
103 | 0 | nsresult rv; |
104 | 0 |
|
105 | 0 | nsCOMPtr<nsIProperties> dirService = |
106 | 0 | do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); |
107 | 0 | ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr); |
108 | 0 |
|
109 | 0 | // Retrieve the current working directory. |
110 | 0 | nsCOMPtr<nsIFile> file; |
111 | 0 | rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR, |
112 | 0 | NS_GET_IID(nsIFile), getter_AddRefs(file)); |
113 | 0 | ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); |
114 | 0 |
|
115 | 0 | // Construct the final path by appending the working path to the current |
116 | 0 | // working directory. |
117 | 0 | file->AppendNative(nsDependentCString(aRelativePath)); |
118 | 0 |
|
119 | 0 | // Construct an input stream for the requested file. |
120 | 0 | nsCOMPtr<nsIInputStream> inputStream; |
121 | 0 | rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file); |
122 | 0 | ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); |
123 | 0 |
|
124 | 0 | // Ensure the resulting input stream is buffered. |
125 | 0 | if (!NS_InputStreamIsBuffered(inputStream)) { |
126 | 0 | nsCOMPtr<nsIInputStream> bufStream; |
127 | 0 | rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), |
128 | 0 | inputStream.forget(), 1024); |
129 | 0 | ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); |
130 | 0 | inputStream = bufStream; |
131 | 0 | } |
132 | 0 |
|
133 | 0 | return inputStream.forget(); |
134 | 0 | } |
135 | | |
136 | | bool |
137 | | IsSolidColor(SourceSurface* aSurface, |
138 | | BGRAColor aColor, |
139 | | uint8_t aFuzz /* = 0 */) |
140 | 0 | { |
141 | 0 | IntSize size = aSurface->GetSize(); |
142 | 0 | return RectIsSolidColor(aSurface, IntRect(0, 0, size.width, size.height), |
143 | 0 | aColor, aFuzz); |
144 | 0 | } |
145 | | |
146 | | bool |
147 | | IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor) |
148 | 0 | { |
149 | 0 | RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); |
150 | 0 | return PalettedRectIsSolidColor(aDecoder, currentFrame->GetRect(), aColor); |
151 | 0 | } |
152 | | |
153 | | bool |
154 | | RowsAreSolidColor(SourceSurface* aSurface, |
155 | | int32_t aStartRow, |
156 | | int32_t aRowCount, |
157 | | BGRAColor aColor, |
158 | | uint8_t aFuzz /* = 0 */) |
159 | 0 | { |
160 | 0 | IntSize size = aSurface->GetSize(); |
161 | 0 | return RectIsSolidColor(aSurface, IntRect(0, aStartRow, size.width, aRowCount), |
162 | 0 | aColor, aFuzz); |
163 | 0 | } |
164 | | |
165 | | bool |
166 | | PalettedRowsAreSolidColor(Decoder* aDecoder, |
167 | | int32_t aStartRow, |
168 | | int32_t aRowCount, |
169 | | uint8_t aColor) |
170 | 0 | { |
171 | 0 | RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); |
172 | 0 | IntRect frameRect = currentFrame->GetRect(); |
173 | 0 | IntRect solidColorRect(frameRect.X(), aStartRow, frameRect.Width(), aRowCount); |
174 | 0 | return PalettedRectIsSolidColor(aDecoder, solidColorRect, aColor); |
175 | 0 | } |
176 | | |
177 | | bool |
178 | | RectIsSolidColor(SourceSurface* aSurface, |
179 | | const IntRect& aRect, |
180 | | BGRAColor aColor, |
181 | | uint8_t aFuzz /* = 0 */) |
182 | 0 | { |
183 | 0 | IntSize surfaceSize = aSurface->GetSize(); |
184 | 0 | IntRect rect = |
185 | 0 | aRect.Intersect(IntRect(0, 0, surfaceSize.width, surfaceSize.height)); |
186 | 0 |
|
187 | 0 | RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); |
188 | 0 | ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false); |
189 | 0 |
|
190 | 0 | DataSourceSurface::ScopedMap mapping(dataSurface, |
191 | 0 | DataSourceSurface::MapType::READ); |
192 | 0 | ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false); |
193 | 0 | ASSERT_EQ_OR_RETURN(mapping.GetStride(), surfaceSize.width * 4, false); |
194 | 0 |
|
195 | 0 | uint8_t* data = mapping.GetData(); |
196 | 0 | ASSERT_TRUE_OR_RETURN(data != nullptr, false); |
197 | 0 |
|
198 | 0 | BGRAColor pmColor = aColor.Premultiply(); |
199 | 0 | int32_t rowLength = mapping.GetStride(); |
200 | 0 | for (int32_t row = rect.Y(); row < rect.YMost(); ++row) { |
201 | 0 | for (int32_t col = rect.X(); col < rect.XMost(); ++col) { |
202 | 0 | int32_t i = row * rowLength + col * 4; |
203 | 0 | if (aFuzz != 0) { |
204 | 0 | ASSERT_LE_OR_RETURN(abs(pmColor.mBlue - data[i + 0]), aFuzz, false); |
205 | 0 | ASSERT_LE_OR_RETURN(abs(pmColor.mGreen - data[i + 1]), aFuzz, false); |
206 | 0 | ASSERT_LE_OR_RETURN(abs(pmColor.mRed - data[i + 2]), aFuzz, false); |
207 | 0 | ASSERT_LE_OR_RETURN(abs(pmColor.mAlpha - data[i + 3]), aFuzz, false); |
208 | 0 | } else { |
209 | 0 | ASSERT_EQ_OR_RETURN(pmColor.mBlue, data[i + 0], false); |
210 | 0 | ASSERT_EQ_OR_RETURN(pmColor.mGreen, data[i + 1], false); |
211 | 0 | ASSERT_EQ_OR_RETURN(pmColor.mRed, data[i + 2], false); |
212 | 0 | ASSERT_EQ_OR_RETURN(pmColor.mAlpha, data[i + 3], false); |
213 | 0 | } |
214 | 0 | } |
215 | 0 | } |
216 | 0 |
|
217 | 0 | return true; |
218 | 0 | } |
219 | | |
220 | | bool |
221 | | PalettedRectIsSolidColor(Decoder* aDecoder, const IntRect& aRect, uint8_t aColor) |
222 | 0 | { |
223 | 0 | RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); |
224 | 0 | uint8_t* imageData; |
225 | 0 | uint32_t imageLength; |
226 | 0 | currentFrame->GetImageData(&imageData, &imageLength); |
227 | 0 | ASSERT_TRUE_OR_RETURN(imageData, false); |
228 | 0 |
|
229 | 0 | // Clamp to the frame rect. If any pixels outside the frame rect are included, |
230 | 0 | // we immediately fail, because such pixels don't have any "color" in the |
231 | 0 | // sense this function measures - they're transparent, and that doesn't |
232 | 0 | // necessarily correspond to any color palette index at all. |
233 | 0 | IntRect frameRect = currentFrame->GetRect(); |
234 | 0 | ASSERT_EQ_OR_RETURN(imageLength, uint32_t(frameRect.Area()), false); |
235 | 0 | IntRect rect = aRect.Intersect(frameRect); |
236 | 0 | ASSERT_EQ_OR_RETURN(rect.Area(), aRect.Area(), false); |
237 | 0 |
|
238 | 0 | // Translate |rect| by |frameRect.TopLeft()| to reflect the fact that the |
239 | 0 | // frame rect's offset doesn't actually mean anything in terms of the |
240 | 0 | // in-memory representation of the surface. The image data starts at the upper |
241 | 0 | // left corner of the frame rect, in other words. |
242 | 0 | rect -= frameRect.TopLeft(); |
243 | 0 |
|
244 | 0 | // Walk through the image data and make sure that the entire rect has the |
245 | 0 | // palette index |aColor|. |
246 | 0 | int32_t rowLength = frameRect.Width(); |
247 | 0 | for (int32_t row = rect.Y(); row < rect.YMost(); ++row) { |
248 | 0 | for (int32_t col = rect.X(); col < rect.XMost(); ++col) { |
249 | 0 | int32_t i = row * rowLength + col; |
250 | 0 | ASSERT_EQ_OR_RETURN(aColor, imageData[i], false); |
251 | 0 | } |
252 | 0 | } |
253 | 0 |
|
254 | 0 | return true; |
255 | 0 | } |
256 | | |
257 | | bool |
258 | | RowHasPixels(SourceSurface* aSurface, |
259 | | int32_t aRow, |
260 | | const vector<BGRAColor>& aPixels) |
261 | 0 | { |
262 | 0 | ASSERT_GE_OR_RETURN(aRow, 0, false); |
263 | 0 |
|
264 | 0 | IntSize surfaceSize = aSurface->GetSize(); |
265 | 0 | ASSERT_EQ_OR_RETURN(aPixels.size(), size_t(surfaceSize.width), false); |
266 | 0 | ASSERT_LT_OR_RETURN(aRow, surfaceSize.height, false); |
267 | 0 |
|
268 | 0 | RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); |
269 | 0 | ASSERT_TRUE_OR_RETURN(dataSurface, false); |
270 | 0 |
|
271 | 0 | DataSourceSurface::ScopedMap mapping(dataSurface, |
272 | 0 | DataSourceSurface::MapType::READ); |
273 | 0 | ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false); |
274 | 0 | ASSERT_EQ_OR_RETURN(mapping.GetStride(), surfaceSize.width * 4, false); |
275 | 0 |
|
276 | 0 | uint8_t* data = mapping.GetData(); |
277 | 0 | ASSERT_TRUE_OR_RETURN(data != nullptr, false); |
278 | 0 |
|
279 | 0 | int32_t rowLength = mapping.GetStride(); |
280 | 0 | for (int32_t col = 0; col < surfaceSize.width; ++col) { |
281 | 0 | int32_t i = aRow * rowLength + col * 4; |
282 | 0 | ASSERT_EQ_OR_RETURN(aPixels[col].mBlue, data[i + 0], false); |
283 | 0 | ASSERT_EQ_OR_RETURN(aPixels[col].mGreen, data[i + 1], false); |
284 | 0 | ASSERT_EQ_OR_RETURN(aPixels[col].mRed, data[i + 2], false); |
285 | 0 | ASSERT_EQ_OR_RETURN(aPixels[col].mAlpha, data[i + 3], false); |
286 | 0 | } |
287 | 0 |
|
288 | 0 | return true; |
289 | 0 | } |
290 | | |
291 | | |
292 | | /////////////////////////////////////////////////////////////////////////////// |
293 | | // SurfacePipe Helpers |
294 | | /////////////////////////////////////////////////////////////////////////////// |
295 | | |
296 | | already_AddRefed<Decoder> |
297 | | CreateTrivialDecoder() |
298 | 0 | { |
299 | 0 | gfxPrefs::GetSingleton(); |
300 | 0 | DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif"); |
301 | 0 | auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>(); |
302 | 0 | RefPtr<Decoder> decoder = |
303 | 0 | DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(), |
304 | 0 | DefaultDecoderFlags(), |
305 | 0 | DefaultSurfaceFlags()); |
306 | 0 | return decoder.forget(); |
307 | 0 | } |
308 | | |
309 | | void |
310 | | AssertCorrectPipelineFinalState(SurfaceFilter* aFilter, |
311 | | const gfx::IntRect& aInputSpaceRect, |
312 | | const gfx::IntRect& aOutputSpaceRect) |
313 | 0 | { |
314 | 0 | EXPECT_TRUE(aFilter->IsSurfaceFinished()); |
315 | 0 | Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect(); |
316 | 0 | EXPECT_TRUE(invalidRect.isSome()); |
317 | 0 | EXPECT_EQ(aInputSpaceRect, invalidRect->mInputSpaceRect); |
318 | 0 | EXPECT_EQ(aOutputSpaceRect, invalidRect->mOutputSpaceRect); |
319 | 0 | } |
320 | | |
321 | | void |
322 | | CheckGeneratedImage(Decoder* aDecoder, |
323 | | const IntRect& aRect, |
324 | | uint8_t aFuzz /* = 0 */) |
325 | 0 | { |
326 | 0 | RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); |
327 | 0 | RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface(); |
328 | 0 | const IntSize surfaceSize = surface->GetSize(); |
329 | 0 |
|
330 | 0 | // This diagram shows how the surface is divided into regions that the code |
331 | 0 | // below tests for the correct content. The output rect is the bounds of the |
332 | 0 | // region labeled 'C'. |
333 | 0 | // |
334 | 0 | // +---------------------------+ |
335 | 0 | // | A | |
336 | 0 | // +---------+--------+--------+ |
337 | 0 | // | B | C | D | |
338 | 0 | // +---------+--------+--------+ |
339 | 0 | // | E | |
340 | 0 | // +---------------------------+ |
341 | 0 |
|
342 | 0 | // Check that the output rect itself is green. (Region 'C'.) |
343 | 0 | EXPECT_TRUE(RectIsSolidColor(surface, aRect, BGRAColor::Green(), aFuzz)); |
344 | 0 |
|
345 | 0 | // Check that the area above the output rect is transparent. (Region 'A'.) |
346 | 0 | EXPECT_TRUE(RectIsSolidColor(surface, |
347 | 0 | IntRect(0, 0, surfaceSize.width, aRect.Y()), |
348 | 0 | BGRAColor::Transparent(), aFuzz)); |
349 | 0 |
|
350 | 0 | // Check that the area to the left of the output rect is transparent. (Region 'B'.) |
351 | 0 | EXPECT_TRUE(RectIsSolidColor(surface, |
352 | 0 | IntRect(0, aRect.Y(), aRect.X(), aRect.YMost()), |
353 | 0 | BGRAColor::Transparent(), aFuzz)); |
354 | 0 |
|
355 | 0 | // Check that the area to the right of the output rect is transparent. (Region 'D'.) |
356 | 0 | const int32_t widthOnRight = surfaceSize.width - aRect.XMost(); |
357 | 0 | EXPECT_TRUE(RectIsSolidColor(surface, |
358 | 0 | IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()), |
359 | 0 | BGRAColor::Transparent(), aFuzz)); |
360 | 0 |
|
361 | 0 | // Check that the area below the output rect is transparent. (Region 'E'.) |
362 | 0 | const int32_t heightBelow = surfaceSize.height - aRect.YMost(); |
363 | 0 | EXPECT_TRUE(RectIsSolidColor(surface, |
364 | 0 | IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow), |
365 | 0 | BGRAColor::Transparent(), aFuzz)); |
366 | 0 | } |
367 | | |
368 | | void |
369 | | CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect) |
370 | 0 | { |
371 | 0 | RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); |
372 | 0 | IntSize imageSize = currentFrame->GetImageSize(); |
373 | 0 |
|
374 | 0 | // This diagram shows how the surface is divided into regions that the code |
375 | 0 | // below tests for the correct content. The output rect is the bounds of the |
376 | 0 | // region labeled 'C'. |
377 | 0 | // |
378 | 0 | // +---------------------------+ |
379 | 0 | // | A | |
380 | 0 | // +---------+--------+--------+ |
381 | 0 | // | B | C | D | |
382 | 0 | // +---------+--------+--------+ |
383 | 0 | // | E | |
384 | 0 | // +---------------------------+ |
385 | 0 |
|
386 | 0 | // Check that the output rect itself is all 255's. (Region 'C'.) |
387 | 0 | EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, aRect, 255)); |
388 | 0 |
|
389 | 0 | // Check that the area above the output rect is all 0's. (Region 'A'.) |
390 | 0 | EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, |
391 | 0 | IntRect(0, 0, imageSize.width, aRect.Y()), |
392 | 0 | 0)); |
393 | 0 |
|
394 | 0 | // Check that the area to the left of the output rect is all 0's. (Region 'B'.) |
395 | 0 | EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, |
396 | 0 | IntRect(0, aRect.Y(), aRect.X(), aRect.YMost()), |
397 | 0 | 0)); |
398 | 0 |
|
399 | 0 | // Check that the area to the right of the output rect is all 0's. (Region 'D'.) |
400 | 0 | const int32_t widthOnRight = imageSize.width - aRect.XMost(); |
401 | 0 | EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, |
402 | 0 | IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()), |
403 | 0 | 0)); |
404 | 0 |
|
405 | 0 | // Check that the area below the output rect is transparent. (Region 'E'.) |
406 | 0 | const int32_t heightBelow = imageSize.height - aRect.YMost(); |
407 | 0 | EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, |
408 | 0 | IntRect(0, aRect.YMost(), imageSize.width, heightBelow), |
409 | 0 | 0)); |
410 | 0 | } |
411 | | |
412 | | void |
413 | | CheckWritePixels(Decoder* aDecoder, |
414 | | SurfaceFilter* aFilter, |
415 | | const Maybe<IntRect>& aOutputRect /* = Nothing() */, |
416 | | const Maybe<IntRect>& aInputRect /* = Nothing() */, |
417 | | const Maybe<IntRect>& aInputWriteRect /* = Nothing() */, |
418 | | const Maybe<IntRect>& aOutputWriteRect /* = Nothing() */, |
419 | | uint8_t aFuzz /* = 0 */) |
420 | 0 | { |
421 | 0 | IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100)); |
422 | 0 | IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100)); |
423 | 0 | IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect); |
424 | 0 | IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect); |
425 | 0 |
|
426 | 0 | // Fill the image. |
427 | 0 | int32_t count = 0; |
428 | 0 | auto result = aFilter->WritePixels<uint32_t>([&] { |
429 | 0 | ++count; |
430 | 0 | return AsVariant(BGRAColor::Green().AsPixel()); |
431 | 0 | }); |
432 | 0 | EXPECT_EQ(WriteState::FINISHED, result); |
433 | 0 | EXPECT_EQ(inputWriteRect.Width() * inputWriteRect.Height(), count); |
434 | 0 |
|
435 | 0 | AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect); |
436 | 0 |
|
437 | 0 | // Attempt to write more data and make sure nothing changes. |
438 | 0 | const int32_t oldCount = count; |
439 | 0 | result = aFilter->WritePixels<uint32_t>([&] { |
440 | 0 | ++count; |
441 | 0 | return AsVariant(BGRAColor::Green().AsPixel()); |
442 | 0 | }); |
443 | 0 | EXPECT_EQ(oldCount, count); |
444 | 0 | EXPECT_EQ(WriteState::FINISHED, result); |
445 | 0 | EXPECT_TRUE(aFilter->IsSurfaceFinished()); |
446 | 0 | Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect(); |
447 | 0 | EXPECT_TRUE(invalidRect.isNothing()); |
448 | 0 |
|
449 | 0 | // Attempt to advance to the next row and make sure nothing changes. |
450 | 0 | aFilter->AdvanceRow(); |
451 | 0 | EXPECT_TRUE(aFilter->IsSurfaceFinished()); |
452 | 0 | invalidRect = aFilter->TakeInvalidRect(); |
453 | 0 | EXPECT_TRUE(invalidRect.isNothing()); |
454 | 0 |
|
455 | 0 | // Check that the generated image is correct. |
456 | 0 | CheckGeneratedImage(aDecoder, outputWriteRect, aFuzz); |
457 | 0 | } |
458 | | |
459 | | void |
460 | | CheckPalettedWritePixels(Decoder* aDecoder, |
461 | | SurfaceFilter* aFilter, |
462 | | const Maybe<IntRect>& aOutputRect /* = Nothing() */, |
463 | | const Maybe<IntRect>& aInputRect /* = Nothing() */, |
464 | | const Maybe<IntRect>& aInputWriteRect /* = Nothing() */, |
465 | | const Maybe<IntRect>& aOutputWriteRect /* = Nothing() */, |
466 | | uint8_t aFuzz /* = 0 */) |
467 | 0 | { |
468 | 0 | IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100)); |
469 | 0 | IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100)); |
470 | 0 | IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect); |
471 | 0 | IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect); |
472 | 0 |
|
473 | 0 | // Fill the image. |
474 | 0 | int32_t count = 0; |
475 | 0 | auto result = aFilter->WritePixels<uint8_t>([&] { |
476 | 0 | ++count; |
477 | 0 | return AsVariant(uint8_t(255)); |
478 | 0 | }); |
479 | 0 | EXPECT_EQ(WriteState::FINISHED, result); |
480 | 0 | EXPECT_EQ(inputWriteRect.Width() * inputWriteRect.Height(), count); |
481 | 0 |
|
482 | 0 | AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect); |
483 | 0 |
|
484 | 0 | // Attempt to write more data and make sure nothing changes. |
485 | 0 | const int32_t oldCount = count; |
486 | 0 | result = aFilter->WritePixels<uint8_t>([&] { |
487 | 0 | ++count; |
488 | 0 | return AsVariant(uint8_t(255)); |
489 | 0 | }); |
490 | 0 | EXPECT_EQ(oldCount, count); |
491 | 0 | EXPECT_EQ(WriteState::FINISHED, result); |
492 | 0 | EXPECT_TRUE(aFilter->IsSurfaceFinished()); |
493 | 0 | Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect(); |
494 | 0 | EXPECT_TRUE(invalidRect.isNothing()); |
495 | 0 |
|
496 | 0 | // Attempt to advance to the next row and make sure nothing changes. |
497 | 0 | aFilter->AdvanceRow(); |
498 | 0 | EXPECT_TRUE(aFilter->IsSurfaceFinished()); |
499 | 0 | invalidRect = aFilter->TakeInvalidRect(); |
500 | 0 | EXPECT_TRUE(invalidRect.isNothing()); |
501 | 0 |
|
502 | 0 | // Check that the generated image is correct. |
503 | 0 | RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); |
504 | 0 | uint8_t* imageData; |
505 | 0 | uint32_t imageLength; |
506 | 0 | currentFrame->GetImageData(&imageData, &imageLength); |
507 | 0 | ASSERT_TRUE(imageData != nullptr); |
508 | 0 | ASSERT_EQ(outputWriteRect.Width() * outputWriteRect.Height(), int32_t(imageLength)); |
509 | 0 | for (uint32_t i = 0; i < imageLength; ++i) { |
510 | 0 | ASSERT_EQ(uint8_t(255), imageData[i]); |
511 | 0 | } |
512 | 0 | } |
513 | | |
514 | | |
515 | | /////////////////////////////////////////////////////////////////////////////// |
516 | | // Test Data |
517 | | /////////////////////////////////////////////////////////////////////////////// |
518 | | |
519 | | ImageTestCase GreenPNGTestCase() |
520 | 0 | { |
521 | 0 | return ImageTestCase("green.png", "image/png", IntSize(100, 100)); |
522 | 0 | } |
523 | | |
524 | | ImageTestCase GreenGIFTestCase() |
525 | 0 | { |
526 | 0 | return ImageTestCase("green.gif", "image/gif", IntSize(100, 100)); |
527 | 0 | } |
528 | | |
529 | | ImageTestCase GreenJPGTestCase() |
530 | 0 | { |
531 | 0 | return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100), |
532 | 0 | TEST_CASE_IS_FUZZY); |
533 | 0 | } |
534 | | |
535 | | ImageTestCase GreenBMPTestCase() |
536 | 0 | { |
537 | 0 | return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100)); |
538 | 0 | } |
539 | | |
540 | | ImageTestCase GreenICOTestCase() |
541 | 0 | { |
542 | 0 | // This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default |
543 | 0 | // when the BMP is embedded in an ICO, so it's transparent. |
544 | 0 | return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100), |
545 | 0 | TEST_CASE_IS_TRANSPARENT); |
546 | 0 | } |
547 | | |
548 | | ImageTestCase GreenIconTestCase() |
549 | 0 | { |
550 | 0 | return ImageTestCase("green.icon", "image/icon", IntSize(100, 100), |
551 | 0 | TEST_CASE_IS_TRANSPARENT); |
552 | 0 | } |
553 | | |
554 | | ImageTestCase GreenFirstFrameAnimatedGIFTestCase() |
555 | 0 | { |
556 | 0 | return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100), |
557 | 0 | TEST_CASE_IS_ANIMATED); |
558 | 0 | } |
559 | | |
560 | | ImageTestCase GreenFirstFrameAnimatedPNGTestCase() |
561 | 0 | { |
562 | 0 | return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100), |
563 | 0 | TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED); |
564 | 0 | } |
565 | | |
566 | | ImageTestCase CorruptTestCase() |
567 | 0 | { |
568 | 0 | return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100), |
569 | 0 | TEST_CASE_HAS_ERROR); |
570 | 0 | } |
571 | | |
572 | | ImageTestCase CorruptBMPWithTruncatedHeader() |
573 | 0 | { |
574 | 0 | // This BMP has a header which is truncated right between the BIH and the |
575 | 0 | // bitfields, which is a particularly error-prone place w.r.t. the BMP decoder |
576 | 0 | // state machine. |
577 | 0 | return ImageTestCase("invalid-truncated-metadata.bmp", "image/bmp", |
578 | 0 | IntSize(100, 100), TEST_CASE_HAS_ERROR); |
579 | 0 | } |
580 | | |
581 | | ImageTestCase CorruptICOWithBadBMPWidthTestCase() |
582 | 0 | { |
583 | 0 | // This ICO contains a BMP icon which has a width that doesn't match the size |
584 | 0 | // listed in the corresponding ICO directory entry. |
585 | 0 | return ImageTestCase("corrupt-with-bad-bmp-width.ico", "image/x-icon", |
586 | 0 | IntSize(100, 100), TEST_CASE_HAS_ERROR); |
587 | 0 | } |
588 | | |
589 | | ImageTestCase CorruptICOWithBadBMPHeightTestCase() |
590 | 0 | { |
591 | 0 | // This ICO contains a BMP icon which has a height that doesn't match the size |
592 | 0 | // listed in the corresponding ICO directory entry. |
593 | 0 | return ImageTestCase("corrupt-with-bad-bmp-height.ico", "image/x-icon", |
594 | 0 | IntSize(100, 100), TEST_CASE_HAS_ERROR); |
595 | 0 | } |
596 | | |
597 | | ImageTestCase CorruptICOWithBadBppTestCase() |
598 | 0 | { |
599 | 0 | // This test case is an ICO with a BPP (15) in the ICO header which differs |
600 | 0 | // from that in the BMP header itself (1). It should ignore the ICO BPP when |
601 | 0 | // the BMP BPP is available and thus correctly decode the image. |
602 | 0 | return ImageTestCase("corrupt-with-bad-ico-bpp.ico", "image/x-icon", |
603 | 0 | IntSize(100, 100), TEST_CASE_IS_TRANSPARENT); |
604 | 0 | } |
605 | | |
606 | | ImageTestCase TransparentPNGTestCase() |
607 | 0 | { |
608 | 0 | return ImageTestCase("transparent.png", "image/png", IntSize(32, 32), |
609 | 0 | TEST_CASE_IS_TRANSPARENT); |
610 | 0 | } |
611 | | |
612 | | ImageTestCase TransparentGIFTestCase() |
613 | 0 | { |
614 | 0 | return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16), |
615 | 0 | TEST_CASE_IS_TRANSPARENT); |
616 | 0 | } |
617 | | |
618 | | ImageTestCase FirstFramePaddingGIFTestCase() |
619 | 0 | { |
620 | 0 | return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16), |
621 | 0 | TEST_CASE_IS_TRANSPARENT); |
622 | 0 | } |
623 | | |
624 | | ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags) |
625 | 0 | { |
626 | 0 | // This is a BMP that is only transparent when decoded as if it is within an |
627 | 0 | // ICO file. (Note: aFlags needs to be set to TEST_CASE_DEFAULT_FLAGS or |
628 | 0 | // TEST_CASE_IS_TRANSPARENT accordingly.) |
629 | 0 | return ImageTestCase("transparent-if-within-ico.bmp", "image/bmp", |
630 | 0 | IntSize(32, 32), aFlags); |
631 | 0 | } |
632 | | |
633 | | ImageTestCase RLE4BMPTestCase() |
634 | 0 | { |
635 | 0 | return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240), |
636 | 0 | TEST_CASE_IS_TRANSPARENT); |
637 | 0 | } |
638 | | |
639 | | ImageTestCase RLE8BMPTestCase() |
640 | 0 | { |
641 | 0 | return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32), |
642 | 0 | TEST_CASE_IS_TRANSPARENT); |
643 | 0 | } |
644 | | |
645 | | ImageTestCase NoFrameDelayGIFTestCase() |
646 | 0 | { |
647 | 0 | // This is an invalid (or at least, questionably valid) GIF that's animated |
648 | 0 | // even though it specifies a frame delay of zero. It's animated, but it's not |
649 | 0 | // marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that |
650 | 0 | // it's animated. |
651 | 0 | return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100)); |
652 | 0 | } |
653 | | |
654 | | ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase() |
655 | 0 | { |
656 | 0 | // This is a corrupt GIF that has extra image sub blocks between the first and |
657 | 0 | // second frame. |
658 | 0 | return ImageTestCase("animated-with-extra-image-sub-blocks.gif", "image/gif", |
659 | 0 | IntSize(100, 100)); |
660 | 0 | } |
661 | | |
662 | | ImageTestCase DownscaledPNGTestCase() |
663 | 0 | { |
664 | 0 | // This testcase (and all the other "downscaled") testcases) consists of 25 |
665 | 0 | // lines of green, followed by 25 lines of red, followed by 25 lines of green, |
666 | 0 | // followed by 25 more lines of red. It's intended that tests downscale it |
667 | 0 | // from 100x100 to 20x20, so we specify a 20x20 output size. |
668 | 0 | return ImageTestCase("downscaled.png", "image/png", IntSize(100, 100), |
669 | 0 | IntSize(20, 20)); |
670 | 0 | } |
671 | | |
672 | | ImageTestCase DownscaledGIFTestCase() |
673 | 0 | { |
674 | 0 | return ImageTestCase("downscaled.gif", "image/gif", IntSize(100, 100), |
675 | 0 | IntSize(20, 20)); |
676 | 0 | } |
677 | | |
678 | | ImageTestCase DownscaledJPGTestCase() |
679 | 0 | { |
680 | 0 | return ImageTestCase("downscaled.jpg", "image/jpeg", IntSize(100, 100), |
681 | 0 | IntSize(20, 20)); |
682 | 0 | } |
683 | | |
684 | | ImageTestCase DownscaledBMPTestCase() |
685 | 0 | { |
686 | 0 | return ImageTestCase("downscaled.bmp", "image/bmp", IntSize(100, 100), |
687 | 0 | IntSize(20, 20)); |
688 | 0 | } |
689 | | |
690 | | ImageTestCase DownscaledICOTestCase() |
691 | 0 | { |
692 | 0 | return ImageTestCase("downscaled.ico", "image/x-icon", IntSize(100, 100), |
693 | 0 | IntSize(20, 20), TEST_CASE_IS_TRANSPARENT); |
694 | 0 | } |
695 | | |
696 | | ImageTestCase DownscaledIconTestCase() |
697 | 0 | { |
698 | 0 | return ImageTestCase("downscaled.icon", "image/icon", IntSize(100, 100), |
699 | 0 | IntSize(20, 20), TEST_CASE_IS_TRANSPARENT); |
700 | 0 | } |
701 | | |
702 | | ImageTestCase DownscaledTransparentICOWithANDMaskTestCase() |
703 | 0 | { |
704 | 0 | // This test case is an ICO with AND mask transparency. We want to ensure that |
705 | 0 | // we can downscale it without crashing or triggering ASAN failures, but its |
706 | 0 | // content isn't simple to verify, so for now we don't check the output. |
707 | 0 | return ImageTestCase("transparent-ico-with-and-mask.ico", "image/x-icon", |
708 | 0 | IntSize(32, 32), IntSize(20, 20), |
709 | 0 | TEST_CASE_IS_TRANSPARENT | TEST_CASE_IGNORE_OUTPUT); |
710 | 0 | } |
711 | | |
712 | | ImageTestCase TruncatedSmallGIFTestCase() |
713 | 0 | { |
714 | 0 | return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1)); |
715 | 0 | } |
716 | | |
717 | | ImageTestCase LargeICOWithBMPTestCase() |
718 | 0 | { |
719 | 0 | return ImageTestCase("green-large-bmp.ico", "image/x-icon", IntSize(256, 256), |
720 | 0 | TEST_CASE_IS_TRANSPARENT); |
721 | 0 | } |
722 | | |
723 | | ImageTestCase LargeICOWithPNGTestCase() |
724 | 0 | { |
725 | 0 | return ImageTestCase("green-large-png.ico", "image/x-icon", IntSize(512, 512), |
726 | 0 | TEST_CASE_IS_TRANSPARENT); |
727 | 0 | } |
728 | | |
729 | | ImageTestCase GreenMultipleSizesICOTestCase() |
730 | 0 | { |
731 | 0 | return ImageTestCase("green-multiple-sizes.ico", "image/x-icon", |
732 | 0 | IntSize(256, 256)); |
733 | 0 | } |
734 | | |
735 | | } // namespace image |
736 | | } // namespace mozilla |