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