Index: ios/web/public/webp_decoder_unittest.mm |
diff --git a/ios/web/public/webp_decoder_unittest.mm b/ios/web/public/webp_decoder_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..40d20a16d413f1eea5a06785fa06a85ef8e1587f |
--- /dev/null |
+++ b/ios/web/public/webp_decoder_unittest.mm |
@@ -0,0 +1,263 @@ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ios/web/public/webp_decoder.h" |
+ |
+#import <CoreGraphics/CoreGraphics.h> |
+#import <Foundation/Foundation.h> |
+ |
+#include "base/base_paths.h" |
+#include "base/files/file_path.h" |
+#include "base/logging.h" |
+#include "base/mac/scoped_cftyperef.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/path_service.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace web { |
+namespace { |
+ |
+class WebpDecoderDelegate : public WebpDecoder::Delegate { |
+ public: |
+ WebpDecoderDelegate() : image_([[NSMutableData alloc] init]) { |
+ } |
+ |
+ NSData* GetImage() const { return image_; } |
+ |
+ // WebpDecoder::Delegate methods. |
+ MOCK_METHOD1(OnFinishedDecoding, void(bool success)); |
+ MOCK_METHOD2(SetImageFeatures, |
+ void(size_t total_size, WebpDecoder::DecodedImageFormat format)); |
+ virtual void OnDataDecoded(NSData* data) { [image_ appendData:data]; } |
+ |
+ private: |
+ virtual ~WebpDecoderDelegate() {} |
+ |
+ base::scoped_nsobject<NSMutableData> image_; |
+}; |
+ |
+class WebpDecoderTest : public testing::Test { |
+ public: |
+ WebpDecoderTest() |
+ : delegate_(new WebpDecoderDelegate), |
+ decoder_(new WebpDecoder(delegate_.get())) {} |
+ |
+ NSData* LoadImage(const base::FilePath& filename) { |
+ base::FilePath path; |
+ PathService::Get(base::DIR_SOURCE_ROOT, &path); |
+ path = path.AppendASCII("ios/web/test/data").Append(filename); |
+ return |
+ [NSData dataWithContentsOfFile:base::SysUTF8ToNSString(path.value())]; |
+ } |
+ |
+ std::vector<uint8_t>* DecompressData(NSData* data, |
+ WebpDecoder::DecodedImageFormat format) { |
+ base::ScopedCFTypeRef<CGDataProviderRef> provider( |
+ CGDataProviderCreateWithCFData((CFDataRef)data)); |
+ base::ScopedCFTypeRef<CGImageRef> image; |
+ switch (format) { |
+ case WebpDecoder::JPEG: |
+ image.reset(CGImageCreateWithJPEGDataProvider( |
+ provider, NULL, false, kCGRenderingIntentDefault)); |
+ break; |
+ case WebpDecoder::PNG: |
+ image.reset(CGImageCreateWithPNGDataProvider( |
+ provider, NULL, false, kCGRenderingIntentDefault)); |
+ break; |
+ case WebpDecoder::TIFF: |
+ ADD_FAILURE() << "Data already decompressed"; |
+ return nil; |
+ case WebpDecoder::DECODED_FORMAT_COUNT: |
+ ADD_FAILURE() << "Unknown format"; |
+ return nil; |
+ } |
+ size_t width = CGImageGetWidth(image); |
+ size_t height = CGImageGetHeight(image); |
+ base::ScopedCFTypeRef<CGColorSpaceRef> color_space( |
+ CGColorSpaceCreateDeviceRGB()); |
+ size_t bytes_per_pixel = 4; |
+ size_t bytes_per_row = bytes_per_pixel * width; |
+ size_t bits_per_component = 8; |
+ std::vector<uint8_t>* result = |
+ new std::vector<uint8_t>(width * height * bytes_per_pixel, 0); |
+ base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( |
+ &result->front(), width, height, bits_per_component, bytes_per_row, |
+ color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big)); |
+ CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); |
+ // Check that someting has been written in |result|. |
+ std::vector<uint8_t> zeroes(width * height * bytes_per_pixel, 0); |
+ EXPECT_NE(0, memcmp(&result->front(), &zeroes.front(), zeroes.size())) |
+ << "Decompression failed."; |
+ return result; |
+ } |
+ |
+ // Compares data, allowing an averaged absolute difference of 1. |
+ bool CompareUncompressedData(const uint8_t* ptr_1, |
+ const uint8_t* ptr_2, |
+ size_t size) { |
+ uint64_t difference = 0; |
+ for (size_t i = 0; i < size; ++i) { |
+ // Casting to int to avoid overflow. |
+ int error = abs(int(ptr_1[i]) - int(ptr_2[i])); |
+ EXPECT_GE(difference + error, difference) |
+ << "Image difference too big (overflow)."; |
+ difference += error; |
+ } |
+ double average_difference = double(difference) / double(size); |
+ DLOG(INFO) << "Average image difference: " << average_difference; |
+ return average_difference < 1.5; |
+ } |
+ |
+ bool CheckCompressedImagesEqual(NSData* data_1, |
+ NSData* data_2, |
+ WebpDecoder::DecodedImageFormat format) { |
+ scoped_ptr<std::vector<uint8_t> > uncompressed_1( |
+ DecompressData(data_1, format)); |
+ scoped_ptr<std::vector<uint8_t> > uncompressed_2( |
+ DecompressData(data_2, format)); |
+ if (uncompressed_1->size() != uncompressed_2->size()) { |
+ DLOG(ERROR) << "Image sizes don't match"; |
+ return false; |
+ } |
+ return CompareUncompressedData(&uncompressed_1->front(), |
+ &uncompressed_2->front(), |
+ uncompressed_1->size()); |
+ } |
+ |
+ bool CheckTiffImagesEqual(NSData* image_1, NSData* image_2) { |
+ if ([image_1 length] != [image_2 length]) { |
+ DLOG(ERROR) << "Image lengths don't match"; |
+ return false; |
+ } |
+ // Compare headers. |
+ const size_t kHeaderSize = WebpDecoder::GetHeaderSize(); |
+ NSData* header_1 = [image_1 subdataWithRange:NSMakeRange(0, kHeaderSize)]; |
+ NSData* header_2 = [image_2 subdataWithRange:NSMakeRange(0, kHeaderSize)]; |
+ if (!header_1 || !header_2) |
+ return false; |
+ if (![header_1 isEqualToData:header_2]) { |
+ DLOG(ERROR) << "Headers don't match."; |
+ return false; |
+ } |
+ return CompareUncompressedData( |
+ static_cast<const uint8_t*>([image_1 bytes]) + kHeaderSize, |
+ static_cast<const uint8_t*>([image_2 bytes]) + kHeaderSize, |
+ [image_1 length] - kHeaderSize); |
+ } |
+ |
+ protected: |
+ scoped_refptr<WebpDecoderDelegate> delegate_; |
+ scoped_refptr<WebpDecoder> decoder_; |
+}; |
+ |
+} // namespace |
+ |
+TEST_F(WebpDecoderTest, DecodeToJpeg) { |
+ // Load a WebP image from disk. |
+ base::scoped_nsobject<NSData> webp_image( |
+ [LoadImage(base::FilePath("test.webp")) retain]); |
+ ASSERT_TRUE(webp_image != nil); |
+ // Load reference image. |
+ base::scoped_nsobject<NSData> jpg_image( |
+ [LoadImage(base::FilePath("test.jpg")) retain]); |
+ ASSERT_TRUE(jpg_image != nil); |
+ // Convert to JPEG. |
+ EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); |
+ EXPECT_CALL(*delegate_, |
+ SetImageFeatures(testing::_, WebpDecoder::JPEG)).Times(1); |
+ decoder_->OnDataReceived(webp_image); |
+ // Compare to reference image. |
+ EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(), |
+ WebpDecoder::JPEG)); |
+} |
+ |
+TEST_F(WebpDecoderTest, DecodeToPng) { |
+ // Load a WebP image from disk. |
+ base::scoped_nsobject<NSData> webp_image( |
+ [LoadImage(base::FilePath("test_alpha.webp")) retain]); |
+ ASSERT_TRUE(webp_image != nil); |
+ // Load reference image. |
+ base::scoped_nsobject<NSData> png_image( |
+ [LoadImage(base::FilePath("test_alpha.png")) retain]); |
+ ASSERT_TRUE(png_image != nil); |
+ // Convert to PNG. |
+ EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); |
+ EXPECT_CALL(*delegate_, |
+ SetImageFeatures(testing::_, WebpDecoder::PNG)).Times(1); |
+ decoder_->OnDataReceived(webp_image); |
+ // Compare to reference image. |
+ EXPECT_TRUE(CheckCompressedImagesEqual(png_image, delegate_->GetImage(), |
+ WebpDecoder::PNG)); |
+} |
+ |
+TEST_F(WebpDecoderTest, DecodeToTiff) { |
+ // Load a WebP image from disk. |
+ base::scoped_nsobject<NSData> webp_image( |
+ [LoadImage(base::FilePath("test_small.webp")) retain]); |
+ ASSERT_TRUE(webp_image != nil); |
+ // Load reference image. |
+ base::scoped_nsobject<NSData> tiff_image( |
+ [LoadImage(base::FilePath("test_small.tiff")) retain]); |
+ ASSERT_TRUE(tiff_image != nil); |
+ // Convert to TIFF. |
+ EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); |
+ EXPECT_CALL(*delegate_, SetImageFeatures([tiff_image length], |
+ WebpDecoder::TIFF)).Times(1); |
+ decoder_->OnDataReceived(webp_image); |
+ // Compare to reference image. |
+ EXPECT_TRUE(CheckTiffImagesEqual(tiff_image, delegate_->GetImage())); |
+} |
+ |
+TEST_F(WebpDecoderTest, StreamedDecode) { |
+ // Load a WebP image from disk. |
+ base::scoped_nsobject<NSData> webp_image( |
+ [LoadImage(base::FilePath("test.webp")) retain]); |
+ ASSERT_TRUE(webp_image != nil); |
+ // Load reference image. |
+ base::scoped_nsobject<NSData> jpg_image( |
+ [LoadImage(base::FilePath("test.jpg")) retain]); |
+ ASSERT_TRUE(jpg_image != nil); |
+ // Convert to JPEG in chunks. |
+ EXPECT_CALL(*delegate_, OnFinishedDecoding(true)).Times(1); |
+ EXPECT_CALL(*delegate_, |
+ SetImageFeatures(testing::_, WebpDecoder::JPEG)).Times(1); |
+ const size_t kChunkSize = 10; |
+ unsigned int num_chunks = 0; |
+ while ([webp_image length] > kChunkSize) { |
+ base::scoped_nsobject<NSData> chunk( |
+ [[webp_image subdataWithRange:NSMakeRange(0, kChunkSize)] retain]); |
+ decoder_->OnDataReceived(chunk); |
+ webp_image.reset([[webp_image subdataWithRange:NSMakeRange( |
+ kChunkSize, [webp_image length] - kChunkSize)] retain]); |
+ ++num_chunks; |
+ } |
+ if ([webp_image length] > 0u) { |
+ decoder_->OnDataReceived(webp_image); |
+ ++num_chunks; |
+ } |
+ ASSERT_GT(num_chunks, 3u) << "Not enough chunks"; |
+ // Compare to reference image. |
+ EXPECT_TRUE(CheckCompressedImagesEqual(jpg_image, delegate_->GetImage(), |
+ WebpDecoder::JPEG)); |
+} |
+ |
+TEST_F(WebpDecoderTest, InvalidFormat) { |
+ EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1); |
+ const char dummy_image[] = "(>'-')> <('-'<) ^('-')^ <('-'<) (>'-')>"; |
+ base::scoped_nsobject<NSData> data( |
+ [[NSData alloc] initWithBytes:dummy_image length:arraysize(dummy_image)]); |
+ decoder_->OnDataReceived(data); |
+ EXPECT_EQ(0u, [delegate_->GetImage() length]); |
+} |
+ |
+TEST_F(WebpDecoderTest, DecodeAborted) { |
+ EXPECT_CALL(*delegate_, OnFinishedDecoding(false)).Times(1); |
+ decoder_->Stop(); |
+ EXPECT_EQ(0u, [delegate_->GetImage() length]); |
+} |
+ |
+} // namespace web |