In java, how do you write a java.awt.image.BufferedImage to an 8-bit png file?

10,988

Solution 1

The build in imageio png writer will write 32bit png files on all the platforms I have used it on, no matter what the source image is. You should also be aware that many people have complained that the resulting compression is much lower than what is possible with the png format. There are several independent png libraries available that allow you to specify the exact format, but I don't actually have any experience with any of them.

Solution 2

It is an interesting question... It is late, I will experiment tomorrow. I will first try and use a BufferedImage.TYPE_BYTE_INDEXED (perhaps after drawing) to see if Java is smart enough to generate an 8bit PNG.
Or perhaps some image library can allow that.

[EDIT] Some years later... Actually, I made the code at the time, but forgot to update this thread... I used the code pointed at by Kat, with a little refinement on the handling of transparency, and saving in PNG format instead of Gif format. It works in making a 8-bit PNG file with all-or-nothing transparency.

You can find a working test file at http://bazaar.launchpad.net/~philho/+junk/Java/view/head:/Tests/src/org/philhosoft/tests/image/AddTransparency.java using my ImageUtil class.

Since the code isn't that big, for posterity sake, I post it here, without the JavaDoc to save some lines.

public class ImageUtil
{
  public static int ALPHA_BIT_MASK = 0xFF000000;

  public static BufferedImage imageToBufferedImage(Image image, int width, int height)
  {
    return imageToBufferedImage(image, width, height, BufferedImage.TYPE_INT_ARGB);
  }

  public static BufferedImage imageToBufferedImage(Image image, int width, int height, int type)
  {
    BufferedImage dest = new BufferedImage(width, height, type);
    Graphics2D g2 = dest.createGraphics();
    g2.drawImage(image, 0, 0, null);
    g2.dispose();
    return dest;
  }

  public static BufferedImage convertRGBAToIndexed(BufferedImage srcImage)
  {
    // Create a non-transparent palletized image
    Image flattenedImage = transformTransparencyToMagenta(srcImage);
    BufferedImage flatImage = imageToBufferedImage(flattenedImage,
        srcImage.getWidth(), srcImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
    BufferedImage destImage = makeColorTransparent(flatImage, 0, 0);
    return destImage;
  }

  private static Image transformTransparencyToMagenta(BufferedImage image)
  {
    ImageFilter filter = new RGBImageFilter()
    {
      @Override
      public final int filterRGB(int x, int y, int rgb)
      {
        int pixelValue = 0;
        int opacity = (rgb & ALPHA_BIT_MASK) >>> 24;
        if (opacity < 128)
        {
          // Quite transparent: replace color with transparent magenta
          // (traditional color for binary transparency)
          pixelValue = 0x00FF00FF;
        }
        else
        {
          // Quite opaque: get pure color
          pixelValue = (rgb & 0xFFFFFF) | ALPHA_BIT_MASK;
        }
        return pixelValue;
      }
    };

    ImageProducer ip = new FilteredImageSource(image.getSource(), filter);
      return Toolkit.getDefaultToolkit().createImage(ip);
  }

  public static BufferedImage makeColorTransparent(BufferedImage image, int x, int y)
  {
    ColorModel cm = image.getColorModel();
    if (!(cm instanceof IndexColorModel))
      return image; // No transparency added as we don't have an indexed image

    IndexColorModel originalICM = (IndexColorModel) cm;
    WritableRaster raster = image.getRaster();
    int colorIndex = raster.getSample(x, y, 0); // colorIndex is an offset in the palette of the ICM'
    // Number of indexed colors
    int size = originalICM.getMapSize();
    byte[] reds = new byte[size];
    byte[] greens = new byte[size];
    byte[] blues = new byte[size];
    originalICM.getReds(reds);
    originalICM.getGreens(greens);
    originalICM.getBlues(blues);
    IndexColorModel newICM = new IndexColorModel(8, size, reds, greens, blues, colorIndex);
    return new BufferedImage(newICM, raster, image.isAlphaPremultiplied(), null);
  }
}

Solution 3

I found the answer as to how to convert RGBA to Indexed here: http://www.eichberger.de/2007/07/transparent-gifs-in-java.html

However, the resulting 8-bit png file only has 100% or 0% transparency. You could probably tweak the IndexColorModel arrays, but we have decided to make the generated file (what was an overlay mask) into an underlay jpg and use what was the static base as the transparent overlay.

Share:
10,988
Admin
Author by

Admin

Updated on June 21, 2022

Comments

  • Admin
    Admin almost 2 years

    I am trying to write out a png file from a java.awt.image.BufferedImage. Everything works fine but the resulting png is a 32-bit file.

    Is there a way to make the png file be 8-bit? The image is grayscale, but I do need transparency as this is an overlay image. I am using java 6, and I would prefer to return an OutputStream so that I can have the calling class deal with writing out the file to disk/db.

    Here is the relevant portion of the code:

     public static ByteArrayOutputStream createImage(InputStream originalStream)
                throws IOException {
    
            ByteArrayOutputStream oStream = null;
    
            java.awt.Image newImg = javax.imageio.ImageIO.read(originalStream);
            int imgWidth = newImg.getWidth(null);
            int imgHeight = newImg.getHeight(null);
            java.awt.image.BufferedImage bim = new java.awt.image.BufferedImage(imgWidth,
                    imgHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
    
            Color bckgrndColor = new Color(0x80, 0x80, 0x80);
    
            Graphics2D gf = (Graphics2D)bim.getGraphics();
    
            // set transparency for fill image
            gf.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
            gf.setColor(bckgrndColor);
            gf.fillRect(0, 0, imgWidth, imgHeight);
    
            oStream = new ByteArrayOutputStream();
            javax.imageio.ImageIO.write(bim, "png", oStream);
            oStream.close();
    
            return oStream;
        }
    
  • Jason Coco
    Jason Coco over 15 years
    Instead of tacking on answers to your post, you can actually add information to your original question by clicking edit. Users can see what's been changed and it doesn't just keep adding onto the answers column.
  • mjaggard
    mjaggard over 11 years
    4 years later, still waiting for this to be an answer rather than another question.
  • PhiLho
    PhiLho over 11 years
    I don't see a question on my response, although honestly I don't see an answer either (hey, I was new on SO at the time...). Anyway, despite the tone, thanks for the heads up.
  • mjaggard
    mjaggard over 11 years
    Sorry for the tone, it wasn't supposed to sound aggressive. SO asks you to specify why you're down-voting so I was just answering that question.