| 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 |