Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(187)

Unified Diff: ios/web/public/webp_decoder.mm

Issue 771723002: Add support for decoding WebP images (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@chrome-browser-state
Patch Set: Rebase Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ios/web/public/webp_decoder.h ('k') | ios/web/public/webp_decoder_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/web/public/webp_decoder.mm
diff --git a/ios/web/public/webp_decoder.mm b/ios/web/public/webp_decoder.mm
new file mode 100644
index 0000000000000000000000000000000000000000..3ac1cda60c1b04926d904dc77e45b1b8e994d12f
--- /dev/null
+++ b/ios/web/public/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 "ios/web/public/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 web {
+
+// 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(), NULL, &width, &height, NULL);
+ 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 web
« no previous file with comments | « ios/web/public/webp_decoder.h ('k') | ios/web/public/webp_decoder_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698