Skip to content

Commit

Permalink
start with patches
Browse files Browse the repository at this point in the history
  • Loading branch information
mo271 committed Dec 3, 2024
1 parent c75bd2c commit 3b90808
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 5 deletions.
Binary file added jxl/resources/test/GrayscalePatchesModular.jxl
Binary file not shown.
Binary file added jxl/resources/test/GrayscalePatchesVarDCT.jxl
Binary file not shown.
4 changes: 4 additions & 0 deletions jxl/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ pub enum Error {
InvalidPredictor(u32),
#[error("Invalid modular mode property: {0}")]
InvalidProperty(u32),
#[error("Too many patches: {0}, limit is {1}")]
PatchesTooMany(u32, u32),
#[error("Reference too large: {0}, limit is {1}")]
PatchesRefTooLarge(u32, u32),
#[error("Too many splines: {0}, limit is {1}")]
SplinesTooMany(u32, u32),
#[error("Too many control points for splines: {0}, limit is {1}")]
Expand Down
1 change: 1 addition & 0 deletions jxl/src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

pub mod patches;
pub mod noise;
pub mod spline;
130 changes: 130 additions & 0 deletions jxl/src/features/patches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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.

// TODO(firsching): remove once we use this!
#![allow(dead_code)]

use crate::{
bit_reader::BitReader,
entropy_coding::decode::Histograms,
error::{Error, Result},
util::tracing_wrappers::*,
};

// TODO(firsching): move to some common place?
const MAX_NUM_REFERENCE_FRAMES : u32 = 4;

// Context numbers as specified in Section C.4.5, Listing C.2:
const NUM_REF_PATCH_CONTEXT: usize = 0;
const REFERENCE_FRAME_CONTEXT: usize = 1;
const PATCH_SIZE_CONTEXT: usize = 2;
const PATCH_REFERENCE_POSITION_CONTEXT: usize = 3;
const PATCH_POSITION_CONTEXT: usize = 4;
const PATCH_BLEND_MODE_CONTEXT: usize = 5;
const PATCH_OFFSET_CONTEXT: usize = 6;
const PATCH_COUNT_CONTEXT: usize = 7;
const PATCH_ALPHA_CHANNEL_CONTEXT: usize = 8;
const PATCH_CLAMP_CONTEXT: usize = 9;
const NUM_PATCH_DICTIONARY_CONTEXTS: usize = 10;

// Blend modes
// The new values are the old ones. Useful to skip some channels.
const PATCH_BLEND_MODE_NONE: u8 = 0;
// The new values (in the crop) replace the old ones: sample = new
const PATCH_BLEND_MODE_REPLACE: u8 = 1;
// The new values (in the crop) get added to the old ones: sample = old + new
const PATCH_BLEND_MODE_ADD: u8 = 2;
// The new values (in the crop) get multiplied by the old ones:
// sample = old * new
// This blend mode is only supported if BlendColorSpace is kEncoded. The
// range of the new value matters for multiplication purposes, and its
// nominal range of 0..1 is computed the same way as this is done for the
// alpha values in kBlend and kAlphaWeightedAdd.
const PATCH_BLEND_MODE_MUL: u8 = 3;
// The new values (in the crop) replace the old ones if alpha>0:
// For first alpha channel:
// alpha = old + new * (1 - old)
// For other channels if !alpha_associated:
// sample = ((1 - new_alpha) * old * old_alpha + new_alpha * new) / alpha
// For other channels if alpha_associated:
// sample = (1 - new_alpha) * old + new
// The alpha formula applies to the alpha used for the division in the other
// channels formula, and applies to the alpha channel itself if its
// blend_channel value matches itself.
// If using kBlendAbove, new is the patch and old is the original image; if
// using kBlendBelow, the meaning is inverted.
const PATCH_BLEND_MODE_BLEND_ABOVE: u8 = 4;
const PATCH_BLEND_MODE_BLEND_BELOW: u8 = 5;
// The new values (in the crop) are added to the old ones if alpha>0:
// For first alpha channel: sample = sample = old + new * (1 - old)
// For other channels: sample = old + alpha * new
const PATCH_BLEND_MODE_ALPHA_WEIGHTED_ADD_ABOVE: u8 = 6;
const PATCH_BLEND_MODE_ALPHA_WEIGHTED_ADD_BELOW: u8 = 7;
const PATCH_BLEND_MODE_NUM_BLEND_MODES: u8 = 8;

#[derive(Debug, Clone, Copy)]
struct PatchBlending {
mode: u8,
alpha_channel: u32,
clamp: bool,
}

#[derive(Debug, Clone, Copy)]
pub struct PatchReferencePosition {
// Not using `ref` like in the spec here, because it is a keyword.
reference: u8,
x0: usize,
y0: usize,
xsize: usize,
ysize: usize,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PatchPosition {
x: usize,
y: usize,
ref_pos_idx: usize,
}

#[derive(Debug, Default)]
pub struct PatchesDictionary {
pub positions: Vec<PatchPosition>,
pub ref_positions: Vec<PatchReferencePosition>,
blendings: Vec<PatchBlending>,
num_patches: Vec<usize>,
sorted_patches_y0: Vec<(usize, usize)>,
sorted_patches_y1: Vec<(usize, usize)>,
}

impl PatchesDictionary {
#[instrument(level = "debug", skip(br), ret, err)]
pub fn read(br: &mut BitReader, xsize: u32, ysize: u32) -> Result<PatchesDictionary> {
trace!(pos = br.total_bits_read());
let patches_histograms = Histograms::decode(NUM_PATCH_DICTIONARY_CONTEXTS, br, true)?;
let mut patches_reader = patches_histograms.make_reader(br)?;
let num_ref_patch = 1 + patches_reader.read(br, NUM_REF_PATCH_CONTEXT)?;
let num_pixels = xsize * ysize;
let max_ref_patches = 1024 + num_pixels / 4;
let max_patches = max_ref_patches * 4;
let _max_blending_infos = max_patches * 4;
if num_ref_patch > max_ref_patches {
return Err(Error::PatchesTooMany(num_ref_patch, max_ref_patches));
}

let mut ref_positions: Vec<PatchReferencePosition> = Vec::with_capacity(num_ref_patch as usize);
for _ in 0..num_ref_patch {
let reference = patches_reader.read(br, REFERENCE_FRAME_CONTEXT)?;
if reference >= MAX_NUM_REFERENCE_FRAMES {
return Err(Error::PatchesRefTooLarge(reference, MAX_NUM_REFERENCE_FRAMES));
}

// TODO: fill in correct numbers here
ref_positions.push(PatchReferencePosition {reference:0, x0:0, y0:0, xsize:0, ysize: 0})

}

todo!("implement patch decoding")
}
}
2 changes: 1 addition & 1 deletion jxl/src/features/spline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl QuantizedSpline {
max_control_points,
));
}
let mut control_points = Vec::with_capacity(num_control_points as usize);
let mut control_points: Vec<(i64, i64)> = Vec::with_capacity(num_control_points as usize);
for _ in 0..num_control_points {
let x = splines_reader.read_signed(br, CONTROL_POINTS_CONTEXT)? as i64;
let y = splines_reader.read_signed(br, CONTROL_POINTS_CONTEXT)? as i64;
Expand Down
28 changes: 24 additions & 4 deletions jxl/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
bit_reader::BitReader,
error::Result,
features::{noise::Noise, spline::Splines},
features::patches::PatchesDictionary,
headers::{
color_encoding::ColorSpace,
encodings::UnconditionalCoder,
Expand All @@ -32,7 +33,7 @@ pub enum Section {

#[allow(dead_code)]
pub struct LfGlobalState {
// TODO(veluca93): patches
patches: Option<PatchesDictionary>,
// TODO(veluca93): splines
splines: Option<Splines>,
noise: Option<Noise>,
Expand Down Expand Up @@ -135,11 +136,19 @@ impl Frame {
assert!(self.lf_global.is_none());
trace!(pos = br.total_bits_read());

if self.header.has_patches() {
let patches = if self.header.has_patches() {
info!("decoding patches");
todo!("patches not implemented");
}
Some(PatchesDictionary::read(
br,
self.header.width,
self.header.height,
)?)
} else {
None
};

let splines = if self.header.has_splines() {
info!("decoding splines");
Some(Splines::read(br, self.header.width * self.header.height)?)
} else {
None
Expand Down Expand Up @@ -181,6 +190,7 @@ impl Frame {
)?;

self.lf_global = Some(LfGlobalState {
patches,
splines,
noise,
lf_quant,
Expand Down Expand Up @@ -214,6 +224,16 @@ mod test_frame {
Ok(result)
}


#[test]
fn patches() -> Result<(), Error> {
let frame = read_frame(include_bytes!("../resources/test/GrayscalePatchesVarDCT.jxl"))?;
let lf_global = frame.lf_global.unwrap();
let patches_dict = lf_global.patches.unwrap();
// TODO continue here
Ok(())
}

#[test]
fn splines() -> Result<(), Error> {
let frame = read_frame(include_bytes!("../resources/test/splines.jxl"))?;
Expand Down

0 comments on commit 3b90808

Please sign in to comment.