awi*_*bra 11 java graphics bufferedimage
在Windows上运行的一些Java代码中,我正在从磁盘读取一些大块RGB数据,并希望尽快将其显示在屏幕上.RGB数据是每通道8位,没有任何alpha.目前我有以下代码来创建BufferedImage.
BufferedImage getBufferedImage(File file, int width, int height) {
byte[] rgbData = readRGBFromFile(file);
WritableRaster raster = Raster.createInterleavedRaster(
rgbData, width, height,
width * 3, // scanlineStride
3, // pixelStride
new int[]{0, 1, 2}, // bandOffsets
null);
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8}, // bits
false, // hasAlpha
false, // isPreMultiplied
ComponentColorModel.OPAQUE,
DataBuffer.TYPE_BYTE);
return new BufferedImage(colorModel, raster, false, null);
}
Run Code Online (Sandbox Code Playgroud)
问题是将此渲染到屏幕的性能非常慢.大约250 - 300毫秒.我已经读过,为了获得最佳性能,您需要在与屏幕兼容的BufferedImage中显示.为此,我将从上述方法返回的缓冲图像传递给这样的方法.
BufferedImage createCompatibleImage(BufferedImage image)
{
GraphicsConfiguration gc = GraphicsEnvironment.
getLocalGraphicsEnvironment().
getDefaultScreenDevice().
getDefaultConfiguration();
BufferedImage newImage = gc.createCompatibleImage(
image.getWidth(),
image.getHeight(),
Transparency.TRANSLUCENT);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
Run Code Online (Sandbox Code Playgroud)
该方法在Windows上基本上将其从RGB转换为ARGB,并且它确实加快了显示速度,但对于1600 x 1200 RGB数据块,此方法需要约300 ms.所以现在我基本上将绘图问题的性能影响转换为转换问题.
300ms与从磁盘加载RGB数据的时间差不多.我想我可以更快地做点什么.
有没有更好的方法可以进行转换?或者,如果我事先修改了RGB数据并添加了alpha通道,它会有帮助吗?如果是这样,我的Raster和ColorModel会是什么样子.此外,由于我的RGB数据不包含透明度,我可以通过使用预乘的alpha或其他东西来获得任何性能改进吗?
对不起,我有点迷失在这个ColorModel,Raster的东西上.
谢谢!
Bre*_*ode 21
我意识到这是一个非常古老的问题,我只是为其他可能偶然发现这个问题寻找更多选择的人发布这个问题.我最近遇到了一个问题,我试图获取一个大的(720p)RGB字节[]并将其渲染为a BufferedImage.我使用的原始实现看起来像这样(在这里简化):
public void processFrame(byte[] frame, int width, int height)
{
DataBuffer videoBuffer = new DataBufferByte(frame,frame.length);
BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
ComponentSampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE,width,height,3,width*3,new int[] {2,1,0});
Raster raster = Raster.createRaster(sampleModel,videoBuffer,null);
currentImage.setData(raster);
}
Run Code Online (Sandbox Code Playgroud)
即使像创建优化BufferedImage和ComponentSampleModel一次并重复使用它们,调用的最后步骤中setData对BufferedImage依然在采取的50-60毫秒量级,这是不可接受的.
我最终意识到,至少对于我的场景,你实际上可以BufferedImage直接写入直接的后备字节数组并绕过大部分中间处理(假设图像的后备元数据已经正确).所以我改变了我的代码看起来像这样:
public void processFrame(byte[] frame, int width, int height)
{
BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
byte[] imgData = ((DataBufferByte)currentImage.getRaster().getDataBuffer()).getData();
System.arraycopy(frame,0,imgData,0,frame.length);
}
Run Code Online (Sandbox Code Playgroud)
通过这样做,我的性能提高了大约20倍.我现在在3-5毫秒而不是50-60毫秒处理相同的帧.
这可能不适用于所有情况,但我认为我会分享以防其他人认为它有用.
awi*_*bra 12
在玩了这个之后,如果当前的图形配置使用ARGB整数打包栅格,我有一个适合Windows的答案.
我所做的是首先创建兼容的BufferedImage,然后我手动将我的RGB字节数组转换为ARGB int数组.然后我从兼容的BufferedImage中获取Raster并将我的ARGB注入其中.这要快得多.
我还有一个类来检查兼容的BufferedImage是否采用我期望的格式,如果不是,则默认为较旧的较慢方法.
这是班级.希望它能帮到你.
/**
* This class can read chunks of RGB image data out of a file and return a BufferedImage.
* It may use an optimized technique for loading images that relies on assumptions about the
* default image format on Windows.
*/
public class RGBImageLoader
{
private byte[] tempBuffer_;
private boolean fastLoading_;
public RGBImageLoader()
{
fastLoading_ = canUseFastLoadingTechnique();
}
private boolean canUseFastLoadingTechnique()
{
// Create an image that's compatible with the screen
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(100, 100, Transparency.TRANSLUCENT);
// On windows this should be an ARGB integer packed raster. If it is then we can
// use our optimization technique
if(image.getType() != BufferedImage.TYPE_INT_ARGB)
return false;
WritableRaster raster = image.getRaster();
if(!(raster instanceof IntegerInterleavedRaster))
return false;
if(!(raster.getDataBuffer() instanceof DataBufferInt))
return false;
if(!(image.getColorModel() instanceof DirectColorModel))
return false;
DirectColorModel colorModel = (DirectColorModel) image.getColorModel();
if(!(colorModel.getColorSpace() instanceof ICC_ColorSpace) ||
colorModel.getNumComponents() != 4 ||
colorModel.getAlphaMask() != 0xff000000 ||
colorModel.getRedMask() != 0xff0000 ||
colorModel.getGreenMask() != 0xff00 ||
colorModel.getBlueMask() != 0xff)
return false;
if(raster.getNumBands() != 4 ||
raster.getNumDataElements() != 1 ||
!(raster.getSampleModel() instanceof SinglePixelPackedSampleModel))
return false;
return true;
}
public BufferedImage loadImage(File file, int width, int height, long imageOffset) throws IOException
{
if(fastLoading_)
return loadImageUsingFastTechnique(file, width, height, imageOffset);
else
return loadImageUsingCompatibleTechnique(file, width, height, imageOffset);
}
private BufferedImage loadImageUsingFastTechnique(File file, int width, int height, long imageOffset) throws IOException
{
int sizeBytes = width * height * 3;
// Make sure buffer is big enough
if(tempBuffer_ == null || tempBuffer_.length < sizeBytes)
tempBuffer_ = new byte[sizeBytes];
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
raf.seek(imageOffset);
int bytesRead = raf.read(tempBuffer_, 0, sizeBytes);
if (bytesRead != sizeBytes)
throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
WritableRaster raster = image.getRaster();
DataBufferInt dataBuffer = (DataBufferInt) raster.getDataBuffer();
addAlphaChannel(tempBuffer_, sizeBytes, dataBuffer.getData());
return image;
}
finally
{
try
{
if(raf != null)
raf.close();
}
catch(Exception ex)
{
}
}
}
private BufferedImage loadImageUsingCompatibleTechnique(File file, int width, int height, long imageOffset) throws IOException
{
int sizeBytes = width * height * 3;
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
// Lets navigate to the offset
raf.seek(imageOffset);
DataBufferByte dataBuffer = new DataBufferByte(sizeBytes);
byte[] bytes = dataBuffer.getData();
int bytesRead = raf.read(bytes, 0, sizeBytes);
if (bytesRead != sizeBytes)
throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, // dataBuffer
width, // width
height, // height
width * 3, // scanlineStride
3, // pixelStride
new int[]{0, 1, 2}, // bandOffsets
null); // location
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), // ColorSpace
new int[]{8, 8, 8}, // bits
false, // hasAlpha
false, // isPreMultiplied
ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage loadImage = new BufferedImage(colorModel, raster, false, null);
// Convert it into a buffered image that's compatible with the current screen.
// Not ideal creating this image twice....
BufferedImage image = createCompatibleImage(loadImage);
return image;
}
finally
{
try
{
if(raf != null)
raf.close();
}
catch(Exception ex)
{
}
}
}
private BufferedImage createCompatibleImage(BufferedImage image)
{
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage newImage = gc.createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
private void addAlphaChannel(byte[] rgbBytes, int bytesLen, int[] argbInts)
{
for(int i=0, j=0; i<bytesLen; i+=3, j++)
{
argbInts[j] = ((byte) 0xff) << 24 | // Alpha
(rgbBytes[i] << 16) & (0xff0000) | // Red
(rgbBytes[i+1] << 8) & (0xff00) | // Green
(rgbBytes[i+2]) & (0xff); // Blue
}
}
}
Run Code Online (Sandbox Code Playgroud)