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

Side by Side Diff: ios/web/public/image_fetcher/webp_decoder.mm

Issue 2699633006: Move WebpDecoder from ios/web to components/image_fetcher (Closed)
Patch Set: Separate header Created 3 years, 10 months 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
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 #import "ios/web/public/image_fetcher/webp_decoder.h"
6
7 #import <Foundation/Foundation.h>
8 #include <stdint.h>
9 #import <UIKit/UIKit.h>
10
11 #include "base/logging.h"
12 #include "base/metrics/histogram_macros.h"
13
14 #if !defined(__has_feature) || !__has_feature(objc_arc)
15 #error "This file requires ARC support."
16 #endif
17
18 namespace {
19
20 class WebpDecoderDelegate : public webp_transcode::WebpDecoder::Delegate {
21 public:
22 WebpDecoderDelegate() = default;
23
24 NSData* data() const { return decoded_image_; }
25
26 // WebpDecoder::Delegate methods
27 void OnFinishedDecoding(bool success) override {
28 if (!success)
29 decoded_image_ = nil;
30 }
31
32 void SetImageFeatures(
33 size_t total_size,
34 webp_transcode::WebpDecoder::DecodedImageFormat format) override {
35 decoded_image_ = [[NSMutableData alloc] initWithCapacity:total_size];
36 }
37
38 void OnDataDecoded(NSData* data) override {
39 DCHECK(decoded_image_);
40 [decoded_image_ appendData:data];
41 }
42
43 private:
44 ~WebpDecoderDelegate() override {}
45 NSMutableData* decoded_image_;
46
47 DISALLOW_COPY_AND_ASSIGN(WebpDecoderDelegate);
48 };
49
50 // Content-type header for WebP images.
51 const char kWEBPFirstMagicPattern[] = "RIFF";
52 const char kWEBPSecondMagicPattern[] = "WEBP";
53
54 const uint8_t kNumIfdEntries = 15;
55 const unsigned int kExtraDataSize = 16;
56 // 10b for signature/header + n * 12b entries + 4b for IFD terminator:
57 const unsigned int kExtraDataOffset = 10 + 12 * kNumIfdEntries + 4;
58 const unsigned int kHeaderSize = kExtraDataOffset + kExtraDataSize;
59 const int kRecompressionThreshold = 64 * 64; // Threshold in pixels.
60 const CGFloat kJpegQuality = 0.85;
61
62 // Adapted from libwebp example dwebp.c.
63 void PutLE16(uint8_t* const dst, uint32_t value) {
64 dst[0] = (value >> 0) & 0xff;
65 dst[1] = (value >> 8) & 0xff;
66 }
67
68 void PutLE32(uint8_t* const dst, uint32_t value) {
69 PutLE16(dst + 0, (value >> 0) & 0xffff);
70 PutLE16(dst + 2, (value >> 16) & 0xffff);
71 }
72
73 void WriteTiffHeader(uint8_t* dst,
74 int width,
75 int height,
76 int bytes_per_px,
77 bool has_alpha) {
78 // For non-alpha case, we omit tag 0x152 (ExtraSamples).
79 const uint8_t num_ifd_entries =
80 has_alpha ? kNumIfdEntries : kNumIfdEntries - 1;
81 uint8_t tiff_header[kHeaderSize] = {
82 0x49, 0x49, 0x2a, 0x00, // little endian signature
83 8, 0, 0, 0, // offset to the unique IFD that follows
84 // IFD (offset = 8). Entries must be written in increasing tag order.
85 num_ifd_entries, 0, // Number of entries in the IFD (12 bytes each).
86 0x00, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 10: Width (TBD)
87 0x01, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 22: Height (TBD)
88 0x02, 0x01, 3, 0, bytes_per_px, 0, 0, 0, // 34: BitsPerSample: 8888
89 kExtraDataOffset + 0, 0, 0, 0, 0x03, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0,
90 0, // 46: Compression: none
91 0x06, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 58: Photometric: RGB
92 0x11, 0x01, 4, 0, 1, 0, 0, 0, // 70: Strips offset:
93 kHeaderSize, 0, 0, 0, // data follows header
94 0x12, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 82: Orientation: topleft
95 0x15, 0x01, 3, 0, 1, 0, 0, 0, // 94: SamplesPerPixels
96 bytes_per_px, 0, 0, 0, 0x16, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0,
97 0, // 106: Rows per strip (TBD)
98 0x17, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 118: StripByteCount (TBD)
99 0x1a, 0x01, 5, 0, 1, 0, 0, 0, // 130: X-resolution
100 kExtraDataOffset + 8, 0, 0, 0, 0x1b, 0x01, 5, 0, 1, 0, 0,
101 0, // 142: Y-resolution
102 kExtraDataOffset + 8, 0, 0, 0, 0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0,
103 0, // 154: PlanarConfiguration
104 0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 166: ResolutionUnit (inch)
105 0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 178: ExtraSamples: rgbA
106 0, 0, 0, 0, // 190: IFD terminator
107 // kExtraDataOffset:
108 8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample
109 72, 0, 0, 0, 1, 0, 0, 0 // 72 pixels/inch, for X/Y-resolution
110 };
111
112 // Fill placeholders in IFD:
113 PutLE32(tiff_header + 10 + 8, width);
114 PutLE32(tiff_header + 22 + 8, height);
115 PutLE32(tiff_header + 106 + 8, height);
116 PutLE32(tiff_header + 118 + 8, width * bytes_per_px * height);
117 if (!has_alpha)
118 PutLE32(tiff_header + 178, 0);
119
120 memcpy(dst, tiff_header, kHeaderSize);
121 }
122
123 } // namespace
124
125 namespace webp_transcode {
126
127 // static
128 NSData* WebpDecoder::DecodeWebpImage(NSData* webp_image) {
129 scoped_refptr<WebpDecoderDelegate> delegate(new WebpDecoderDelegate);
130
131 scoped_refptr<webp_transcode::WebpDecoder> decoder(
132 new webp_transcode::WebpDecoder(delegate.get()));
133
134 decoder->OnDataReceived(webp_image);
135 DLOG_IF(ERROR, !delegate->data()) << "WebP image decoding failed.";
136 return delegate->data();
137 }
138
139 // static
140 bool WebpDecoder::IsWebpImage(const std::string& image_data) {
141 if (image_data.length() < 12)
142 return false;
143 return image_data.compare(0, 4, kWEBPFirstMagicPattern) == 0 &&
144 image_data.compare(8, 4, kWEBPSecondMagicPattern) == 0;
145 }
146
147 // static
148 size_t WebpDecoder::GetHeaderSize() {
149 return kHeaderSize;
150 }
151
152 WebpDecoder::WebpDecoder(WebpDecoder::Delegate* delegate)
153 : delegate_(delegate), state_(READING_FEATURES), has_alpha_(0) {
154 DCHECK(delegate_.get());
155 const bool rv = WebPInitDecoderConfig(&config_);
156 DCHECK(rv);
157 }
158
159 WebpDecoder::~WebpDecoder() {
160 WebPFreeDecBuffer(&config_.output);
161 }
162
163 void WebpDecoder::OnDataReceived(NSData* data) {
164 DCHECK(data);
165 switch (state_) {
166 case READING_FEATURES:
167 DoReadFeatures(data);
168 break;
169 case READING_DATA:
170 DoReadData(data);
171 break;
172 case DONE:
173 DLOG(WARNING) << "Received WebP data but decoding is finished. Ignoring.";
174 break;
175 }
176 }
177
178 void WebpDecoder::Stop() {
179 if (state_ != DONE) {
180 state_ = DONE;
181 DLOG(WARNING) << "Unexpected end of WebP data.";
182 delegate_->OnFinishedDecoding(false);
183 }
184 }
185
186 void WebpDecoder::DoReadFeatures(NSData* data) {
187 DCHECK_EQ(READING_FEATURES, state_);
188 DCHECK(data);
189 if (features_)
190 [features_ appendData:data];
191 else
192 features_.reset([[NSMutableData alloc] initWithData:data]);
193 VP8StatusCode status =
194 WebPGetFeatures(static_cast<const uint8_t*>([features_ bytes]),
195 [features_ length], &config_.input);
196 switch (status) {
197 case VP8_STATUS_OK: {
198 has_alpha_ = config_.input.has_alpha;
199 const uint32_t width = config_.input.width;
200 const uint32_t height = config_.input.height;
201 const size_t bytes_per_px = has_alpha_ ? 4 : 3;
202 const int stride = bytes_per_px * width;
203 const size_t image_data_size = stride * height;
204 const size_t total_size = image_data_size + kHeaderSize;
205 // Force pre-multiplied alpha.
206 config_.output.colorspace = has_alpha_ ? MODE_rgbA : MODE_RGB;
207 config_.output.u.RGBA.stride = stride;
208 // Create the output buffer.
209 config_.output.u.RGBA.size = image_data_size;
210 uint8_t* dst = static_cast<uint8_t*>(malloc(total_size));
211 if (!dst) {
212 DLOG(ERROR) << "Could not allocate WebP decoding buffer (size = "
213 << total_size << ").";
214 delegate_->OnFinishedDecoding(false);
215 state_ = DONE;
216 break;
217 }
218 WriteTiffHeader(dst, width, height, bytes_per_px, has_alpha_);
219 output_buffer_.reset([[NSData alloc] initWithBytesNoCopy:dst
220 length:total_size
221 freeWhenDone:YES]);
222 config_.output.is_external_memory = 1;
223 config_.output.u.RGBA.rgba = dst + kHeaderSize;
224 // Start decoding.
225 state_ = READING_DATA;
226 incremental_decoder_.reset(WebPINewDecoder(&config_.output));
227 DoReadData(features_);
228 features_.reset();
229 break;
230 }
231 case VP8_STATUS_NOT_ENOUGH_DATA:
232 // Do nothing.
233 break;
234 default:
235 DLOG(ERROR) << "Error in WebP image features.";
236 delegate_->OnFinishedDecoding(false);
237 state_ = DONE;
238 break;
239 }
240 }
241
242 void WebpDecoder::DoReadData(NSData* data) {
243 DCHECK_EQ(READING_DATA, state_);
244 DCHECK(incremental_decoder_);
245 DCHECK(data);
246 VP8StatusCode status =
247 WebPIAppend(incremental_decoder_.get(),
248 static_cast<const uint8_t*>([data bytes]), [data length]);
249 switch (status) {
250 case VP8_STATUS_SUSPENDED:
251 // Do nothing: re-compression to JPEG or PNG cannot be done incrementally.
252 // Wait for the whole image to be decoded.
253 break;
254 case VP8_STATUS_OK: {
255 bool rv = DoSendData();
256 DLOG_IF(ERROR, !rv) << "Error in WebP image conversion.";
257 state_ = DONE;
258 delegate_->OnFinishedDecoding(rv);
259 break;
260 }
261 default:
262 DLOG(ERROR) << "Error in WebP image decoding.";
263 delegate_->OnFinishedDecoding(false);
264 state_ = DONE;
265 break;
266 }
267 }
268
269 bool WebpDecoder::DoSendData() {
270 DCHECK_EQ(READING_DATA, state_);
271 int width, height;
272 uint8_t* data_ptr = WebPIDecGetRGB(incremental_decoder_.get(), nullptr,
273 &width, &height, nullptr);
274 if (!data_ptr)
275 return false;
276 DCHECK_EQ(static_cast<const uint8_t*>([output_buffer_ bytes]) + kHeaderSize,
277 data_ptr);
278 base::scoped_nsobject<NSData> result_data;
279 // When the WebP image is larger than |kRecompressionThreshold| it is
280 // compressed to JPEG or PNG. Otherwise, the uncompressed TIFF is used.
281 DecodedImageFormat format = TIFF;
282 if (width * height > kRecompressionThreshold) {
283 base::scoped_nsobject<UIImage> tiff_image(
284 [[UIImage alloc] initWithData:output_buffer_]);
285 if (!tiff_image)
286 return false;
287 // Compress to PNG if the image is transparent, JPEG otherwise.
288 // TODO(droger): Use PNG instead of JPEG if the WebP image is lossless.
289 if (has_alpha_) {
290 result_data.reset(UIImagePNGRepresentation(tiff_image));
291 format = PNG;
292 } else {
293 result_data.reset(UIImageJPEGRepresentation(tiff_image, kJpegQuality));
294 format = JPEG;
295 }
296 if (!result_data)
297 return false;
298 } else {
299 result_data.reset(output_buffer_);
300 }
301 UMA_HISTOGRAM_ENUMERATION("WebP.DecodedImageFormat", format,
302 DECODED_FORMAT_COUNT);
303 delegate_->SetImageFeatures([result_data length], format);
304 delegate_->OnDataDecoded(result_data);
305 output_buffer_.reset();
306 return true;
307 }
308
309 } // namespace webp_transcode
OLDNEW
« no previous file with comments | « ios/web/public/image_fetcher/webp_decoder.h ('k') | ios/web/public/image_fetcher/webp_decoder_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698