Skip to content

Replace in-tree JPEG encoder with jpeg-encoder crate#2636

Merged
197g merged 22 commits intoimage-rs:mainfrom
Shnatsel:jpeg-encoder
Nov 19, 2025
Merged

Replace in-tree JPEG encoder with jpeg-encoder crate#2636
197g merged 22 commits intoimage-rs:mainfrom
Shnatsel:jpeg-encoder

Conversation

@Shnatsel
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Member Author

Shnatsel commented Nov 2, 2025

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

Copy link
Copy Markdown
Member

@197g 197g left a comment

Choose a reason for hiding this comment

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

With the SemVer part resolved, I'm fine with the slight reduction in API surface. At least it's more consistent. Just great work all around that I'm happy to adopt instead of our in-tree part that is not any easier to maintain. I would not worry too much about pixel density indication; orientation of the image would seem more pressing and a coherent way of exposing those format details rather than just doing it for one jpeg option seems preferrable. With 1.0 we should paint ourselves out of that corner of configuring it on the encoder, which is inaccessible behind a trait abstraction.

@197g 197g merged commit 44a9fe0 into image-rs:main Nov 19, 2025
31 of 32 checks passed
This was referenced Nov 19, 2025
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: Done

Development

Successfully merging this pull request may close these issues.

Use the jpeg-encoder crate for a 2x speedup

2 participants