| 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 "components/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("components/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_, SetImageFeatures([tiff_image length], | |
| 231 WebpDecoder::TIFF)).Times(1); | |
| 232 decoder_->OnDataReceived(webp_image); | |
| 233 // Compare to reference image. | |
| 234 EXPECT_TRUE(CheckTiffImagesEqual(tiff_image, delegate_->GetImage())); | |
| 235 } | |
| 236 | |
| 237 TEST_F(WebpDecoderTest, StreamedDecode) { | |
| 238 // TODO(droger): This test fails on iOS 9 x64 devices. http://crbug.com/523235 | |
| 239 #if defined(OS_IOS) && defined(ARCH_CPU_ARM64) && !TARGET_IPHONE_SIMULATOR | |
| 240 if (base::ios::IsRunningOnIOS9OrLater()) | |
| 241 return; | |
| 242 #endif | |
| 243 // Load a WebP image from disk. | |
| 244 base::scoped_nsobject<NSData> webp_image( | |
| 245 [LoadImage(base::FilePath("test.webp")) retain]); | |
| 246 ASSERT_TRUE(webp_image != nil); | |
| 247 // Load reference image. | |
| 248 base::scoped_nsobject<NSData> jpg_image( | |
| 249 [LoadImage(base::FilePath("test.jpg")) retain]); | |
| 250 ASSERT_TRUE(jpg_image != nil); | |
| 251 // Convert to JPEG in chunks. | |
| 252 EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); | |
| 253 EXPECT_CALL(*delegate_, SetImageFeatures(testing::_, WebpDecoder::JPEG)) | |
| 254 .Times(1); | |
| 255 const size_t kChunkSize = 10; | |
| 256 unsigned int num_chunks = 0; | |
| 257 while ([webp_image length] > kChunkSize) { | |
| 258 base::scoped_nsobject<NSData> chunk( | |
| 259 [[webp_image subdataWithRange:NSMakeRange(0, kChunkSize)] retain]); | |
| 260 decoder_->OnDataReceived(chunk); | |
| 261 webp_image.reset([[webp_image | |
| 262 subdataWithRange:NSMakeRange(kChunkSize, [webp_image length] - | |
| 263 kChunkSize)] retain]); | |
| 264 ++num_chunks; | |
| 265 } | |
| 266 if ([webp_image length] > 0u) { | |
| 267 decoder_->OnDataReceived(webp_image); | |
| 268 ++num_chunks; | |
| 269 } | |
| 270 ASSERT_GT(num_chunks, 3u) << "Not enough chunks"; | |
| 271 // Compare to reference image. | |
| 272 EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(), | |
| 273 WebpDecoder::JPEG)); | |
| 274 } | |
| 275 | |
| 276 TEST_F(WebpDecoderTest, InvalidFormat) { | |
| 277 EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1); | |
| 278 const char dummy_image[] = "(>'-')> <('-'<) ^('-')^ <('-'<) (>'-')>"; | |
| 279 base::scoped_nsobject<NSData> data( | |
| 280 [[NSData alloc] initWithBytes:dummy_image length:arraysize(dummy_image)]); | |
| 281 decoder_->OnDataReceived(data); | |
| 282 EXPECT_EQ(0u, [delegate_->GetImage() length]); | |
| 283 } | |
| 284 | |
| 285 TEST_F(WebpDecoderTest, DecodeAborted) { | |
| 286 EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1); | |
| 287 decoder_->Stop(); | |
| 288 EXPECT_EQ(0u, [delegate_->GetImage() length]); | |
| 289 } | |
| 290 | |
| 291 } // namespace webp_transcode | |
| OLD | NEW |