【问题标题】:How to create BufferedImage for 32 bits per sample, 3 samples image data如何为每个样本创建 32 位的 BufferedImage,3 个样本图像数据
【发布时间】:2015-01-08 14:58:16
【问题描述】:

我正在尝试从一些图像数据创建一个 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() 只是将字节数组转换为具有正确字节序的 int 数组。我确定这个是正确的,因为我已经与其他方法进行了交叉检查以生成相同的 int 数组。

我猜这个问题可能源于我使用所有 32 位 int 来表示包含负值的颜色。看起来我需要 long 数据类型,但是 long 没有 DataBuffer 类型。

使用传输类型创建的 ComponentColorModel 实例 DataBuffer.TYPE_BYTE、DataBuffer.TYPE_USHORT 和 DataBuffer.TYPE_INT 具有被视为无符号整数的像素样本值 价值观。

以上引用来自 ComponentColorModel 的 Java 文档。这意味着 32 位样本确实被视为无符号整数值。那么问题可能出在其他地方。

有没有人遇到过类似的问题并找到了解决方法,或者我可能在这里做错了什么?

Update2:“真正的”问题在于,当使用 32 位样本时,ComponentColorModel 的算法将 1 向左移动 0 次(1

更新:根据 HaraldK 和 cmets 的回答,我们终于同意问题出在 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;
   }
}

【问题讨论】:

  • new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); 或其他类型常量之一,尤其是 TYPE_INT_RGB?
  • @JoopEggen:BufferedImage.TYPE_4BYTE_ABGR 仅适用于每个样本 8 位。我的是每个样本 32 位。
  • 我明白了,仍在使用 TIFF 阅读器。 ;-) 看起来有点像负面形象?可能是颜色模型问题?通过对我自己的 TIFFImageReader(尚不支持 RGB [32、32、32])进行一些快速更改,我读到了一张看起来就像你在右边的图像......
  • @haraldK:你敢打赌。我还尝试了 jai-imageio,它显示的和右边的一样。但无法弄清楚出了什么问题。

标签: java image rgb bufferedimage 32-bit


【解决方案1】:

更新:

这似乎是一个已知错误:ComponentColorModel.getNormalizedComponents() does not handle 32-bit TYPE_INT,10(十!)年前报告,针对 Java 5。

好处是,Java 现在部分开源。我们现在可以提出一个补丁,如果运气好的话,它将针对 Java 9 左右进行评估...... :-P

该错误提出了以下解决方法:

子类 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);
    }
}

对我来说,这似乎表明使用 transferType TYPE_INTColorModel 有问题。但我很乐意犯错。 ;-)

您可以尝试的另一件事是将值缩小到 16 位,使用TYPE_USHORT 光栅和颜色模型,看看是否有区别。我敢打赌它会,但我懒得尝试。 ;-)

【讨论】:

  • 现在我倾向于相信 ColorModel 对于 32 位样本的 TransferType TYPE_INT 已损坏。当我将 32 位值缩放为 24 位并使用相同的传输类型 TYPE_INT 时,它显示正常。
  • @dragon66 看来你最初的猜测是正确的,API doc 是错误的......太糟糕了。我只在 OS X Java 上测试过,其他 Java 版本可能会有所不同?你用的是哪个版本?值得测试,如果问题在 Java 7 和 8 上相同,可能会提交错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-13
  • 2014-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多