Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splat adam7 interlacing #576

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

thirumurugan-git
Copy link
Contributor

As a follow-up to this issue in Chromium, I created a corresponding PR in image-png.

CC: @anforowicz

@thirumurugan-git
Copy link
Contributor Author

@anforowicz Please review this PR and share your suggestions on the TODOs.

for i in 0..m {
let start = i * n;
let end = start + n;
dst[start..end].copy_from_slice(src);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for dst in dst.chunks_exact_mut(n) will reduce bounds checks.

Copy link
Contributor

@anforowicz anforowicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Adam7Info {
pub(crate) pass: u8,
pub(crate) line: u32,
pub(crate) width: u32,
}

// Pairs are (x_repeat, y_repeat)
static SPLAT_EXPAND: &[(usize, usize)] = &[(8, 8), (4, 8), (4, 4), (2, 4), (2, 2), (1, 2), (1, 1)];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few different pass-specific constants in adam7.rs:

  • line_mul, line_off, samp_mul, samp_off in fn expand_adam7_bits here
  • hardcoded constants in fn init_pass here
  • The new constants from this PR.

I wonder if these constants (and their usages) could be consolidated into something like:

struct PassConstants {
    line_mul: u8,
    line_off: u8,
    samp_mul: u8,
    samp_off: u8,
}

impl PassConstants {
    const fn new(line_mul: u8, line_off: u8, samp_mul: u8, samp_off: u8) -> Self {
        Self { line_mul, line_off, samp_mul, samp_off }
    }
    const fn x_repeat(&self) -> u8 { self.samp_mul - self.samp_off }  // right?
    const fn y_repeat(&self) -> u8 { self.line_mul - self.line_off }  // right?
}

const PASS_CONSTANTS: [PassConstants; 7] = {
    PassConstants::new(8, 0, 8, 0),
    PassConstants::new(8, 0, 8, 4),
    PassConstants::new(8, 4, 4, 0),
    PassConstants::new(4, 0, 4, 2),
    PassConstants::new(4, 2, 2, 0),
    PassConstants::new(2, 0, 2, 1),
    PassConstants::new(2, 1, 1, 0),
}

);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(no action needed) I wondered for a moment whether to deduplicate against expand_pass, but these are fairly short functions, so it doesn't seem worth the extra complexity.

bits_pp: usize,
bit_pos: usize,
) {
let height = ((img.len() * 8 - bit_pos) as f32 / stride as f32).ceil() as usize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need floating-point math here? Would img.len() * 8 - bit_pos + stride - 1) / stride also work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this math should likely be using u64 rather than usize. On 32-bit platforms it only takes 256 MB worth of bits to overflow a usize.

Another thing you might want to look at are the provided methods on the builtin integer types. In particular, div_ceil might be helpful here.

// |stride| is bit-count here, unlike in |byte_splat_expand|, where it is byte-count.
fn bit_splat_expand(
img: &mut [u8],
stride: usize,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could you please rename stride to stride_in_bits?

}
}

fn copy_nbits_mtimes(img: &mut [u8], bit_pos: usize, bits_pp: usize, px: u8, m: usize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC the implementation assumes that we never need to copy across two bytes. So maybe the asserts below could be used to document this:

debug_assert_eq!(bit_pos % bits_pp, 0);
debug_assert!(bits_pp == 1 || bits_pp == 2 || bits_pp == 4);

bytes_pp: usize,
start_byte: usize,
) {
let height = ((img.len() - start_byte) as f32 / stride as f32).ceil() as usize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here: can we avoid floating-point math here?

}

// TODO: Can we eliminate the duplicate code in |bit_splat_expand| and |byte_splat_expand|?
// |stride| is bit-count here, unlike in |byte_splat_expand|, where it is byte-count.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(no action needed) Maybe this can be tried as a follow-up PR? I am not sure if the deduplication would be better or not, but hopefully looking at a specific/actual change will help us decide.

);
assert_eq!(actual_img, expected_img);

// Third pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confused me for a moment. I wonder if you could please expand the comment to something like:

/// Third pass.  (Not "second pass" because for a 4-pixel-wide image `Adam7Iterator` will skip the no-op 2nd pass.)

Did I get that right in the proposed comment?

@@ -233,6 +238,98 @@ pub fn expand_pass(
}
}

// TODO: Export this function in lib.rs.
// Should we have a separate function, or can we use an enum to determine whether
// to use default or splat?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(no action needed) FWIW I vote for a separate function - ending up with png::expand_interlaced_row and png::splat_interlaced_row, but I am curious what others think. At any rate, we can probably discuss later, in a PR that adds a public API for this.

// TODO: Export this function in lib.rs.
// Should we have a separate function, or can we use an enum to determine whether
// to use default or splat?
pub fn splat_expand_pass(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add a doc comment here explaining what "splat expand" means. I understand from the linked chromium issue, but having it right in the code will help for future reference.

@kornelski
Copy link
Contributor

I think filling the image pixels completely is the expected behavior, so it would be nice to make this easy/default, but I'm not sure how such thing could be integrated with the library API.

Doing this in expand_interlaced_row would needlessly overdraw pixels when decoding multiple passes at once. So even though standalone function doesn't feel like the most elegant solution to me, I don't have a suggestion for anything better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants