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

Side by Side 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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.
4
5 #include "ios/web/public/webp_decoder.h"
6
7 #import <Foundation/Foundation.h>
8 #import <UIKit/UIKit.h>
9
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12
13 namespace {
14
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;
22
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;
27 }
28
29 void PutLE32(uint8_t* const dst, uint32_t value) {
30 PutLE16(dst + 0, (value >> 0) & 0xffff);
31 PutLE16(dst + 2, (value >> 16) & 0xffff);
32 }
33
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 = has_alpha ? kNumIfdEntries
41 : 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 };
72
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);
80
81 memcpy(dst, tiff_header, kHeaderSize);
82 }
83
84 } // namespace
85
86 namespace web {
87
88 // static
89 size_t WebpDecoder::GetHeaderSize() {
90 return kHeaderSize;
91 }
92
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);
98 }
99
100 WebpDecoder::~WebpDecoder() {
101 WebPFreeDecBuffer(&config_.output);
102 }
103
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 }
117 }
118
119 void WebpDecoder::Stop() {
120 if (state_ != DONE) {
121 state_ = DONE;
122 DLOG(WARNING) << "Unexpected end of WebP data.";
123 delegate_->OnFinishedDecoding(false);
124 }
125 }
126
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 }
181 }
182
183 void WebpDecoder::DoReadData(NSData* data) {
184 DCHECK_EQ(READING_DATA, state_);
185 DCHECK(incremental_decoder_);
186 DCHECK(data);
187 VP8StatusCode status = WebPIAppend(incremental_decoder_.get(),
188 static_cast<const uint8_t*>([data bytes]),
189 [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 }
208 }
209
210 bool WebpDecoder::DoSendData() {
211 DCHECK_EQ(READING_DATA, state_);
212 int width, height;
213 uint8_t* data_ptr =
214 WebPIDecGetRGB(incremental_decoder_.get(), NULL, &width, &height, NULL);
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(
244 "WebP.DecodedImageFormat", format, DECODED_FORMAT_COUNT);
245 delegate_->SetImageFeatures([result_data length], format);
246 delegate_->OnDataDecoded(result_data);
247 output_buffer_.reset();
248 return true;
249 }
250
251 } // namespace web
OLDNEW
« 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