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