Rescaling image turns it blue?

B

Ben Phillips

My code does essentially this:

File inFile = new File("C:\\path\\to\\a\\jpeg\\file.jpg");
File outFile = new File("C:\\path\\to\\a\\jpeg\\fileNew.jpg");
ImageReader reader =
ImageIO.getImageReadersByMIMEType("image/jpeg").next();
reader.setInput(ImageIO.createImageInputStream(inFile));
BufferedImage image = reader.read(0);
AffineTransform scaleTransform = new AffineTransform();
scaleTransform.scale(0.1, 0.1);
AffineTransformOp scaleOp = new AffineTransformOp(scaleTransform,
AffineTransformOp.TYPE_BICUBIC);
BufferedImage result = scaleOp.filter(image, null);
ImageIO.write(result, "jpeg", outFile);


The output file is generated, and has the appropriate dimensions, but it
has turned blue! It looks like the red and green channels are somehow
getting dropped.

What gives?
 
J

John B. Matthews

Ben Phillips said:
My code does essentially this: [...]
ImageReader reader =
ImageIO.getImageReadersByMIMEType("image/jpeg").next();

Are there other ImageReaders for that MIME type?

[...]
The output file is generated, and has the appropriate dimensions, but
it has turned blue! It looks like the red and green channels are
somehow getting dropped.

What gives?

Bad .jpg, maybe? I am unable to reproduce this:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;

public class ScaleImage extends JPanel {

BufferedImage original;
BufferedImage scaled;

public ScaleImage() {
try {
original = ImageIO.read(new File("image.jpg"));

AffineTransform scaleTransform = new AffineTransform();
scaleTransform.scale(1.25, 1.25);
AffineTransformOp scaleOp = new AffineTransformOp(
scaleTransform, AffineTransformOp.TYPE_BICUBIC);
scaled = scaleOp.filter(original, null);

setPreferredSize(new Dimension(
scaled.getWidth(),scaled.getHeight()));

} catch (IOException ioe) {
ioe.printStackTrace();
}
}

public void paintComponent(Graphics g) {
g.drawImage(scaled, 0, 0, null);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new ScaleImage(), BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
}
 
K

Knute Johnson

Ben said:
My code does essentially this:

File inFile = new File("C:\\path\\to\\a\\jpeg\\file.jpg");
File outFile = new File("C:\\path\\to\\a\\jpeg\\fileNew.jpg");
ImageReader reader =
ImageIO.getImageReadersByMIMEType("image/jpeg").next();
reader.setInput(ImageIO.createImageInputStream(inFile));
BufferedImage image = reader.read(0);
AffineTransform scaleTransform = new AffineTransform();
scaleTransform.scale(0.1, 0.1);
AffineTransformOp scaleOp = new AffineTransformOp(scaleTransform,
AffineTransformOp.TYPE_BICUBIC);
BufferedImage result = scaleOp.filter(image, null);
ImageIO.write(result, "jpeg", outFile);


The output file is generated, and has the appropriate dimensions, but it
has turned blue! It looks like the red and green channels are somehow
getting dropped.

What gives?

I've seen that before. I don't know for sure what causes it. Sorry I
can't be more help, I was not able to reproduce it reliably.

It may have something to do with the type of image you get from the JPEG
file or a ColorSpace problem but I can't be sure.
 
B

Ben Phillips

John said:
Ben Phillips said:
My code does essentially this: [...]
ImageReader reader =
ImageIO.getImageReadersByMIMEType("image/jpeg").next();

Are there other ImageReaders for that MIME type?

[...]
The output file is generated, and has the appropriate dimensions, but
it has turned blue! It looks like the red and green channels are
somehow getting dropped.

What gives?

Bad .jpg, maybe? I am unable to reproduce this:

original = ImageIO.read(new File("image.jpg"));

Did you try it going explicitly through ImageInputStream and ImageReader?
 
B

Ben Phillips

Knute said:
I've seen that before. I don't know for sure what causes it. Sorry I
can't be more help, I was not able to reproduce it reliably.

It may have something to do with the type of image you get from the JPEG
file or a ColorSpace problem but I can't be sure.

How did you fix it when it did occur?

If it is a color space problem, why isn't it correctly setting the same
color space on the output image? The documentation for
AffineTransformOp.filter(src, dst) clearly says that if dst is null it
creates a new BufferedImage of the appropriate dimensions to just hold
the transformed image and with the same color space as the source image.
 
J

John B. Matthews

Ben Phillips said:
John said:
Ben Phillips said:
My code does essentially this: [...]
ImageReader reader =
ImageIO.getImageReadersByMIMEType("image/jpeg").next();

Are there other ImageReaders for that MIME type?

[...]
The output file is generated, and has the appropriate dimensions, but
it has turned blue! It looks like the red and green channels are
somehow getting dropped.

What gives?

Bad .jpg, maybe? I am unable to reproduce this:

original = ImageIO.read(new File("image.jpg"));

Did you try it going explicitly through ImageInputStream and ImageReader?

Yes, I was curious if getImageReadersByMIMEType("image/jpeg") had more
than one "hit." When that wasn't fruitful, I fell back. More than one on
your platform?

I wonder if the reader isn't done when the filter is applied. Perhaps an
IIOReadProgressListener would help. I tested with ~100KB image; I'm
guessing yours is larger.
 
B

Ben Phillips

John said:
I wonder if the reader isn't done when the filter is applied. Perhaps an
IIOReadProgressListener would help. I tested with ~100KB image; I'm
guessing yours is larger.

Yes, by a whopping 9KB. :)

