我正在尝试从一些字节数组的图像数据创建BufferedImage。图像为RGB格式,每个像素有3个样本 - R,G和B,每个样本32位(每个样本,不是所有3个样本)。
现在我想从这个字节数组创建一个BufferedImage。这就是我所做的:
ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {32, 32, 32}, false, false, Transparency.OPAQUE, DataBuffer.TYPE_INT);
Object tempArray = ArrayUtils.toNBits(bitsPerSample, pixels, samplesPerPixel*imageWidth, endian == IOUtils.BIG_ENDIAN);
WritableRaster raster = cm.createCompatibleWritableRaster(imageWidth, imageHeight);
raster.setDataElements(0, 0, imageWidth, imageHeight, tempArray);
BufferedImage bi = new BufferedImage(cm, raster, false, null);
上述代码适用于每个样本RGB图像24位,但不是每个样本32位。生成的图像是垃圾,显示在图像的右侧。它应该像图像的左侧。
注意:我的机器上唯一可以读取此图像的图像阅读器是ImageMagick。所有其他显示的结果与下图中右侧的垃圾相似。
ArrayUtils.toNBits()只是将字节数组转换为具有正确endianess的int数组。我确定这个是正确的,因为我已经与其他方法交叉检查以生成相同的int数组。
我想问题可能是因为我使用所有32位int来表示包含负值的颜色。看起来我需要长数据类型,但长期没有DataBuffer类型。
使用传输类型创建的ComponentColorModel实例 DataBuffer.TYPE_BYTE,DataBuffer.TYPE_USHORT和DataBuffer.TYPE_INT 像素样本值被视为无符号整数 值。
以上引用来自ComponentColorModel的Java文档。这意味着32位样本确实被视为无符号整数值。然后问题可能出在其他地方。
是否有任何机构遇到类似的问题并得到了解决方法,或者我可能在这里做错了什么?
Update2 :“真实”问题在于当使用32位样本时,ComponentColorModel的算法将向左移动1次0(1 <0) on int总是在0~31之间。这不是预期值。要解决这个问题(实际左移32次),唯一需要做的就是将1从int更改为long类型为1L,如下面的修复所示。
更新:从HaraldK的回答和评论中,我们终于同意问题来自Java的ComponentColorModel,它没有正确处理32位样本。 HaraldK的拟议修复也适用于我的案例。以下是我的版本:
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
public class Int32ComponentColorModel extends ComponentColorModel {
//
public Int32ComponentColorModel(ColorSpace cs, boolean alpha) {
super(cs, alpha, false, alpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE, DataBuffer.TYPE_INT);
}
@Override
public float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset) {
int numComponents = getNumComponents();
if (normComponents == null || normComponents.length < numComponents + normOffset) {
normComponents = new float[numComponents + normOffset];
}
switch (transferType) {
case DataBuffer.TYPE_INT:
int[] ipixel = (int[]) pixel;
for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
normComponents[nc] = ipixel[c] / ((float) ((1L << getComponentSize(c)) - 1));
}
break;
default: // I don't think we can ever come this far. Just in case!!!
throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
}
return normComponents;
}
}
答案 0 :(得分:2)
更新
这似乎是一个已知的错误:ComponentColorModel.getNormalizedComponents() does not handle 32-bit TYPE_INT,报告10年(TEN!)年前,针对Java 5。
优点是,Java现在部分是开源的。我们现在可以提出一个补丁,幸运的是它将被评估为Java 9左右...... :-P
该bug提出了以下解决方法:
Subclass ComponentColorModel并覆盖getNormalizedComponents()以在处理此数据时将传入像素值除以'Math.pow(2,32) - 1',而不是使用错误位,从而正确处理每个样本TYPE_INT数据32位转移。 (使用浮点值是可以的,因为getNormalizedComponents()无论如何都会将所有内容转换为浮点数。)
我的解决方案有点不同,但基本想法是一样的(根据您的意愿随意优化: - )):
private static class TypeIntComponentColorModel extends ComponentColorModel {
public TypeIntComponentColorModel(final ColorSpace cs, final boolean alpha) {
super(cs, alpha, false, alpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_INT);
}
@Override
public float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset) {
int numComponents = getNumComponents();
if (normComponents == null) {
normComponents = new float[numComponents + normOffset];
}
switch (transferType) {
case DataBuffer.TYPE_INT:
int[] ipixel = (int[]) pixel;
for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
normComponents[nc] = ((float) (ipixel[c] & 0xffffffffl)) / ((float) ((1l << getComponentSize(c)) - 1));
}
break;
default:
throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
}
return normComponents;
}
}
考虑以下代码。如果按原样运行,对我来说它会显示一个大部分是黑色的图像,右上角的四分之一白色覆盖着一个黑色的圆圈。如果我将数据类型更改为TYPE_USHORT
(取消注释transferType
行),它将显示半/半白色和从黑色到白色的线性渐变,中间有一个橙色圆圈(应该如此)。
使用ColorConvertOp
转换为标准类型似乎没有任何区别。
public class Int32Image {
public static void main(String[] args) {
// Define dimensions and layout of the image
int w = 300;
int h = 200;
int transferType = DataBuffer.TYPE_INT;
// int transferType = DataBuffer.TYPE_USHORT;
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, Transparency.OPAQUE, transferType);
WritableRaster raster = colorModel.createCompatibleWritableRaster(w, h);
BufferedImage image = new BufferedImage(colorModel, raster, false, null);
// Start with linear gradient
if (raster.getTransferType() == DataBuffer.TYPE_INT) {
DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
int[] data = buffer.getData();
for (int y = 0; y < h; y++) {
int value = (int) (y * 0xffffffffL / h);
for (int x = 0; x < w; x++) {
int offset = y * w * 3 + x * 3;
data[offset] = value;
data[offset + 1] = value;
data[offset + 2] = value;
}
}
}
else if (raster.getTransferType() == DataBuffer.TYPE_USHORT) {
DataBufferUShort buffer = (DataBufferUShort) raster.getDataBuffer();
short[] data = buffer.getData();
for (int y = 0; y < h; y++) {
short value = (short) (y * 0xffffL / h);
for (int x = 0; x < w; x++) {
int offset = y * w * 3 + x * 3;
data[offset] = value;
data[offset + 1] = value;
data[offset + 2] = value;
}
}
}
// Paint something (in color)
Graphics2D g = image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, w / 2, h);
g.setColor(Color.ORANGE);
g.fillOval(100, 50, w - 200, h - 100);
g.dispose();
System.out.println("image = " + image);
// image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
JFrame frame = new JFrame();
frame.add(new JLabel(new ImageIcon(image)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
对我来说,这似乎表明ColorModel
使用transferType TYPE_INT
有问题。但我会很高兴出错。 ; - )
您可以尝试的另一件事是将值缩小到16位,使用TYPE_USHORT
栅格和颜色模型,看看是否有所不同。我打赌它会,但我懒得试试。 ; - )