Skip to content

Commit 324b734

Browse files
committed
Merge branch 'main' into safe-simd-ycbcr
2 parents bf3ab88 + fd7b408 commit 324b734

File tree

10 files changed

+203
-32
lines changed

10 files changed

+203
-32
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "jpeg-encoder"
3-
version = "0.6.1"
3+
version = "0.7.0"
44
authors = ["Volker Ströbel <volkerstroebel@mysurdity.de>"]
5-
edition = "2021"
5+
edition = "2024"
66
license = "(MIT OR Apache-2.0) AND IJG"
77
description = "JPEG encoder"
88
categories = ["multimedia::images"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ encoder.encode(&data, 2, 2, ColorType::Rgb)?;
4141

4242
## Minimum Supported Version of Rust (MSRV)
4343

44-
This crate needs at least 1.61 or higher.
44+
This crate needs at least 1.87 or higher.
4545

4646
## License
4747

criterion/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ harness = false
2828
[[bench]]
2929
name = "fdct"
3030
harness = false
31+
32+
[[bench]]
33+
name = "ycbcr"
34+
harness = false

criterion/benches/ycbcr.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
3+
use jpeg_encoder::{ImageBuffer, RgbImage};
4+
use std::time::Duration;
5+
6+
fn create_bench_img() -> (Vec<u8>, u16, u16) {
7+
let width = 1001;
8+
let height = 500;
9+
10+
let mut data = Vec::with_capacity(width * height * 3);
11+
12+
for y in 0..height {
13+
for x in 0..width {
14+
if (x * y) % 13 == 0 {
15+
data.push(0);
16+
data.push(0);
17+
data.push(0);
18+
} else if (x * y) % 17 == 0 {
19+
data.push(255);
20+
data.push(255);
21+
data.push(255);
22+
} else if (x * y) % 19 == 0 {
23+
data.push(255);
24+
data.push(0);
25+
data.push(0);
26+
} else if (x * y) % 21 == 0 {
27+
data.push(0);
28+
data.push(0);
29+
data.push(255);
30+
} else if (x * y) % 23 == 0 {
31+
data.push(0);
32+
data.push(255);
33+
data.push(0);
34+
} else if (x * y) % 25 == 0 {
35+
data.push(96);
36+
data.push(255);
37+
data.push(96);
38+
} else if (x * y) % 27 == 0 {
39+
data.push(255);
40+
data.push(96);
41+
data.push(96);
42+
} else if (x * y) % 29 == 0 {
43+
data.push(96);
44+
data.push(96);
45+
data.push(255);
46+
} else {
47+
data.push((x % 256) as u8);
48+
data.push((x % 256) as u8);
49+
data.push(((x * y) % 256) as u8);
50+
}
51+
}
52+
}
53+
54+
(data, width as u16, height as u16)
55+
}
56+
57+
fn criterion_benchmark(c: &mut Criterion) {
58+
let (data, width, height) = create_bench_img();
59+
let res1 = Vec::with_capacity(usize::from(width));
60+
let res2 = Vec::with_capacity(usize::from(width));
61+
let res3 = Vec::with_capacity(usize::from(width));
62+
let res4 = Vec::with_capacity(usize::from(width));
63+
64+
let mut res = [res1, res2, res3, res4];
65+
66+
let mut group = c.benchmark_group("ycbcr");
67+
group.measurement_time(Duration::from_secs(45));
68+
group.warm_up_time(Duration::from_secs(10));
69+
70+
group.bench_function("default ycbcr", |b| {
71+
let image_buffer = RgbImage(&data, width, height);
72+
73+
b.iter(|| {
74+
for y in 0..height {
75+
res[0].clear();
76+
res[1].clear();
77+
res[2].clear();
78+
image_buffer.fill_buffers(y, black_box(&mut res));
79+
}
80+
black_box(&res);
81+
});
82+
});
83+
84+
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
85+
group.bench_function("ycbcr avx2", |b| {
86+
use jpeg_encoder::RgbImageAVX2;
87+
let image_buffer = RgbImageAVX2(&data, width, height);
88+
89+
b.iter(|| {
90+
for y in 0..height {
91+
res[0].clear();
92+
res[1].clear();
93+
res[2].clear();
94+
image_buffer.fill_buffers(y, black_box(&mut res));
95+
}
96+
black_box(&res);
97+
})
98+
});
99+
100+
group.finish();
101+
}
102+
103+
criterion_group!(benches, criterion_benchmark);
104+
criterion_main!(benches);

src/avx2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod ycbcr;
33

44
use crate::encoder::Operations;
55
pub use fdct::fdct_avx2;
6-
pub(crate) use ycbcr::*;
6+
pub use ycbcr::*;
77

88
pub(crate) struct AVX2Operations;
99

src/avx2/ycbcr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{rgb_to_ycbcr, ImageBuffer, JpegColorType};
1616

1717
macro_rules! ycbcr_image_avx2 {
1818
($name:ident, $num_colors:expr, $o1:expr, $o2:expr, $o3:expr) => {
19-
pub(crate) struct $name<'a>(pub &'a [u8], pub u16, pub u16);
19+
pub struct $name<'a>(pub &'a [u8], pub u16, pub u16);
2020

2121
impl<'a> $name<'a> {
2222
#[target_feature(enable = "avx2")]

src/encoder.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::image_buffer::*;
44
use crate::marker::Marker;
55
use crate::quantization::{QuantizationTable, QuantizationTableType};
66
use crate::writer::{JfifWrite, JfifWriter, ZIGZAG};
7-
use crate::{Density, EncodingError};
7+
use crate::{PixelDensity, EncodingError};
88

99
use alloc::vec;
1010
use alloc::vec::Vec;
@@ -194,7 +194,7 @@ macro_rules! add_component {
194194
/// # The JPEG encoder
195195
pub struct Encoder<W: JfifWrite> {
196196
writer: JfifWriter<W>,
197-
density: Density,
197+
density: PixelDensity,
198198
quality: u8,
199199

200200
components: Vec<Component>,
@@ -243,7 +243,7 @@ impl<W: JfifWrite> Encoder<W> {
243243

244244
Encoder {
245245
writer: JfifWriter::new(w),
246-
density: Density::None,
246+
density: PixelDensity::default(),
247247
quality,
248248
components: vec![],
249249
quantization_tables,
@@ -259,12 +259,12 @@ impl<W: JfifWrite> Encoder<W> {
259259
/// Set pixel density for the image
260260
///
261261
/// By default, this value is None which is equal to "1 pixel per pixel".
262-
pub fn set_density(&mut self, density: Density) {
262+
pub fn set_density(&mut self, density: PixelDensity) {
263263
self.density = density;
264264
}
265265

266266
/// Return pixel density
267-
pub fn density(&self) -> Density {
267+
pub fn density(&self) -> PixelDensity {
268268
self.density
269269
}
270270

@@ -400,6 +400,24 @@ impl<W: JfifWrite> Encoder<W> {
400400
Ok(())
401401
}
402402

403+
/// Embeds Exif metadata into the image
404+
///
405+
/// The maximum allowed data length is 65,528 bytes.
406+
///
407+
/// # Errors
408+
///
409+
/// Returns an Error if the data exceeds the maximum size for the Exif metadata
410+
pub fn add_exif_metadata(&mut self, data: &[u8]) -> Result<(), EncodingError> {
411+
// E x i f \0 \0
412+
/// The header for an EXIF APP1 segment
413+
const EXIF_HEADER: [u8; 6] = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00];
414+
415+
let mut formatted = EXIF_HEADER.to_vec();
416+
formatted.extend_from_slice(data);
417+
418+
self.add_app_segment(1, &formatted)
419+
}
420+
403421
/// Encode an image
404422
///
405423
/// Data format and length must conform to specified width, height and color type.

src/image_buffer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ fn get_line(data: &[u8], y: u16, width:u16, num_colors: usize) -> &[u8] {
134134

135135
macro_rules! ycbcr_image {
136136
($name:ident, $num_colors:expr, $o1:expr, $o2:expr, $o3:expr) => {
137-
pub(crate) struct $name<'a>(pub &'a [u8], pub u16, pub u16);
137+
pub struct $name<'a>(pub &'a [u8], pub u16, pub u16);
138138

139139
impl<'a> ImageBuffer for $name<'a> {
140140
fn get_jpeg_color_type(&self) -> JpegColorType {

src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,20 @@ pub use encoder::{ColorType, Encoder, JpegColorType, SamplingFactor};
4646
pub use error::EncodingError;
4747
pub use image_buffer::{cmyk_to_ycck, rgb_to_ycbcr, ImageBuffer};
4848
pub use quantization::QuantizationTableType;
49-
pub use writer::{Density, JfifWrite};
49+
pub use writer::{PixelDensity, PixelDensityUnit, JfifWrite};
5050

5151
#[cfg(feature = "benchmark")]
5252
pub use fdct::fdct;
53+
54+
#[cfg(feature = "benchmark")]
55+
pub use image_buffer::RgbImage;
56+
5357
#[cfg(all(feature = "benchmark", feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
5458
pub use avx2::fdct_avx2;
5559

60+
#[cfg(all(feature = "benchmark", feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
61+
pub use avx2::RgbImageAVX2;
62+
5663
#[cfg(test)]
5764
mod tests {
5865
use crate::image_buffer::rgb_to_ycbcr;

src/writer.rs

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,58 @@ use crate::marker::{Marker, SOFType};
44
use crate::quantization::QuantizationTable;
55
use crate::EncodingError;
66

7-
/// Density settings
8-
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
9-
pub enum Density {
10-
/// No pixel density is set, which means "1 pixel per pixel"
11-
None,
7+
/// Represents the pixel density of an image
8+
///
9+
/// For example, a 300 DPI image is represented by:
10+
///
11+
/// ```rust
12+
/// # use jpeg_encoder::{PixelDensity, PixelDensityUnit};
13+
/// let hdpi = PixelDensity::dpi(300);
14+
/// assert_eq!(hdpi, PixelDensity {density: (300,300), unit: PixelDensityUnit::Inches})
15+
/// ```
16+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17+
pub struct PixelDensity {
18+
/// A couple of values for (Xdensity, Ydensity)
19+
pub density: (u16, u16),
20+
/// The unit in which the density is measured
21+
pub unit: PixelDensityUnit,
22+
}
23+
24+
impl PixelDensity {
25+
/// Creates the most common pixel density type:
26+
/// the horizontal and the vertical density are equal,
27+
/// and measured in pixels per inch.
28+
#[must_use]
29+
pub fn dpi(density: u16) -> Self {
30+
PixelDensity {
31+
density: (density, density),
32+
unit: PixelDensityUnit::Inches,
33+
}
34+
}
35+
}
36+
37+
impl Default for PixelDensity {
38+
/// Returns a pixel density with a pixel aspect ratio of 1
39+
fn default() -> Self {
40+
PixelDensity {
41+
density: (1, 1),
42+
unit: PixelDensityUnit::PixelAspectRatio,
43+
}
44+
}
45+
}
46+
47+
/// Represents a unit in which the density of an image is measured
48+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49+
pub enum PixelDensityUnit {
50+
/// Represents the absence of a unit, the values indicate only a
51+
/// [pixel aspect ratio](https://en.wikipedia.org/wiki/Pixel_aspect_ratio)
52+
PixelAspectRatio,
1253

13-
/// Horizontal and vertical dots per inch (dpi)
14-
Inch { x: u16, y: u16 },
54+
/// Pixels per inch (2.54 cm)
55+
Inches,
1556

16-
/// Horizontal and vertical dots per centimeters
17-
Centimeter { x: u16, y: u16 },
57+
/// Pixels per centimeter
58+
Centimeters,
1859
}
1960

2061
/// Zig-zag sequence of quantized DCT coefficients
@@ -172,30 +213,27 @@ impl<W: JfifWrite> JfifWriter<W> {
172213
Ok(())
173214
}
174215

175-
pub fn write_header(&mut self, density: &Density) -> Result<(), EncodingError> {
216+
pub fn write_header(&mut self, density: &PixelDensity) -> Result<(), EncodingError> {
176217
self.write_marker(Marker::APP(0))?;
177218
self.write_u16(16)?;
178219

179220
self.write(b"JFIF\0")?;
180221
self.write(&[0x01, 0x02])?;
181222

182-
match *density {
183-
Density::None => {
223+
match density.unit {
224+
PixelDensityUnit::PixelAspectRatio => {
184225
self.write_u8(0x00)?;
185-
self.write_u16(1)?;
186-
self.write_u16(1)?;
187226
}
188-
Density::Inch { x, y } => {
227+
PixelDensityUnit::Inches => {
189228
self.write_u8(0x01)?;
190-
self.write_u16(x)?;
191-
self.write_u16(y)?;
192229
}
193-
Density::Centimeter { x, y } => {
230+
PixelDensityUnit::Centimeters => {
194231
self.write_u8(0x02)?;
195-
self.write_u16(x)?;
196-
self.write_u16(y)?;
197232
}
198233
}
234+
let (x, y) = density.density;
235+
self.write_u16(x)?;
236+
self.write_u16(y)?;
199237

200238
self.write(&[0x00, 0x00])
201239
}

0 commit comments

Comments
 (0)