Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions src/codecs/avif/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
/// [AVIF]: https://aomediacodec.github.io/av1-avif/
use std::borrow::Cow;
use std::cmp::min;
use std::io::Write;
use std::io::{Seek, Write};
use std::mem::size_of;

use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba};
use crate::error::{
EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
};
use crate::{ExtendedColorType, ImageBuffer, ImageEncoder, ImageFormat, Pixel};
use crate::{EncoderOptions, ExtendedColorType, ImageBuffer, ImageEncoder, ImageFormat, Pixel};
use crate::{ImageError, ImageResult};

use bytemuck::{try_cast_slice, try_cast_slice_mut, Pod, PodCastError};
Expand Down Expand Up @@ -52,6 +52,53 @@ enum RgbColor<'buf> {
Rgba8(Img<&'buf [RGBA8]>),
}

/// Encoding options for the AVIF format.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AvifOptions {
/// The quality of the AVIF encoding, from 1 to 100. Higher is better quality and larger file size.
///
/// Defaults to **80**.
pub quality: u8,
/// The speed of the AVIF encoding, from 1 to 10. Higher is faster but lower quality.
///
/// Defaults to **4**.
pub speed: u8,
/// The color space to encode with.
///
/// If `None`, the color space will be chosen dynamically for each image. No particular choice
/// is guaranteed and the chosen color space may change without warning between versions of the
/// library.
///
/// Defaults to [`ColorSpace::Bt709`].
pub color_space: ColorSpace,
/// The number of threads to use for encoding.
///
/// If `None`, the encoder will use all available threads.
///
/// Defaults to `None`.
// TODO: Using `usize` is weird. Also None == all seems weird too. Why not usize::MAX == all?
pub num_threads: Option<usize>,
}
impl Default for AvifOptions {
fn default() -> Self {
Self {
quality: 80,
speed: 4,
color_space: ColorSpace::Bt709,
num_threads: None,
}
}
}
impl EncoderOptions for AvifOptions {
fn build<W: Write + Seek>(self, w: W) -> ImageResult<impl ImageEncoder> {
let encoder = AvifEncoder::new_with_speed_quality(w, self.speed, self.quality)
.with_colorspace(self.color_space)
.with_num_threads(self.num_threads);
Ok(encoder)
}
}

