4

This is my function to resize images. The quality is not photoshop but it's acceptable.

What's not acceptable is the behaviour on indexed png.
We expect that if we scale down an image with a 256 colors palette with a transparent index we would get a resized image with same transparency, but this it not the case.

So we did the resize on a new ARGB image and then we reduce it to 256 colors. The problem is how to "reintroduce" the transparent pixel index.

private static BufferedImage internalResize(BufferedImage source, int destWidth, int destHeight) {
    int sourceWidth = source.getWidth();
    int sourceHeight = source.getHeight();
    double xScale = ((double) destWidth) / (double) sourceWidth;
    double yScale = ((double) destHeight) / (double) sourceHeight;
    Graphics2D g2d = null;

    BufferedImage resizedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TRANSLUCENT);

    log.debug("resizing image to  w:" + destWidth + " h:" + destHeight);
    try {

        g2d = resizedImage.createGraphics();

        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

        AffineTransform at = AffineTransform.getScaleInstance(xScale, yScale);

        g2d.drawRenderedImage(source, at);

    } finally {
        if (g2d != null)
            g2d.dispose();
    }

//doesn't keep the transparency
    if (source.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
        log.debug("reducing to color-indexed image");

        BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED);

        try {
            Graphics g = indexedImage.createGraphics();
            g.drawImage(resizedImage, 0, 0, null);
        } finally {
            if (g != null)
                g.dispose();
        }
        System.err.println("source" + ((IndexColorModel) source.getColorModel()).getTransparentPixel()
                         + "   " + ((IndexColorModel) indexedImage.getColorModel()).getTransparentPixel());

        return indexedImage;
    }

    return resizedImage;

}
Uberto
  • 2,712
  • 3
  • 25
  • 27

2 Answers2

5

Try changing

BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED);

to

    BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) source.getColorModel());

Even if that specifically doesn't help you (which it might not if the resizing, for whatever reason, changes what specific color values are indexed), the fact that you can create a new BufferedImage with a given IndexColorModel will probably be quite useful for you.

http://download.oracle.com/javase/6/docs/api/java/awt/image/BufferedImage.html#BufferedImage%28int,%20int,%20int,%20java.awt.image.IndexColorModel%29

EDIT: Just noticed that your resizedImage constructor should probably use BufferedImage.TYPE_INT_ARGB rather than BufferedImage.TRANSLUCENT. Not sure if that will change how it works, but BufferedImage.TRANSLUCENT isn't supposed to be passed to that form of the constructor. http://download.oracle.com/javase/1,5.0/docs/api/java/awt/image/BufferedImage.html#BufferedImage%28int,%20int,%20int%29

Anyway, maybe try something like this:

DirectColorModel resizedModel = (DirectColorModel) resizedImage.getColorModel();
int numPixels = resizedImage.getWidth() * resizedImage.getHeight();

byte[numPixels] reds;
byte[numPixels] blues;
byte[numPixels] greens;
byte[numPixels] alphas;
int curIndex = 0;
int curPixel;

for (int i = 0; i < resizedImage.getWidth(); i++)
{
    for (int j = 0; j < resizedImage.getHeight(); j++)
    {
        curPixel = resizedImage.getRGB(i, j);
        reds[curIndex] = resizedModel.getRed(curPixel);
        blues[curIndex]= resizedModel.getBlue(curPixel);
        greens[curIndex] = resizedModel.getGreen(curPixel);
        alphas[curIndex] = resizedModel.getAlpha(curPixel);
        curIndex++;
    }
}

BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED, new IndexColorModel(resizedModel.pixel_bits, numPixels, reds, blues, greens, alphas));

Don't know if this will actually work, though.

JAB
  • 20,783
  • 6
  • 71
  • 80
  • I'd tried but that is still worse, because it use the transparent index of the original image, that's not the correct one. – Uberto Jun 03 '11 at 16:18
  • well, probably the correct solution is to create a new set of indexed colors, but I need a smarter way and I also need to mark the transparent one. – Uberto Jun 04 '11 at 08:43
  • Assuming at least one pixel remains completely transparent after the transformation, `getTransparentPixel()` should tell you the index for that. If not, then there aren't any purely transparent pixels left in the image and you've got partial transparency. I think. – JAB Jun 06 '11 at 02:20
  • @JAB, that's not true. In the final image getTransparentPixel() == -1 but the original color that should be transparent is still there. I'm speaking of a simple image with a black writing on a trasparent background. – Uberto Jun 06 '11 at 08:18
  • @Uberto: First, use `getTransparency()` to check that the indexed color model is actually being created as a transparent one (i.e. the returned value is not `Transparency.OPAQUE`). Then, if that is not the case, iterate through all pixels in the resultant image and check the alpha value of each one. If none of them have an alpha value of 0, then of course `getTransparentPixel()` will return -1, as it only returns the index of a pixel if there is at least one with alpha 0 or specified as trans pixel. If one of them DOES have an alpha value of 0, then there's an error in something somewhere. – JAB Jun 06 '11 at 15:02
  • (And, of course, if you are getting the case where the transparent background no longer remains fully transparent, then you may either want to mark a threshold of sorts and set the alpha value to 0 for those pixels whose alpha value is below that threshold, or simply leave the semitransparent pixels be and not worry about it.) – JAB Jun 06 '11 at 15:05
  • I put the answer as answered because I'm much more near now, still I need to understand how to mark an indexed color as transparent. I have to check the pixel alpha, but I thing for indexed images alpha is not used. – Uberto Jun 07 '11 at 09:12
  • @Uberto: http://download.oracle.com/javase/6/docs/api/java/awt/image/IndexColorModel.html "The colormap specifies red, green, blue, _and optional alpha_ components corresponding to each index." – JAB Jun 07 '11 at 14:26
0

Indexed images with transparency are a hack. They only work under certain conditions and resizing isn't one of them.

An image with transparency doesn't just have fully opaque and fully transparent pixels. In particular at a irregularly shaped borders, there are many pixels with partial transparency. If you save it in a format with indexed colors where a single color is used for transparent pixels, you have to decide what color the background will have. All pixels with partial transparency are then blended between their color and the background color (according to their transparency) and become fully opaque. Only the fully transparent pixel are assigned the transparent pseudo color.

If such an image is displayed against a background with differnt color, an ugly border will become apparent. It's an artifact of the inadequate transparency handling.

When you resize the image, you introduce more artifacts. The color of a new pixels is usually blended from several neighboring pixels. If some are transparent and some are opaque, the result is a partially transparent pixel. When you save it, the partially transparent pixel is blended against the background color and becomes opaque. As a result, the opaque area (and the associated artifacts) grow with each resize (or most other image manipulations).

Whatever programming language or graphics library you use, the artifacts will grow and the result will become worse. I recommend you use a ARGB buffer and save the image as a non-indexed PNG file.

Codo
  • 75,595
  • 17
  • 168
  • 206
  • well, of course there are limitations, but I'm speaking about shrinking images, not enlarging them. Shrinking 20% an image with 256 colors palette, usually result in a smaller but still acceptable image. – Uberto Jun 03 '11 at 19:40
  • Codo, apparently the Java libraries allow you to use partial transparency with certain indexed color models. I found that pretty interesting. – JAB Jun 03 '11 at 20:51