Scaling and then saving as JPEG: wrong colors

O

Oliver

Hi,

I've googled around and found some references to CMYK color space, but
nothing which would indicate a solution to my problem: I'm reading an
image (png, jpeg, ...), scale it with an AffineTransformOp and save it
again as JPEG into a file.

Problem: the resulting JPEG file has wrong colours, red goes to light
blue, white goes to reddish, light blue goes to green etc. (note: the
colors are _not_ just inverted, they look like CMYK colors, but
displayed as RGB values (?)).

Now when I _don't_ scale the image, but simply read it and save it as
JPEG then everything is okay! What's more, when I scale it but change
the output format to PNG the image looks good as well!

Here's the code:

BufferedImage image = ImageIO.read (new File (inputImagePath));
AffineTransform tx = new AffineTransform();
tx.scale (x, y);
AffineTransformOp op = new AffineTransformOp(tx,

AffineTransformOp.TYPE_BILINEAR);
BufferedImage scaledImage = op.filter(image,

op.createCompatibleDestImage(image,
ColorModel.getRGBdefault()));
ImageIO.write(image, "jpeg", imageFile);

I've also simply tried:

BufferedImage scaledImage = op.filter(image, null);


The debug output message says:

System.out.println ("Color model: " +
scaledImage.getColorModel().toString());

"Color model: DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff
amask=ff000000"

That should indicate that the transformation calculates in RGB space,
no?


I've tried several input images (jpg, png) and also created a test
input jpg images which I'm sure is stored in RGB format (not CMYK). The
original (unscaled) images are also displayed fine in some JLabel...

Do I have to explicitly set some JPEG encoder paramters as to enforce
the proper color model? What's going wrong?

I'm using the jdk1.5.0_04 on Windows XP


Thanks, Oliver
 
O

Oliver

Oliver said:
Hi,

...
Problem: the resulting JPEG file has wrong colours, red goes to light
blue, white goes to reddish, light blue goes to green etc. (note: the
colors are _not_ just inverted, they look like CMYK colors, but
displayed as RGB values (?)).

Here's a complete example program - I still get wrong colors when
saving to JPEG (but only after the scale transformation):

package net.tillart.imagescaler;

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

public class Main {

public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: app [input] [output]");
return;
}

try {
BufferedImage image = ImageIO.read (new File(args[0]));
AffineTransform xform = new AffineTransform();
xform.scale(0.5, 0.5);
AffineTransformOp op = new AffineTransformOp (xform,
AffineTransformOp.TYPE_BILINEAR);
BufferedImage out = op.filter(image, null);
ImageIO.write(out, "jpeg", new File(args[1]));

}
catch (Exception ex) {
ex.printStackTrace();
}

}

}

So how to I get the colors right when saving to JPEG? Do I have to take
care of color space issues during the transformation? And I thought it
would be that simple... *sigh*

This time on another computer with WinXP, JDK 1.5.0_1

Any hints very appreciated! Oliver
 
O

Oliver

Interesting: displaying the scaled image with something like this
works:

class ViewComponent extends JComponent
{
private Image image;

public void setImage (Image newImage) {
image = newImage;
}

protected void paintComponent( Graphics g )
{
if ( image != null )
g.drawImage( image, 0, 0, this );
}
...
}

where I set the BufferedImage (which just has been scaled) with
ViewComponent.setImage(). Colors are correct then on the screen:

BufferedImage out = op.filter(...);
viewComponent.setImage(out); // image shows okay!

Ohhh...! And even better: when I load the just saved and scaled JPG
image and display this one instead, it _also_ shows correctly!

viewComponent.setImage(ImageIO.read (new File ("out.jpg"))); // also
okay!

In other words: the JPEG decoder/encoder (provided with ImageIO) is
able to write/read the image and displaying it in a Java JComponent (or
wherever) works fine, but when loading the JPEG image with another
viewer (I use The GIMP and the Windows Explorer) the colors are wrong!

Do I need to convert the scaled image to an RGB image first before
saving it to JPEG, so external decoders show the image correctly?

I don't get it...

Any help still appreciated (I don't really want yet to use another
toolkit as JAI, JIMI or whatever...)

Cheers, Oliver
 
O

Oliver

One last thing: After scaling the "BufferedImage out" the following