What happens if you use your code, above, with ImageReader instead of
just ImageIO.read()?
 
R

Roland de Ruiter

How did you fix it when it did occur?

If it is a color space problem, why isn't it correctly setting the same
color space on the output image? The documentation for
AffineTransformOp.filter(src, dst) clearly says that if dst is null it
creates a new BufferedImage of the appropriate dimensions to just hold
the transformed image and with the same color space as the source image.

I've seen it too (my image got pink...).

It seems to happen with TYPE_BICUBIC and TYPE_BILINEAR interpolation
types, but not with TYPE_NEAREST_NEIGHBOR.

If the second parameter of the filter method is null, the
AffineTransformOp creates a compatible image from the source image
(using its createCompatibleDestImage method). Somehow the resulting
color model or transparency don't make sense for a JPEG image. Don't
know if it is a bug or if it is intended behavior.

You could, of course, create your own destination image (calculating the
new size) and pass that as second parameter to the filter method. In my
tests this solved the problem.


BufferedImage image = ImageIO.read(inFile);

final double scaleX = 0.1;
final double scaleY = 0.1;

AffineTransform scaleTransform = new AffineTransform();
scaleTransform.scale(scaleX, scaleY);

AffineTransformOp scaleOp = new AffineTransformOp(scaleTransform,
AffineTransformOp.TYPE_BICUBIC
// or: AffineTransformOp.TYPE_NEAREST_NEIGHBOR
// or: AffineTransformOp.TYPE_BILINEAR
);

int newWidth;
int newHeight;


// EITHER calculate new dimensions yourself.
// newWidth = (int) (scaleX * image.getWidth());
// newHeight = (int) (scaleY * image.getHeight());


// OR have the AffineTransformOp calculate them for you.
//
// This is what AffineTransformOp would do for
// anAffineTransformOp.filter(image, null);
//
// This is VERY useful if the transformation isn't simply
// scaling the image, but also includes a rotation or a shear
//
Rectangle newBounds = scaleOp.getBounds2D(image).getBounds();
newWidth = newBounds.x + newBounds.width;
newHeight = newBounds.y + newBounds.height;

// Creates new image of new size and the original image's type
BufferedImage dst = new BufferedImage(newWidth, newHeight, image
.getType());

BufferedImage result = scaleOp.filter(image, dst);

ImageIO.write(result, "jpeg", outFile);
 
B

Ben Phillips

Roland said:
I've seen it too (my image got pink...).

It seems to happen with TYPE_BICUBIC and TYPE_BILINEAR interpolation
types, but not with TYPE_NEAREST_NEIGHBOR.

If the second parameter of the filter method is null, the
AffineTransformOp creates a compatible image from the source image
(using its createCompatibleDestImage method). Somehow the resulting
color model or transparency don't make sense for a JPEG image. Don't
know if it is a bug or if it is intended behavior.

You could, of course, create your own destination image (calculating the
new size) and pass that as second parameter to the filter method. In my
tests this solved the problem.

That's what I had originally, but I got an ImagingOpException. Output
was a new BufferedImage with TYPE_INT_RGB and the calculated dimensions.
Unfortunately the docs are not very clear on why this exception might
get thrown.

I got something useful in the way of new information though.

First, if the BufferedImage is used to make an ImageIcon and displayed
through Swing or the AWT, it looks fine. It just b0rks if output with
ImageIO.write.

Second, this is the output from System.out.println(src.getColorModel())
followed by System.out.println(dst.getColorModel()):

ColorModel: #pixelBits = 24 numComponents = 3 color space =
java.awt.color.ICC_ColorSpace@1c4795e transparency = 1 has alpha = false
isAlphaPre = false
DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000

They're not the same! The docs apparently lied.

This is with TYPE_BICUBIC, by the way.

Both have a colorspace type of 5, so you'd THINK they'd be compatible
anyway...

I have solved all of my other problems except for this one and the combo
box problem (setSelectedItem does not work, getEditor().setItem neither...)

The frame corner getting centered problem was simple -- moving the
setLocationRelativeTo() after the pack() fixed it. It must have a size
of zero until it's packed, making it not work properly otherwise.
 
B

Ben Phillips

