webp_decoder_unittest.mm 9.6 KB

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