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