Ben said:
That's what I had originally, but I got an ImagingOpException. Output
was a new BufferedImage with TYPE_INT_RGB and the calculated dimensions.
Unfortunately the docs are not very clear on why this exception might
get thrown.

Fixed, but not very cleanly, as the comment suggests:

AffineTransformOp scaleOp1 = new AffineTransformOp(scaleTransform,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
AffineTransformOp scaleOp2 = new AffineTransformOp(scaleTransform,
AffineTransformOp.TYPE_BICUBIC);
BufferedImage result = scaleOp2.filter(image, scaleOp1.filter(image,
null)); // MEGA-HACK

It seems that while

new BufferedImage(scaledWidth, scaledHeight, TYPE_INT_RGB)

was not acceptable as scaleOp2.filter's right hand argument, the output
of scaleOp1 was.

I wonder how much bloody CPU time this will waste? Nearest neighbor
won't seem very fast when added over 100,000 images ranging up to the
ten-megapixel range...

Oh well. At least it works.
 
J

John B. Matthews

Ben Phillips said:
Yes, by a whopping 9KB. :)

What happens if you use your code, above, with ImageReader instead of
just ImageIO.read()?

Yep, cut and pasted from yours. I also tried some ~1MB images scaled up
and down: normal, if slower, results. I saw your "mega-hack;" is this an
implementation issue? I'm on Mac OS X 10.4.11 (PPC), Java version
1.5.0_16.
 
R

Roland de Ruiter

Roland said:
Knute Johnson wrote:
I've seen that before. I don't know for sure what causes it. Sorry
I can't be more help, I was not able to reproduce it reliably.

It may have something to do with the type of image you get from the
JPEG file or a ColorSpace problem but I can't be sure.

How did you fix it when it did occur?

If it is a color space problem, why isn't it correctly setting the
same color space on the output image? The documentation for
AffineTransformOp.filter(src, dst) clearly says that if dst is null
it creates a new BufferedImage of the appropriate dimensions to just
hold the transformed image and with the same color space as the
source image.
[...]
You could, of course, create your own destination image (calculating
the new size) and pass that as second parameter to the filter method.
In my tests this solved the problem.

That's what I had originally, but I got an ImagingOpException. Output
was a new BufferedImage with TYPE_INT_RGB and the calculated dimensions.

Instead of TYPE_INT_RGB, have you tried creating a BufferedImage of the
same type as the type of the source image?

BufferedImage dst =
new BufferedImage(newWidth, newHeight, src.getType());
 
J

John B. Matthews

Roland de Ruiter said:
On 8-10-2008 15:00, Ben Phillips wrote: [...]
That's what I had originally, but I got an ImagingOpException.
Output was a new BufferedImage with TYPE_INT_RGB and the calculated
dimensions.

Instead of TYPE_INT_RGB, have you tried creating a BufferedImage of
the same type as the type of the source image?

BufferedImage dst =
new BufferedImage(newWidth, newHeight, src.getType());

Ah, thank you, Roland. I wondered how to get the type. The
AffineTransform can be used to get the new width and height. Does this
work on your implementation, Ben?

<code>
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;

public class ScaleImage extends JPanel {

BufferedImage image;
BufferedImage scaled;

public ScaleImage() {
try {
image = ImageIO.read(new File("image.jpg"));

AffineTransform scaleTransform = new AffineTransform();
scaleTransform.scale(1.25, 1.25);
AffineTransformOp scaleOp = new AffineTransformOp(
scaleTransform, AffineTransformOp.TYPE_BICUBIC);
Point2D p = new Point2D.Double();
p.setLocation(image.getWidth(), image.getHeight());
p = scaleTransform.transform(p, p);
scaled = new BufferedImage(
(int) p.getX(), (int) p.getY(), image.getType());
scaled = scaleOp.filter(image, scaled);

setPreferredSize(new Dimension(
scaled.getWidth(), scaled.getHeight()));

} catch (IOException ioe) {
ioe.printStackTrace();
}
}

public void paintComponent(Graphics g) {
g.drawImage(scaled, 0, 0, null);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new ScaleImage(), BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
}
 
B

Ben Phillips

John said:
Roland de Ruiter said:
On 8-10-2008 15:00, Ben Phillips wrote: [...]
That's what I had originally, but I got an ImagingOpException.
Output was a new BufferedImage with TYPE_INT_RGB and the calculated
dimensions.
Instead of TYPE_INT_RGB, have you tried creating a BufferedImage of
the same type as the type of the source image?

BufferedImage dst =
new BufferedImage(newWidth, newHeight, src.getType());

Ah, thank you, Roland. I wondered how to get the type. The
AffineTransform can be used to get the new width and height. Does this
work on your implementation, Ben?

Nope. ImagingOpException.

This is very strange. I also tried bumping the width and height by one
in case the rounding down of the (int) cast made it slightly too small.

At least the mega-hack works, even if it is inefficient.
 

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

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,234
Latest member
SkyeWeems

Latest Threads

Top