summaryrefslogtreecommitdiffstats
path: root/components/webp_transcode
diff options
context:
space:
mode:
authordroger <droger@chromium.org>2014-12-19 03:19:49 -0800
committerCommit bot <commit-bot@chromium.org>2014-12-19 11:20:24 +0000
commit7ebec077726d438198bdf599dc8705a6d1d76964 (patch)
treed2ba7f6db0391fb71aca9f32158955df1caa2ab5 /components/webp_transcode
parentfb53807a4c50eadfe63a7f1b5afce972a82a7cc9 (diff)
downloadchromium_src-7ebec077726d438198bdf599dc8705a6d1d76964.zip
chromium_src-7ebec077726d438198bdf599dc8705a6d1d76964.tar.gz
chromium_src-7ebec077726d438198bdf599dc8705a6d1d76964.tar.bz2
Move iOS WebP decoding to a component
This CL moves WebP decoding out of the web layer to a component that does not depend on web. This will allow to use the WebP decoding without pulling a dependency on the web layer. BUG=430110 TBR=cbentzel Review URL: https://codereview.chromium.org/814013003 Cr-Commit-Position: refs/heads/master@{#309181}
Diffstat (limited to 'components/webp_transcode')
-rw-r--r--components/webp_transcode/DEPS10
-rw-r--r--components/webp_transcode/OWNERS2
-rw-r--r--components/webp_transcode/README2
-rw-r--r--components/webp_transcode/webp_decoder.h74
-rw-r--r--components/webp_transcode/webp_decoder.mm251
-rw-r--r--components/webp_transcode/webp_decoder_unittest.mm264
6 files changed, 603 insertions, 0 deletions
diff --git a/components/webp_transcode/DEPS b/components/webp_transcode/DEPS
new file mode 100644
index 0000000..0e26ac6
--- /dev/null
+++ b/components/webp_transcode/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+ "+net",
+ # Only WebP decoding is allowed (no encoding).
+ "+third_party/libwebp/webp/decode.h",
+
+ # webp_transcode should not depend on //ios for library size reasons.
+ "-ios",
+ # webp_transcode is only used by iOS.
+ "-content",
+]
diff --git a/components/webp_transcode/OWNERS b/components/webp_transcode/OWNERS
new file mode 100644
index 0000000..fa3d0c2
--- /dev/null
+++ b/components/webp_transcode/OWNERS
@@ -0,0 +1,2 @@
+droger@chromium.org
+sdefresne@chromium.org
diff --git a/components/webp_transcode/README b/components/webp_transcode/README
new file mode 100644
index 0000000..4484825
--- /dev/null
+++ b/components/webp_transcode/README
@@ -0,0 +1,2 @@
+webp_transcode is used to convert WebP images to other formats supported by the
+iOS web views.
diff --git a/components/webp_transcode/webp_decoder.h b/components/webp_transcode/webp_decoder.h
new file mode 100644
index 0000000..64126c2
--- /dev/null
+++ b/components/webp_transcode/webp_decoder.h
@@ -0,0 +1,74 @@
+// Copyright 2013 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.
+
+#ifndef COMPONENTS_WEBP_TRANSCODE_WEBP_DECODER_H_
+#define COMPONENTS_WEBP_TRANSCODE_WEBP_DECODER_H_
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/libwebp/webp/decode.h"
+
+@class NSData;
+
+namespace webp_transcode {
+
+// Decodes a WebP image into either JPEG, PNG or uncompressed TIFF.
+class WebpDecoder : public base::RefCountedThreadSafe<WebpDecoder> {
+ public:
+ // Format of the decoded image.
+ // This enum is used for UMA reporting, keep it in sync with the histogram
+ // definition.
+ enum DecodedImageFormat { JPEG = 1, PNG, TIFF, DECODED_FORMAT_COUNT };
+
+ class Delegate : public base::RefCountedThreadSafe<WebpDecoder::Delegate> {
+ public:
+ virtual void OnFinishedDecoding(bool success) = 0;
+ virtual void SetImageFeatures(size_t total_size, // In bytes.
+ DecodedImageFormat format) = 0;
+ virtual void OnDataDecoded(NSData* data) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<WebpDecoder::Delegate>;
+ virtual ~Delegate() {}
+ };
+
+ explicit WebpDecoder(WebpDecoder::Delegate* delegate);
+
+ // For tests.
+ static size_t GetHeaderSize();
+
+ // Main entry point.
+ void OnDataReceived(const base::scoped_nsobject<NSData>& data);
+
+ // Stops the decoding.
+ void Stop();
+
+ private:
+ struct WebPIDecoderDeleter {
+ inline void operator()(WebPIDecoder* ptr) const { WebPIDelete(ptr); }
+ };
+
+ enum State { READING_FEATURES, READING_DATA, DONE };
+
+ friend class base::RefCountedThreadSafe<WebpDecoder>;
+ virtual ~WebpDecoder();
+
+ // Implements WebP image decoding state machine steps.
+ void DoReadFeatures(NSData* data);
+ void DoReadData(NSData* data);
+ bool DoSendData();
+
+ scoped_refptr<WebpDecoder::Delegate> delegate_;
+ WebPDecoderConfig config_;
+ WebpDecoder::State state_;
+ scoped_ptr<WebPIDecoder, WebPIDecoderDeleter> incremental_decoder_;
+ base::scoped_nsobject<NSData> output_buffer_;
+ base::scoped_nsobject<NSMutableData> features_;
+ int has_alpha_;
+};
+
+} // namespace webp_transcode
+
+#endif // COMPONENTS_WEBP_TRANSCODE_WEBP_DECODER_H_
diff --git a/components/webp_transcode/webp_decoder.mm b/components/webp_transcode/webp_decoder.mm
new file mode 100644
index 0000000..05c834b
--- /dev/null
+++ b/components/webp_transcode/webp_decoder.mm
@@ -0,0 +1,251 @@
+// Copyright 2013 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 "components/webp_transcode/webp_decoder.h"
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+
+namespace {
+
+const uint8_t kNumIfdEntries = 15;
+const unsigned int kExtraDataSize = 16;
+// 10b for signature/header + n * 12b entries + 4b for IFD terminator:
+const unsigned int kExtraDataOffset = 10 + 12 * kNumIfdEntries + 4;
+const unsigned int kHeaderSize = kExtraDataOffset + kExtraDataSize;
+const int kRecompressionThreshold = 64 * 64; // Threshold in pixels.
+const CGFloat kJpegQuality = 0.85;
+
+// Adapted from libwebp example dwebp.c.
+void PutLE16(uint8_t* const dst, uint32_t value) {
+ dst[0] = (value >> 0) & 0xff;
+ dst[1] = (value >> 8) & 0xff;
+}
+
+void PutLE32(uint8_t* const dst, uint32_t value) {
+ PutLE16(dst + 0, (value >> 0) & 0xffff);
+ PutLE16(dst + 2, (value >> 16) & 0xffff);
+}
+
+void WriteTiffHeader(uint8_t* dst,
+ int width,
+ int height,
+ int bytes_per_px,
+ bool has_alpha) {
+ // For non-alpha case, we omit tag 0x152 (ExtraSamples).
+ const uint8_t num_ifd_entries =
+ has_alpha ? kNumIfdEntries : kNumIfdEntries - 1;
+ uint8_t tiff_header[kHeaderSize] = {
+ 0x49, 0x49, 0x2a, 0x00, // little endian signature
+ 8, 0, 0, 0, // offset to the unique IFD that follows
+ // IFD (offset = 8). Entries must be written in increasing tag order.
+ num_ifd_entries, 0, // Number of entries in the IFD (12 bytes each).
+ 0x00, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 10: Width (TBD)
+ 0x01, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 22: Height (TBD)
+ 0x02, 0x01, 3, 0, bytes_per_px, 0, 0, 0, // 34: BitsPerSample: 8888
+ kExtraDataOffset + 0, 0, 0, 0,
+ 0x03, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 46: Compression: none
+ 0x06, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 58: Photometric: RGB
+ 0x11, 0x01, 4, 0, 1, 0, 0, 0, // 70: Strips offset:
+ kHeaderSize, 0, 0, 0, // data follows header
+ 0x12, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 82: Orientation: topleft
+ 0x15, 0x01, 3, 0, 1, 0, 0, 0, // 94: SamplesPerPixels
+ bytes_per_px, 0, 0, 0,
+ 0x16, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 106: Rows per strip (TBD)
+ 0x17, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 118: StripByteCount (TBD)
+ 0x1a, 0x01, 5, 0, 1, 0, 0, 0, // 130: X-resolution
+ kExtraDataOffset + 8, 0, 0, 0,
+ 0x1b, 0x01, 5, 0, 1, 0, 0, 0, // 142: Y-resolution
+ kExtraDataOffset + 8, 0, 0, 0,
+ 0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 154: PlanarConfiguration
+ 0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 166: ResolutionUnit (inch)
+ 0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 178: ExtraSamples: rgbA
+ 0, 0, 0, 0, // 190: IFD terminator
+ // kExtraDataOffset:
+ 8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample
+ 72, 0, 0, 0, 1, 0, 0, 0 // 72 pixels/inch, for X/Y-resolution
+ };
+
+ // Fill placeholders in IFD:
+ PutLE32(tiff_header + 10 + 8, width);
+ PutLE32(tiff_header + 22 + 8, height);
+ PutLE32(tiff_header + 106 + 8, height);
+ PutLE32(tiff_header + 118 + 8, width * bytes_per_px * height);
+ if (!has_alpha)
+ PutLE32(tiff_header + 178, 0);
+
+ memcpy(dst, tiff_header, kHeaderSize);
+}
+
+} // namespace
+
+namespace webp_transcode {
+
+// static
+size_t WebpDecoder::GetHeaderSize() {
+ return kHeaderSize;
+}
+
+WebpDecoder::WebpDecoder(WebpDecoder::Delegate* delegate)
+ : delegate_(delegate), state_(READING_FEATURES), has_alpha_(0) {
+ DCHECK(delegate_.get());
+ const bool rv = WebPInitDecoderConfig(&config_);
+ DCHECK(rv);
+}
+
+WebpDecoder::~WebpDecoder() {
+ WebPFreeDecBuffer(&config_.output);
+}
+
+void WebpDecoder::OnDataReceived(const base::scoped_nsobject<NSData>& data) {
+ DCHECK(data);
+ switch (state_) {
+ case READING_FEATURES:
+ DoReadFeatures(data);
+ break;
+ case READING_DATA:
+ DoReadData(data);
+ break;
+ case DONE:
+ DLOG(WARNING) << "Received WebP data but decoding is finished. Ignoring.";
+ break;
+ }
+}
+
+void WebpDecoder::Stop() {
+ if (state_ != DONE) {
+ state_ = DONE;
+ DLOG(WARNING) << "Unexpected end of WebP data.";
+ delegate_->OnFinishedDecoding(false);
+ }
+}
+
+void WebpDecoder::DoReadFeatures(NSData* data) {
+ DCHECK_EQ(READING_FEATURES, state_);
+ DCHECK(data);
+ if (features_)
+ [features_ appendData:data];
+ else
+ features_.reset([[NSMutableData alloc] initWithData:data]);
+ VP8StatusCode status =
+ WebPGetFeatures(static_cast<const uint8_t*>([features_ bytes]),
+ [features_ length], &config_.input);
+ switch (status) {
+ case VP8_STATUS_OK: {
+ has_alpha_ = config_.input.has_alpha;
+ const uint32_t width = config_.input.width;
+ const uint32_t height = config_.input.height;
+ const size_t bytes_per_px = has_alpha_ ? 4 : 3;
+ const int stride = bytes_per_px * width;
+ const size_t image_data_size = stride * height;
+ const size_t total_size = image_data_size + kHeaderSize;
+ // Force pre-multiplied alpha.
+ config_.output.colorspace = has_alpha_ ? MODE_rgbA : MODE_RGB;
+ config_.output.u.RGBA.stride = stride;
+ // Create the output buffer.
+ config_.output.u.RGBA.size = image_data_size;
+ uint8_t* dst = static_cast<uint8_t*>(malloc(total_size));
+ if (!dst) {
+ DLOG(ERROR) << "Could not allocate WebP decoding buffer (size = "
+ << total_size << ").";
+ delegate_->OnFinishedDecoding(false);
+ state_ = DONE;
+ break;
+ }
+ WriteTiffHeader(dst, width, height, bytes_per_px, has_alpha_);
+ output_buffer_.reset([[NSData alloc] initWithBytesNoCopy:dst
+ length:total_size
+ freeWhenDone:YES]);
+ config_.output.is_external_memory = 1;
+ config_.output.u.RGBA.rgba = dst + kHeaderSize;
+ // Start decoding.
+ state_ = READING_DATA;
+ incremental_decoder_.reset(WebPINewDecoder(&config_.output));
+ DoReadData(features_);
+ features_.reset();
+ break;
+ }
+ case VP8_STATUS_NOT_ENOUGH_DATA:
+ // Do nothing.
+ break;
+ default:
+ DLOG(ERROR) << "Error in WebP image features.";
+ delegate_->OnFinishedDecoding(false);
+ state_ = DONE;
+ break;
+ }
+}
+
+void WebpDecoder::DoReadData(NSData* data) {
+ DCHECK_EQ(READING_DATA, state_);
+ DCHECK(incremental_decoder_);
+ DCHECK(data);
+ VP8StatusCode status =
+ WebPIAppend(incremental_decoder_.get(),
+ static_cast<const uint8_t*>([data bytes]), [data length]);
+ switch (status) {
+ case VP8_STATUS_SUSPENDED:
+ // Do nothing: re-compression to JPEG or PNG cannot be done incrementally.
+ // Wait for the whole image to be decoded.
+ break;
+ case VP8_STATUS_OK: {
+ bool rv = DoSendData();
+ DLOG_IF(ERROR, !rv) << "Error in WebP image conversion.";
+ state_ = DONE;
+ delegate_->OnFinishedDecoding(rv);
+ break;
+ }
+ default:
+ DLOG(ERROR) << "Error in WebP image decoding.";
+ delegate_->OnFinishedDecoding(false);
+ state_ = DONE;
+ break;
+ }
+}
+
+bool WebpDecoder::DoSendData() {
+ DCHECK_EQ(READING_DATA, state_);
+ int width, height;
+ uint8_t* data_ptr = WebPIDecGetRGB(incremental_decoder_.get(), nullptr,
+ &width, &height, nullptr);
+ if (!data_ptr)
+ return false;
+ DCHECK_EQ(static_cast<const uint8_t*>([output_buffer_ bytes]) + kHeaderSize,
+ data_ptr);
+ base::scoped_nsobject<NSData> result_data;
+ // When the WebP image is larger than |kRecompressionThreshold| it is
+ // compressed to JPEG or PNG. Otherwise, the uncompressed TIFF is used.
+ DecodedImageFormat format = TIFF;
+ if (width * height > kRecompressionThreshold) {
+ base::scoped_nsobject<UIImage> tiff_image(
+ [[UIImage alloc] initWithData:output_buffer_]);
+ if (!tiff_image)
+ return false;
+ // Compress to PNG if the image is transparent, JPEG otherwise.
+ // TODO(droger): Use PNG instead of JPEG if the WebP image is lossless.
+ if (has_alpha_) {
+ result_data.reset([UIImagePNGRepresentation(tiff_image) retain]);
+ format = PNG;
+ } else {
+ result_data.reset(
+ [UIImageJPEGRepresentation(tiff_image, kJpegQuality) retain]);
+ format = JPEG;
+ }
+ if (!result_data)
+ return false;
+ } else {
+ result_data.reset([output_buffer_ retain]);
+ }
+ UMA_HISTOGRAM_ENUMERATION("WebP.DecodedImageFormat", format,
+ DECODED_FORMAT_COUNT);
+ delegate_->SetImageFeatures([result_data length], format);
+ delegate_->OnDataDecoded(result_data);
+ output_buffer_.reset();
+ return true;
+}
+
+} // namespace webp_transcode
diff --git a/components/webp_transcode/webp_decoder_unittest.mm b/components/webp_transcode/webp_decoder_unittest.mm
new file mode 100644
index 0000000..5130ca7
--- /dev/null
+++ b/components/webp_transcode/webp_decoder_unittest.mm
@@ -0,0 +1,264 @@
+// 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 "components/webp_transcode/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 webp_transcode {
+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));
+ void OnDataDecoded(NSData* data) override { [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("components/test/data/webp_transcode")
+ .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, nullptr, false, kCGRenderingIntentDefault));
+ break;
+ case WebpDecoder::PNG:
+ image.reset(CGImageCreateWithPNGDataProvider(
+ provider, nullptr, 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 webp_transcode