Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JPEG decoding yields discolored image (basic 8-bit YUV 444 JPEG with no / GIMP default sRGB color profile). #249

Open
gergo-salyi opened this issue Jan 30, 2025 · 0 comments

Comments

@gergo-salyi
Copy link

I'm relaying this from my issue at the image crate using zune-jpg for JPEG decoding: image-rs/image#2411

Tested on Linux with x86_64 CPU (i5-1035G4) 10th gen Intel having AVX2, etc...

# Convert with ImageMagick for reference
magick start.jpg good.png
# Cargo.toml
[dependencies]
image = "0.25.5"
// main.rs
fn main() {
    image::open("start.jpg").unwrap().save("bad.png").unwrap();
}

start.jpg:

Image

good.png:

Image

bad.png:

Image

Lower half of bad.png is visibly more green then start.jpg or good.png. E.g. pixel at index (150, 200) became #131910 instead of #151811 . I expect the the JPEG decoder to yield identical result to ImageMagick.

Not sure about the cause, but I suspect a YCbCr -> RGB conversion problem. zune-jpeg does this:

//! 1. The YCbCr to RGB use integer approximations and not the floating point equivalent.
//! That means we may be +- 2 of pixels generated by libjpeg-turbo jpeg decoding
//! (also libjpeg uses routines like `Y  =  0.29900 * R + 0.33700 * G + 0.11400 * B + 0.25000 * G`)

//! 1. The YCbCr to RGB use integer approximations and not the floating point equivalent.
//! That means we may be +- 2 of pixels generated by libjpeg-turbo jpeg decoding
//! (also libjpeg uses routines like `Y = 0.29900 * R + 0.33700 * G + 0.11400 * B + 0.25000 * G`)

This integer approximation is known to have caused problems elsewhere in the past, see: https://en.wikipedia.org/wiki/YCbCr#Approximate_8-bit_matrices_for_BT.601

The JPEG File Interchange Format Version 1.02 spec defines on page 3:

RGB can be computed directly from YCbCr (256 levels) as follows:
R = Y + 1.402 (Cr-128)
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
B = Y + 1.772 (Cb-128)

These formulas don't have +-1/255 error. Again I'm not 100% sure that YCbCr -> RGB conversion is the cause of the discoloration, but I have this suspicion seeing the sloppy integer math.

Being off by +-1 in a 0-255 ranged 8-bit color intensity (in a non-random / non-dithered way) is a visually significant error, basically JPEG images which are supposed to be visually lossless (encoded with quality 90 in the above example) are completely violated in their usage intent.

Please also see image-rs/image#2411 , the maintainer there posted some statistical measurements about the above images.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant