1use crate::decoder::decode_lossy_vp8_to_yuv;
4use crate::decoder::quant::{AC_TABLE, DC_TABLE};
5use crate::decoder::tree::{BMODES_PROBA, COEFFS_PROBA0, COEFFS_UPDATE_PROBA, Y_MODES_INTRA4};
6use crate::decoder::vp8i::{
7 B_DC_PRED, B_HD_PRED, B_HE_PRED, B_HU_PRED, B_LD_PRED, B_PRED, B_RD_PRED, B_TM_PRED, B_VE_PRED,
8 B_VL_PRED, B_VR_PRED, DC_PRED, H_PRED, MB_FEATURE_TREE_PROBS, NUM_BANDS, NUM_BMODES, NUM_CTX,
9 NUM_MB_SEGMENTS, NUM_PROBAS, NUM_TYPES, TM_PRED, V_PRED,
10};
11use crate::encoder::container::{wrap_still_webp, StillImageChunk};
12use crate::encoder::vp8_bool_writer::Vp8BoolWriter;
13use crate::encoder::EncoderError;
14use crate::ImageBuffer;
15
16const MAX_WEBP_DIMENSION: usize = 1 << 14;
17const MAX_PARTITION0_LENGTH: usize = (1 << 19) - 1;
18const YUV_FIX: i32 = 16;
19const YUV_HALF: i32 = 1 << (YUV_FIX - 1);
20const VP8_TRANSFORM_AC3_C1: i32 = 20_091;
21const VP8_TRANSFORM_AC3_C2: i32 = 35_468;
22
23const CAT3: [u8; 4] = [173, 148, 140, 0];
24const CAT4: [u8; 5] = [176, 155, 140, 135, 0];
25const CAT5: [u8; 6] = [180, 157, 141, 134, 130, 0];
26const CAT6: [u8; 12] = [254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0];
27const ZIGZAG: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
28const BANDS: [usize; 17] = [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0];
29
30type CoeffProbTables = [[[[u8; NUM_PROBAS]; NUM_CTX]; NUM_BANDS]; NUM_TYPES];
31type CoeffStats = [[[[u32; NUM_PROBAS]; NUM_CTX]; NUM_BANDS]; NUM_TYPES];
32
33const DEFAULT_LOSSY_OPTIMIZATION_LEVEL: u8 = 0;
34const MAX_LOSSY_OPTIMIZATION_LEVEL: u8 = 9;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct LossyEncodingOptions {
39 pub quality: u8,
41 pub optimization_level: u8,
46}
47
48impl Default for LossyEncodingOptions {
49 fn default() -> Self {
50 Self {
51 quality: 90,
52 optimization_level: DEFAULT_LOSSY_OPTIMIZATION_LEVEL,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, Default)]
58struct NonZeroContext {
59 nz: u8,
60 nz_dc: u8,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64struct MacroblockMode {
65 luma: u8,
66 sub_luma: [u8; 16],
67 chroma: u8,
68 segment: u8,
69 skip: bool,
70}
71
72#[derive(Debug, Clone, Copy)]
73struct QuantMatrices {
74 y1: [u16; 2],
75 y2: [u16; 2],
76 uv: [u16; 2],
77}
78
79#[derive(Debug, Clone, Copy)]
80struct RdMultipliers {
81 i16: u32,
82 i4: u32,
83 uv: u32,
84 mode: u32,
85}
86
87#[derive(Debug, Clone)]
88struct Planes {
89 y_stride: usize,
90 uv_stride: usize,
91 y: Vec<u8>,
92 u: Vec<u8>,
93 v: Vec<u8>,
94}
95
96#[derive(Debug, Clone)]
97struct SegmentConfig {
98 use_segment: bool,
99 update_map: bool,
100 quantizer: [u8; NUM_MB_SEGMENTS],
101 filter_strength: [i8; NUM_MB_SEGMENTS],
102 probs: [u8; MB_FEATURE_TREE_PROBS],
103 segments: Vec<u8>,
104}
105
106#[derive(Debug, Clone, Copy)]
107struct FilterConfig {
108 simple: bool,
109 level: u8,
110 sharpness: u8,
111}
112
113#[derive(Debug, Clone)]
114struct EncodedLossyCandidate {
115 base_quant: u8,
116 segment: SegmentConfig,
117 probabilities: CoeffProbTables,
118 modes: Vec<MacroblockMode>,
119 token_partition: Vec<u8>,
120}
121
122#[derive(Debug, Clone, Copy)]
123struct LossySearchProfile {
124 fast_mode_search: bool,
125 allow_i4x4: bool,
126 refine_i16: bool,
127 refine_i4_search: bool,
128 refine_i4_final: bool,
129 refine_chroma: bool,
130 refine_y2: bool,
131 update_probabilities: bool,
132}
133
134fn validate_rgba(width: usize, height: usize, rgba: &[u8]) -> Result<(), EncoderError> {
136 if width == 0 || height == 0 {
137 return Err(EncoderError::InvalidParam(
138 "image dimensions must be non-zero",
139 ));
140 }
141 if width > MAX_WEBP_DIMENSION || height > MAX_WEBP_DIMENSION {
142 return Err(EncoderError::InvalidParam(
143 "image dimensions exceed VP8 limits",
144 ));
145 }
146 let expected_len = width
147 .checked_mul(height)
148 .and_then(|pixels| pixels.checked_mul(4))
149 .ok_or(EncoderError::InvalidParam("image dimensions overflow"))?;
150 if rgba.len() != expected_len {
151 return Err(EncoderError::InvalidParam(
152 "RGBA buffer length does not match dimensions",
153 ));
154 }
155 if rgba.chunks_exact(4).any(|pixel| pixel[3] != 0xff) {
156 return Err(EncoderError::InvalidParam(
157 "lossy encoder does not support alpha yet",
158 ));
159 }
160 Ok(())
161}
162
163fn validate_options(options: &LossyEncodingOptions) -> Result<(), EncoderError> {
165 if options.quality > 100 {
166 return Err(EncoderError::InvalidParam(
167 "lossy quality must be in 0..=100",
168 ));
169 }
170 if options.optimization_level > MAX_LOSSY_OPTIMIZATION_LEVEL {
171 return Err(EncoderError::InvalidParam(
172 "lossy optimization level must be in 0..=9",
173 ));
174 }
175 Ok(())
176}
177
178fn base_quantizer_from_quality(quality: u8) -> i32 {
180 (((100 - quality as i32) * 127) + 50) / 100
181}
182
183fn build_quant_matrices(base_q: i32) -> QuantMatrices {
185 let q = base_q.clamp(0, 127) as usize;
186 QuantMatrices {
187 y1: [DC_TABLE[q] as u16, AC_TABLE[q]],
188 y2: [
189 (DC_TABLE[q] as u16) * 2,
190 ((AC_TABLE[q] as u32 * 101_581) >> 16).max(8) as u16,
191 ],
192 uv: [DC_TABLE[q.min(117)] as u16, AC_TABLE[q]],
193 }
194}
195
196fn build_rd_multipliers(quant: &QuantMatrices) -> RdMultipliers {
198 let q_i4 = u32::from(quant.y1[1].max(8));
199 let q_i16 = u32::from(quant.y2[1].max(8));
200 let q_uv = u32::from(quant.uv[1].max(8));
201 RdMultipliers {
202 i16: ((3 * q_i16 * q_i16).max(128)) >> 0,
203 i4: ((3 * q_i4 * q_i4).max(128)) >> 7,
204 uv: ((3 * q_uv * q_uv).max(128)) >> 6,
205 mode: (q_i4 * q_i4).max(128) >> 7,
206 }
207}
208
209fn clipped_quantizer(value: i32) -> u8 {
211 value.clamp(0, 127) as u8
212}
213
214fn filter_candidates(base_quant: i32) -> Vec<FilterConfig> {
216 let mut levels = vec![
217 0u8,
218 clipped_quantizer((base_quant + 1) / 2).min(63),
219 clipped_quantizer(base_quant).min(63),
220 clipped_quantizer((base_quant * 3 + 1) / 2).min(63),
221 clipped_quantizer(base_quant * 2).min(63),
222 ];
223 levels.sort_unstable();
224 levels.dedup();
225 levels
226 .into_iter()
227 .map(|level| FilterConfig {
228 simple: false,
229 level,
230 sharpness: 0,
231 })
232 .collect()
233}
234
235fn heuristic_filter(base_quant: i32) -> FilterConfig {
237 let level = if base_quant <= 10 {
238 0
239 } else {
240 clipped_quantizer((base_quant * 3 + 2) / 4).min(63)
241 };
242 FilterConfig {
243 simple: false,
244 level,
245 sharpness: 0,
246 }
247}
248
249fn lossy_search_profile(optimization_level: u8) -> LossySearchProfile {
251 match optimization_level {
252 0 => LossySearchProfile {
253 fast_mode_search: true,
254 allow_i4x4: false,
255 refine_i16: false,
256 refine_i4_search: false,
257 refine_i4_final: false,
258 refine_chroma: false,
259 refine_y2: false,
260 update_probabilities: false,
261 },
262 1 | 2 => LossySearchProfile {
263 fast_mode_search: false,
264 allow_i4x4: false,
265 refine_i16: false,
266 refine_i4_search: false,
267 refine_i4_final: false,
268 refine_chroma: false,
269 refine_y2: false,
270 update_probabilities: true,
271 },
272 3 | 4 => LossySearchProfile {
273 fast_mode_search: false,
274 allow_i4x4: true,
275 refine_i16: false,
276 refine_i4_search: false,
277 refine_i4_final: false,
278 refine_chroma: false,
279 refine_y2: false,
280 update_probabilities: true,
281 },
282 5 => LossySearchProfile {
283 fast_mode_search: false,
284 allow_i4x4: true,
285 refine_i16: false,
286 refine_i4_search: false,
287 refine_i4_final: false,
288 refine_chroma: true,
289 refine_y2: false,
290 update_probabilities: true,
291 },
292 6 => LossySearchProfile {
293 fast_mode_search: false,
294 allow_i4x4: true,
295 refine_i16: true,
296 refine_i4_search: false,
297 refine_i4_final: true,
298 refine_chroma: true,
299 refine_y2: false,
300 update_probabilities: true,
301 },
302 7 => LossySearchProfile {
303 fast_mode_search: false,
304 allow_i4x4: true,
305 refine_i16: true,
306 refine_i4_search: true,
307 refine_i4_final: true,
308 refine_chroma: true,
309 refine_y2: false,
310 update_probabilities: true,
311 },
312 _ => LossySearchProfile {
313 fast_mode_search: false,
314 allow_i4x4: true,
315 refine_i16: true,
316 refine_i4_search: true,
317 refine_i4_final: true,
318 refine_chroma: true,
319 refine_y2: true,
320 update_probabilities: true,
321 },
322 }
323}
324
325fn use_exhaustive_segment_search(optimization_level: u8) -> bool {
327 optimization_level >= 9
328}
329
330fn use_exhaustive_filter_search(optimization_level: u8, mb_count: usize) -> bool {
332 if optimization_level >= 9 {
333 return true;
334 }
335 if optimization_level >= 6 {
336 return mb_count < 2_048;
337 }
338 mb_count < 1_024
339}
340
341fn segment_with_uniform_filter(segment: &SegmentConfig, level: u8) -> SegmentConfig {
343 let mut filtered = segment.clone();
344 if filtered.use_segment {
345 filtered.filter_strength[..].fill(level as i8);
346 }
347 filtered
348}
349
350fn get_proba(a: usize, b: usize) -> u8 {
352 let total = a + b;
353 if total == 0 {
354 255
355 } else {
356 ((255 * a + total / 2) / total) as u8
357 }
358}
359
360fn build_segment_quantizers(segment: &SegmentConfig) -> [QuantMatrices; NUM_MB_SEGMENTS] {
362 std::array::from_fn(|index| build_quant_matrices(segment.quantizer[index] as i32))
363}
364
365fn disabled_segment_config(mb_count: usize, base_quant: u8) -> SegmentConfig {
367 SegmentConfig {
368 use_segment: false,
369 update_map: false,
370 quantizer: [base_quant; NUM_MB_SEGMENTS],
371 filter_strength: [0; NUM_MB_SEGMENTS],
372 probs: [255; MB_FEATURE_TREE_PROBS],
373 segments: vec![0; mb_count],
374 }
375}
376
377fn rgb_to_y(r: u8, g: u8, b: u8) -> u8 {
379 let luma = 16_839 * r as i32 + 33_059 * g as i32 + 6_420 * b as i32;
380 ((luma + YUV_HALF + (16 << YUV_FIX)) >> YUV_FIX) as u8
381}
382
383fn clip_uv(value: i32, rounding: i32) -> u8 {
385 let uv = (value + rounding + (128 << (YUV_FIX + 2))) >> (YUV_FIX + 2);
386 uv.clamp(0, 255) as u8
387}
388
389fn rgb_to_u(r: i32, g: i32, b: i32) -> u8 {
391 clip_uv(-9_719 * r - 19_081 * g + 28_800 * b, YUV_HALF << 2)
392}
393
394fn rgb_to_v(r: i32, g: i32, b: i32) -> u8 {
396 clip_uv(28_800 * r - 24_116 * g - 4_684 * b, YUV_HALF << 2)
397}
398
399fn rgba_to_yuv420(
401 width: usize,
402 height: usize,
403 rgba: &[u8],
404 mb_width: usize,
405 mb_height: usize,
406) -> Planes {
407 let y_stride = mb_width * 16;
408 let uv_stride = mb_width * 8;
409 let y_height = mb_height * 16;
410 let uv_height = mb_height * 8;
411 let mut y = vec![0u8; y_stride * y_height];
412 let mut u = vec![0u8; uv_stride * uv_height];
413 let mut v = vec![0u8; uv_stride * uv_height];
414
415 for py in 0..y_height {
416 let src_y = py.min(height - 1);
417 for px in 0..y_stride {
418 let src_x = px.min(width - 1);
419 let offset = (src_y * width + src_x) * 4;
420 y[py * y_stride + px] = rgb_to_y(rgba[offset], rgba[offset + 1], rgba[offset + 2]);
421 }
422 }
423
424 for py in 0..uv_height {
425 for px in 0..uv_stride {
426 let mut sum_r = 0i32;
427 let mut sum_g = 0i32;
428 let mut sum_b = 0i32;
429 for dy in 0..2 {
430 let src_y = (py * 2 + dy).min(height - 1);
431 for dx in 0..2 {
432 let src_x = (px * 2 + dx).min(width - 1);
433 let offset = (src_y * width + src_x) * 4;
434 sum_r += rgba[offset] as i32;
435 sum_g += rgba[offset + 1] as i32;
436 sum_b += rgba[offset + 2] as i32;
437 }
438 }
439 u[py * uv_stride + px] = rgb_to_u(sum_r, sum_g, sum_b);
440 v[py * uv_stride + px] = rgb_to_v(sum_r, sum_g, sum_b);
441 }
442 }
443
444 Planes {
445 y_stride,
446 uv_stride,
447 y,
448 u,
449 v,
450 }
451}
452
453fn empty_reconstructed_planes(mb_width: usize, mb_height: usize) -> Planes {
455 let y_stride = mb_width * 16;
456 let uv_stride = mb_width * 8;
457 let y_height = mb_height * 16;
458 let uv_height = mb_height * 8;
459 Planes {
460 y_stride,
461 uv_stride,
462 y: vec![0; y_stride * y_height],
463 u: vec![0; uv_stride * uv_height],
464 v: vec![0; uv_stride * uv_height],
465 }
466}
467
468fn macroblock_activity(source: &Planes, mb_x: usize, mb_y: usize) -> u32 {
470 let x0 = mb_x * 16;
471 let y0 = mb_y * 16;
472 let mut activity = 0u32;
473
474 for row in 0..16 {
475 let row_offset = (y0 + row) * source.y_stride + x0;
476 let pixels = &source.y[row_offset..row_offset + 16];
477 for col in 1..16 {
478 activity += pixels[col].abs_diff(pixels[col - 1]) as u32;
479 }
480 if row > 0 {
481 let prev_offset = (y0 + row - 1) * source.y_stride + x0;
482 let prev = &source.y[prev_offset..prev_offset + 16];
483 for col in 0..16 {
484 activity += pixels[col].abs_diff(prev[col]) as u32;
485 }
486 }
487 }
488
489 activity
490}
491
492fn build_segment_probs(counts: &[usize; NUM_MB_SEGMENTS]) -> [u8; MB_FEATURE_TREE_PROBS] {
494 [
495 get_proba(counts[0] + counts[1], counts[2] + counts[3]),
496 get_proba(counts[0], counts[1]),
497 get_proba(counts[2], counts[3]),
498 ]
499}
500
501fn build_segment_config(
503 activities: &[u32],
504 sorted_activities: &[u32],
505 flat_percent: usize,
506 flat_delta: i32,
507 detail_delta: i32,
508 base_quant: i32,
509) -> Option<SegmentConfig> {
510 if activities.len() < 8 {
511 return None;
512 }
513 let flat_count = (activities.len() * flat_percent / 100).clamp(1, activities.len() - 1);
514 let threshold = sorted_activities[flat_count - 1];
515
516 let mut segments = vec![0u8; activities.len()];
517 let mut counts = [0usize; NUM_MB_SEGMENTS];
518 for (index, &activity) in activities.iter().enumerate() {
519 let segment = if activity <= threshold { 0 } else { 1 };
520 segments[index] = segment;
521 counts[segment as usize] += 1;
522 }
523 if counts[0] == 0 || counts[1] == 0 {
524 return None;
525 }
526
527 let quant0 = clipped_quantizer(base_quant + flat_delta);
528 let quant1 = clipped_quantizer(base_quant + detail_delta);
529 if quant0 == quant1 {
530 return None;
531 }
532
533 let probs = build_segment_probs(&counts);
534 let update_map = probs.iter().any(|&prob| prob != 255);
535 if !update_map {
536 return None;
537 }
538
539 let mut quantizer = [quant0; NUM_MB_SEGMENTS];
540 quantizer[1] = quant1;
541 Some(SegmentConfig {
542 use_segment: true,
543 update_map,
544 quantizer,
545 filter_strength: [0; NUM_MB_SEGMENTS],
546 probs,
547 segments,
548 })
549}
550
551fn build_multi_segment_config(
553 activities: &[u32],
554 sorted_activities: &[u32],
555 percentiles: &[usize],
556 deltas: &[i32],
557 base_quant: i32,
558) -> Option<SegmentConfig> {
559 let segment_count = deltas.len();
560 if !(2..=NUM_MB_SEGMENTS).contains(&segment_count) || percentiles.len() + 1 != segment_count {
561 return None;
562 }
563
564 let mut thresholds = Vec::with_capacity(percentiles.len());
565 for &percentile in percentiles {
566 let split = (activities.len() * percentile / 100).clamp(1, activities.len() - 1);
567 thresholds.push(sorted_activities[split - 1]);
568 }
569 thresholds.sort_unstable();
570
571 let mut segments = vec![0u8; activities.len()];
572 let mut counts = [0usize; NUM_MB_SEGMENTS];
573 for (index, &activity) in activities.iter().enumerate() {
574 let segment = thresholds.partition_point(|&threshold| activity > threshold);
575 segments[index] = segment as u8;
576 counts[segment] += 1;
577 }
578
579 if counts[..segment_count].iter().any(|&count| count == 0) {
580 return None;
581 }
582
583 let mut quantizer = [clipped_quantizer(base_quant); NUM_MB_SEGMENTS];
584 let mut distinct = false;
585 for (index, &delta) in deltas.iter().enumerate() {
586 quantizer[index] = clipped_quantizer(base_quant + delta);
587 if index > 0 && quantizer[index] != quantizer[index - 1] {
588 distinct = true;
589 }
590 }
591 if !distinct {
592 return None;
593 }
594
595 let probs = build_segment_probs(&counts);
596 let update_map = probs.iter().any(|&prob| prob != 255);
597 if !update_map {
598 return None;
599 }
600
601 Some(SegmentConfig {
602 use_segment: true,
603 update_map,
604 quantizer,
605 filter_strength: [0; NUM_MB_SEGMENTS],
606 probs,
607 segments,
608 })
609}
610
611fn build_segment_candidates(
613 source: &Planes,
614 mb_width: usize,
615 mb_height: usize,
616 base_quant: i32,
617 optimization_level: u8,
618) -> Vec<SegmentConfig> {
619 let mb_count = mb_width * mb_height;
620 let mut candidates = vec![disabled_segment_config(
621 mb_count,
622 clipped_quantizer(base_quant),
623 )];
624 if mb_count < 8 || optimization_level == 0 {
625 return candidates;
626 }
627
628 let mut activities = Vec::with_capacity(mb_count);
629 for mb_y in 0..mb_height {
630 for mb_x in 0..mb_width {
631 activities.push(macroblock_activity(source, mb_x, mb_y));
632 }
633 }
634 let mut sorted = activities.clone();
635 sorted.sort_unstable();
636
637 if !use_exhaustive_segment_search(optimization_level) && mb_count >= 1_024 {
638 if let Some(config) = build_segment_config(&activities, &sorted, 65, 12, -2, base_quant) {
639 return vec![config];
640 }
641 return candidates;
642 }
643
644 let two_segment_presets: &[(usize, i32, i32)] = if optimization_level <= 2 {
645 &[(65usize, 12i32, -2i32)]
646 } else if mb_count >= 2_048 && !use_exhaustive_segment_search(optimization_level) {
647 &[(65usize, 12i32, -2i32), (55, 10, 0)]
648 } else {
649 &[(55usize, 10i32, 0i32), (65, 12, -2), (45, 8, 0)]
650 };
651 for &(flat_percent, flat_delta, detail_delta) in two_segment_presets {
652 if let Some(config) = build_segment_config(
653 &activities,
654 &sorted,
655 flat_percent,
656 flat_delta,
657 detail_delta,
658 base_quant,
659 ) {
660 candidates.push(config);
661 }
662 }
663
664 if optimization_level >= 4
665 && (use_exhaustive_segment_search(optimization_level) || mb_count < 2_048)
666 {
667 for (percentiles, deltas) in [
668 (&[35usize, 72usize][..], &[12i32, 4i32, -4i32][..]),
669 (
670 &[25usize, 50usize, 78usize][..],
671 &[16i32, 8i32, 1i32, -7i32][..],
672 ),
673 (
674 &[30usize, 58usize, 84usize][..],
675 &[18i32, 10i32, 2i32, -8i32][..],
676 ),
677 ] {
678 if let Some(config) =
679 build_multi_segment_config(&activities, &sorted, percentiles, deltas, base_quant)
680 {
681 candidates.push(config);
682 }
683 }
684 }
685
686 candidates
687}
688
689mod api;
690mod bitstream;
691mod predict;
692
693pub use api::*;