Don't preload rarely seen large images
[chromium-blink-merge.git] / components / webp_transcode / webp_decoder.mm
blob05c834b822668358bc25b260ea786ca07742a8ac
1 // Copyright 2013 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.
5 #include "components/webp_transcode/webp_decoder.h"
7 #import <Foundation/Foundation.h>
8 #import <UIKit/UIKit.h>
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
13 namespace {
15 const uint8_t kNumIfdEntries = 15;
16 const unsigned int kExtraDataSize = 16;
17 // 10b for signature/header + n * 12b entries + 4b for IFD terminator:
18 const unsigned int kExtraDataOffset = 10 + 12 * kNumIfdEntries + 4;
19 const unsigned int kHeaderSize = kExtraDataOffset + kExtraDataSize;
20 const int kRecompressionThreshold = 64 * 64;  // Threshold in pixels.
21 const CGFloat kJpegQuality = 0.85;
23 // Adapted from libwebp example dwebp.c.
24 void PutLE16(uint8_t* const dst, uint32_t value) {
25   dst[0] = (value >> 0) & 0xff;
26   dst[1] = (value >> 8) & 0xff;
29 void PutLE32(uint8_t* const dst, uint32_t value) {
30   PutLE16(dst + 0, (value >> 0) & 0xffff);
31   PutLE16(dst + 2, (value >> 16) & 0xffff);
34 void WriteTiffHeader(uint8_t* dst,
35                      int width,
36                      int height,
37                      int bytes_per_px,
38                      bool has_alpha) {
39   // For non-alpha case, we omit tag 0x152 (ExtraSamples).
40   const uint8_t num_ifd_entries =
41       has_alpha ? kNumIfdEntries : kNumIfdEntries - 1;
42   uint8_t tiff_header[kHeaderSize] = {
43     0x49, 0x49, 0x2a, 0x00,   // little endian signature
44     8, 0, 0, 0,               // offset to the unique IFD that follows
45     // IFD (offset = 8). Entries must be written in increasing tag order.
46     num_ifd_entries, 0,       // Number of entries in the IFD (12 bytes each).
47     0x00, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,    //  10: Width  (TBD)
48     0x01, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,    //  22: Height (TBD)
49     0x02, 0x01, 3, 0, bytes_per_px, 0, 0, 0,     //  34: BitsPerSample: 8888
50         kExtraDataOffset + 0, 0, 0, 0,
51     0x03, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0,    //  46: Compression: none
52     0x06, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,    //  58: Photometric: RGB
53     0x11, 0x01, 4, 0, 1, 0, 0, 0,                //  70: Strips offset:
54         kHeaderSize, 0, 0, 0,                    //      data follows header
55     0x12, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0,    //  82: Orientation: topleft
56     0x15, 0x01, 3, 0, 1, 0, 0, 0,                //  94: SamplesPerPixels
57         bytes_per_px, 0, 0, 0,
58     0x16, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,    // 106: Rows per strip (TBD)
59     0x17, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0,    // 118: StripByteCount (TBD)
60     0x1a, 0x01, 5, 0, 1, 0, 0, 0,                // 130: X-resolution
61         kExtraDataOffset + 8, 0, 0, 0,
62     0x1b, 0x01, 5, 0, 1, 0, 0, 0,                // 142: Y-resolution
63         kExtraDataOffset + 8, 0, 0, 0,
64     0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0,    // 154: PlanarConfiguration
65     0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,    // 166: ResolutionUnit (inch)
66     0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0,    // 178: ExtraSamples: rgbA
67     0, 0, 0, 0,                                  // 190: IFD terminator
68     // kExtraDataOffset:
69     8, 0, 8, 0, 8, 0, 8, 0,      // BitsPerSample
70     72, 0, 0, 0, 1, 0, 0, 0      // 72 pixels/inch, for X/Y-resolution
71   };
73   // Fill placeholders in IFD:
74   PutLE32(tiff_header + 10 + 8, width);
75   PutLE32(tiff_header + 22 + 8, height);
76   PutLE32(tiff_header + 106 + 8, height);
77   PutLE32(tiff_header + 118 + 8, width * bytes_per_px * height);
78   if (!has_alpha)
79     PutLE32(tiff_header + 178, 0);
81   memcpy(dst, tiff_header, kHeaderSize);
84 }  // namespace
86 namespace webp_transcode {
88 // static
89 size_t WebpDecoder::GetHeaderSize() {
90   return kHeaderSize;
93 WebpDecoder::WebpDecoder(WebpDecoder::Delegate* delegate)
94     : delegate_(delegate), state_(READING_FEATURES), has_alpha_(0) {
95   DCHECK(delegate_.get());
96   const bool rv = WebPInitDecoderConfig(&config_);
97   DCHECK(rv);
100 WebpDecoder::~WebpDecoder() {
101   WebPFreeDecBuffer(&config_.output);
104 void WebpDecoder::OnDataReceived(const base::scoped_nsobject<NSData>& data) {
105   DCHECK(data);
106   switch (state_) {
107     case READING_FEATURES:
108       DoReadFeatures(data);
109       break;
110     case READING_DATA:
111       DoReadData(data);
112       break;
113     case DONE:
114       DLOG(WARNING) << "Received WebP data but decoding is finished. Ignoring.";
115       break;
116   }
119 void WebpDecoder::Stop() {
120   if (state_ != DONE) {
121     state_ = DONE;
122     DLOG(WARNING) << "Unexpected end of WebP data.";
123     delegate_->OnFinishedDecoding(false);
124   }
127 void WebpDecoder::DoReadFeatures(NSData* data) {
128   DCHECK_EQ(READING_FEATURES, state_);
129   DCHECK(data);
130   if (features_)
131     [features_ appendData:data];
132   else
133     features_.reset([[NSMutableData alloc] initWithData:data]);
134   VP8StatusCode status =
135       WebPGetFeatures(static_cast<const uint8_t*>([features_ bytes]),
136                       [features_ length], &config_.input);
137   switch (status) {
138     case VP8_STATUS_OK: {
139       has_alpha_ = config_.input.has_alpha;
140       const uint32_t width = config_.input.width;
141       const uint32_t height = config_.input.height;
142       const size_t bytes_per_px = has_alpha_ ? 4 : 3;
143       const int stride = bytes_per_px * width;
144       const size_t image_data_size = stride * height;
145       const size_t total_size = image_data_size + kHeaderSize;
146       // Force pre-multiplied alpha.
147       config_.output.colorspace = has_alpha_ ? MODE_rgbA : MODE_RGB;
148       config_.output.u.RGBA.stride = stride;
149       // Create the output buffer.
150       config_.output.u.RGBA.size = image_data_size;
151       uint8_t* dst = static_cast<uint8_t*>(malloc(total_size));
152       if (!dst) {
153         DLOG(ERROR) << "Could not allocate WebP decoding buffer (size = "
154                     << total_size << ").";
155         delegate_->OnFinishedDecoding(false);
156         state_ = DONE;
157         break;
158       }
159       WriteTiffHeader(dst, width, height, bytes_per_px, has_alpha_);
160       output_buffer_.reset([[NSData alloc] initWithBytesNoCopy:dst
161                                                         length:total_size
162                                                   freeWhenDone:YES]);
163       config_.output.is_external_memory = 1;
164       config_.output.u.RGBA.rgba = dst + kHeaderSize;
165       // Start decoding.
166       state_ = READING_DATA;
167       incremental_decoder_.reset(WebPINewDecoder(&config_.output));
168       DoReadData(features_);
169       features_.reset();
170       break;
171     }
172     case VP8_STATUS_NOT_ENOUGH_DATA:
173       // Do nothing.
174       break;
175     default:
176       DLOG(ERROR) << "Error in WebP image features.";
177       delegate_->OnFinishedDecoding(false);
178       state_ = DONE;
179       break;
180   }
183 void WebpDecoder::DoReadData(NSData* data) {
184   DCHECK_EQ(READING_DATA, state_);
185   DCHECK(incremental_decoder_);
186   DCHECK(data);
187   VP8StatusCode status =
188       WebPIAppend(incremental_decoder_.get(),
189                   static_cast<const uint8_t*>([data bytes]), [data length]);
190   switch (status) {
191     case VP8_STATUS_SUSPENDED:
192       // Do nothing: re-compression to JPEG or PNG cannot be done incrementally.
193       // Wait for the whole image to be decoded.
194       break;
195     case VP8_STATUS_OK: {
196       bool rv = DoSendData();
197       DLOG_IF(ERROR, !rv) << "Error in WebP image conversion.";
198       state_ = DONE;
199       delegate_->OnFinishedDecoding(rv);
200       break;
201     }
202     default:
203       DLOG(ERROR) << "Error in WebP image decoding.";
204       delegate_->OnFinishedDecoding(false);
205       state_ = DONE;
206       break;
207   }
210 bool WebpDecoder::DoSendData() {
211   DCHECK_EQ(READING_DATA, state_);
212   int width, height;
213   uint8_t* data_ptr = WebPIDecGetRGB(incremental_decoder_.get(), nullptr,
214                                      &width, &height, nullptr);
215   if (!data_ptr)
216     return false;
217   DCHECK_EQ(static_cast<const uint8_t*>([output_buffer_ bytes]) + kHeaderSize,
218             data_ptr);
219   base::scoped_nsobject<NSData> result_data;
220   // When the WebP image is larger than |kRecompressionThreshold| it is
221   // compressed to JPEG or PNG. Otherwise, the uncompressed TIFF is used.
222   DecodedImageFormat format = TIFF;
223   if (width * height > kRecompressionThreshold) {
224     base::scoped_nsobject<UIImage> tiff_image(
225         [[UIImage alloc] initWithData:output_buffer_]);
226     if (!tiff_image)
227       return false;
228     // Compress to PNG if the image is transparent, JPEG otherwise.
229     // TODO(droger): Use PNG instead of JPEG if the WebP image is lossless.
230     if (has_alpha_) {
231       result_data.reset([UIImagePNGRepresentation(tiff_image) retain]);
232       format = PNG;
233     } else {
234       result_data.reset(
235           [UIImageJPEGRepresentation(tiff_image, kJpegQuality) retain]);
236       format = JPEG;
237     }
238     if (!result_data)
239       return false;
240   } else {
241     result_data.reset([output_buffer_ retain]);
242   }
243   UMA_HISTOGRAM_ENUMERATION("WebP.DecodedImageFormat", format,
244                             DECODED_FORMAT_COUNT);
245   delegate_->SetImageFeatures([result_data length], format);
246   delegate_->OnDataDecoded(result_data);
247   output_buffer_.reset();
248   return true;
251 }  // namespace webp_transcode