在 Java 中调整索引图像的大小而不丢失透明度

发布于 2024-11-13 09:43:05 字数 2380 浏览 4 评论 0原文

这是我调整图像大小的功能。 质量虽然不是photoshop的,但也可以接受。

不可接受的是索引 png 上的行为。
我们预计,如果我们缩小具有透明索引的 256 色调色板的图像,我们将得到具有相同透明度的调整大小的图像,但事实并非如此。

因此,我们对新的 ARGB 图像进行了调整大小,然后将其减少到 256 种颜色。问题是如何“重新引入”透明像素索引。

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;

}

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;

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

皇甫轩 2024-11-20 09:43:05

尝试更改

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

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

即使这对您没有任何帮助(如果调整大小,无论出于何种原因,改变了索引的特定颜色值,则可能不会有帮助),事实上您可以创建一个新的 BufferedImage 给定的 IndexColorModel 可能对您非常有用。

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

编辑:刚刚注意到您的 resizedImage 构造函数可能应该使用 BufferedImage.TYPE_INT_ARGB 而不是 BufferedImage.TRANSLUCENT代码>.不确定这是否会改变它的工作方式,但 BufferedImage.TRANSLUCENT 不应该传递给该形式的构造函数。 http://download.oracle.com/javase/1,5.0/docs/api/java/awt/image/BufferedImage.html#BufferedImage%28int,%20int,%20int%29

无论如何,也许尝试这样的事情:

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));

不过,不知道这是否真的有效。

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.

满栀 2024-11-20 09:43:05

具有透明度的索引图像是一种黑客手段。它们仅在某些条件下起作用,而调整大小不是其中之一。

具有透明度的图像不仅仅具有完全不透明和完全透明的像素。特别是在不规则形状的边界处,存在许多具有部分透明度的像素。如果将其保存为具有索引颜色的格式,其中单一颜色用于透明像素,则必须决定背景的颜色。然后,所有具有部分透明度的像素在其颜色和背景颜色之间混合(根据其透明度)并变得完全不透明。只有完全透明的像素才被分配透明伪色。

如果这样的图像在不同颜色的背景下显示,丑陋的边框就会变得明显。这是透明度处理不当造成的。

调整图像大小时,会引入更多伪影。新像素的颜色通常是由几个相邻像素混合而成的。如果有些是透明的,有些是不透明的,则结果是部分透明的像素。保存时,部分透明的像素将与背景颜色混合并变得不透明。因此,不透明区域(以及相关的伪像)会随着每次调整大小(或大多数其他图像操作)而增长。

无论您使用什么编程语言或图形库,伪影都会增加,结果也会变得更糟。我建议您使用 ARGB 缓冲区并将图像保存为非索引 PNG 文件。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文