OLD | NEW |
| (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 | |
OLD | NEW |