Skip to content

Conversation

@Shnatsel
Copy link
Member

@Shnatsel Shnatsel commented Oct 30, 2025

This PR replaces the in-tree JPEG encoder with the jpeg-encoder crate. Closes #1885 and #2500.

Background

jpeg-encoder is a fork of our in-tree JPEG encoder with multiple enhancements (optimized huffman tables, more subsampling options, AVX optimizations). It is already used in production by Glycin (GNOME) instead of our built-in encoder.

Correctness

This encoder used by Glycin so it is pretty well tested. I've added tests for roundtripping Exif and ICC.

Performance

The end-to-end time for converting from PNG to JPEG with wondermagick goes down 2x, so the speedup for JPEG encoding alone has to be more than 2x. On x86 with AVX2 anyway; ARM doesn't gain as much because jpeg-encoder doesn't have a NEON implementation.

Safety

Thanks to Rust 1.86 making most SIMD intrinsics safe, I've removed the vast majority of unsafe code in vstroebel/jpeg-encoder#17 and vstroebel/jpeg-encoder#18. The remaining small amount of unsafe is trivial.

New functionality

This PR also exposes functions to control chroma subsampling factor. Our old encoder only supported 4:2:2, while jpeg-encoder supports 4:4:4, 4:2:2, 4:2:0, and a bunch of more obscure configurations. This should improve compression ratio.

This PR also lets the user opt into optimizing the Huffman tables of the generated JPEG file, further reducing file size.

Breaking changes

A casualty of this migration is a JPEG-exclusive option to generate an image on the fly via a GenericImageView, but with the planned changes to image views this would have to be removed anyway. pub fn encode() is also removed because its signature would have to change and there is no reason to keep it around when it just duplicates write_image() exactly.

Future work

TODO: handle PixelDensityUnit::PixelAspectRatio variant: vstroebel/jpeg-encoder#21

jpeg-encoder supports more in-depth customization of the encoding process such as encoding progressive JPEGs, restart markets, etc. Exposing that functionality is not part of this PR.

@Shnatsel Shnatsel added next: breaking Information tag for PRs and ideas that require an interface break topic: formats Towards better encoding format coverage labels Oct 30, 2025
@Shnatsel
Copy link
Member Author

Shnatsel commented Oct 30, 2025

Note that while jpeg-encoder reports IJG license due to SIMD code ported from libjpeg-turbo, it's actually ZLIB licensed according to https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/LICENSE.md

Both licenses are permissive so it shouldn't be a problem regardless but I could change it to ZLIB upstream if you prefer your cargo deny to be more straightforward. I think they included a bit of scalar code under IJG as well, but again it's entirely permissive so that shouldn't be a problem.

@Shnatsel
Copy link
Member Author

Shnatsel commented Nov 2, 2025

Now that #2624 is merged I've incorporated its changes into this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next: breaking Information tag for PRs and ideas that require an interface break topic: formats Towards better encoding format coverage

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Use the jpeg-encoder crate for a 2x speedup

1 participant