// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. use std::{collections::HashMap, sync::atomic::AtomicUsize}; use num_traits::FromPrimitive; use crate::{ error::{Error, Result}, frame::modular::{ ChannelInfo, ModularBuffer, ModularBufferInfo, ModularGridKind, Predictor, transforms::step::{TransformStep, TransformStepChunk}, }, headers::{self, frame_header::FrameHeader, modular::TransformId}, image::Rect, util::{ShiftRightCeil, tracing_wrappers::*}, }; use super::{RctOp, RctPermutation}; #[instrument(level = "trace", err)] fn check_equal_channels( channels: &[(usize, ChannelInfo)], first_chan: usize, num: usize, ) -> Result<()> { if first_chan + num > channels.len() { return Err(Error::InvalidChannelRange( first_chan, first_chan + num, channels.len(), )); } for inc in 1..num { if !channels[first_chan] .1 .is_equivalent(&channels[first_chan + inc].1) { return Err(Error::MixingDifferentChannels); } } Ok(()) } pub(super) fn meta_apply_single_transform( transform: &headers::modular::Transform, header: &headers::modular::GroupHeader, channels: &mut Vec<(usize, ChannelInfo)>, transform_steps: &mut Vec, mut add_transform_buffer: impl FnMut(ChannelInfo, String) -> usize, ) -> Result<()> { match transform.id { TransformId::Rct => { let begin_channel = transform.begin_channel as usize; let op = RctOp::from_u32(transform.rct_type % 7).unwrap(); let perm = RctPermutation::from_u32(transform.rct_type / 7) .expect("header decoding should ensure rct_type < 42"); check_equal_channels(channels, begin_channel, 3)?; let mut buf_in = [0; 3]; let buf_out = [ channels[begin_channel].0, channels[begin_channel + 1].0, channels[begin_channel + 2].0, ]; for i in 0..3 { let c = &mut channels[begin_channel + i]; let mut info = c.1; info.output_channel_idx = None; c.0 = add_transform_buffer( info, format!( "RCT (op {op:?} perm {perm:?}) starting at channel {begin_channel}, \ input {i}" ), ); buf_in[i] = c.0; } transform_steps.push(TransformStep::Rct { buf_out, buf_in, op, perm, }); trace!("applied RCT: {channels:?}"); } TransformId::Squeeze => { let steps = if transform.squeezes.is_empty() { super::squeeze::default_squeeze(channels) } else { transform.squeezes.clone() }; let mut transform_step_for_buf = HashMap::new(); for step in steps { super::squeeze::check_squeeze_params(channels, &step)?; let in_place = step.in_place; let horizontal = step.horizontal; let begin_channel = step.begin_channel as usize; let num_channels = step.num_channels as usize; let end_channel = begin_channel + num_channels; let new_chan_offset = if in_place { end_channel } else { channels.len() }; for ic in 0..num_channels { let chan = &channels[begin_channel + ic].1; let new_shift = if let Some(shift) = chan.shift { if shift.0 > 30 || shift.1 > 30 { return Err(Error::TooManySqueezes); } if horizontal { Some((shift.0 + 1, shift.1)) } else { Some((shift.0, shift.1 + 1)) } } else { None }; let w = chan.size.0; let h = chan.size.1; let (new_size_0, new_size_1) = if horizontal { ((w.div_ceil(2), h), (w - w.div_ceil(2), h)) } else { ((w, h.div_ceil(2)), (w, h - h.div_ceil(2))) }; let new_0 = ChannelInfo { output_channel_idx: None, shift: new_shift, size: new_size_0, bit_depth: chan.bit_depth, }; let buf_0 = add_transform_buffer( new_0, format!("Squeezed channel, original channel {}", begin_channel + ic), ); let new_1 = ChannelInfo { output_channel_idx: None, shift: new_shift, size: new_size_1, bit_depth: chan.bit_depth, }; let buf_1 = add_transform_buffer( new_1, format!("Squeeze residual, original channel {}", begin_channel + ic), ); transform_step_for_buf.insert(buf_0, (transform_steps.len(), horizontal)); let buf_out = channels[begin_channel + ic].0; if let Some(t) = transform_step_for_buf.get(&buf_out) && t.1 != horizontal { let (TransformStep::VSqueeze { buf_in_avg, .. } | TransformStep::HSqueeze { buf_in_avg, .. }) = &mut transform_steps[t.0] else { unreachable!() }; *buf_in_avg = Some([buf_0, buf_1]); } if horizontal { transform_steps.push(TransformStep::HSqueeze { buf_in: [buf_0, buf_1], buf_out, buf_in_avg: None, }); } else { transform_steps.push(TransformStep::VSqueeze { buf_in: [buf_0, buf_1], buf_out, buf_in_avg: None, }); } channels[begin_channel + ic] = (buf_0, new_0); channels.insert(new_chan_offset + ic, (buf_1, new_1)); trace!("applied squeeze: {channels:?}"); } } } TransformId::Palette => { let begin_channel = transform.begin_channel as usize; let num_channels = transform.num_channels as usize; let num_colors = transform.num_colors as usize; let num_deltas = transform.num_deltas as usize; let pred = Predictor::from_u32(transform.predictor_id) .expect("header decoding should ensure a valid predictor"); check_equal_channels(channels, begin_channel, num_channels)?; // We already checked the bit_depth for all channels from `begin_channel` is // equal in the line above. let bit_depth = channels[begin_channel].1.bit_depth; let pchan_info = ChannelInfo { output_channel_idx: None, shift: None, size: (num_colors + num_deltas, num_channels), bit_depth, }; let pchan = add_transform_buffer( pchan_info, format!( "Palette for palette transform starting at channel {begin_channel} with \ {num_channels} channels" ), ); let mut inchan_info = channels[begin_channel].1; inchan_info.output_channel_idx = None; let inchan = add_transform_buffer( inchan_info, format!( "Pixel data for palette transform starting at channel {begin_channel} with \ {num_channels} channels", ), ); transform_steps.push(TransformStep::Palette { buf_in: inchan, buf_pal: pchan, buf_out: channels[begin_channel..(begin_channel + num_channels)] .iter() .map(|x| x.0) .collect(), num_colors, num_deltas, predictor: pred, wp_header: header.wp_header.clone(), }); channels.drain(begin_channel + 1..begin_channel + num_channels); channels[begin_channel].0 = inchan; channels.insert(0, (pchan, pchan_info)); trace!("applied palette: {channels:?}"); } TransformId::Invalid => { unreachable!("header decoding for invalid transforms should fail"); } } Ok(()) } #[instrument(level = "trace", ret)] pub fn meta_apply_transforms( channels: &[ChannelInfo], header: &headers::modular::GroupHeader, ) -> Result<(Vec, Vec)> { let mut buffer_info = vec![]; let mut transform_steps = vec![]; // (buffer id, channel info) let mut channels: Vec<_> = channels.iter().cloned().enumerate().collect(); // First, add all the pre-transform channels to the buffer list. for chan in channels.iter() { buffer_info.push(ModularBufferInfo { info: chan.1, coded_channel_id: -1, description: format!( "Input channel {}, size {}x{}", chan.0, chan.1.size.0, chan.1.size.1 ), // To be filled by make_grids. grid_kind: ModularGridKind::None, grid_shape: (0, 0), buffer_grid: vec![], }); } let mut add_transform_buffer = |info, description| { buffer_info.push(ModularBufferInfo { info, coded_channel_id: -1, description, // To be filled by make_grids. grid_kind: ModularGridKind::None, grid_shape: (0, 0), buffer_grid: vec![], }); buffer_info.len() - 1 }; // Apply transforms to the channel list. for transform in &header.transforms { meta_apply_single_transform( transform, header, &mut channels, &mut transform_steps, &mut add_transform_buffer, )?; } // All the channels left over at the end of applying transforms are the channels that are // actually coded. for (chid, chan) in channels.iter().enumerate() { buffer_info[chan.0].coded_channel_id = chid as isize; } #[cfg(feature = "tracing")] for (i, transform) in transform_steps.iter().enumerate() { trace!("Transform step {i}: {transform:?}"); } Ok((buffer_info, transform_steps)) } #[instrument(level = "trace", skip_all, ret)] pub fn make_grids( frame_header: &FrameHeader, transform_steps: Vec, section_buffer_indices: &[Vec], buffer_info: &mut Vec, color_channels: usize, ) -> Vec { // Initialize grid sizes, starting from coded channels. for i in section_buffer_indices[1].iter() { buffer_info[*i].grid_kind = ModularGridKind::Lf; } for buffer_indices in section_buffer_indices.iter().skip(2) { for i in buffer_indices.iter() { buffer_info[*i].grid_kind = ModularGridKind::Hf; } } trace!(?buffer_info, "post set grid kind for coded channels"); // Transforms can be un-applied in the opposite order they appear with in the array, // so we can use that information to propagate grid kinds. for step in transform_steps.iter().rev() { match step { TransformStep::Rct { buf_in, buf_out, .. } => { let grid_in = buffer_info[buf_in[0]].grid_kind; for i in 0..3 { assert_eq!(grid_in, buffer_info[buf_in[i]].grid_kind); } for i in 0..3 { buffer_info[buf_out[i]].grid_kind = grid_in; } } TransformStep::Palette { buf_in, buf_out, .. } => { for buf in buf_out.iter() { buffer_info[*buf].grid_kind = buffer_info[*buf_in].grid_kind; } } TransformStep::HSqueeze { buf_in, buf_out, .. } | TransformStep::VSqueeze { buf_in, buf_out, .. } => { let mut grid_kind = buffer_info[buf_in[0]] .grid_kind .max(buffer_info[buf_in[1]].grid_kind); if grid_kind == ModularGridKind::None && !buffer_info[*buf_out] .info .is_meta_or_small(frame_header.group_dim()) { grid_kind = ModularGridKind::Hf; } buffer_info[*buf_out].grid_kind = grid_kind; } TransformStep::Output { .. } => { unreachable!() } } } // Set grid shapes. for buf in buffer_info.iter_mut() { buf.grid_shape = buf.grid_kind.grid_shape(frame_header); } trace!(?buffer_info, "post propagate grid kind"); let get_grid_indices = |shape: (usize, usize)| { (0..shape.1).flat_map(move |y| (0..shape.0).map(move |x| (x as isize, y as isize))) }; // Create grids. for g in buffer_info.iter_mut() { g.buffer_grid = get_grid_indices(g.grid_shape) .map(|(x, y)| { ModularBuffer::new( g.get_grid_rect(frame_header, g.grid_kind, (x as usize, y as usize)) .size, ) }) .collect(); } trace!(?buffer_info, "with grids"); let add_transform_step = |transform: &TransformStep, grid_pos: (isize, isize), grid_transform_steps: &mut Vec| { let ts = grid_transform_steps.len(); grid_transform_steps.push(TransformStepChunk { step: transform.clone(), grid_pos: (grid_pos.0 as usize, grid_pos.1 as usize), missing_final_deps: 0, missing_deps: AtomicUsize::new(0), }); ts }; let add_grid_use = |ts: usize, input_buffer_idx: usize, output_grid_kind: ModularGridKind, output_grid_shape: (usize, usize), output_grid_pos: (isize, isize), grid_transform_steps: &mut Vec, buffer_info: &mut Vec| { let output_grid_size = (output_grid_shape.0 as isize, output_grid_shape.1 as isize); if output_grid_pos.0 < 0 || output_grid_pos.0 >= output_grid_size.0 || output_grid_pos.1 < 0 || output_grid_pos.1 >= output_grid_size.1 { // Skip adding uses of non-existent grid positions. return; } let output_grid_pos = (output_grid_pos.0 as usize, output_grid_pos.1 as usize); let input_grid_pos = buffer_info[input_buffer_idx].get_grid_idx(output_grid_kind, output_grid_pos); let grid = &mut buffer_info[input_buffer_idx].buffer_grid[input_grid_pos]; if !grid.used_by_transforms_final.contains(&ts) { grid_transform_steps[ts].missing_final_deps += 1; grid.used_by_transforms_final.push(ts); } }; // Add grid-ed transforms. let mut grid_transform_steps = vec![]; for transform in transform_steps { match &transform { TransformStep::Rct { buf_in, buf_out, .. } => { // Easy case: we just depend on the 3 input buffers in the same location. let out_kind = buffer_info[buf_out[0]].grid_kind; let out_shape = buffer_info[buf_out[0]].grid_shape; for (x, y) in get_grid_indices(out_shape) { let ts = add_transform_step(&transform, (x, y), &mut grid_transform_steps); for bin in buf_in { add_grid_use( ts, *bin, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); } } } TransformStep::Palette { buf_in, buf_pal, buf_out, predictor, .. } if predictor.requires_full_row() => { // Delta palette with AverageAll or Weighted. Those are special, because we can // only make progress one full image row at a time (since we need decoded values // from the previous row or two rows). let out_kind = buffer_info[buf_out[0]].grid_kind; let out_shape = buffer_info[buf_out[0]].grid_shape; let mut ts = 0; for (x, y) in get_grid_indices(out_shape) { if x == 0 { ts = add_transform_step(&transform, (x, y), &mut grid_transform_steps); add_grid_use( ts, *buf_pal, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); } add_grid_use( ts, *buf_in, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); for out in buf_out.iter() { add_grid_use( ts, *out, out_kind, out_shape, (x, y - 1), &mut grid_transform_steps, buffer_info, ); } } } TransformStep::Palette { buf_in, buf_pal, buf_out, predictor, .. } => { // Maybe-delta palette: we depend on the palette and the input buffer in the same // location. We may also depend on other grid positions in the output buffer, // according to the used predictor. let out_kind = buffer_info[buf_out[0]].grid_kind; let out_shape = buffer_info[buf_out[0]].grid_shape; for (x, y) in get_grid_indices(out_shape) { let ts = add_transform_step(&transform, (x, y), &mut grid_transform_steps); add_grid_use( ts, *buf_pal, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); add_grid_use( ts, *buf_in, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); let offsets = match predictor { Predictor::Zero => [].as_slice(), _ => &[(0, -1), (-1, 0), (-1, -1)], }; for (dx, dy) in offsets { for out in buf_out.iter() { add_grid_use( ts, *out, out_kind, out_shape, (x + dx, y + dy), &mut grid_transform_steps, buffer_info, ); } } } } TransformStep::HSqueeze { buf_in, buf_out, .. } => { let out_kind = buffer_info[*buf_out].grid_kind; let out_shape = buffer_info[*buf_out].grid_shape; for (x, y) in get_grid_indices(out_shape) { let ts = add_transform_step(&transform, (x, y), &mut grid_transform_steps); // Average and residuals from the same position for bin in buf_in { add_grid_use( ts, *bin, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); } // Next average add_grid_use( ts, buf_in[0], out_kind, out_shape, (x + 1, y), &mut grid_transform_steps, buffer_info, ); // Previous decoded add_grid_use( ts, *buf_out, out_kind, out_shape, (x - 1, y), &mut grid_transform_steps, buffer_info, ); } } TransformStep::VSqueeze { buf_in, buf_out, .. } => { let out_kind = buffer_info[*buf_out].grid_kind; let out_shape = buffer_info[*buf_out].grid_shape; for (x, y) in get_grid_indices(out_shape) { let ts = add_transform_step(&transform, (x, y), &mut grid_transform_steps); // Average and residuals from the same position for bin in buf_in { add_grid_use( ts, *bin, out_kind, out_shape, (x, y), &mut grid_transform_steps, buffer_info, ); } // Next average add_grid_use( ts, buf_in[0], out_kind, out_shape, (x, y + 1), &mut grid_transform_steps, buffer_info, ); // Previous decoded add_grid_use( ts, *buf_out, out_kind, out_shape, (x, y - 1), &mut grid_transform_steps, buffer_info, ); } } TransformStep::Output { .. } => { unreachable!() } } } // Write on transform outputs which step produces them. for (ts, step) in grid_transform_steps.iter().enumerate() { for &(buf, grid) in step.outputs(buffer_info).iter() { buffer_info[buf].buffer_grid[grid].produced_by_step = Some(ts); } } // Add "output" transform steps. // TODO(veluca): filter this by pipeline_used_channels. for (idx, bi) in buffer_info.iter_mut().enumerate() { if let Some(chan) = bi.info.output_channel_idx { let (shift_x, shift_y) = bi.info.shift.unwrap_or((0, 0)); let gsx = frame_header.group_dim().shrc(shift_x); let gsy = frame_header.group_dim().shrc(shift_y); // If the channel has a grid size of Hf groups, then we can just output // the entire buffer. Otherwise, we need to copy the region of the image // that corresponds to a HF group. let grid_dim = match bi.grid_kind { ModularGridKind::None => frame_header.size().0.max(frame_header.size().1), ModularGridKind::Hf => frame_header.group_dim(), ModularGridKind::Lf => frame_header.lf_group_dim(), }; let gtx = grid_dim.shrc(shift_x); let gty = grid_dim.shrc(shift_y); let group_shape = frame_header.size_groups(); for y in 0..group_shape.1 { let gy = y * gsy / gty; let gyo = y * gsy - gy * gty; for x in 0..group_shape.0 { let gx = x * gsx / gtx; let gxo = x * gsx - gx * gtx; let grid = gy * bi.grid_shape.0 + gx; let rect = if gxo == 0 && gyo == 0 && gtx == gsx && gty == gsy { None } else { let rect = Rect { origin: (gxo, gyo), size: (gsx, gsy), }; Some(rect.clip(bi.buffer_grid[grid].size)) }; let chans = if chan == 0 && color_channels == 1 { 0..=2 } else { chan..=chan }; for c in chans { bi.buffer_grid[grid] .used_by_transforms_final .push(grid_transform_steps.len()); grid_transform_steps.push(TransformStepChunk { step: TransformStep::Output { buf_in: idx, rect, group: y * frame_header.size_groups().0 + x, channel: c, }, grid_pos: (gx, gy), missing_final_deps: 1, missing_deps: AtomicUsize::new(0), }); } } } } } trace!(?grid_transform_steps, ?buffer_info); grid_transform_steps }