如何在不失去透明度的情况下将图像转换为灰度?

4

我很困扰如何将一张有透明像素的彩色图像转换为灰度图。我在这个网站上搜索了相关问题,但没有找到能解决我的问题的答案。

我已定义了一个名为“convertType”的方法,具体内容如下:

/*------------------------------
attempts to convert the type of the image argument to the chosen type
for example: BufferedImage newImage= ImageUtilities.convertType(oldImage, BufferedImage.TYPE_3BYTE_BGR);
11/28/2013 WARNING-it remains to be seen how well this method will work for various type conversions
------------------------------*/
public static BufferedImage convertType (BufferedImage image,int newType){
 if (image.getType() == newType){
   return (image);
 }else {
    int w= image.getWidth();
    int h= image.getHeight ();
    BufferedImage modifiedImage = new BufferedImage( w,h,newType);
    Graphics2D g = ( Graphics2D)modifiedImage.getGraphics();
    g.drawImage( image, null, 0,0);
    g.dispose();
    return (modifiedImage);
}
}

我从一个名为“result”的TYPE_4BYTE_ABGR BufferedImage开始,然后:
 result= convertType (result, BufferedImage.TYPE_BYTE_GRAY);//transparency is lost
 result=convertType (result, BufferedImage.TYPE_INT_ARGB);//pixels will now support transparency

对于原始图像中的有颜色的不透明像素,以上步骤可以很好地运行: 例如,(32,51,81,255)->(49,49,49,255)
然而,原始图像中的透明像素被变为不透明: 例如,(0,0,0,0)->(0,0,0,255)
我理解正在发生的事情,如果我可以使用Java算法来转换灰度图像,那么我就可以解决问题。我已经下载了源代码并仔细查看,但是还没有找到算法。如果有人可以告诉我:
1. 包含灰度转换算法的类 2. 建议我完成我要做的事情的另一种方法
我将非常感激。
4个回答

6
您考虑过使用 ColorConvertOp 过滤器吗?
例如... AlphaGray
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestAlphaGrayScale {

    public static void main(String[] args) {
        new TestAlphaGrayScale();
    }

    public TestAlphaGrayScale() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage master;
        private BufferedImage gray;

        public TestPane() {
            setBackground(Color.RED);
            try {
                master = ImageIO.read(new File("C:\\hold\\thumbnails\\Miho_Alpha.png"));
                gray = new BufferedImage(master.getWidth(), master.getHeight(), BufferedImage.TYPE_INT_ARGB);

                // Automatic converstion....
                ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
                op.filter(master, gray);

                setLayout(new GridLayout(1, 2));

                add(new JLabel(new ImageIcon(master)));
                add(new JLabel(new ImageIcon(gray)));
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }
    }

}

你好, ColorConvertOp过滤器运作得非常好。我也非常感谢其他帖子中添加的评论。这对我进一步的图像处理非常有用。 非常感谢, 乔 PS-我想将MADProgrammer的回复标记为“已回答”,但我不知道如何操作!? - user3204892
1
在“投票”下面应该有一个小的“半透明”勾号。只需单击它,使其变为绿色... - MadProgrammer

6
重要提示: 如果您想要正确的灰度转换,建议使用@MadProgrammers的解决方案(ColorConvertOp)。我投了赞成票。 :-)

以下答案更多是为了完整性:

如果您真的想要一个带有CS_GRAY颜色空间和透明度的BufferedImage,可以创建一个自定义(将导致TYPE_CUSTOM)的BufferedImage。下面的代码将创建类似于“TYPE_2BYTE_GRAY_ALPHA”类型(每个样本8位,每个像素2个样本)。它将使用TYPE_INT_ARGBTYPE_4BYTE_ABGR图像一半的内存,但很可能会失去硬件加速,并且可能会错过一些Java2D绘图流程中优化的循环(这是说,我的测试显示在OS X上完全可接受的性能)。

/** Color Model usesd for raw GRAY + ALPHA */
private static final ColorModel CM_GRAY_ALPHA =
        new ComponentColorModel(
                ColorSpace.getInstance(ColorSpace.CS_GRAY),
                true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE
        );

