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