12

I work with a lot of monochrome image data and this morning I noticed that there appears to be a significant difference between the way libjpeg and the .Net jpeg codec handle monochrome data. It appears that a monochrome image saved at ANY quality setting using libjpeg and opened using the default .Net jpeg codec actually only loads with 16 different shades of gray and all intermediate shades are rendered as stippled.

Here is the histogram of a smooth gradient saved by libjpeg and loaded by .net

Histogram of codec conflict affected gradient

The histogram should have been perfectly level.

And here is a (zoomed in) sample of what that gradient looks like (it should a be perfectly smooth transition) enter image description here

This should be a smooth transition from gray 85 on the left to gray 136 on the right but only 4 shades of gray are actually rendered to make that transition.

My question is am I crazy and if not just how far does this codec discrepancy go? Is there a good workaround if you are using both libraries in different programs?

I am not blaming either codec, just pointing out what appears to be a discrepancy. I noticed this with images I knew were created using libjpeg, assumed it was a quality setting issue, tried using faststone image resizer to create test images and got the same result, tried using irfanview and got the same result again. As both of those programs must use some jpeg library I tend to assume they are also using libjpeg and there is a genuine codec conflict.

On the loading side I have run into the same result loading images with both my own .net code and using Paint.net.

Finally, here is a sample at normal resolution so you can download it and try it yourself. Loading it in some programs will give you a nice gradient (e.g. your browser) but loading it with your own .Net code, or Paint.Net will give you a dithered gradient like the above rendered using only 16 shades of gray.

enter image description here

Does anyone know more about this, how far it goes and what good workarounds might be?

  • 1
    There is no code to examine here... – Heath Hunnicutt May 11 '11 at 18:29
  • That doesn't look like a JPEG **at all**. The dithering causes sharp edges which should trigger JPEG artifacts, and I don't see any in your blown up sample. I suspect Irfanview settings here - blaming this on libjpeg is totally premature. – Mark Ransom May 11 '11 at 18:31
  • 1
    P.S. attaching an actual JPEG generated by your process would be very helpful too. – Mark Ransom May 11 '11 at 18:38
  • Yes, I know, part of my conclusion that I was seeing the same thing from libjpeg, irfanview and faststone saved files was because the look one gets is so distinctively unlike what you expect from a jpeg. Great suggestion to attach an example file. Open it with some .Net code, or paint.net and see if you don't get the same weird thing which is different, zoomed in, than what you get if you open it in various other programs (including the browser currently rendering it and windows live photo gallery). Again, not placing blame, just noting that there is a discrepancy. –  May 11 '11 at 20:26
  • Have you considered just wrapping libjpeg in your .Net implementation? I explored similar issues here and concluded the same - the .Net JPEG codec is so bad it should be avoided for any Production use. http://b9dev.blogspot.com/2013/06/nets-built-in-jpeg-encoder-convenient.html – Chris Moschini Mar 15 '14 at 20:25
  • http://bitmiracle.com/libjpeg/ – Chris Moschini Mar 15 '14 at 20:41

1 Answers1

4

I am able to reproduce your symptoms by opening your sample image in the version of Paint bundled with Windows 7. Analyzing the file shows it to be a valid JPEG, and having it show up properly in the browser confirms this. It looks like Microsoft really messed up bad. It has already been submitted as a bug:

https://connect.microsoft.com/VisualStudio/feedback/details/597657/grayscale-jpeg-image-read-as-format8bppindexed-but-quantized-as-format4bppindexed#details

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/9132e2bd-23cc-4e5a-a783-1fa4abe11624/

The workaround would be to create your JPEG as a full color image, but only put grayscale pixel values into it.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • @johnmortal, please post a duplicate of the image created with .Net. I'm really curious to see how it compares. – Mark Ransom May 16 '11 at 16:16
  • All right, I deleted my previous comment because it was incorrect. Testing with jpegdump my impression is that just 1 component jpegs are loaded incorrectly. Paint.Net nicely sidesteps this because all jpegs are saved as 3 component jpegs (because it offers no explicit control over the number of components). That won't cause much inflation because jpeg doesn't define colorspaces so they are almost always wrapped in a JFIF and a 3 color JFIF is required to have components YCbCr and the two extra CbCr components will all be zero so they should compress significantly. –  May 19 '11 at 15:28
  • Saving an array of 1 byte per pixel data in .Net code with pixelformat.gray8 using JpegBitmapEncoder creates a 1 component jpeg which loads incorrectly. –  May 19 '11 at 15:29
  • So yes, the work around seems to be to save jpegs as 3 component jpegs, which means more memory usage. I have not done a reliable test of the difference in file size (3 components, but since YCbCr probably not 3 times as big once compressed). The 3 component jpeg should be bigger, but not 3 times bigger, but it could be 2 times bigger(?). –  May 19 '11 at 15:32
  • @johnmortal, I don't have access to the software that would let me do the test at this moment either. However I think I've done it in the past and found the difference to be really minimal. It's certainly worth trying. – Mark Ransom May 19 '11 at 15:50
  • But I guess that is just a workaround for saving them so that they will be reloaded correctly. It does not provide a workaround for loading existing or third party monochrome jpegs. –  May 19 '11 at 16:04