public static BufferedImage create2ByteGrayAlphaImage(int width, int height) {
    int[] bandOffsets = new int[] {1, 0}; // gray + alpha
    int bands = bandOffsets.length; // 2, that is

    // Init data buffer of type byte
    DataBuffer buffer = new DataBufferByte(width * height * bands);

    // Wrap the data buffer in a raster
    WritableRaster raster =
            Raster.createInterleavedRaster(buffer, width, height,
                    width * bands, bands, bandOffsets, new Point(0, 0));

    // Create a custom BufferedImage with the raster and a suitable color model
    return new BufferedImage(CM_GRAY_ALPHA, raster, false, null);
}

6
在我写这篇文章的时候,你已经得到了“它是如何完成的”答案,所以我会解释一下正在发生的事情以及为什么你没有成功。
你可能知道,当图像以ARGB(TYPE_INT_ARGB)表示时,透明度存储在第四个字节中(A-Alpha通道)。 A = 0 表示透明,A = 255 表示不透明。
TYPE_BYTE_GRAY 表示只是一个带有亮度分量的单个字节,在这种表示中没有 Alpha 通道。当你将图像绘制到新缓冲区中时,亮度从 RGB 通道计算并存储。 Alpha 通道被丢弃。
实现你所做的最简单的方法是自己计算亮度,并将其存储在新的 ARGB 图像的三个 RGB 通道中,同时将 Alpha 通道从原始图像复制到现在的灰度图像中。
亮度是 YCbCr 图像表示中的 Y 分量。 Wikipedia 上有关于如何计算它的详细信息(它只是 R、G 和 B 的加权平均值)。
这种方法的问题在于你会失去硬件加速。幸运的是,Java提供了颜色空间转换类(根据平台)应该是硬件加速的,更加健壮,因为它们不依赖于输入图像是否为ARGB。@MadProgrammer的详细答案展示了如何实现这一点。

我会点赞,因为这个信息很有趣且通常有帮助。 - MadProgrammer
如果整数长度为8字节,那么会发生什么?前4个字节应该是0。 - Bionix1441
@Bionix1441,你能解释一下你所指的是什么吗?通常在ARGB32中使用ARGB表示法(即每个通道一个字节,共32位)。当使用此表示法时,图像中的每个像素都被打包成32位。还有一种ARGB64表示法,它使用8个字节,但每个通道都是16位。我不明白你所说的“前四个字节应该为0”的意思。 - Eli Algranti

0

由于只有BITMASK支持透明度,因此首先使用TYPE_BYTE_GRAY将图像转换为灰度,然后使用BITMARK将图像转换为具有透明度的图像。希望这可以帮到您。我曾经遇到过同样的问题,我使用了这种方法。

//grey scale image 
BufferedImage b = new BufferedImage(
        colorImage.getWidth(),
        colorImage.getHeight(),
        BufferedImage.TYPE_BYTE_GRAY);
File lostFGFile = new File(lostFgFileName);
Graphics g = b.createGraphics();

g.drawImage(colorImage, 0, 0, null);
g.setColor(Color.gray);
ImageIO.write(b, "png", lostFGFile);
g.dispose();

//convert the lost image as transparent
BufferedImage greyImage = ImageIO.read(new FileInputStream(lostFgFileName));
b = new BufferedImage(
        colorImage.getWidth(),
        colorImage.getHeight(),
        BufferedImage.BITMASK);
g = b.createGraphics();

g.drawImage(greyImage, 0, 0, null);
ImageIO.write(b, "png", lostFGFile);
g.dispose();

3
好的,我会尽力完成翻译任务。请问您需要翻译什么内容? - gdbj
这段代码之所以“有效”,仅仅是因为 BufferedImage.BITMASK(或者说 Transparency.BITMASK)有一个常量值 2,与 BufferedImage.TYPE_INT_ARGB 相同。因此,你在这里创建了一个带有完整 alpha 通道的普通 ARGB 图像(而不是位掩码)。但我仍然不明白这段代码如何保留原始图像的透明度... - Harald K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接