System.out.println("Transformed image " + out.toString() + " written
to: " + args[1]);

yields:

Transformed image BufferedImage@17d5d2a: type = 2 DirectColorModel:
rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000
IntegerInterleavedRaster: width = 200 height = 150 #Bands = 4 xOff = 0
yOff = 0 dataOffset[0] 0 written to: output.jpg

Anything suspicious in here? Again, when I _don't_ scale the input
image and save directly the JPEG looks okay in external JPEG viewers.

Cheers, Oliver
 
O

Oliver Wong

The only way I was able to fix your problem was to set the transform
type to "NEAREST_NEIGHBOR" instead of "BILINEAR". Note though that the
transformed image isn't scaled by 0.5,0.5. It looks like more than 0.8,0.8
which makes me suspect the classes are very buggy (or completely
misunderstood by us). I also added some code to output the ColorModels of
the input and output image. Note that the color model changes when you use
any type other than NEAREST_NEIGHBOR.

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Main {

public static void main(String[] args) throws IOException {
if (args.length < 2) {
System.out.println("Usage: app [input] [output]");
return;
}

BufferedImage image = ImageIO.read(new File(args[0]));
AffineTransform xform = new AffineTransform();
xform.scale(0.5, 0.5);
AffineTransformOp op = new AffineTransformOp(xform,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
BufferedImage out = op.createCompatibleDestImage(image,
image.getColorModel());
out = op.filter(image, null);
ImageIO.write(out, "jpeg", new File(args[1]));
System.out.println(image.getColorModel().toString());
System.out.println(out.getColorModel().toString());
}

}
 
R

Roedy Green

Anything suspicious in here? Again, when I _don't_ scale the input
image and save directly the JPEG looks okay in external JPEG viewers.

Viewers or viewer. Try looking at it with some browsers, paint
programs etc. The problem may be with ambiguities in the JPG
standard. JPG is not an easy format to peek at with a hex viewer, but
at some point that is what you may have to do to find out what Java is
doing. That still won't help you, unless you are prepared to patch
the images afterward to make a correction.
At least you can make an impressing bug report.

see http://mindprod.com/jgloss/bugs.html
 
O

Oliver

Thanks for replying: well with "viewers" I really meant "Windows Image
Viewer", "The Gimp" (paint program), "KView" etc.

But I've solved the problem somewhat in the meantime: in short: the
problem is that the affine transformation creates a new BufferedImage
which contains an alpha channel, but ONLY if you use
AffineTransformOp.TYPE_BILINEAR or _CUBIC.

The ImageIO JPEG encoder (as in ImageIO.write (out, "JPEG", filePath))
somehow stores this alpha channel together with the RGB values - yes I
know, JPEG doesn't support alpha channels/transparency, and I guess
it's exactly this which confuses other "viewers". Note though that when
reading in this JPEG again and displaying it e.g. in a JComponent the
image looks fine, in other words: within "the Java world" the JPEG file
is handled correctly.

So if I want to be able to see this JPEG e.g. in a browser there are
(at least) two possiblities which I have found out:

a) use TYPE_NEAREST_NEIGHBOR when transforming. Disadvantage: this
looks not as nice when scaling up instead of down (or rotating,
shearing)

b) create a 2nd BufferedImage with the proper RGB setup (without alpha
channel) like this:

// 'out': the result of the affine transformation:
BufferedImage out2 = new BufferedImage (out.getWidth(),
out.getHeight(), BufferedImage.TYPE_INT_RGB);

Then draw the result 'out' of the transformation into this newly
created image 'out2':

Graphics2D g = out2.createGraphics();
g.drawRenderedImage(out, null);

(Note that the 2nd parameter - the optional AffineTransformation - is
null. In the case of scaling we could easily pass the
AffineTransformation in here instead and paint the original image
directly, because it's easy to predict the resulting image size here
which we need when allocating the BufferedImage 'out2'. When rotating
or shearing it's not that easy anymore to calculate the exact resulting
size, hence "first transform, then paint again to proper format").

This 'out2' has the proper image format and the JPEG writing produces a
"good JPEG".

I also tried to get rid of the alpha channel while saving to JPEG by
explicitly setting the ImageWriteParam and querying a suitable JPEG
writer:

iwparam.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));

But I didn't succeed with that and finally gave up. I have a working
solution now (using a 2nd buffer and an image draw) and it's alright.
Any better solution very welcome :)

