1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "gtest/gtest.h"
8
9 #include "mozilla/gfx/2D.h"
10 #include "Common.h"
11 #include "Decoder.h"
12 #include "DecoderFactory.h"
13 #include "SourceBuffer.h"
14 #include "SurfacePipe.h"
15
16 using namespace mozilla;
17 using namespace mozilla::gfx;
18 using namespace mozilla::image;
19
20 namespace mozilla {
21 namespace image {
22
23 class TestSurfacePipeFactory {
24 public:
SimpleSurfacePipe()25 static SurfacePipe SimpleSurfacePipe() {
26 SurfacePipe pipe;
27 return pipe;
28 }
29
30 template <typename T>
SurfacePipeFromPipeline(T && aPipeline)31 static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline) {
32 return SurfacePipe{std::move(aPipeline)};
33 }
34
35 private:
TestSurfacePipeFactory()36 TestSurfacePipeFactory() {}
37 };
38
39 } // namespace image
40 } // namespace mozilla
41
CheckSurfacePipeMethodResults(SurfacePipe * aPipe,image::Decoder * aDecoder,const IntRect & aRect=IntRect (0,0,100,100))42 void CheckSurfacePipeMethodResults(SurfacePipe* aPipe, image::Decoder* aDecoder,
43 const IntRect& aRect = IntRect(0, 0, 100,
44 100)) {
45 // Check that the pipeline ended up in the state we expect. Note that we're
46 // explicitly testing the SurfacePipe versions of these methods, so we don't
47 // want to use AssertCorrectPipelineFinalState() here.
48 EXPECT_TRUE(aPipe->IsSurfaceFinished());
49 Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
50 EXPECT_TRUE(invalidRect.isSome());
51 EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
52 EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
53
54 // Check the generated image.
55 CheckGeneratedImage(aDecoder, aRect);
56
57 // Reset and clear the image before the next test.
58 aPipe->ResetToFirstRow();
59 EXPECT_FALSE(aPipe->IsSurfaceFinished());
60 invalidRect = aPipe->TakeInvalidRect();
61 EXPECT_TRUE(invalidRect.isNothing());
62
63 uint32_t count = 0;
64 auto result = aPipe->WritePixels<uint32_t>([&]() {
65 ++count;
66 return AsVariant(BGRAColor::Transparent().AsPixel());
67 });
68 EXPECT_EQ(WriteState::FINISHED, result);
69 EXPECT_EQ(100u * 100u, count);
70
71 EXPECT_TRUE(aPipe->IsSurfaceFinished());
72 invalidRect = aPipe->TakeInvalidRect();
73 EXPECT_TRUE(invalidRect.isSome());
74 EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
75 EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
76
77 aPipe->ResetToFirstRow();
78 EXPECT_FALSE(aPipe->IsSurfaceFinished());
79 invalidRect = aPipe->TakeInvalidRect();
80 EXPECT_TRUE(invalidRect.isNothing());
81 }
82
83 class ImageSurfacePipeIntegration : public ::testing::Test {
84 protected:
85 AutoInitializeImageLib mInit;
86 };
87
TEST_F(ImageSurfacePipeIntegration,SurfacePipe)88 TEST_F(ImageSurfacePipeIntegration, SurfacePipe) {
89 // Test that SurfacePipe objects can be initialized and move constructed.
90 SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
91
92 // Test that SurfacePipe objects can be move assigned.
93 pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
94
95 // Test that SurfacePipe objects can be initialized with a pipeline.
96 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
97 ASSERT_TRUE(decoder != nullptr);
98
99 auto sink = MakeUnique<SurfaceSink>();
100 nsresult rv = sink->Configure(
101 SurfaceConfig{decoder, IntSize(100, 100), SurfaceFormat::OS_RGBA, false});
102 ASSERT_TRUE(NS_SUCCEEDED(rv));
103
104 pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
105
106 // Test that WritePixels() gets passed through to the underlying pipeline.
107 {
108 uint32_t count = 0;
109 auto result = pipe.WritePixels<uint32_t>([&]() {
110 ++count;
111 return AsVariant(BGRAColor::Green().AsPixel());
112 });
113 EXPECT_EQ(WriteState::FINISHED, result);
114 EXPECT_EQ(100u * 100u, count);
115 CheckSurfacePipeMethodResults(&pipe, decoder);
116 }
117
118 // Create a buffer the same size as one row of the surface, containing all
119 // green pixels. We'll use this for the WriteBuffer() tests.
120 uint32_t buffer[100];
121 for (int i = 0; i < 100; ++i) {
122 buffer[i] = BGRAColor::Green().AsPixel();
123 }
124
125 // Test that WriteBuffer() gets passed through to the underlying pipeline.
126 {
127 uint32_t count = 0;
128 WriteState result = WriteState::NEED_MORE_DATA;
129 while (result == WriteState::NEED_MORE_DATA) {
130 result = pipe.WriteBuffer(buffer);
131 ++count;
132 }
133 EXPECT_EQ(WriteState::FINISHED, result);
134 EXPECT_EQ(100u, count);
135 CheckSurfacePipeMethodResults(&pipe, decoder);
136 }
137
138 // Test that the 3 argument version of WriteBuffer() gets passed through to
139 // the underlying pipeline.
140 {
141 uint32_t count = 0;
142 WriteState result = WriteState::NEED_MORE_DATA;
143 while (result == WriteState::NEED_MORE_DATA) {
144 result = pipe.WriteBuffer(buffer, 0, 100);
145 ++count;
146 }
147 EXPECT_EQ(WriteState::FINISHED, result);
148 EXPECT_EQ(100u, count);
149 CheckSurfacePipeMethodResults(&pipe, decoder);
150 }
151
152 // Test that WritePixelBlocks() gets passed through to the underlying
153 // pipeline.
154 {
155 uint32_t count = 0;
156 WriteState result = pipe.WritePixelBlocks<uint32_t>(
157 [&](uint32_t* aBlockStart, int32_t aLength) {
158 ++count;
159 EXPECT_EQ(int32_t(100), aLength);
160 memcpy(aBlockStart, buffer, 100 * sizeof(uint32_t));
161 return MakeTuple(int32_t(100), Maybe<WriteState>());
162 });
163
164 EXPECT_EQ(WriteState::FINISHED, result);
165 EXPECT_EQ(100u, count);
166 CheckSurfacePipeMethodResults(&pipe, decoder);
167 }
168
169 // Test that WriteEmptyRow() gets passed through to the underlying pipeline.
170 {
171 uint32_t count = 0;
172 WriteState result = WriteState::NEED_MORE_DATA;
173 while (result == WriteState::NEED_MORE_DATA) {
174 result = pipe.WriteEmptyRow();
175 ++count;
176 }
177 EXPECT_EQ(WriteState::FINISHED, result);
178 EXPECT_EQ(100u, count);
179 CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
180 }
181
182 // Mark the frame as finished so we don't get an assertion.
183 RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
184 currentFrame->Finish();
185 }
186
TEST_F(ImageSurfacePipeIntegration,DeinterlaceDownscaleWritePixels)187 TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels) {
188 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
189 ASSERT_TRUE(decoder != nullptr);
190
191 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
192 CheckWritePixels(aDecoder, aFilter,
193 /* aOutputRect = */ Some(IntRect(0, 0, 25, 25)));
194 };
195
196 WithFilterPipeline(
197 decoder, test,
198 DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true},
199 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA},
200 SurfaceConfig{decoder, IntSize(25, 25), SurfaceFormat::OS_RGBA, false});
201 }
202
TEST_F(ImageSurfacePipeIntegration,RemoveFrameRectBottomRightDownscaleWritePixels)203 TEST_F(ImageSurfacePipeIntegration,
204 RemoveFrameRectBottomRightDownscaleWritePixels) {
205 // This test case uses a frame rect that extends beyond the borders of the
206 // image to the bottom and to the right. It looks roughly like this (with the
207 // box made of '#'s representing the frame rect):
208 //
209 // +------------+
210 // + +
211 // + +------------+
212 // + +############+
213 // +------+############+
214 // +############+
215 // +------------+
216
217 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
218 ASSERT_TRUE(decoder != nullptr);
219
220 // Note that aInputWriteRect is 100x50 because RemoveFrameRectFilter ignores
221 // trailing rows that don't show up in the output. (Leading rows unfortunately
222 // can't be ignored.) So the action of the pipeline is as follows:
223 //
224 // (1) RemoveFrameRectFilter reads a 100x50 region of the input.
225 // (aInputWriteRect captures this fact.) The remaining 50 rows are ignored
226 // because they extend off the bottom of the image due to the frame rect's
227 // (50, 50) offset. The 50 columns on the right also don't end up in the
228 // output, so ultimately only a 50x50 region in the output contains data
229 // from the input. The filter's output is not 50x50, though, but 100x100,
230 // because what RemoveFrameRectFilter does is introduce blank rows or
231 // columns as necessary to transform an image that needs a frame rect into
232 // an image that doesn't.
233 //
234 // (2) DownscalingFilter reads the output of RemoveFrameRectFilter (100x100)
235 // and downscales it to 20x20.
236 //
237 // (3) The surface owned by SurfaceSink logically has only a 10x10 region
238 // region in it that's non-blank; this is the downscaled version of the
239 // 50x50 region discussed in (1). (aOutputWriteRect captures this fact.)
240 // Some fuzz, as usual, is necessary when dealing with Lanczos
241 // downscaling.
242
243 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
244 CheckWritePixels(aDecoder, aFilter,
245 /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
246 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
247 /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)),
248 /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)),
249 /* aFuzz = */ 0x33);
250 };
251
252 WithFilterPipeline(
253 decoder, test, RemoveFrameRectConfig{IntRect(50, 50, 100, 100)},
254 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA},
255 SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false});
256 }
257
TEST_F(ImageSurfacePipeIntegration,RemoveFrameRectTopLeftDownscaleWritePixels)258 TEST_F(ImageSurfacePipeIntegration,
259 RemoveFrameRectTopLeftDownscaleWritePixels) {
260 // This test case uses a frame rect that extends beyond the borders of the
261 // image to the top and to the left. It looks roughly like this (with the
262 // box made of '#'s representing the frame rect):
263 //
264 // +------------+
265 // +############+
266 // +############+------+
267 // +############+ +
268 // +------------+ +
269 // + +
270 // +------------+
271
272 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
273 ASSERT_TRUE(decoder != nullptr);
274
275 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
276 CheckWritePixels(aDecoder, aFilter,
277 /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
278 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
279 /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)),
280 /* aOutputWriteRect = */ Some(IntRect(0, 0, 10, 10)),
281 /* aFuzz = */ 0x21);
282 };
283
284 WithFilterPipeline(
285 decoder, test, RemoveFrameRectConfig{IntRect(-50, -50, 100, 100)},
286 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA},
287 SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false});
288 }
289
TEST_F(ImageSurfacePipeIntegration,DeinterlaceRemoveFrameRectWritePixels)290 TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels) {
291 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
292 ASSERT_TRUE(decoder != nullptr);
293
294 // Note that aInputRect is the full 100x100 size even though
295 // RemoveFrameRectFilter is part of this pipeline, because deinterlacing
296 // requires reading every row.
297
298 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
299 CheckWritePixels(aDecoder, aFilter,
300 /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
301 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
302 /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)),
303 /* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50)));
304 };
305
306 WithFilterPipeline(
307 decoder, test,
308 DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true},
309 RemoveFrameRectConfig{IntRect(50, 50, 100, 100)},
310 SurfaceConfig{decoder, IntSize(100, 100), SurfaceFormat::OS_RGBA, false});
311 }
312
TEST_F(ImageSurfacePipeIntegration,DeinterlaceRemoveFrameRectDownscaleWritePixels)313 TEST_F(ImageSurfacePipeIntegration,
314 DeinterlaceRemoveFrameRectDownscaleWritePixels) {
315 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
316 ASSERT_TRUE(decoder != nullptr);
317
318 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
319 CheckWritePixels(aDecoder, aFilter,
320 /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
321 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
322 /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)),
323 /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)),
324 /* aFuzz = */ 33);
325 };
326
327 WithFilterPipeline(
328 decoder, test,
329 DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true},
330 RemoveFrameRectConfig{IntRect(50, 50, 100, 100)},
331 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA},
332 SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false});
333 }
334
TEST_F(ImageSurfacePipeIntegration,ConfiguringHugeDeinterlacingBufferFails)335 TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails) {
336 RefPtr<image::Decoder> decoder = CreateTrivialDecoder();
337 ASSERT_TRUE(decoder != nullptr);
338
339 // When DownscalingFilter is used, we may succeed in allocating an output
340 // surface for huge images, because we only need to store the scaled-down
341 // version of the image. However, regardless of downscaling,
342 // DeinterlacingFilter needs to allocate a buffer as large as the size of the
343 // input. This can cause OOMs on operating systems that allow overcommit. This
344 // test makes sure that we reject such allocations.
345 AssertConfiguringPipelineFails(
346 decoder, DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true},
347 DownscalingConfig{IntSize(60000, 60000), SurfaceFormat::OS_RGBA},
348 SurfaceConfig{decoder, IntSize(600, 600), SurfaceFormat::OS_RGBA, false});
349 }
350