impl<W: Write> AvifEncoder<W> {
/// Create a new encoder that writes its output to `w`.
pub fn new(w: W) -> Self {
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/avif/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#[cfg(feature = "avif-native")]
pub use self::decoder::AvifDecoder;
#[cfg(feature = "avif")]
pub use self::encoder::{AvifEncoder, ColorSpace};
pub use self::encoder::{AvifEncoder, AvifOptions, ColorSpace};

#[cfg(feature = "avif-native")]
mod decoder;
Expand Down
56 changes: 53 additions & 3 deletions src/codecs/jpeg/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#![allow(clippy::too_many_arguments)]
use std::io::Write;
use std::io::{Seek, Write};
use std::{error, fmt};

use crate::error::{
EncodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind,
};
use crate::{ColorType, DynamicImage, ExtendedColorType, ImageEncoder, ImageFormat};
use crate::{
ColorType, DynamicImage, EncoderOptions, ExtendedColorType, ImageEncoder, ImageFormat,
};

use jpeg_encoder::Encoder;

Expand Down Expand Up @@ -41,7 +43,7 @@ pub enum ChromaSubsampling {
S422,
/// **4:2:0** The resolution of color information is reduced by a factor of 2 both horizontally and vertically.
///
/// Results in a smaller file size. Well suited for photographs where it incurs no visial quality loss.
/// Results in a smaller file size. Well suited for photographs where it incurs no visual quality loss.
S420,
}

Expand Down Expand Up @@ -134,6 +136,54 @@ impl From<EncoderError> for ImageError {

impl error::Error for EncoderError {}

/// Encoding options for the JPEG format.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct JpegOptions {
/// The quality of the JPEG encoding, from 1 to 100. Higher is better quality and larger file size.
///
/// Defaults to **75**.
pub quality: u8,
/// The chroma subsampling mode. See [ChromaSubsampling] for details.
pub chroma_subsampling: ChromaSubsampling,
/// Spend extra time optimizing Huffman tables. Slightly reduces file size at the cost of encoding speed.
///
/// Defaults to **false**.
pub optimize_huffman_tables: bool,
/// Progressive files allow showing a low-resolution view of the entire image before it's fully downloaded.
/// Useful for large images that will be displayed on the web.
///
/// Defaults to **false**.
pub progress: bool,
/// The pixel density of the images the encoder will encode.
/// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied,
/// and no DPI information will be stored in the image.
pub pixel_density: Option<PixelDensity>,
}
impl Default for JpegOptions {
fn default() -> Self {
JpegOptions {
quality: 75,
chroma_subsampling: ChromaSubsampling::S420,
optimize_huffman_tables: false,
progress: false,
pixel_density: None,
}
}
}
impl EncoderOptions for JpegOptions {
fn build<W: Write + Seek>(self, w: W) -> ImageResult<impl ImageEncoder> {
let mut encoder = JpegEncoder::new_with_quality(w, self.quality);
encoder.set_chroma_subsampling(self.chroma_subsampling);
encoder.set_optimize_huffman_tables(self.optimize_huffman_tables);
encoder.set_progressive(self.progress);
if let Some(pixel_density) = self.pixel_density {
encoder.set_pixel_density(pixel_density);
}
Ok(encoder)
}
}

/// The representation of a JPEG encoder
pub struct JpegEncoder<W: Write> {
encoder: Encoder<W>,
Expand Down
4 changes: 3 additions & 1 deletion src/codecs/jpeg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
//! * <http://www.w3.org/Graphics/JPEG/itu-t81.pdf> - The JPEG specification

pub use self::decoder::JpegDecoder;
pub use self::encoder::{ChromaSubsampling, JpegEncoder, PixelDensity, PixelDensityUnit};
pub use self::encoder::{
ChromaSubsampling, JpegEncoder, JpegOptions, PixelDensity, PixelDensityUnit,
};

mod decoder;
mod encoder;
43 changes: 37 additions & 6 deletions src/codecs/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use crate::math::Rect;
use crate::metadata::LoopCount;
use crate::utils::vec_try_with_capacity;
use crate::{
DynamicImage, GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat,
ImageLayout, Limits, Luma, LumaA, Rgb, Rgba,
DynamicImage, EncoderOptions, GenericImage, GenericImageView, ImageDecoder, ImageEncoder,
ImageFormat, ImageLayout, Limits, Luma, LumaA, Rgb, Rgba,
};

// http://www.w3.org/TR/PNG-Structure.html
Expand Down Expand Up @@ -691,9 +691,8 @@ pub struct PngEncoder<W: Write> {
}

/// DEFLATE compression level of a PNG encoder. The default setting is `Fast`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
#[non_exhaustive]
#[derive(Default)]
pub enum CompressionType {
/// No compression whatsoever
Uncompressed,
Expand All @@ -711,9 +710,8 @@ pub enum CompressionType {
/// Filter algorithms used to process image data to improve compression.
///
/// The default filter is `Adaptive`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
#[non_exhaustive]
#[derive(Default)]
pub enum FilterType {
/// No processing done, best used for low bit depth grayscale or data with a
/// low color count
Expand All @@ -732,6 +730,39 @@ pub enum FilterType {
Adaptive,
}

/// Encoding options for the PNG format.
///
/// It is best to view the options as a _hint_ to the implementation on the smallest or fastest
/// option for encoding a particular image. That is, using options that map directly to a PNG
/// image parameter will use this parameter where possible. But variants that have no direct
/// mapping may be interpreted differently in minor versions. The exact output is expressly
/// __not__ part of the SemVer stability guarantee.
#[derive(Default, Debug, Clone)]
#[non_exhaustive]
pub struct PngOptions {
/// DEFLATE compression level of a PNG encoder.
///
/// Defaults to [`CompressionType::Fast`].
pub compression: CompressionType,
/// Filter algorithms used to process image data.
///
/// Note that it is not optimal to use a single filter type, so an adaptive
/// filter type is selected as the default. The filter which best minimizes
/// file size may change with the type of compression used.
///
/// Defaults to [`FilterType::Adaptive`].
pub filter: FilterType,
}
impl EncoderOptions for PngOptions {
fn build<W: Write + Seek>(self, w: W) -> ImageResult<impl ImageEncoder> {
Ok(PngEncoder::new_with_quality(
w,
self.compression,
self.filter,
))
}
}

impl<W: Write> PngEncoder<W> {
/// Create a new encoder that writes its output to ```w```
pub fn new(w: W) -> PngEncoder<W> {
Expand Down
25 changes: 25 additions & 0 deletions src/codecs/pnm/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Encoding of PNM Images
use crate::utils::vec_try_with_capacity;
use crate::EncoderOptions;
use std::fmt;
use std::io;
use std::io::Seek;
use std::io::Write;

use super::AutoBreak;
Expand Down Expand Up @@ -29,6 +31,29 @@ pub enum FlatSamples<'a> {
U16(&'a [u16]),
}

/// Encoding options for the PNM format.
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct PnmOptions {
/// The specific PNM subtype to encode to.
///
/// If `None`, the subtype will be chosen dynamically for each image. No
/// particular choice is guaranteed and the chosen subtype may change
/// without warning between versions of the library.
///
/// Defaults to `None`.
pub subtype: Option<PnmSubtype>,
}
impl EncoderOptions for PnmOptions {
fn build<W: Write + Seek>(self, w: W) -> ImageResult<impl ImageEncoder> {
let mut encoder = PnmEncoder::new(w);
if let Some(subtype) = self.subtype {
encoder = encoder.with_subtype(subtype);
}
Ok(encoder)
}
}

/// Encodes images to any of the `pnm` image formats.
pub struct PnmEncoder<W: Write> {
writer: W,
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/pnm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! interpretation as an image and will be rejected.
use self::autobreak::AutoBreak;
pub use self::decoder::PnmDecoder;
pub use self::encoder::PnmEncoder;
pub use self::encoder::{PnmEncoder, PnmOptions};
use self::header::HeaderRecord;
pub use self::header::{
ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader,
Expand Down
30 changes: 29 additions & 1 deletion src/codecs/tga/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use super::header::Header;
use crate::{codecs::tga::header::ImageType, error::EncodingError, utils::vec_try_with_capacity};
use crate::{DynamicImage, ExtendedColorType, ImageEncoder, ImageError, ImageFormat, ImageResult};
use crate::{
DynamicImage, EncoderOptions, ExtendedColorType, ImageEncoder, ImageError, ImageFormat,
ImageResult,
};
use std::io::Seek;
use std::{error, fmt, io::Write};

/// Errors that can occur during encoding and saving of a TGA image.
Expand Down Expand Up @@ -34,6 +38,30 @@ impl From<EncoderError> for ImageError {

impl error::Error for EncoderError {}

/// Encoding options for the TGA format.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct TgaOptions {
/// Whether to use run-length encoding (RLE) for the image data.
///
/// Defaults to `true`.
pub use_rle: bool,
}
impl Default for TgaOptions {
fn default() -> Self {
Self { use_rle: true }
}
}
impl EncoderOptions for TgaOptions {
fn build<W: Write + Seek>(self, w: W) -> ImageResult<impl ImageEncoder> {
let mut encoder = TgaEncoder::new(w);
if !self.use_rle {
encoder = encoder.disable_rle();
}
Ok(encoder)
}
}

/// TGA encoder.
pub struct TgaEncoder<W: Write> {
writer: W,
Expand Down
2 changes: 1 addition & 1 deletion src/codecs/tga/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

pub use self::decoder::TgaDecoder;

pub use self::encoder::TgaEncoder;
pub use self::encoder::{TgaEncoder, TgaOptions};

mod decoder;
mod encoder;
Expand Down
23 changes: 22 additions & 1 deletion src/images/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ use crate::{
metadata::{Cicp, CicpColorPrimaries, CicpTransferCharacteristics, CicpTransform},
save_buffer, save_buffer_with_format, write_buffer_with_format, ImageError,
};
use crate::{DynamicImage, GenericImage, GenericImageView, ImageEncoder, ImageFormat, Primitive};
use crate::{
save_buffer_with_options, DynamicImage, EncoderOptions, GenericImage, GenericImageView,
ImageEncoder, ImageFormat, Primitive,
};

/// Iterate over rows of an image
///
Expand Down Expand Up @@ -1161,6 +1164,24 @@ where
)
}

/// Saves the buffer to a file at the specified path with the given options.
///
/// See [`save_buffer_with_options`](crate::save_buffer_with_options) for
/// supported types.
pub fn save_with_options<Q>(&self, path: Q, options: impl EncoderOptions) -> ImageResult<()>
where
Q: AsRef<Path>,
{
save_buffer_with_options(
path,
self.subpixels().as_bytes(),
self.width(),
self.height(),
P::COLOR_TYPE,
options,
)
}

/// Writes the buffer to a writer in the specified format.
///
/// Assumes the writer is buffered. In most cases, you should wrap your writer in a `BufWriter`
Expand Down
Loading
Loading