问题
I am having problems converting a colored image with some transparent pixels to grayscale. I have searched and found related questions on this website but nothing I have been able to use to solve my problem.
I have defined a method "convertType" as follows:
/*------------------------------
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);
}
}
I start with a BufferedImage of type TYPE_4BYTE_ABGR
named "result", then:
result= convertType (result, BufferedImage.TYPE_BYTE_GRAY);//transparency is lost
result=convertType (result, BufferedImage.TYPE_INT_ARGB);//pixels will now support transparency
For colored opaque pixels in the original image the above sequence works fine: for example, (32, 51, 81, 255)-> (49, 49, 49, 255)
However, transparent pixels in the original image are turned opaque: for example, (0, 0, 0, 0)-> (0, 0, 0, 255)
I understand what is happening and I can work around the problem if I can make use of the Java algorithm used to convert to grayscale. I have downloaded the source code and poked around but have not been able to locate the algorithm. I would greatly appreciate it if someone could either:
- tell me what class contains the grayscale conversion algorithm or
- suggest an alternative way to accomplish what I am trying to do.
回答1:
Have you considered using a ColorConvertOp
filter?
For example...
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();
}
}
}
}
回答2:
While I was writing this you got your "how it is done" answer, so I'll just add some explanations on what is happening and why you did not succeed.
You probably know when an image is represented in ARGB (TYPE_INT_ARGB) transparency is stored in the fourth byte (the A - Alpha channel). A = 0 transparent, A = 255 opaque.
The TYPE_BYTE_GRAY representation is just a single byte with the luminance component, there is no Alpha channel in this representation. When you draw the image into the new buffer, the luminance is computed from the RGB channels and stored. The Alpha channel is dropped.
The easiest way to achieve what you are doing is to compute the luminance yourself and store it in the three RGB channels of a new ARGB image copying the Alpha channel from the original to the now gray-scale image.
The luminance is the Y component in the YCbCr image representation. Wikipedia has details on how to compute it (it's just a weighted average of R, G and B).
The problem with this approach is that you lose hardware acceleration. Luckily Java provides classes to translate color spaces which (depending on platform) should be hardware accelerated, and more robust since they don't depend on the input image being ARGB. @MadProgrammer's detailed answer shows how to do this.
回答3:
Important: You should probably use @MadProgrammers solution (ColorConvertOp
) if proper gray scale conversion is what you want. I voted for that. :-)
This answer is more for completeness:
If you really want a BufferedImage
that has CS_GRAY
color space and transparency, it's possible to create a custom (will result in TYPE_CUSTOM
) BufferedImage
. The code below will make something like a "TYPE_2BYTE_GRAY_ALPHA" type (8 bits per sample, 2 samples per pixel). It will use half the memory of a TYPE_INT_ARGB
or TYPE_4BYTE_ABGR
image, but it will most likely lose hardware acceleration, and possibly miss some optimized loops in the Java2D drawing pipelines (that said, my testing shows fully acceptable performance on 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);
}
回答4:
As only BITMASK support transparency, first convert the image to greyScale using TYPE_BYTE_GRAY, then use BITMARK to convert the image to have transparency. Hope this helps. I had the same problem I used this approach.
//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();
来源:https://stackoverflow.com/questions/21176754/how-can-i-convert-an-image-to-grayscale-without-losing-transparency