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