1use gamut_core::{Dimensions, Error, Result};
18
19use crate::vp8l::bit_io::BitWriter;
20use crate::vp8l::color_cache::ColorCache;
21use crate::vp8l::div_round_up;
22use crate::vp8l::header::Vp8lHeader;
23use crate::vp8l::lz77::{BackwardRefs, pixel_distance_to_code, value_to_prefix};
24use crate::vp8l::prefix::{
25 MAX_CODE_LENGTH, NUM_DISTANCE_CODES, NUM_LENGTH_CODES, NUM_LITERAL_CODES, PrefixEncoder,
26 build_length_limited_lengths, green_alphabet_size, write_normal_prefix_code,
27};
28use crate::vp8l::transform::{
29 COLOR_INDEXING_TRANSFORM, COLOR_TRANSFORM, PREDICTOR_TRANSFORM, SUBTRACT_GREEN_TRANSFORM,
30 alpha, blue, forward_color, forward_color_indexing, forward_predictor, forward_subtract_green,
31 green, red, subtract_pixels,
32};
33
34const TRANSFORM_SIZE_BITS: u8 = 4;
37
38const MAX_PALETTE_SIZE: usize = 256;
40
41const LENGTH_CODE_BASE: usize = NUM_LITERAL_CODES;
43const CACHE_CODE_BASE: usize = NUM_LITERAL_CODES + NUM_LENGTH_CODES;
45
46const PREFIX_BITS: u32 = 4;
48const MAX_GROUPS: u32 = 256;
51
52enum Token {
54 Literal(u32),
56 Copy { len: u32, dist: u32 },
58 CacheIndex(u16),
60}
61
62impl Token {
63 fn pixel_count(&self) -> usize {
65 match self {
66 Token::Copy { len, .. } => *len as usize,
67 Token::Literal(_) | Token::CacheIndex(_) => 1,
68 }
69 }
70
71 fn green_symbol(&self) -> usize {
74 match *self {
75 Token::Literal(p) => green(p) as usize,
76 Token::Copy { len, .. } => LENGTH_CODE_BASE + value_to_prefix(len).0 as usize,
77 Token::CacheIndex(idx) => CACHE_CODE_BASE + idx as usize,
78 }
79 }
80}
81
82pub fn encode(argb: &[u32], dims: Dimensions) -> Result<Vec<u8>> {
90 check_dimensions(argb, dims)?;
91 let alpha_is_used = argb.iter().any(|&p| alpha(p) != 0xff);
92 let header = Vp8lHeader::from_dimensions(dims, alpha_is_used)?;
93 let mut w = BitWriter::new();
94 header.write(&mut w);
95 write_image_body(&mut w, argb, dims);
96 Ok(w.finish())
97}
98
99pub fn encode_image(argb: &[u32], dims: Dimensions) -> Result<Vec<u8>> {
108 check_dimensions(argb, dims)?;
109 let mut w = BitWriter::new();
110 write_image_body(&mut w, argb, dims);
111 Ok(w.finish())
112}
113
114fn check_dimensions(argb: &[u32], dims: Dimensions) -> Result<()> {
116 if (dims.width as usize).checked_mul(dims.height as usize) == Some(argb.len()) {
117 Ok(())
118 } else {
119 Err(Error::InvalidInput(
120 "VP8L: pixel buffer does not match dimensions",
121 ))
122 }
123}
124
125fn write_image_body(w: &mut BitWriter, argb: &[u32], dims: Dimensions) {
129 match build_palette(argb) {
130 Some(palette) => encode_palette(w, argb, dims, &palette),
131 None => encode_spatial(w, argb, dims),
132 }
133}
134
135fn encode_spatial(w: &mut BitWriter, argb: &[u32], dims: Dimensions) {
138 let (width, height) = (dims.width, dims.height);
139 let mut pixels = argb.to_vec();
140
141 forward_subtract_green(&mut pixels);
142 write_transform_tag(w, SUBTRACT_GREEN_TRANSFORM);
143
144 let (residual, predictor_sub) = forward_predictor(&pixels, width, height, TRANSFORM_SIZE_BITS);
145 pixels = residual;
146 write_transform_tag(w, PREDICTOR_TRANSFORM);
147 w.write_bits(u32::from(TRANSFORM_SIZE_BITS - 2), 3);
148 write_sub_image(w, &predictor_sub);
149
150 let color_sub = forward_color(&mut pixels, width, height, TRANSFORM_SIZE_BITS);
151 write_transform_tag(w, COLOR_TRANSFORM);
152 w.write_bits(u32::from(TRANSFORM_SIZE_BITS - 2), 3);
153 write_sub_image(w, &color_sub);
154
155 w.write_bits(0, 1); write_main_image(w, &pixels, width);
157}
158
159fn encode_palette(w: &mut BitWriter, argb: &[u32], dims: Dimensions, palette: &[u32]) {
162 write_transform_tag(w, COLOR_INDEXING_TRANSFORM);
163 w.write_bits((palette.len() - 1) as u32, 8);
164
165 let mut palette_image = vec![0u32; palette.len()];
167 palette_image[0] = palette[0];
168 for i in 1..palette.len() {
169 palette_image[i] = subtract_pixels(palette[i], palette[i - 1]);
170 }
171 write_sub_image(w, &palette_image);
172
173 let (bundled, bundled_width) = forward_color_indexing(argb, dims.width, dims.height, palette);
174 w.write_bits(0, 1); write_main_image(w, &bundled, bundled_width);
176}
177
178fn build_palette(pixels: &[u32]) -> Option<Vec<u32>> {
182 use std::collections::HashSet;
183 let mut seen = HashSet::new();
184 let mut palette = Vec::new();
185 for &p in pixels {
186 if seen.insert(p) {
187 if palette.len() == MAX_PALETTE_SIZE {
188 return None;
189 }
190 palette.push(p);
191 }
192 }
193 Some(palette)
194}
195
196fn write_transform_tag(w: &mut BitWriter, transform_type: u8) {
198 w.write_bits(1, 1);
199 w.write_bits(u32::from(transform_type), 2);
200}
201
202struct Histograms {
204 green: Vec<u32>,
205 red: Vec<u32>,
206 blue: Vec<u32>,
207 alpha: Vec<u32>,
208 distance: Vec<u32>,
209}
210
211impl Histograms {
212 fn new(cache_size: usize) -> Self {
213 Self {
214 green: vec![0; green_alphabet_size(cache_size)],
215 red: vec![0; NUM_LITERAL_CODES],
216 blue: vec![0; NUM_LITERAL_CODES],
217 alpha: vec![0; NUM_LITERAL_CODES],
218 distance: vec![0; NUM_DISTANCE_CODES],
219 }
220 }
221
222 fn add(&mut self, token: &Token, width: u32) {
224 match *token {
225 Token::Literal(p) => {
226 self.green[green(p) as usize] += 1;
227 self.red[red(p) as usize] += 1;
228 self.blue[blue(p) as usize] += 1;
229 self.alpha[alpha(p) as usize] += 1;
230 }
231 Token::Copy { len, dist } => {
232 self.green[LENGTH_CODE_BASE + value_to_prefix(len).0 as usize] += 1;
233 let dist_code = pixel_distance_to_code(dist, width);
234 self.distance[value_to_prefix(dist_code).0 as usize] += 1;
235 }
236 Token::CacheIndex(idx) => self.green[CACHE_CODE_BASE + idx as usize] += 1,
237 }
238 }
239
240 fn build(&self) -> CodeGroup {
242 CodeGroup {
243 green: build_code(&self.green),
244 red: build_code(&self.red),
245 blue: build_code(&self.blue),
246 alpha: build_code(&self.alpha),
247 distance: build_code(&self.distance),
248 }
249 }
250}
251
252struct CodeGroup {
254 green: PrefixEncoder,
255 red: PrefixEncoder,
256 blue: PrefixEncoder,
257 alpha: PrefixEncoder,
258 distance: PrefixEncoder,
259}
260
261impl CodeGroup {
262 fn write_descriptions(&self, w: &mut BitWriter) {
264 write_normal_prefix_code(w, self.green.lengths());
265 write_normal_prefix_code(w, self.red.lengths());
266 write_normal_prefix_code(w, self.blue.lengths());
267 write_normal_prefix_code(w, self.alpha.lengths());
268 write_normal_prefix_code(w, self.distance.lengths());
269 }
270
271 fn write_token(&self, w: &mut BitWriter, token: &Token, width: u32) {
273 match *token {
274 Token::Literal(p) => {
275 self.green.write_symbol(w, green(p) as usize);
276 self.red.write_symbol(w, red(p) as usize);
277 self.blue.write_symbol(w, blue(p) as usize);
278 self.alpha.write_symbol(w, alpha(p) as usize);
279 }
280 Token::Copy { len, dist } => {
281 let (len_code, len_bits, len_extra) = value_to_prefix(len);
282 self.green
283 .write_symbol(w, LENGTH_CODE_BASE + len_code as usize);
284 w.write_bits(len_extra, u32::from(len_bits));
285 let (dist_sym, dist_bits, dist_extra) =
286 value_to_prefix(pixel_distance_to_code(dist, width));
287 self.distance.write_symbol(w, dist_sym as usize);
288 w.write_bits(dist_extra, u32::from(dist_bits));
289 }
290 Token::CacheIndex(idx) => self.green.write_symbol(w, CACHE_CODE_BASE + idx as usize),
291 }
292 }
293}
294
295fn write_sub_image(w: &mut BitWriter, pixels: &[u32]) {
298 w.write_bits(0, 1); let mut hist = Histograms::new(0);
300 for &p in pixels {
301 hist.add(&Token::Literal(p), 0);
302 }
303 let codes = hist.build();
304 codes.write_descriptions(w);
305 for &p in pixels {
306 codes.write_token(w, &Token::Literal(p), 0);
307 }
308}
309
310fn write_main_image(w: &mut BitWriter, pixels: &[u32], width: u32) {
313 let cache_bits = pick_cache_bits(pixels.len());
314 let cache_size = if cache_bits > 0 {
315 1usize << cache_bits
316 } else {
317 0
318 };
319 if cache_bits > 0 {
320 w.write_bits(1, 1);
321 w.write_bits(cache_bits, 4);
322 } else {
323 w.write_bits(0, 1);
324 }
325
326 let tokens = tokenize(pixels, cache_bits);
327 let height = (pixels.len() as u32).checked_div(width).unwrap_or(0);
328 let groups = assign_groups(&tokens, width, height);
329
330 if groups.num_groups > 1 {
331 w.write_bits(1, 1); w.write_bits(groups.prefix_bits - 2, 3);
333 write_sub_image(w, &groups.entropy_image());
334 } else {
335 w.write_bits(0, 1); }
337
338 let mut histograms: Vec<Histograms> = (0..groups.num_groups)
341 .map(|_| Histograms::new(cache_size))
342 .collect();
343 let mut pos = 0usize;
344 for token in &tokens {
345 histograms[groups.group_at(pos, width)].add(token, width);
346 pos += token.pixel_count();
347 }
348 let code_groups: Vec<CodeGroup> = histograms.iter().map(Histograms::build).collect();
349 for group in &code_groups {
350 group.write_descriptions(w);
351 }
352 let mut pos = 0usize;
353 for token in &tokens {
354 code_groups[groups.group_at(pos, width)].write_token(w, token, width);
355 pos += token.pixel_count();
356 }
357}
358
359struct GroupAssignment {
361 prefix_bits: u32,
362 grid_width: u32,
363 block_group: Vec<u32>,
364 num_groups: u32,
365}
366
367impl GroupAssignment {
368 fn group_at(&self, pos: usize, width: u32) -> usize {
370 if self.num_groups <= 1 || width == 0 {
371 return 0;
372 }
373 let x = pos as u32 % width;
374 let y = pos as u32 / width;
375 let block = (y >> self.prefix_bits) * self.grid_width + (x >> self.prefix_bits);
376 self.block_group.get(block as usize).copied().unwrap_or(0) as usize
377 }
378
379 fn entropy_image(&self) -> Vec<u32> {
381 self.block_group
382 .iter()
383 .map(|&g| crate::vp8l::transform::make_argb(0xff, 0, g as u8, 0))
384 .collect()
385 }
386}
387
388fn assign_groups(tokens: &[Token], width: u32, height: u32) -> GroupAssignment {
392 use std::collections::HashMap;
393 let grid_width = div_round_up(width, 1 << PREFIX_BITS);
394 let grid_height = div_round_up(height, 1 << PREFIX_BITS);
395 let num_blocks = (grid_width as usize) * (grid_height as usize);
396 let single = GroupAssignment {
397 prefix_bits: PREFIX_BITS,
398 grid_width,
399 block_group: vec![0; num_blocks.max(1)],
400 num_groups: 1,
401 };
402 if num_blocks <= 1 || width == 0 {
403 return single;
404 }
405
406 let mut counts: Vec<HashMap<usize, u32>> = (0..num_blocks).map(|_| HashMap::new()).collect();
408 let mut pos = 0usize;
409 for token in tokens {
410 let x = pos as u32 % width;
411 let y = pos as u32 / width;
412 let block = ((y >> PREFIX_BITS) * grid_width + (x >> PREFIX_BITS)) as usize;
413 if let Some(counter) = counts.get_mut(block) {
414 *counter.entry(token.green_symbol()).or_insert(0) += 1;
415 }
416 pos += token.pixel_count();
417 }
418
419 let mut signature_group: HashMap<usize, u32> = HashMap::new();
420 let mut block_group = vec![0u32; num_blocks];
421 let mut num_groups = 0u32;
422 for (block, counter) in counts.iter().enumerate() {
423 let signature = counter
424 .iter()
425 .max_by_key(|&(_, &count)| count)
426 .map_or(0, |(&sym, _)| sym);
427 let group = match signature_group.get(&signature) {
428 Some(&g) => g,
429 None => {
430 let id = num_groups;
431 signature_group.insert(signature, id);
432 num_groups += 1;
433 id
434 }
435 };
436 block_group[block] = group;
437 }
438
439 if num_groups <= 1 || num_groups > MAX_GROUPS {
440 return single;
441 }
442 GroupAssignment {
443 prefix_bits: PREFIX_BITS,
444 grid_width,
445 block_group,
446 num_groups,
447 }
448}
449
450fn tokenize(pixels: &[u32], cache_bits: u32) -> Vec<Token> {
455 let n = pixels.len();
456 let mut tokens = Vec::new();
457 let mut refs = BackwardRefs::new(n);
458 let mut cache = if cache_bits > 0 {
459 ColorCache::new(cache_bits).ok()
460 } else {
461 None
462 };
463
464 let mut i = 0;
465 while i < n {
466 if let Some((len, dist)) = refs.find(pixels, i) {
467 tokens.push(Token::Copy { len, dist });
468 let end = i + len as usize;
469 while i < end {
470 refs.insert(pixels, i);
471 if let Some(c) = cache.as_mut() {
472 c.insert(pixels[i]);
473 }
474 i += 1;
475 }
476 } else {
477 let pixel = pixels[i];
478 let hit_slot = cache.as_ref().and_then(|c| {
479 let slot = c.slot(pixel);
480 (c.lookup(slot as u32) == pixel).then_some(slot)
481 });
482 match hit_slot {
483 Some(slot) => tokens.push(Token::CacheIndex(slot as u16)),
484 None => tokens.push(Token::Literal(pixel)),
485 }
486 if let Some(c) = cache.as_mut() {
487 c.insert(pixel);
488 }
489 refs.insert(pixels, i);
490 i += 1;
491 }
492 }
493 tokens
494}
495
496fn pick_cache_bits(num_pixels: usize) -> u32 {
499 if num_pixels < 16 {
500 0
501 } else {
502 (usize::BITS - (num_pixels - 1).leading_zeros()).clamp(1, 10)
503 }
504}
505
506fn build_code(histogram: &[u32]) -> PrefixEncoder {
509 let mut lengths = build_length_limited_lengths(histogram, MAX_CODE_LENGTH as u8);
510 if !lengths.is_empty() && lengths.iter().all(|&l| l == 0) {
512 lengths[0] = 1;
513 }
514 PrefixEncoder::from_lengths(&lengths)
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520 use crate::vp8l::decoder::decode;
521 use crate::vp8l::transform::make_argb;
522
523 fn round_trip(argb: &[u32], width: u32, height: u32) {
524 let dims = Dimensions { width, height };
525 let bitstream = encode(argb, dims).expect("encode");
526 let (decoded_dims, pixels) = decode(&bitstream).expect("decode");
527 assert_eq!(decoded_dims, dims);
528 assert_eq!(pixels, argb, "round-trip mismatch at {width}x{height}");
529 }
530
531 #[test]
532 fn round_trips_single_pixel() {
533 round_trip(&[make_argb(0xff, 0x12, 0x34, 0x56)], 1, 1);
534 }
535
536 #[test]
537 fn round_trips_gradient() {
538 let (w, h) = (8u32, 5u32);
539 let img: Vec<u32> = (0..w * h)
540 .map(|i| make_argb(0xff, (i * 3) as u8, (i * 7) as u8, i as u8))
541 .collect();
542 round_trip(&img, w, h);
543 }
544
545 #[test]
546 fn round_trips_solid_color() {
547 let img = vec![make_argb(0xff, 9, 9, 9); 17 * 9];
548 round_trip(&img, 17, 9);
549 }
550
551 #[test]
552 fn round_trips_many_colors_via_spatial_path() {
553 let (w, h) = (64u32, 48u32);
555 let img: Vec<u32> = (0..(w * h))
556 .map(|i| {
557 make_argb(
558 0xff,
559 (i & 0xff) as u8,
560 ((i >> 8) & 0xff) as u8,
561 (i * 13) as u8,
562 )
563 })
564 .collect();
565 assert!(
566 build_palette(&img).is_none(),
567 "test image must exceed the palette limit"
568 );
569 round_trip(&img, w, h);
570 }
571
572 #[test]
573 fn round_trips_repetitive_spatial_with_backward_refs() {
574 let (tile_w, h) = (40u32, 12u32);
576 let tile: Vec<u32> = (0..(tile_w * h))
577 .map(|i| {
578 make_argb(
579 0xff,
580 (i & 0xff) as u8,
581 ((i >> 8) & 0xff) as u8,
582 (i * 7) as u8,
583 )
584 })
585 .collect();
586 let w = tile_w * 2;
587 let img: Vec<u32> = (0..(w * h))
588 .map(|i| {
589 let (x, y) = (i % w, i / w);
590 tile[(y * tile_w + x % tile_w) as usize]
591 })
592 .collect();
593 assert!(build_palette(&img).is_none());
594 round_trip(&img, w, h);
595 }
596
597 #[test]
598 fn round_trips_repetitive_palette_with_backward_refs() {
599 let palette = [
601 make_argb(0xff, 1, 2, 3),
602 make_argb(0xff, 4, 5, 6),
603 make_argb(0xff, 7, 8, 9),
604 ];
605 let (w, h) = (32u32, 8u32);
606 let img: Vec<u32> = (0..(w * h)).map(|i| palette[(i % 3) as usize]).collect();
607 round_trip(&img, w, h);
608 }
609
610 #[test]
611 fn round_trips_horizontal_run() {
612 let (w, h) = (50u32, 4u32);
614 let img: Vec<u32> = (0..(w * h))
615 .map(|i| {
616 if (i / w) % 2 == 0 {
617 make_argb(0xff, 200, 100, 50)
618 } else {
619 make_argb(0xff, 10, 20, 30)
620 }
621 })
622 .collect();
623 round_trip(&img, w, h);
624 }
625
626 #[test]
627 fn distinct_regions_use_multiple_groups() {
628 let palette: Vec<u32> = (0..32)
633 .map(|i| make_argb(0xff, i as u8, (i * 7) as u8, (i * 13) as u8))
634 .collect();
635 let (w, h) = (32u32, 32u32);
636 let img: Vec<u32> = (0..(w * h))
637 .map(|i| {
638 let (x, y) = ((i % w) as usize, (i / w) as usize);
639 let scatter = (x * 7 + y * 11) % 16;
640 let idx = if y < 16 { scatter } else { 16 + scatter };
641 palette[idx]
642 })
643 .collect();
644
645 let detected = build_palette(&img).expect("few-color image has a palette");
647 let (bundled, bundled_width) = forward_color_indexing(&img, w, h, &detected);
648 let tokens = tokenize(&bundled, pick_cache_bits(bundled.len()));
649 let assignment =
650 assign_groups(&tokens, bundled_width, bundled.len() as u32 / bundled_width);
651 assert!(
652 assignment.num_groups >= 2,
653 "expected multiple groups, got {}",
654 assignment.num_groups
655 );
656
657 round_trip(&img, w, h);
658 }
659
660 #[test]
661 fn assign_groups_merges_uniform_blocks() {
662 let tokens: Vec<Token> = (0..1024)
664 .map(|_| Token::Literal(make_argb(0xff, 0, 7, 0)))
665 .collect();
666 let assignment = assign_groups(&tokens, 32, 32);
667 assert_eq!(assignment.num_groups, 1);
668 }
669
670 #[test]
671 fn round_trips_two_color_palette() {
672 let (a, b) = (make_argb(0xff, 0, 0, 0), make_argb(0xff, 255, 255, 255));
674 let img: Vec<u32> = (0..30).map(|i| if i % 3 == 0 { a } else { b }).collect();
675 round_trip(&img, 6, 5);
676 }
677
678 #[test]
679 fn preserves_non_opaque_alpha() {
680 let img: Vec<u32> = (0..16)
681 .map(|i| make_argb((i * 16) as u8, i as u8, (255 - i) as u8, 0))
682 .collect();
683 round_trip(&img, 4, 4);
684 let bitstream = encode(
686 &img,
687 Dimensions {
688 width: 4,
689 height: 4,
690 },
691 )
692 .unwrap();
693 let mut r = crate::vp8l::bit_io::BitReader::new(&bitstream);
694 let header = Vp8lHeader::read(&mut r).unwrap();
695 assert!(header.alpha_is_used);
696 }
697
698 #[test]
699 fn rejects_dimension_mismatch() {
700 assert!(matches!(
701 encode(
702 &[0, 0, 0],
703 Dimensions {
704 width: 2,
705 height: 2
706 }
707 ),
708 Err(Error::InvalidInput(_))
709 ));
710 }
711}