Details can be found here:
http://forum.java.sun.com/thread.jspa?threadID=665585&tstart=0
 
O

Oliver

Hi Oliver ;)

Thanks for replying! I have tried a LOT; in the meantime I've been also
told the "solution" with using TYPE_NEAREST_NEIGHBOR.

I found out that any other TYPE_* adds an alpha channel to the
resulting BufferedImage,and when writing this to JPEG (which obviously
somehow encodes this alpha value as well into the resulting JPEG
stream) this confuses external viewers (The GIMP, browsers, Windows
Image Viewer...). Java displays the JPEG properly though, that is the
internal JPEG decoder handles the data properly.

See my other post in this thread for the solutions I've found and a
more detailed discussion here:

http://forum.java.sun.com/thread.jspa?threadID=665585&tstart=0
 
O

Oliver

The problem really seems to be the JPEG encoder which encodes the alpha
value if there is one. I really don't know whether this is a bug or not
- after all, the stored JPEG can be succesfully re-read and displayed -
but only within the Java application e.g. by painting it on a
JComponent.

This is a minimal example which demonstrates the problem. You need a
PNG _with alpha channel_ as input to reproduce:


import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Main {

public static void main(String[] args) throws IOException {
if (args.length < 2) {
System.out.println("Usage: app [input] [output]");
return;
}

BufferedImage image = ImageIO.read(new File(args[0]));
ImageIO.write(image, "jpeg", new File(args[1]));
System.out.println("Image: " + image.toString());

// re-read the created image
BufferedImage result = ImageIO.read(new File(args[1]));
System.out.println("Result: " + result.toString());
}
}

With a PNG with alpha image as input I get the following:

Image: BufferedImage@dbe178: type = 0 ColorModel: #pixelBits = 32
numComponents = 4 color space = java.awt.color.ICC_ColorSpace@1af9e22
transparency = 3 has alpha = true isAlphaPre = false
ByteInterleavedRaster: width = 400 height = 300 #numDataElements 4
dataOff[0] = 0

Result: BufferedImage@14ed9ff: type = 0 DirectColorModel:
rmask=ff000000 gmask=ff0000 bmask=ff00 amask=ff
IntegerInterleavedRaster: width = 400 height = 300 #Bands = 4 xOff = 0
yOff = 0 dataOffset[0] 0



Note in the resulting image: Bands = 4 which indicates that there
really is an alpha channel (or whatever is written in there) in the
JPEG stream (even though JPEG only supports RGB)!



With another JPEG I get this:

Image: BufferedImage@1af9e22: type = 5 ColorModel: #pixelBits = 24
numComponents = 3 color space = java.awt.color.ICC_ColorSpace@b6ece5
transparency = 1 has alpha = false isAlphaPre = false
ByteInterleavedRaster: width = 400 height = 300 #numDataElements 3
dataOff[0] = 2

Result: BufferedImage@18eb9e6: type = 5 ColorModel: #pixelBits = 24
numComponents = 3 color space = java.awt.color.ICC_ColorSpace@b6ece5
transparency = 1 has alpha = false isAlphaPre = false
ByteInterleavedRaster: width = 400 height = 300 #numDataElements 3
dataOff[0] = 2



With a PNG _without_ alpha I get this:

Image: BufferedImage@dbe178: type = 0 ColorModel: #pixelBits = 24
numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1af9e22
transparency = 1 has alpha = false isAlphaPre = false
ByteInterleavedRaster: width = 400 height = 300 #numDataElements 3
dataOff[0] = 0

Result: BufferedImage@14ed9ff: type = 5 ColorModel: #pixelBits = 24
numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1af9e22
transparency = 1 has alpha = false isAlphaPre = false
ByteInterleavedRaster: width = 400 height = 300 #numDataElements 3
dataOff[0] = 2



In both the two last cases the resulting image (type = 5, has alpha =
false etc.) looks good in external viewers.


Question: how do I enforce the JPEG writer to ignore the alpha channel
and produce the proper image type? I tried with:


ImageWriter writer = ...;
ImageWriteParam iwparam = writer.getDefaultWriteParam();
iwparam.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));

but the resulting image type was still type = 0... (= bad looking in
viewers).


Thanks, Oliver
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,150
Latest member
MakersCBDReviews
Top