3

How can I save BufferedImage with TYPE_INT_ARGB to jpg?

Program generates me that image:

enter image description here

And it's OK, but when I save it in that way:

    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    BufferedOutputStream bos = new BufferedOutputStream(byteStream);
    try {
        ImageIO.write(buffImg, "jpg", bos);
        // argb
        byteStream.flush();
        byte[] newImage = byteStream.toByteArray();

        OutputStream out = new BufferedOutputStream(new FileOutputStream("D:\\test.jpg"));
        out.write(newImage);
        out.close();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

The result is: enter image description here

Understand that this is due to the alpha layer, but don't know how to fix it. Png format does not suit me, need jpg.

Gwalk
  • 211
  • 4
  • 13
  • Have you tried replace alpha channel with white color? – neuraminidase7 Sep 03 '14 at 19:07
  • Unrelated: Why are you writing into an byte array and then the byte into a file, when you can write into a `FileOutputStream` directly? Related: Searches with keywords like *" BufferedImage JPG wrong colors"* yield some results, it seems to happen in some situations – Marco13 Sep 03 '14 at 23:28
  • I agree. Maybe not the most optimal way. But it doesn't matter. Now sving it with ImageWriter to control image quality. – Gwalk Sep 04 '14 at 04:34

2 Answers2

6

OK! I've solved it. Everything was pretty easy. Don't know is it a good decision and how fast it is. I have not found any other. So.. everything we need is define new BufferedImage.

BufferedImage buffImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = buffImg.createGraphics();

// ... other code we need

BufferedImage img= new BufferedImage(buffImg.getWidth(), buffImg.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(buffImg, 0, 0, null);
g2d.dispose();

If there any ideas to improve this method, please, your welcome.

Gwalk
  • 211
  • 4
  • 13
  • 2
    FYI: JPEG doesn't have an alpha channel, so it's getting handled incorrectly when it is read/written. As you have demonstrated, you need convert the image to a non-alpha based result – MadProgrammer Sep 04 '14 at 03:18
  • Thanks! But is this more or less normal way to convert BufferedImage? – Gwalk Sep 04 '14 at 04:29
  • In this case, yes. This is actually a bug in the way that the `ImageIO` reader/writer handles alpha values – MadProgrammer Sep 04 '14 at 04:37
  • @Gwalk Congratulations for solving your issue yourself. As this seems a rather good solution, please, self-accept your answer to mark this question as resolved. – Sylvain Leroux Sep 04 '14 at 08:05
  • @MadProgrammer Actually, *JFIF* (JPEG Interchange Format) only specifies samples as YCbCr or Grayscale (as Y), ie. no alpha. I think Exif does the same. However, the *JPEG* *compression* itself allows compressing any data channel, including alpha. Most commonly it's written as either YCbCrA or RGBA (non-subsampled). Unfortunately, this isn't well implemented in software, and this is the reason we see the pink background weird colored image above (and the reason why it's usually better to store JPEGs without alpha). :-) – Harald K Sep 04 '14 at 08:30
  • 1
    @haraldK All I know is JPEG + alpha = bad :P - Thanks for the info though – MadProgrammer Sep 04 '14 at 09:32
0

Images having 4 color channels should not be written to a jpeg file. We can convert between ARGB and RGB images without duplicating pixel values. This comes in handy for large images. An example:

int a = 10_000;

BufferedImage im = new BufferedImage(a, a, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = im.createGraphics();
g.setColor(Color.RED);
g.fillRect(a/10, a/10, a/5, a*8/10);
g.setColor(Color.GREEN);
g.fillRect(a*4/10, a/10, a/5, a*8/10);
g.setColor(Color.BLUE);
g.fillRect(a*7/10, a/10, a/5, a*8/10);

//preserve transparency in a png file
ImageIO.write(im, "png", new File("d:/rgba.png"));

//pitfall: in a jpeg file 4 channels will be interpreted as CMYK... this is no good
ImageIO.write(im, "jpg", new File("d:/nonsense.jpg"));

//we need a 3-channel BufferedImage to write an RGB-colored jpeg file
//we can make up a new image referencing part of the existing raster
WritableRaster ras = im.getRaster().createWritableChild(0, 0, a, a, 0, 0, new int[] {0, 1, 2}); //0=r, 1=g, 2=b, 3=alpha
ColorModel cm = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB).getColorModel(); //we need a proper ColorModel
BufferedImage imRGB = new BufferedImage(cm, ras, cm.isAlphaPremultiplied(), null);

//this image we can encode to jpeg format
ImageIO.write(imRGB, "jpg", new File("d:/rgb1.jpg"));
ray_ray_ray
  • 316
  • 1
  • 14