| 1 | // Copyright 2011 Google Inc. All Rights Reserved. |
| 2 | // |
| 3 | // Use of this source code is governed by a BSD-style license |
| 4 | // that can be found in the COPYING file in the root of the source |
| 5 | // tree. An additional intellectual property rights grant can be found |
| 6 | // in the file PATENTS. All contributing project authors may |
| 7 | // be found in the AUTHORS file in the root of the source tree. |
| 8 | // ----------------------------------------------------------------------------- |
| 9 | // |
| 10 | // Header syntax writing |
| 11 | // |
| 12 | // Author: Skal (pascal.massimino@gmail.com) |
| 13 | |
| 14 | #include <assert.h> |
| 15 | |
| 16 | #include "src/utils/utils.h" |
| 17 | #include "src/webp/format_constants.h" // RIFF constants |
| 18 | #include "src/webp/mux_types.h" // ALPHA_FLAG |
| 19 | #include "src/enc/vp8i_enc.h" |
| 20 | |
| 21 | //------------------------------------------------------------------------------ |
| 22 | // Helper functions |
| 23 | |
| 24 | static int IsVP8XNeeded(const VP8Encoder* const enc) { |
| 25 | return !!enc->has_alpha_; // Currently the only case when VP8X is needed. |
| 26 | // This could change in the future. |
| 27 | } |
| 28 | |
| 29 | static int PutPaddingByte(const WebPPicture* const pic) { |
| 30 | const uint8_t pad_byte[1] = { 0 }; |
| 31 | return !!pic->writer(pad_byte, 1, pic); |
| 32 | } |
| 33 | |
| 34 | //------------------------------------------------------------------------------ |
| 35 | // Writers for header's various pieces (in order of appearance) |
| 36 | |
| 37 | static WebPEncodingError (const VP8Encoder* const enc, |
| 38 | size_t riff_size) { |
| 39 | const WebPPicture* const pic = enc->pic_; |
| 40 | uint8_t riff[RIFF_HEADER_SIZE] = { |
| 41 | 'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'E', 'B', 'P' |
| 42 | }; |
| 43 | assert(riff_size == (uint32_t)riff_size); |
| 44 | PutLE32(riff + TAG_SIZE, (uint32_t)riff_size); |
| 45 | if (!pic->writer(riff, sizeof(riff), pic)) { |
| 46 | return VP8_ENC_ERROR_BAD_WRITE; |
| 47 | } |
| 48 | return VP8_ENC_OK; |
| 49 | } |
| 50 | |
| 51 | static WebPEncodingError (const VP8Encoder* const enc) { |
| 52 | const WebPPicture* const pic = enc->pic_; |
| 53 | uint8_t vp8x[CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE] = { |
| 54 | 'V', 'P', '8', 'X' |
| 55 | }; |
| 56 | uint32_t flags = 0; |
| 57 | |
| 58 | assert(IsVP8XNeeded(enc)); |
| 59 | assert(pic->width >= 1 && pic->height >= 1); |
| 60 | assert(pic->width <= MAX_CANVAS_SIZE && pic->height <= MAX_CANVAS_SIZE); |
| 61 | |
| 62 | if (enc->has_alpha_) { |
| 63 | flags |= ALPHA_FLAG; |
| 64 | } |
| 65 | |
| 66 | PutLE32(vp8x + TAG_SIZE, VP8X_CHUNK_SIZE); |
| 67 | PutLE32(vp8x + CHUNK_HEADER_SIZE, flags); |
| 68 | PutLE24(vp8x + CHUNK_HEADER_SIZE + 4, pic->width - 1); |
| 69 | PutLE24(vp8x + CHUNK_HEADER_SIZE + 7, pic->height - 1); |
| 70 | if (!pic->writer(vp8x, sizeof(vp8x), pic)) { |
| 71 | return VP8_ENC_ERROR_BAD_WRITE; |
| 72 | } |
| 73 | return VP8_ENC_OK; |
| 74 | } |
| 75 | |
| 76 | static WebPEncodingError PutAlphaChunk(const VP8Encoder* const enc) { |
| 77 | const WebPPicture* const pic = enc->pic_; |
| 78 | uint8_t alpha_chunk_hdr[CHUNK_HEADER_SIZE] = { |
| 79 | 'A', 'L', 'P', 'H' |
| 80 | }; |
| 81 | |
| 82 | assert(enc->has_alpha_); |
| 83 | |
| 84 | // Alpha chunk header. |
| 85 | PutLE32(alpha_chunk_hdr + TAG_SIZE, enc->alpha_data_size_); |
| 86 | if (!pic->writer(alpha_chunk_hdr, sizeof(alpha_chunk_hdr), pic)) { |
| 87 | return VP8_ENC_ERROR_BAD_WRITE; |
| 88 | } |
| 89 | |
| 90 | // Alpha chunk data. |
| 91 | if (!pic->writer(enc->alpha_data_, enc->alpha_data_size_, pic)) { |
| 92 | return VP8_ENC_ERROR_BAD_WRITE; |
| 93 | } |
| 94 | |
| 95 | // Padding. |
| 96 | if ((enc->alpha_data_size_ & 1) && !PutPaddingByte(pic)) { |
| 97 | return VP8_ENC_ERROR_BAD_WRITE; |
| 98 | } |
| 99 | return VP8_ENC_OK; |
| 100 | } |
| 101 | |
| 102 | static WebPEncodingError (const WebPPicture* const pic, |
| 103 | size_t vp8_size) { |
| 104 | uint8_t vp8_chunk_hdr[CHUNK_HEADER_SIZE] = { |
| 105 | 'V', 'P', '8', ' ' |
| 106 | }; |
| 107 | assert(vp8_size == (uint32_t)vp8_size); |
| 108 | PutLE32(vp8_chunk_hdr + TAG_SIZE, (uint32_t)vp8_size); |
| 109 | if (!pic->writer(vp8_chunk_hdr, sizeof(vp8_chunk_hdr), pic)) { |
| 110 | return VP8_ENC_ERROR_BAD_WRITE; |
| 111 | } |
| 112 | return VP8_ENC_OK; |
| 113 | } |
| 114 | |
| 115 | static WebPEncodingError (const WebPPicture* const pic, |
| 116 | int profile, size_t size0) { |
| 117 | uint8_t vp8_frm_hdr[VP8_FRAME_HEADER_SIZE]; |
| 118 | uint32_t bits; |
| 119 | |
| 120 | if (size0 >= VP8_MAX_PARTITION0_SIZE) { // partition #0 is too big to fit |
| 121 | return VP8_ENC_ERROR_PARTITION0_OVERFLOW; |
| 122 | } |
| 123 | |
| 124 | // Paragraph 9.1. |
| 125 | bits = 0 // keyframe (1b) |
| 126 | | (profile << 1) // profile (3b) |
| 127 | | (1 << 4) // visible (1b) |
| 128 | | ((uint32_t)size0 << 5); // partition length (19b) |
| 129 | vp8_frm_hdr[0] = (bits >> 0) & 0xff; |
| 130 | vp8_frm_hdr[1] = (bits >> 8) & 0xff; |
| 131 | vp8_frm_hdr[2] = (bits >> 16) & 0xff; |
| 132 | // signature |
| 133 | vp8_frm_hdr[3] = (VP8_SIGNATURE >> 16) & 0xff; |
| 134 | vp8_frm_hdr[4] = (VP8_SIGNATURE >> 8) & 0xff; |
| 135 | vp8_frm_hdr[5] = (VP8_SIGNATURE >> 0) & 0xff; |
| 136 | // dimensions |
| 137 | vp8_frm_hdr[6] = pic->width & 0xff; |
| 138 | vp8_frm_hdr[7] = pic->width >> 8; |
| 139 | vp8_frm_hdr[8] = pic->height & 0xff; |
| 140 | vp8_frm_hdr[9] = pic->height >> 8; |
| 141 | |
| 142 | if (!pic->writer(vp8_frm_hdr, sizeof(vp8_frm_hdr), pic)) { |
| 143 | return VP8_ENC_ERROR_BAD_WRITE; |
| 144 | } |
| 145 | return VP8_ENC_OK; |
| 146 | } |
| 147 | |
| 148 | // WebP Headers. |
| 149 | static int (const VP8Encoder* const enc, size_t size0, |
| 150 | size_t vp8_size, size_t riff_size) { |
| 151 | WebPPicture* const pic = enc->pic_; |
| 152 | WebPEncodingError err = VP8_ENC_OK; |
| 153 | |
| 154 | // RIFF header. |
| 155 | err = PutRIFFHeader(enc, riff_size); |
| 156 | if (err != VP8_ENC_OK) goto Error; |
| 157 | |
| 158 | // VP8X. |
| 159 | if (IsVP8XNeeded(enc)) { |
| 160 | err = PutVP8XHeader(enc); |
| 161 | if (err != VP8_ENC_OK) goto Error; |
| 162 | } |
| 163 | |
| 164 | // Alpha. |
| 165 | if (enc->has_alpha_) { |
| 166 | err = PutAlphaChunk(enc); |
| 167 | if (err != VP8_ENC_OK) goto Error; |
| 168 | } |
| 169 | |
| 170 | // VP8 header. |
| 171 | err = PutVP8Header(pic, vp8_size); |
| 172 | if (err != VP8_ENC_OK) goto Error; |
| 173 | |
| 174 | // VP8 frame header. |
| 175 | err = PutVP8FrameHeader(pic, enc->profile_, size0); |
| 176 | if (err != VP8_ENC_OK) goto Error; |
| 177 | |
| 178 | // All OK. |
| 179 | return 1; |
| 180 | |
| 181 | // Error. |
| 182 | Error: |
| 183 | return WebPEncodingSetError(pic, err); |
| 184 | } |
| 185 | |
| 186 | // Segmentation header |
| 187 | static void (VP8BitWriter* const bw, |
| 188 | const VP8Encoder* const enc) { |
| 189 | const VP8EncSegmentHeader* const hdr = &enc->segment_hdr_; |
| 190 | const VP8EncProba* const proba = &enc->proba_; |
| 191 | if (VP8PutBitUniform(bw, (hdr->num_segments_ > 1))) { |
| 192 | // We always 'update' the quant and filter strength values |
| 193 | const int update_data = 1; |
| 194 | int s; |
| 195 | VP8PutBitUniform(bw, hdr->update_map_); |
| 196 | if (VP8PutBitUniform(bw, update_data)) { |
| 197 | // we always use absolute values, not relative ones |
| 198 | VP8PutBitUniform(bw, 1); // (segment_feature_mode = 1. Paragraph 9.3.) |
| 199 | for (s = 0; s < NUM_MB_SEGMENTS; ++s) { |
| 200 | VP8PutSignedBits(bw, enc->dqm_[s].quant_, 7); |
| 201 | } |
| 202 | for (s = 0; s < NUM_MB_SEGMENTS; ++s) { |
| 203 | VP8PutSignedBits(bw, enc->dqm_[s].fstrength_, 6); |
| 204 | } |
| 205 | } |
| 206 | if (hdr->update_map_) { |
| 207 | for (s = 0; s < 3; ++s) { |
| 208 | if (VP8PutBitUniform(bw, (proba->segments_[s] != 255u))) { |
| 209 | VP8PutBits(bw, proba->segments_[s], 8); |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | // Filtering parameters header |
| 217 | static void (VP8BitWriter* const bw, |
| 218 | const VP8EncFilterHeader* const hdr) { |
| 219 | const int use_lf_delta = (hdr->i4x4_lf_delta_ != 0); |
| 220 | VP8PutBitUniform(bw, hdr->simple_); |
| 221 | VP8PutBits(bw, hdr->level_, 6); |
| 222 | VP8PutBits(bw, hdr->sharpness_, 3); |
| 223 | if (VP8PutBitUniform(bw, use_lf_delta)) { |
| 224 | // '0' is the default value for i4x4_lf_delta_ at frame #0. |
| 225 | const int need_update = (hdr->i4x4_lf_delta_ != 0); |
| 226 | if (VP8PutBitUniform(bw, need_update)) { |
| 227 | // we don't use ref_lf_delta => emit four 0 bits |
| 228 | VP8PutBits(bw, 0, 4); |
| 229 | // we use mode_lf_delta for i4x4 |
| 230 | VP8PutSignedBits(bw, hdr->i4x4_lf_delta_, 6); |
| 231 | VP8PutBits(bw, 0, 3); // all others unused |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | // Nominal quantization parameters |
| 237 | static void PutQuant(VP8BitWriter* const bw, |
| 238 | const VP8Encoder* const enc) { |
| 239 | VP8PutBits(bw, enc->base_quant_, 7); |
| 240 | VP8PutSignedBits(bw, enc->dq_y1_dc_, 4); |
| 241 | VP8PutSignedBits(bw, enc->dq_y2_dc_, 4); |
| 242 | VP8PutSignedBits(bw, enc->dq_y2_ac_, 4); |
| 243 | VP8PutSignedBits(bw, enc->dq_uv_dc_, 4); |
| 244 | VP8PutSignedBits(bw, enc->dq_uv_ac_, 4); |
| 245 | } |
| 246 | |
| 247 | // Partition sizes |
| 248 | static int EmitPartitionsSize(const VP8Encoder* const enc, |
| 249 | WebPPicture* const pic) { |
| 250 | uint8_t buf[3 * (MAX_NUM_PARTITIONS - 1)]; |
| 251 | int p; |
| 252 | for (p = 0; p < enc->num_parts_ - 1; ++p) { |
| 253 | const size_t part_size = VP8BitWriterSize(enc->parts_ + p); |
| 254 | if (part_size >= VP8_MAX_PARTITION_SIZE) { |
| 255 | return WebPEncodingSetError(pic, VP8_ENC_ERROR_PARTITION_OVERFLOW); |
| 256 | } |
| 257 | buf[3 * p + 0] = (part_size >> 0) & 0xff; |
| 258 | buf[3 * p + 1] = (part_size >> 8) & 0xff; |
| 259 | buf[3 * p + 2] = (part_size >> 16) & 0xff; |
| 260 | } |
| 261 | return p ? pic->writer(buf, 3 * p, pic) : 1; |
| 262 | } |
| 263 | |
| 264 | //------------------------------------------------------------------------------ |
| 265 | |
| 266 | static int GeneratePartition0(VP8Encoder* const enc) { |
| 267 | VP8BitWriter* const bw = &enc->bw_; |
| 268 | const int mb_size = enc->mb_w_ * enc->mb_h_; |
| 269 | uint64_t pos1, pos2, pos3; |
| 270 | |
| 271 | pos1 = VP8BitWriterPos(bw); |
| 272 | if (!VP8BitWriterInit(bw, mb_size * 7 / 8)) { // ~7 bits per macroblock |
| 273 | return WebPEncodingSetError(enc->pic_, VP8_ENC_ERROR_OUT_OF_MEMORY); |
| 274 | } |
| 275 | VP8PutBitUniform(bw, 0); // colorspace |
| 276 | VP8PutBitUniform(bw, 0); // clamp type |
| 277 | |
| 278 | PutSegmentHeader(bw, enc); |
| 279 | PutFilterHeader(bw, &enc->filter_hdr_); |
| 280 | VP8PutBits(bw, enc->num_parts_ == 8 ? 3 : |
| 281 | enc->num_parts_ == 4 ? 2 : |
| 282 | enc->num_parts_ == 2 ? 1 : 0, 2); |
| 283 | PutQuant(bw, enc); |
| 284 | VP8PutBitUniform(bw, 0); // no proba update |
| 285 | VP8WriteProbas(bw, &enc->proba_); |
| 286 | pos2 = VP8BitWriterPos(bw); |
| 287 | VP8CodeIntraModes(enc); |
| 288 | VP8BitWriterFinish(bw); |
| 289 | |
| 290 | pos3 = VP8BitWriterPos(bw); |
| 291 | |
| 292 | #if !defined(WEBP_DISABLE_STATS) |
| 293 | if (enc->pic_->stats) { |
| 294 | enc->pic_->stats->header_bytes[0] = (int)((pos2 - pos1 + 7) >> 3); |
| 295 | enc->pic_->stats->header_bytes[1] = (int)((pos3 - pos2 + 7) >> 3); |
| 296 | enc->pic_->stats->alpha_data_size = (int)enc->alpha_data_size_; |
| 297 | } |
| 298 | #else |
| 299 | (void)pos1; |
| 300 | (void)pos2; |
| 301 | (void)pos3; |
| 302 | #endif |
| 303 | if (bw->error_) { |
| 304 | return WebPEncodingSetError(enc->pic_, VP8_ENC_ERROR_OUT_OF_MEMORY); |
| 305 | } |
| 306 | return 1; |
| 307 | } |
| 308 | |
| 309 | void VP8EncFreeBitWriters(VP8Encoder* const enc) { |
| 310 | int p; |
| 311 | VP8BitWriterWipeOut(&enc->bw_); |
| 312 | for (p = 0; p < enc->num_parts_; ++p) { |
| 313 | VP8BitWriterWipeOut(enc->parts_ + p); |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | int VP8EncWrite(VP8Encoder* const enc) { |
| 318 | WebPPicture* const pic = enc->pic_; |
| 319 | VP8BitWriter* const bw = &enc->bw_; |
| 320 | const int task_percent = 19; |
| 321 | const int percent_per_part = task_percent / enc->num_parts_; |
| 322 | const int final_percent = enc->percent_ + task_percent; |
| 323 | int ok = 0; |
| 324 | size_t vp8_size, pad, riff_size; |
| 325 | int p; |
| 326 | |
| 327 | // Partition #0 with header and partition sizes |
| 328 | ok = GeneratePartition0(enc); |
| 329 | if (!ok) return 0; |
| 330 | |
| 331 | // Compute VP8 size |
| 332 | vp8_size = VP8_FRAME_HEADER_SIZE + |
| 333 | VP8BitWriterSize(bw) + |
| 334 | 3 * (enc->num_parts_ - 1); |
| 335 | for (p = 0; p < enc->num_parts_; ++p) { |
| 336 | vp8_size += VP8BitWriterSize(enc->parts_ + p); |
| 337 | } |
| 338 | pad = vp8_size & 1; |
| 339 | vp8_size += pad; |
| 340 | |
| 341 | // Compute RIFF size |
| 342 | // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. |
| 343 | riff_size = TAG_SIZE + CHUNK_HEADER_SIZE + vp8_size; |
| 344 | if (IsVP8XNeeded(enc)) { // Add size for: VP8X header + data. |
| 345 | riff_size += CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; |
| 346 | } |
| 347 | if (enc->has_alpha_) { // Add size for: ALPH header + data. |
| 348 | const uint32_t padded_alpha_size = enc->alpha_data_size_ + |
| 349 | (enc->alpha_data_size_ & 1); |
| 350 | riff_size += CHUNK_HEADER_SIZE + padded_alpha_size; |
| 351 | } |
| 352 | // Sanity check. |
| 353 | if (riff_size > 0xfffffffeU) { |
| 354 | return WebPEncodingSetError(pic, VP8_ENC_ERROR_FILE_TOO_BIG); |
| 355 | } |
| 356 | |
| 357 | // Emit headers and partition #0 |
| 358 | { |
| 359 | const uint8_t* const part0 = VP8BitWriterBuf(bw); |
| 360 | const size_t size0 = VP8BitWriterSize(bw); |
| 361 | ok = ok && PutWebPHeaders(enc, size0, vp8_size, riff_size) |
| 362 | && pic->writer(part0, size0, pic) |
| 363 | && EmitPartitionsSize(enc, pic); |
| 364 | VP8BitWriterWipeOut(bw); // will free the internal buffer. |
| 365 | } |
| 366 | |
| 367 | // Token partitions |
| 368 | for (p = 0; p < enc->num_parts_; ++p) { |
| 369 | const uint8_t* const buf = VP8BitWriterBuf(enc->parts_ + p); |
| 370 | const size_t size = VP8BitWriterSize(enc->parts_ + p); |
| 371 | if (size) ok = ok && pic->writer(buf, size, pic); |
| 372 | VP8BitWriterWipeOut(enc->parts_ + p); // will free the internal buffer. |
| 373 | ok = ok && WebPReportProgress(pic, enc->percent_ + percent_per_part, |
| 374 | &enc->percent_); |
| 375 | } |
| 376 | |
| 377 | // Padding byte |
| 378 | if (ok && pad) { |
| 379 | ok = PutPaddingByte(pic); |
| 380 | } |
| 381 | |
| 382 | enc->coded_size_ = (int)(CHUNK_HEADER_SIZE + riff_size); |
| 383 | ok = ok && WebPReportProgress(pic, final_percent, &enc->percent_); |
| 384 | return ok; |
| 385 | } |
| 386 | |
| 387 | //------------------------------------------------------------------------------ |
| 388 | |
| 389 | |