| 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 |