diff --git a/jxl/src/render/stages/gaborish.rs b/jxl/src/render/stages/gaborish.rs new file mode 100644 index 0000000..6281e06 --- /dev/null +++ b/jxl/src/render/stages/gaborish.rs @@ -0,0 +1,110 @@ +// 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 crate::render::{RenderPipelineInOutStage, RenderPipelineStage}; + +/// Apply Gabor-like filter to a channel. +#[derive(Debug)] +pub struct GaborishStage { + channel: usize, + weight_side: f32, + weight_diag: f32, +} + +impl GaborishStage { + // TODO(tirr-c): remove once we use this! + #[allow(unused)] + pub fn new(channel: usize, weight1: f32, weight2: f32) -> Self { + Self { + channel, + weight_side: weight1, + weight_diag: weight2, + } + } +} + +impl std::fmt::Display for GaborishStage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Gaborish filter for channel {}", self.channel) + } +} + +impl RenderPipelineStage for GaborishStage { + type Type = RenderPipelineInOutStage; + + fn uses_channel(&self, c: usize) -> bool { + c == self.channel + } + + fn process_row_chunk( + &mut self, + _position: (usize, usize), + xsize: usize, + row: &mut [(&[&[f32]], &mut [&mut [f32]])], + ) { + let (rows_in, ref mut rows_out) = row[0]; + let row_out = &mut rows_out[0][..xsize]; + + let weight_side = self.weight_side; + let weight_diag = self.weight_diag; + let weight_total = 1.0 + weight_side * 4.0 + weight_diag * 4.0; + + let kernel_top_bottom = [weight_diag, weight_side, weight_diag].map(|x| x / weight_total); + let kernel_center = [weight_side, 1.0, weight_side].map(|x| x / weight_total); + + for (idx, out) in row_out.iter_mut().enumerate() { + let mut sum = 0f32; + let row_and_kernel = std::iter::zip( + rows_in, + [kernel_top_bottom, kernel_center, kernel_top_bottom], + ); + + for (row_in, kernel) in row_and_kernel { + for (dx, weight) in kernel.into_iter().enumerate() { + sum += row_in[idx + dx] * weight; + } + } + + *out = sum; + } + } +} + +#[cfg(test)] +mod test { + use test_log::test; + + use super::*; + use crate::error::Result; + use crate::image::Image; + use crate::render::test::make_and_run_simple_pipeline; + use crate::util::test::assert_all_almost_eq; + + #[test] + fn consistency() -> Result<()> { + crate::render::test::test_stage_consistency::<_, f32, f32>( + GaborishStage::new(0, 0.115169525, 0.061248592), + (500, 500), + 1, + ) + } + + #[test] + fn checkerboard() -> Result<()> { + let mut image = Image::new((2, 2))?; + image.as_rect_mut().row(0).copy_from_slice(&[0.0, 1.0]); + image.as_rect_mut().row(1).copy_from_slice(&[1.0, 0.0]); + + let stage = GaborishStage::new(0, 0.115169525, 0.061248592); + let (_, output) = + make_and_run_simple_pipeline::<_, f32, f32>(stage, &[image], (2, 2), 256)?; + let output = output[0].as_rect(); + + assert_all_almost_eq!(output.row(0), &[0.20686048, 0.7931395], 1e-6); + assert_all_almost_eq!(output.row(1), &[0.7931395, 0.20686048], 1e-6); + + Ok(()) + } +} diff --git a/jxl/src/render/stages/mod.rs b/jxl/src/render/stages/mod.rs index 4e4a209..20bf3b8 100644 --- a/jxl/src/render/stages/mod.rs +++ b/jxl/src/render/stages/mod.rs @@ -5,6 +5,7 @@ mod chroma_upsample; mod convert; +mod gaborish; mod nearest_neighbor; mod noise; mod save;