Ensure low-memory renderers retry failed loads correctly.
[chromium-blink-merge.git] / components / webp_transcode / webp_decoder_unittest.mm
blobb4084d091a8c752c7c6f27ee56376c252080bcf1
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/webp_transcode/webp_decoder.h"
7 #import <CoreGraphics/CoreGraphics.h>
8 #import <Foundation/Foundation.h>
10 #include "base/base_paths.h"
11 #include "base/files/file_path.h"
12 #include "base/ios/ios_util.h"
13 #include "base/logging.h"
14 #include "base/mac/scoped_cftyperef.h"
15 #include "base/mac/scoped_nsobject.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/path_service.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 namespace webp_transcode {
23 namespace {
25 class WebpDecoderDelegate : public WebpDecoder::Delegate {
26  public:
27   WebpDecoderDelegate() : image_([[NSMutableData alloc] init]) {}
29   NSData* GetImage() const { return image_; }
31   // WebpDecoder::Delegate methods.
32   MOCK_METHOD1(OnFinishedDecoding, void(bool success));
33   MOCK_METHOD2(SetImageFeatures,
34                void(size_t total_size, WebpDecoder::DecodedImageFormat format));
35   void OnDataDecoded(NSData* data) override { [image_ appendData:data]; }
37  private:
38   virtual ~WebpDecoderDelegate() {}
40   base::scoped_nsobject<NSMutableData> image_;
43 class WebpDecoderTest : public testing::Test {
44  public:
45   WebpDecoderTest()
46       : delegate_(new WebpDecoderDelegate),
47         decoder_(new WebpDecoder(delegate_.get())) {}
49   NSData* LoadImage(const base::FilePath& filename) {
50     base::FilePath path;
51     PathService::Get(base::DIR_SOURCE_ROOT, &path);
52     path = path.AppendASCII("components/test/data/webp_transcode")
53                .Append(filename);
54     return
55         [NSData dataWithContentsOfFile:base::SysUTF8ToNSString(path.value())];
56   }
58   std::vector<uint8_t>* DecompressData(NSData* data,
59                                        WebpDecoder::DecodedImageFormat format) {
60     base::ScopedCFTypeRef<CGDataProviderRef> provider(
61         CGDataProviderCreateWithCFData((CFDataRef)data));
62     base::ScopedCFTypeRef<CGImageRef> image;
63     switch (format) {
64       case WebpDecoder::JPEG:
65         image.reset(CGImageCreateWithJPEGDataProvider(
66             provider, nullptr, false, kCGRenderingIntentDefault));
67         break;
68       case WebpDecoder::PNG:
69         image.reset(CGImageCreateWithPNGDataProvider(
70             provider, nullptr, false, kCGRenderingIntentDefault));
71         break;
72       case WebpDecoder::TIFF:
73         ADD_FAILURE() << "Data already decompressed";
74         return nil;
75       case WebpDecoder::DECODED_FORMAT_COUNT:
76         ADD_FAILURE() << "Unknown format";
77         return nil;
78     }
79     size_t width = CGImageGetWidth(image);
80     size_t height = CGImageGetHeight(image);
81     base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
82         CGColorSpaceCreateDeviceRGB());
83     size_t bytes_per_pixel = 4;
84     size_t bytes_per_row = bytes_per_pixel * width;
85     size_t bits_per_component = 8;
86     std::vector<uint8_t>* result =
87         new std::vector<uint8_t>(width * height * bytes_per_pixel, 0);
88     base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
89         &result->front(), width, height, bits_per_component, bytes_per_row,
90         color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big));
91     CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
92     // Check that someting has been written in |result|.
93     std::vector<uint8_t> zeroes(width * height * bytes_per_pixel, 0);
94     EXPECT_NE(0, memcmp(&result->front(), &zeroes.front(), zeroes.size()))
95         << "Decompression failed.";
96     return result;
97   }
99   // Compares data, allowing an averaged absolute difference of 1.
100   bool CompareUncompressedData(const uint8_t* ptr_1,
101                                const uint8_t* ptr_2,
102                                size_t size) {
103     uint64_t difference = 0;
104     for (size_t i = 0; i < size; ++i) {
105       // Casting to int to avoid overflow.
106       int error = abs(int(ptr_1[i]) - int(ptr_2[i]));
107       EXPECT_GE(difference + error, difference)
108           << "Image difference too big (overflow).";
109       difference += error;
110     }
111     double average_difference = double(difference) / double(size);
112     DLOG(INFO) << "Average image difference: " << average_difference;
113     return average_difference < 1.5;
114   }
116   bool CheckCompressedImagesEqual(NSData* data_1,
117                                   NSData* data_2,
118                                   WebpDecoder::DecodedImageFormat format) {
119     scoped_ptr<std::vector<uint8_t>> uncompressed_1(
120         DecompressData(data_1, format));
121     scoped_ptr<std::vector<uint8_t>> uncompressed_2(
122         DecompressData(data_2, format));
123     if (uncompressed_1->size() != uncompressed_2->size()) {
124       DLOG(ERROR) << "Image sizes don't match";
125       return false;
126     }
127     return CompareUncompressedData(&uncompressed_1->front(),
128                                    &uncompressed_2->front(),
129                                    uncompressed_1->size());
130   }
132   bool CheckTiffImagesEqual(NSData* image_1, NSData* image_2) {
133     if ([image_1 length] != [image_2 length]) {
134       DLOG(ERROR) << "Image lengths don't match";
135       return false;
136     }
137     // Compare headers.
138     const size_t kHeaderSize = WebpDecoder::GetHeaderSize();
139     NSData* header_1 = [image_1 subdataWithRange:NSMakeRange(0, kHeaderSize)];
140     NSData* header_2 = [image_2 subdataWithRange:NSMakeRange(0, kHeaderSize)];
141     if (!header_1 || !header_2)
142       return false;
143     if (![header_1 isEqualToData:header_2]) {
144       DLOG(ERROR) << "Headers don't match.";
145       return false;
146     }
147     return CompareUncompressedData(
148         static_cast<const uint8_t*>([image_1 bytes]) + kHeaderSize,
149         static_cast<const uint8_t*>([image_2 bytes]) + kHeaderSize,
150         [image_1 length] - kHeaderSize);
151   }
153  protected:
154   scoped_refptr<WebpDecoderDelegate> delegate_;
155   scoped_refptr<WebpDecoder> decoder_;
158 }  // namespace
160 TEST_F(WebpDecoderTest, DecodeToJpeg) {
161 // TODO(droger): This test fails on iOS 9 x64 devices. http://crbug.com/523235
162 #if defined(OS_IOS) && defined(ARCH_CPU_ARM64) && !TARGET_IPHONE_SIMULATOR
163   if (base::ios::IsRunningOnIOS9OrLater())
164     return;
165 #endif
166   // Load a WebP image from disk.
167   base::scoped_nsobject<NSData> webp_image(
168       [LoadImage(base::FilePath("test.webp")) retain]);
169   ASSERT_TRUE(webp_image != nil);
170   // Load reference image.
171   base::scoped_nsobject<NSData> jpg_image(
172       [LoadImage(base::FilePath("test.jpg")) retain]);
173   ASSERT_TRUE(jpg_image != nil);
174   // Convert to JPEG.
175   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
176   EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG))
177       .Times(1);
178   decoder_->OnDataReceived(webp_image);
179   // Compare to reference image.
180   EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(),
181                                          WebpDecoder::JPEG));
184 TEST_F(WebpDecoderTest, DecodeToPng) {
185 // TODO(droger): This test fails on iOS 9 x64 devices. http://crbug.com/523235
186 #if defined(OS_IOS) && defined(ARCH_CPU_ARM64) && !TARGET_IPHONE_SIMULATOR
187   if (base::ios::IsRunningOnIOS9OrLater())
188     return;
189 #endif
190   // Load a WebP image from disk.
191   base::scoped_nsobject<NSData> webp_image(
192       [LoadImage(base::FilePath("test_alpha.webp")) retain]);
193   ASSERT_TRUE(webp_image != nil);
194   // Load reference image.
195   base::scoped_nsobject<NSData> png_image(
196       [LoadImage(base::FilePath("test_alpha.png")) retain]);
197   ASSERT_TRUE(png_image != nil);
198   // Convert to PNG.
199   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
200   EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::PNG))
201       .Times(1);
202   decoder_->OnDataReceived(webp_image);
203   // Compare to reference image.
204   EXPECT_TRUE(CheckCompressedImagesEqual(png_image, delegate_->GetImage(),
205                                          WebpDecoder::PNG));
208 TEST_F(WebpDecoderTest, DecodeToTiff) {
209 // TODO(droger): This test fails on iOS 9 x64 devices. http://crbug.com/523235
210 #if defined(OS_IOS) && defined(ARCH_CPU_ARM64) && !TARGET_IPHONE_SIMULATOR
211   if (base::ios::IsRunningOnIOS9OrLater())
212     return;
213 #endif
214   // Load a WebP image from disk.
215   base::scoped_nsobject<NSData> webp_image(
216       [LoadImage(base::FilePath("test_small.webp")) retain]);
217   ASSERT_TRUE(webp_image != nil);
218   // Load reference image.
219   base::scoped_nsobject<NSData> tiff_image(
220       [LoadImage(base::FilePath("test_small.tiff")) retain]);
221   ASSERT_TRUE(tiff_image != nil);
222   // Convert to TIFF.
223   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
224   EXPECT_CALL(*delegate_, SetImageFeatures([tiff_image length],
225                                            WebpDecoder::TIFF)).Times(1);
226   decoder_->OnDataReceived(webp_image);
227   // Compare to reference image.
228   EXPECT_TRUE(CheckTiffImagesEqual(tiff_image, delegate_->GetImage()));
231 TEST_F(WebpDecoderTest, StreamedDecode) {
232 // TODO(droger): This test fails on iOS 9 x64 devices. http://crbug.com/523235
233 #if defined(OS_IOS) && defined(ARCH_CPU_ARM64) && !TARGET_IPHONE_SIMULATOR
234   if (base::ios::IsRunningOnIOS9OrLater())
235     return;
236 #endif
237   // Load a WebP image from disk.
238   base::scoped_nsobject<NSData> webp_image(
239       [LoadImage(base::FilePath("test.webp")) retain]);
240   ASSERT_TRUE(webp_image != nil);
241   // Load reference image.
242   base::scoped_nsobject<NSData> jpg_image(
243       [LoadImage(base::FilePath("test.jpg")) retain]);
244   ASSERT_TRUE(jpg_image != nil);
245   // Convert to JPEG in chunks.
246   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
247   EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG))
248       .Times(1);
249   const size_t kChunkSize = 10;
250   unsigned int num_chunks = 0;
251   while ([webp_image length] > kChunkSize) {
252     base::scoped_nsobject<NSData> chunk(
253         [[webp_image subdataWithRange:NSMakeRange(0, kChunkSize)] retain]);
254     decoder_->OnDataReceived(chunk);
255     webp_image.reset([[webp_image
256         subdataWithRange:NSMakeRange(kChunkSize, [webp_image length] -
257                                                      kChunkSize)] retain]);
258     ++num_chunks;
259   }
260   if ([webp_image length] > 0u) {
261     decoder_->OnDataReceived(webp_image);
262     ++num_chunks;
263   }
264   ASSERT_GT(num_chunks, 3u) << "Not enough chunks";
265   // Compare to reference image.
266   EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(),
267                                          WebpDecoder::JPEG));
270 TEST_F(WebpDecoderTest, InvalidFormat) {
271   EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1);
272   const char dummy_image[] = "(>'-')> <('-'<) ^('-')^ <('-'<) (>'-')>";
273   base::scoped_nsobject<NSData> data(
274       [[NSData alloc] initWithBytes:dummy_image length:arraysize(dummy_image)]);
275   decoder_->OnDataReceived(data);
276   EXPECT_EQ(0u, [delegate_->GetImage() length]);
279 TEST_F(WebpDecoderTest, DecodeAborted) {
280   EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1);
281   decoder_->Stop();
282   EXPECT_EQ(0u, [delegate_->GetImage() length]);
285 }  // namespace webp_transcode