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 #import "ios/web/public/image_fetcher/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 #import "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 = | |
63 path.AppendASCII("ios/web/test/data/webp_transcode").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 // Load a WebP image from disk. | |
172 base::scoped_nsobject<NSData> webp_image( | |
173 LoadImage(base::FilePath("test.webp"))); | |
174 ASSERT_TRUE(webp_image != nil); | |
175 // Load reference image. | |
176 base::scoped_nsobject<NSData> jpg_image( | |
177 LoadImage(base::FilePath("test.jpg"))); | |
178 ASSERT_TRUE(jpg_image != nil); | |
179 // Convert to JPEG. | |
180 EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); | |
181 EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG)) | |
182 .Times(1); | |
183 decoder_->OnDataReceived(webp_image); | |
184 // Compare to reference image. | |
185 EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(), | |
186 WebpDecoder::JPEG)); | |
187 } | |
188 | |
189 TEST_F(WebpDecoderTest, DecodeToPng) { | |
190 // Load a WebP image from disk. | |
191 base::scoped_nsobject<NSData> webp_image( | |
192 LoadImage(base::FilePath("test_alpha.webp"))); | |
193 ASSERT_TRUE(webp_image != nil); | |
194 // Load reference image. | |
195 base::scoped_nsobject<NSData> png_image( | |
196 LoadImage(base::FilePath("test_alpha.png"))); | |
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)); | |
206 } | |
207 | |
208 TEST_F(WebpDecoderTest, DecodeToTiff) { | |
209 // Load a WebP image from disk. | |
210 base::scoped_nsobject<NSData> webp_image( | |
211 LoadImage(base::FilePath("test_small.webp"))); | |
212 ASSERT_TRUE(webp_image != nil); | |
213 // Load reference image. | |
214 base::scoped_nsobject<NSData> tiff_image( | |
215 LoadImage(base::FilePath("test_small.tiff"))); | |
216 ASSERT_TRUE(tiff_image != nil); | |
217 // Convert to TIFF. | |
218 EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); | |
219 EXPECT_CALL(*delegate_, | |
220 SetImageFeatures([tiff_image length], WebpDecoder::TIFF)) | |
221 .Times(1); | |
222 decoder_->OnDataReceived(webp_image); | |
223 // Compare to reference image. | |
224 EXPECT_TRUE(CheckTiffImagesEqual(tiff_image, delegate_->GetImage())); | |
225 } | |
226 | |
227 TEST_F(WebpDecoderTest, StreamedDecode) { | |
228 // Load a WebP image from disk. | |
229 base::scoped_nsobject<NSData> webp_image( | |
230 LoadImage(base::FilePath("test.webp"))); | |
231 ASSERT_TRUE(webp_image != nil); | |
232 // Load reference image. | |
233 base::scoped_nsobject<NSData> jpg_image( | |
234 LoadImage(base::FilePath("test.jpg"))); | |
235 ASSERT_TRUE(jpg_image != nil); | |
236 // Convert to JPEG in chunks. | |
237 EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); | |
238 EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG)) | |
239 .Times(1); | |
240 const size_t kChunkSize = 10; | |
241 unsigned int num_chunks = 0; | |
242 while ([webp_image length] > kChunkSize) { | |
243 base::scoped_nsobject<NSData> chunk( | |
244 [webp_image subdataWithRange:NSMakeRange(0, kChunkSize)]); | |
245 decoder_->OnDataReceived(chunk); | |
246 webp_image.reset([webp_image | |
247 subdataWithRange:NSMakeRange(kChunkSize, | |
248 [webp_image length] - kChunkSize)]); | |
249 ++num_chunks; | |
250 } | |
251 if ([webp_image length] > 0u) { | |
252 decoder_->OnDataReceived(webp_image); | |
253 ++num_chunks; | |
254 } | |
255 ASSERT_GT(num_chunks, 3u) << "Not enough chunks"; | |
256 // Compare to reference image. | |
257 EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(), | |
258 WebpDecoder::JPEG)); | |
259 } | |
260 | |
261 TEST_F(WebpDecoderTest, InvalidFormat) { | |
262 EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1); | |
263 const char dummy_image[] = "(>'-')> <('-'<) ^('-')^ <('-'<) (>'-')>"; | |
264 base::scoped_nsobject<NSData> data( | |
265 [[NSData alloc] initWithBytes:dummy_image length:arraysize(dummy_image)]); | |
266 decoder_->OnDataReceived(data); | |
267 EXPECT_EQ(0u, [delegate_->GetImage() length]); | |
268 } | |
269 | |
270 TEST_F(WebpDecoderTest, DecodeAborted) { | |
271 EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1); | |
272 decoder_->Stop(); | |
273 EXPECT_EQ(0u, [delegate_->GetImage() length]); | |
274 } | |
275 | |
276 } // namespace webp_transcode | |
OLD | NEW |