21

I had noticed that when reading in an identical photograph across devices in the JPEG format, the pixel values do not match up. They are close, but different. When converted to PNG files, the pixel values seem to match up.

This would seem that it would be due to the (un)compression algorithms across devices. That's what comes to mind anyways. Is there a way to read in JPEG files so that the same pixels are retrieved from the photograph across devices? I don't see an option within the BitmapFactory Options component.

Currently applying the following to maintain size when working on pixel values of an image across devices:

Options options = new Options();
options.inScaled = false;
options.inPreferQualityOverSpeed = true;

Currently comparing pixels with the following just to look at a few (close matches, but not equal):

int[] pixels = new int[bitmapF.getWidth() * bitmapF.getHeight()];
bitmapF.getPixels(pixels, 0, bitmapF.getWidth(), 0, 0, bitmapF.getWidth(), bitmapF.getHeight());
Log.d("pixel entries", "pixels = " + pixels[233] + " - " + pixels[4002] + " - " + pixels[11391]);

Note: If reading in a PNG version of that same file which is uncompressed, the values are identical as expected.

The Samsung Galaxy S4 for example and the Samsung Galaxy S5 even have different pixels from the same jpeg (running off of the same test activity) stored in the assets folder.

pixel[233] for instance would be -5205635 on s5 but -5336451 on the s4. The pixel[4002] is a little off as well. But pixel[11391] are equal across both devices on this jpeg picture.

rhashimoto
  • 15,650
  • 2
  • 52
  • 80
Jay Snayder
  • 4,298
  • 4
  • 27
  • 53

6 Answers6

19

The JPEG standard does not require that decoder implementations produce bit-for-bit identical output images. Unfortunately the standards document specifying decoder requirements, ISO 10918-2, is apparently not freely available online but Wikipedia says:

...the JPEG standard (and the similar MPEG standards) includes some precision requirements for the decoding, including all parts of the decoding process (variable length decoding, inverse DCT, dequantization, renormalization of outputs); the output from the reference algorithm must not exceed:

  • a maximum 1 bit of difference for each pixel component
  • low mean square error over each 8×8-pixel block
  • [etc.]

Differences between different decoder outputs using the same input are generally due to differing levels of internal precision, particularly in performing the IDCT. Another possible source of differences is smoothing, which attempts to reduce "blockiness" artifacts.

Like you, I would expect that setting inPreferQualityOverSpeed would produce the same output but nothing actually guarantees that. I can think of at least a couple ways that you could get small variations on two different phones:

  1. The phones may run different versions of Android where the implementation of BitmapFactory changed (e.g. perhaps inPreferQualityOverSpeed was broken and then fixed, or vice versa), or
  2. The phones may provide different hardware features (e.g. vector instruction set, DSP co-processor, etc.) that BitmapFactory leverages. Even differences in scalar floating-point units can cause discrepancies, especially with JIT compilation producing the actual machine instructions.

Given the wiggle room in the standard plus your experimental observations, it appears the only way to guarantee bit-for-bit agreement is to perform decoding within your own application. Perhaps you can find some alternative Android-compatible library.

rhashimoto
  • 15,650
  • 2
  • 52
  • 80
  • This provides me with many potential solutions that I can follow-up on and I appreciate the valuable feedback. I think that if the rounding would potentially be an issue for me, I will have to look as you suggested, into other libraries for decoding and see if that can produce identical results for me. – Jay Snayder May 19 '14 at 15:02
  • On identical operating systems, the JPEGs still produced varied results, so while that was also a good suggestion, it will probably be one of mentioned in your response as some sort of internal hardware or the library itself and the way it interacts with hardware. – Jay Snayder May 19 '14 at 15:03
  • @JaySnayder - When you say identical OS, do you mean OS and Device? Have you tried decompressing JPEG on the same device several times? And are the values identical then? 8x8 blocks can be done in parallel by multiple threads, so I am wondering if order of processing along with anti-aliasing algorithms explains the differences. If multiple test runs on same image/os/device shows differences or not, it could help shed light on what's going on. – Les May 22 '14 at 18:29
  • @Les I don't unfortunately have an exact duplicate device for experimentation. So, I just meant at the same operating system. This was just in reference to whether or not it was OS specific. However, yes the same JPEG decompresses the same if done multiple times on the same device. – Jay Snayder May 22 '14 at 19:25
  • a maximum of 1 bit of pixel difference: I have `actual_npy[x, y, :] , expected[x, y, :] (array([176, 176, 168], dtype=uint8), array([176, 175, 170], dtype=uint8))` for same image loaded with different libjpeg versions, through PIL – saurabheights Mar 13 '21 at 14:00
2

I suppose you should also check if compressed PNGs appear the same way across devices.

http://pngquant.org/

If the answer is yes, then the only thing remaining would be to figure out how to convert programmatically on the phone those images to that same kind of compressed png.

Stephan Branczyk
  • 9,363
  • 2
  • 33
  • 49
  • 1
    I actually did do a quick test using the GUI version of pngquant. The uncompressed pngs produced different values than the compressed pngs as expected, but the compressed pngs produced identical values across all devices, unlike the jpegs. – Jay Snayder May 16 '14 at 16:21
1

Much of a JPEG decoder's work involves real number calculations. Usually this is done using fixed point integer arithmetic for performance. This introduces rounding errors. Slight variations are a natural part of working with JPEG.

user3344003
  • 20,574
  • 3
  • 26
  • 62
  • Why would the integer arithmetic vary across devices with the same internal picture? Wouldn't the OS be using the same calculations when decoding the same JPEG image and receive the same rounding error? – Jay Snayder May 16 '14 at 14:43
  • Let's say the decoders use scaled integers for the DCT calculations. A 64-bit system can give higher precision for the same calculations than a 32-bit system. A Decoder might use floating point calculations, giving a different rounding error. Decoders can use different orderings of operations giving different rounding. There are different ways to optimize the DCT/IDCT calculations. If you want the pixel values to be the same, regardless of the hardware, convert PNG on one machine and decode as PNG on the different hardware devices. – user3344003 May 18 '14 at 22:22
  • These images however are just for exprimentation. The actual images will be photographs taken with the built-in camera of the Android device. The images will not be passed around between devices. As of right now, the Android OS only handles 32-bit operating systems, but there is no reason it can't support 64. So this could also be a valid point in the near future on the first count you mentioned. – Jay Snayder May 19 '14 at 14:53
1

Yes, the pixel color values are different across devices. This is very annoying especially if you want to compare colors. The solution is to compare visually equal colors (by human perception).

One of the best methods to compare two colors by human perception is CIE76. The difference is called Delta-E. When it is less than 1, the human eye can not recognize the difference.

You can find wonderful color utilities class (ColorUtils), which includes CIE76 comparison methods. It is written by Daniel Strebel,University of Zurich.

From ColorUtils.class I use the method:

static double colorDifference(int r1, int g1, int b1, int r2, int g2, int b2)

r1,g1,b1 - RGB values of the first color

r2,g2,b2 - RGB values ot the second color that you would like to compare

If you work with Android, you can get these values like this:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);

Ivo Stoyanov
  • 16,256
  • 8
  • 62
  • 65
0

Re-size the media to the required size or use HTML attributes to disable scaling of the image.

Another option would be to allow the user to decide after loading a thumbnail representation to save bandwidth.

Jay
  • 3,276
  • 1
  • 28
  • 38
0

I was having the same problem. PNG and JPEG images seems to be rendered with approximation by different devices. We solved the problem using BMP images (whose dimensions are unfortunately a lot bigger).

smukamuka
  • 1,442
  • 1
  • 15
  • 23