Skip to content

Commit eceee18

Browse files
committed
Merge branch 'refs/heads/main' into gaussian_blur
2 parents ed9078a + 2125965 commit eceee18

File tree

10 files changed

+97
-32
lines changed

10 files changed

+97
-32
lines changed

Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ num-traits = { version = "0.2.0" }
4343
color_quant = { version = "1.1", optional = true }
4444
dav1d = { version = "0.10.3", optional = true }
4545
exr = { version = "1.5.0", optional = true }
46-
gif = { version = "0.13", optional = true }
46+
gif = { version = "0.13.1", optional = true }
4747
image-webp = { version = "0.2.0", optional = true }
4848
mp4parse = { version = "0.17.0", optional = true }
49-
png = { version = "0.17.6", optional = true }
49+
png = { version = "0.17.11", optional = true }
5050
qoi = { version = "0.4", optional = true }
5151
ravif = { version = "0.11.11", default-features = false, optional = true }
5252
rayon = { version = "1.7.0", optional = true }
@@ -114,4 +114,4 @@ harness = false
114114
[[bench]]
115115
path = "benches/blur.rs"
116116
name = "blur"
117-
harness = false
117+
harness = false

deny.toml

+1-9
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,9 @@ allow = [
1919
"BSD-2-Clause",
2020
"BSD-3-Clause",
2121
"MIT",
22-
"MIT-0",
23-
"MPL-2.0",
24-
"Unicode-DFS-2016",
22+
"Unicode-3.0",
2523
]
2624

27-
[[licenses.exceptions]]
28-
allow = ["WTFPL"]
29-
name = "pcx"
30-
version = "*"
31-
3225
[advisories]
3326
yanked = "deny"
3427
ignore = []
@@ -41,5 +34,4 @@ deny = []
4134
skip = [
4235
{ name = "bitflags" }, # Some deps depend on 1.3.2 while others on 2.6.0
4336
{ name = "hashbrown" }, # Some deps depend on 0.13.2 while others on 0.14.5
44-
{ name = "miniz_oxide" } # Some deps depend on 0.7.4 while others on 0.8.0
4537
]

src/buffer.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ where
801801
/// the bounds will not overflow.
802802
fn check_image_fits(width: u32, height: u32, len: usize) -> bool {
803803
let checked_len = Self::image_buffer_len(width, height);
804-
checked_len.map_or(false, |min_len| min_len <= len)
804+
checked_len.is_some_and(|min_len| min_len <= len)
805805
}
806806

807807
fn image_buffer_len(width: u32, height: u32) -> Option<usize> {

src/codecs/avif/yuv.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,23 @@ fn process_halved_chroma_row<
521521

522522
let max_value = (1 << BIT_DEPTH) - 1;
523523

524+
// If the stride is larger than the plane size,
525+
// it might contain junk data beyond the actual valid region.
526+
// To avoid processing artifacts when working with odd-sized images,
527+
// the buffer is reshaped to its actual size,
528+
// preventing accidental use of invalid values from the trailing region.
529+
530+
let y_plane = &image.y_plane[0..image.width];
531+
let chroma_size = (image.width + 1) / 2;
532+
let u_plane = &image.u_plane[0..chroma_size];
533+
let v_plane = &image.v_plane[0..chroma_size];
534+
let rgba = &mut rgba[0..image.width * CHANNELS];
535+
524536
let bias_y = range.bias_y as i32;
525537
let bias_uv = range.bias_uv as i32;
526-
let y_iter = image.y_plane.chunks_exact(2);
538+
let y_iter = y_plane.chunks_exact(2);
527539
let rgb_chunks = rgba.chunks_exact_mut(CHANNELS * 2);
528-
for (((y_src, &u_src), &v_src), rgb_dst) in
529-
y_iter.zip(image.u_plane).zip(image.v_plane).zip(rgb_chunks)
530-
{
540+
for (((y_src, &u_src), &v_src), rgb_dst) in y_iter.zip(u_plane).zip(v_plane).zip(rgb_chunks) {
531541
let y_value: i32 = (y_src[0].as_() - bias_y) * y_coef;
532542
let cb_value: i32 = u_src.as_() - bias_uv;
533543
let cr_value: i32 = v_src.as_() - bias_uv;
@@ -571,13 +581,13 @@ fn process_halved_chroma_row<
571581

572582
// Process remainder if width is odd.
573583
if image.width & 1 != 0 {
574-
let y_left = image.y_plane.chunks_exact(2).remainder();
584+
let y_left = y_plane.chunks_exact(2).remainder();
575585
let rgb_chunks = rgba
576586
.chunks_exact_mut(CHANNELS * 2)
577587
.into_remainder()
578588
.chunks_exact_mut(CHANNELS);
579-
let u_iter = image.u_plane.iter().rev();
580-
let v_iter = image.v_plane.iter().rev();
589+
let u_iter = u_plane.iter().rev();
590+
let v_iter = v_plane.iter().rev();
581591

582592
for (((y_src, u_src), v_src), rgb_dst) in
583593
y_left.iter().zip(u_iter).zip(v_iter).zip(rgb_chunks)

src/codecs/jpeg/encoder.rs

+50
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static SOS: u8 = 0xDA;
3030
static DQT: u8 = 0xDB;
3131
// Application segments start and end
3232
static APP0: u8 = 0xE0;
33+
static APP2: u8 = 0xE2;
3334

3435
// section K.1
3536
// table K.1
@@ -346,6 +347,8 @@ pub struct JpegEncoder<W> {
346347
chroma_actable: Cow<'static, [(u8, u16); 256]>,
347348

348349
pixel_density: PixelDensity,
350+
351+
icc_profile: Vec<u8>,
349352
}
350353

351354
impl<W: Write> JpegEncoder<W> {
@@ -415,6 +418,8 @@ impl<W: Write> JpegEncoder<W> {
415418
chroma_actable: Cow::Borrowed(&STD_CHROMA_AC_HUFF_LUT),
416419

417420
pixel_density: PixelDensity::default(),
421+
422+
icc_profile: Vec::new(),
418423
}
419424
}
420425

@@ -494,6 +499,9 @@ impl<W: Write> JpegEncoder<W> {
494499
build_jfif_header(&mut buf, self.pixel_density);
495500
self.writer.write_segment(APP0, &buf)?;
496501

502+
// Write ICC profile chunks if present
503+
self.write_icc_profile_chunks()?;
504+
497505
build_frame_header(
498506
&mut buf,
499507
8,
@@ -648,6 +656,43 @@ impl<W: Write> JpegEncoder<W> {
648656

649657
Ok(())
650658
}
659+
660+
fn write_icc_profile_chunks(&mut self) -> io::Result<()> {
661+
if self.icc_profile.is_empty() {
662+
return Ok(());
663+
}
664+
665+
const MAX_CHUNK_SIZE: usize = 65533 - 14;
666+
const MAX_CHUNK_COUNT: usize = 255;
667+
const MAX_ICC_PROFILE_SIZE: usize = MAX_CHUNK_SIZE * MAX_CHUNK_COUNT;
668+
669+
if self.icc_profile.len() > MAX_ICC_PROFILE_SIZE {
670+
return Err(io::Error::new(
671+
io::ErrorKind::InvalidInput,
672+
"ICC profile too large",
673+
));
674+
}
675+
676+
let chunk_iter = self.icc_profile.chunks(MAX_CHUNK_SIZE);
677+
let num_chunks = chunk_iter.len() as u8;
678+
let mut segment = Vec::new();
679+
680+
for (i, chunk) in chunk_iter.enumerate() {
681+
let chunk_number = (i + 1) as u8;
682+
let length = 14 + chunk.len();
683+
684+
segment.clear();
685+
segment.reserve(length);
686+
segment.extend_from_slice(b"ICC_PROFILE\0");
687+
segment.push(chunk_number);
688+
segment.push(num_chunks);
689+
segment.extend_from_slice(chunk);
690+
691+
self.writer.write_segment(APP2, &segment)?;
692+
}
693+
694+
Ok(())
695+
}
651696
}
652697

653698
impl<W: Write> ImageEncoder for JpegEncoder<W> {
@@ -661,6 +706,11 @@ impl<W: Write> ImageEncoder for JpegEncoder<W> {
661706
) -> ImageResult<()> {
662707
self.encode(buf, width, height, color_type)
663708
}
709+
710+
fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
711+
self.icc_profile = icc_profile;
712+
Ok(())
713+
}
664714
}
665715

666716
fn build_jfif_header(m: &mut Vec<u8>, density: PixelDensity) {

src/codecs/png.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! * <http://www.w3.org/TR/PNG/> - The PNG Specification
77
//!
88
9+
use std::borrow::Cow;
910
use std::fmt;
1011
use std::io::{BufRead, Seek, Write};
1112

@@ -482,6 +483,7 @@ pub struct PngEncoder<W: Write> {
482483
w: W,
483484
compression: CompressionType,
484485
filter: FilterType,
486+
icc_profile: Vec<u8>,
485487
}
486488

487489
/// Compression level of a PNG encoder. The default setting is `Fast`.
@@ -535,6 +537,7 @@ impl<W: Write> PngEncoder<W> {
535537
w,
536538
compression: CompressionType::default(),
537539
filter: FilterType::default(),
540+
icc_profile: Vec::new(),
538541
}
539542
}
540543

@@ -559,6 +562,7 @@ impl<W: Write> PngEncoder<W> {
559562
w,
560563
compression,
561564
filter,
565+
icc_profile: Vec::new(),
562566
}
563567
}
564568

@@ -604,7 +608,15 @@ impl<W: Write> PngEncoder<W> {
604608
FilterType::Adaptive => (png::FilterType::Sub, png::AdaptiveFilterType::Adaptive),
605609
};
606610

607-
let mut encoder = png::Encoder::new(self.w, width, height);
611+
let mut info = png::Info::with_size(width, height);
612+
613+
if !self.icc_profile.is_empty() {
614+
info.icc_profile = Some(Cow::Borrowed(&self.icc_profile));
615+
}
616+
617+
let mut encoder =
618+
png::Encoder::with_info(self.w, info).map_err(|e| ImageError::IoError(e.into()))?;
619+
608620
encoder.set_color(ct);
609621
encoder.set_depth(bits);
610622
encoder.set_compression(comp);
@@ -669,6 +681,11 @@ impl<W: Write> ImageEncoder for PngEncoder<W> {
669681
))),
670682
}
671683
}
684+
685+
fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
686+
self.icc_profile = icc_profile;
687+
Ok(())
688+
}
672689
}
673690

674691
impl ImageError {

src/codecs/pnm/decoder.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -692,8 +692,8 @@ where
692692

693693
let token = reader
694694
.bytes()
695-
.skip_while(|v| v.as_ref().ok().map_or(false, is_separator))
696-
.take_while(|v| v.as_ref().ok().map_or(false, |c| !is_separator(c)))
695+
.skip_while(|v| v.as_ref().ok().is_some_and(is_separator))
696+
.take_while(|v| v.as_ref().ok().is_some_and(|c| !is_separator(c)))
697697
.collect::<Result<Vec<u8>, _>>()?;
698698

699699
if !token.is_ascii() {

src/color.rs

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ impl<T: Primitive> FromPrimitive<T> for T {
441441
// 1.0 (white) was picked as firefox and chrome choose to map NaN to that.
442442
#[inline]
443443
fn normalize_float(float: f32, max: f32) -> f32 {
444+
#[allow(clippy::neg_cmp_op_on_partial_ord)]
444445
let clamped = if !(float < 1.0) { 1.0 } else { float.max(0.0) };
445446
(clamped * max).round()
446447
}

src/flat.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ impl SampleLayout {
282282
/// Check if a buffer of length `len` is large enough.
283283
#[must_use]
284284
pub fn fits(&self, len: usize) -> bool {
285-
self.min_length().map_or(false, |min| len >= min)
285+
self.min_length().is_some_and(|min| len >= min)
286286
}
287287

288288
/// The extents of this array, in order of increasing strides.
@@ -747,10 +747,7 @@ impl<Buffer> FlatSamples<Buffer> {
747747
where
748748
Buffer: AsRef<[T]>,
749749
{
750-
let min_length = match self.min_length() {
751-
None => return None,
752-
Some(index) => index,
753-
};
750+
let min_length = self.min_length()?;
754751

755752
let slice = self.samples.as_ref();
756753
if slice.len() < min_length {
@@ -765,10 +762,7 @@ impl<Buffer> FlatSamples<Buffer> {
765762
where
766763
Buffer: AsMut<[T]>,
767764
{
768-
let min_length = match self.min_length() {
769-
None => return None,
770-
Some(index) => index,
771-
};
765+
let min_length = self.min_length()?;
772766

773767
let slice = self.samples.as_mut();
774768
if slice.len() < min_length {

src/imageops/sample.rs

+1
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,7 @@ where
879879
let max = S::DEFAULT_MAX_VALUE;
880880
let max: f32 = NumCast::from(max).unwrap();
881881

882+
#[allow(clippy::redundant_guards)]
882883
let sum = match kernel.iter().fold(0.0, |s, &item| s + item) {
883884
x if x == 0.0 => 1.0,
884885
sum => sum,

0 commit comments

Comments
 (0)