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