Don't preload rarely seen large images
[chromium-blink-merge.git] / components / webp_transcode / webp_decoder_unittest.mm
blob5130ca7c8b89a6c6ea744b8c6246d51f7042c247
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/logging.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/path_service.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h"
21 namespace webp_transcode {
22 namespace {
24 class WebpDecoderDelegate : public WebpDecoder::Delegate {
25  public:
26   WebpDecoderDelegate() : image_([[NSMutableData alloc] init]) {}
28   NSData* GetImage() const { return image_; }
30   // WebpDecoder::Delegate methods.
31   MOCK_METHOD1(OnFinishedDecoding, void(bool success));
32   MOCK_METHOD2(SetImageFeatures,
33                void(size_t total_size, WebpDecoder::DecodedImageFormat format));
34   void OnDataDecoded(NSData* data) override { [image_ appendData:data]; }
36  private:
37   virtual ~WebpDecoderDelegate() {}
39   base::scoped_nsobject<NSMutableData> image_;
42 class WebpDecoderTest : public testing::Test {
43  public:
44   WebpDecoderTest()
45       : delegate_(new WebpDecoderDelegate),
46         decoder_(new WebpDecoder(delegate_.get())) {}
48   NSData* LoadImage(const base::FilePath& filename) {
49     base::FilePath path;
50     PathService::Get(base::DIR_SOURCE_ROOT, &path);
51     path = path.AppendASCII("components/test/data/webp_transcode")
52                .Append(filename);
53     return
54         [NSData dataWithContentsOfFile:base::SysUTF8ToNSString(path.value())];
55   }
57   std::vector<uint8_t>* DecompressData(NSData* data,
58                                        WebpDecoder::DecodedImageFormat format) {
59     base::ScopedCFTypeRef<CGDataProviderRef> provider(
60         CGDataProviderCreateWithCFData((CFDataRef)data));
61     base::ScopedCFTypeRef<CGImageRef> image;
62     switch (format) {
63       case WebpDecoder::JPEG:
64         image.reset(CGImageCreateWithJPEGDataProvider(
65             provider, nullptr, false, kCGRenderingIntentDefault));
66         break;
67       case WebpDecoder::PNG:
68         image.reset(CGImageCreateWithPNGDataProvider(
69             provider, nullptr, false, kCGRenderingIntentDefault));
70         break;
71       case WebpDecoder::TIFF:
72         ADD_FAILURE() << "Data already decompressed";
73         return nil;
74       case WebpDecoder::DECODED_FORMAT_COUNT:
75         ADD_FAILURE() << "Unknown format";
76         return nil;
77     }
78     size_t width = CGImageGetWidth(image);
79     size_t height = CGImageGetHeight(image);
80     base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
81         CGColorSpaceCreateDeviceRGB());
82     size_t bytes_per_pixel = 4;
83     size_t bytes_per_row = bytes_per_pixel * width;
84     size_t bits_per_component = 8;
85     std::vector<uint8_t>* result =
86         new std::vector<uint8_t>(width * height * bytes_per_pixel, 0);
87     base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
88         &result->front(), width, height, bits_per_component, bytes_per_row,
89         color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big));
90     CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
91     // Check that someting has been written in |result|.
92     std::vector<uint8_t> zeroes(width * height * bytes_per_pixel, 0);
93     EXPECT_NE(0, memcmp(&result->front(), &zeroes.front(), zeroes.size()))
94         << "Decompression failed.";
95     return result;
96   }
98   // Compares data, allowing an averaged absolute difference of 1.
99   bool CompareUncompressedData(const uint8_t* ptr_1,
100                                const uint8_t* ptr_2,
101                                size_t size) {
102     uint64_t difference = 0;
103     for (size_t i = 0; i < size; ++i) {
104       // Casting to int to avoid overflow.
105       int error = abs(int(ptr_1[i]) - int(ptr_2[i]));
106       EXPECT_GE(difference + error, difference)
107           << "Image difference too big (overflow).";
108       difference += error;
109     }
110     double average_difference = double(difference) / double(size);
111     DLOG(INFO) << "Average image difference: " << average_difference;
112     return average_difference < 1.5;
113   }
115   bool CheckCompressedImagesEqual(NSData* data_1,
116                                   NSData* data_2,
117                                   WebpDecoder::DecodedImageFormat format) {
118     scoped_ptr<std::vector<uint8_t>> uncompressed_1(
119         DecompressData(data_1, format));
120     scoped_ptr<std::vector<uint8_t>> uncompressed_2(
121         DecompressData(data_2, format));
122     if (uncompressed_1->size() != uncompressed_2->size()) {
123       DLOG(ERROR) << "Image sizes don't match";
124       return false;
125     }
126     return CompareUncompressedData(&uncompressed_1->front(),
127                                    &uncompressed_2->front(),
128                                    uncompressed_1->size());
129   }
131   bool CheckTiffImagesEqual(NSData* image_1, NSData* image_2) {
132     if ([image_1 length] != [image_2 length]) {
133       DLOG(ERROR) << "Image lengths don't match";
134       return false;
135     }
136     // Compare headers.
137     const size_t kHeaderSize = WebpDecoder::GetHeaderSize();
138     NSData* header_1 = [image_1 subdataWithRange:NSMakeRange(0, kHeaderSize)];
139     NSData* header_2 = [image_2 subdataWithRange:NSMakeRange(0, kHeaderSize)];
140     if (!header_1 || !header_2)
141       return false;
142     if (![header_1 isEqualToData:header_2]) {
143       DLOG(ERROR) << "Headers don't match.";
144       return false;
145     }
146     return CompareUncompressedData(
147         static_cast<const uint8_t*>([image_1 bytes]) + kHeaderSize,
148         static_cast<const uint8_t*>([image_2 bytes]) + kHeaderSize,
149         [image_1 length] - kHeaderSize);
150   }
152  protected:
153   scoped_refptr<WebpDecoderDelegate> delegate_;
154   scoped_refptr<WebpDecoder> decoder_;
157 }  // namespace
159 TEST_F(WebpDecoderTest, DecodeToJpeg) {
160   // Load a WebP image from disk.
161   base::scoped_nsobject<NSData> webp_image(
162       [LoadImage(base::FilePath("test.webp")) retain]);
163   ASSERT_TRUE(webp_image != nil);
164   // Load reference image.
165   base::scoped_nsobject<NSData> jpg_image(
166       [LoadImage(base::FilePath("test.jpg")) retain]);
167   ASSERT_TRUE(jpg_image != nil);
168   // Convert to JPEG.
169   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
170   EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG))
171       .Times(1);
172   decoder_->OnDataReceived(webp_image);
173   // Compare to reference image.
174   EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(),
175                                          WebpDecoder::JPEG));
178 TEST_F(WebpDecoderTest, DecodeToPng) {
179   // Load a WebP image from disk.
180   base::scoped_nsobject<NSData> webp_image(
181       [LoadImage(base::FilePath("test_alpha.webp")) retain]);
182   ASSERT_TRUE(webp_image != nil);
183   // Load reference image.
184   base::scoped_nsobject<NSData> png_image(
185       [LoadImage(base::FilePath("test_alpha.png")) retain]);
186   ASSERT_TRUE(png_image != nil);
187   // Convert to PNG.
188   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
189   EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::PNG))
190       .Times(1);
191   decoder_->OnDataReceived(webp_image);
192   // Compare to reference image.
193   EXPECT_TRUE(CheckCompressedImagesEqual(png_image, delegate_->GetImage(),
194                                          WebpDecoder::PNG));
197 TEST_F(WebpDecoderTest, DecodeToTiff) {
198   // Load a WebP image from disk.
199   base::scoped_nsobject<NSData> webp_image(
200       [LoadImage(base::FilePath("test_small.webp")) retain]);
201   ASSERT_TRUE(webp_image != nil);
202   // Load reference image.
203   base::scoped_nsobject<NSData> tiff_image(
204       [LoadImage(base::FilePath("test_small.tiff")) retain]);
205   ASSERT_TRUE(tiff_image != nil);
206   // Convert to TIFF.
207   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
208   EXPECT_CALL(*delegate_, SetImageFeatures([tiff_image length],
209                                            WebpDecoder::TIFF)).Times(1);
210   decoder_->OnDataReceived(webp_image);
211   // Compare to reference image.
212   EXPECT_TRUE(CheckTiffImagesEqual(tiff_image, delegate_->GetImage()));
215 TEST_F(WebpDecoderTest, StreamedDecode) {
216   // Load a WebP image from disk.
217   base::scoped_nsobject<NSData> webp_image(
218       [LoadImage(base::FilePath("test.webp")) retain]);
219   ASSERT_TRUE(webp_image != nil);
220   // Load reference image.
221   base::scoped_nsobject<NSData> jpg_image(
222       [LoadImage(base::FilePath("test.jpg")) retain]);
223   ASSERT_TRUE(jpg_image != nil);
224   // Convert to JPEG in chunks.
225   EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1);
226   EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG))
227       .Times(1);
228   const size_t kChunkSize = 10;
229   unsigned int num_chunks = 0;
230   while ([webp_image length] > kChunkSize) {
231     base::scoped_nsobject<NSData> chunk(
232         [[webp_image subdataWithRange:NSMakeRange(0, kChunkSize)] retain]);
233     decoder_->OnDataReceived(chunk);
234     webp_image.reset([[webp_image
235         subdataWithRange:NSMakeRange(kChunkSize, [webp_image length] -
236                                                      kChunkSize)] retain]);
237     ++num_chunks;
238   }
239   if ([webp_image length] > 0u) {
240     decoder_->OnDataReceived(webp_image);
241     ++num_chunks;
242   }
243   ASSERT_GT(num_chunks, 3u) << "Not enough chunks";
244   // Compare to reference image.
245   EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(),
246                                          WebpDecoder::JPEG));
249 TEST_F(WebpDecoderTest, InvalidFormat) {
250   EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1);
251   const char dummy_image[] = "(>'-')> <('-'<) ^('-')^ <('-'<) (>'-')>";
252   base::scoped_nsobject<NSData> data(
253       [[NSData alloc] initWithBytes:dummy_image length:arraysize(dummy_image)]);
254   decoder_->OnDataReceived(data);
255   EXPECT_EQ(0u, [delegate_->GetImage() length]);
258 TEST_F(WebpDecoderTest, DecodeAborted) {
259   EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1);
260   decoder_->Stop();
261   EXPECT_EQ(0u, [delegate_->GetImage() length]);
264 }  // namespace webp_transcode