Как установить пиксель RGB в BufferedImage для отображения PNG с глубиной 16 бит?

Я пытаюсь прочитать и показать файл PNG. У меня нет проблем с изображениями с 8-битной глубиной. Я поступаю следующим образом:

BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

Затем я читаю 3 * 8 = 24 бита каждого пикселя, сохраняю их в массиве байтов data и помещаю их в изображение с помощью:

for (int y = 0; y < height; y++)
   for (int x = 0; x < width; x++)
     result.setRGB(x, y, ((data[x * 3 + 0] & 0xff) << 16)
                       + ((data[x * 3 + 1] & 0xff) << 8)
                       + ((data[x * 3 + 2] & 0xff)));

Проблема теперь с 16-битными изображениями глубины. Конечно, data теперь больше и содержит 48 бит, разделенных на 6 байтов, для каждой тройки RGB: из отладчика data имеет ожидаемые значения. Как я могу установить пиксель RGB? Должен ли я изменить объявление BufferedImage? Возможно с:

BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB);

Спасибо заранее!

P.S.: в соответствии со стандартами PNG изображение имеет тип цвета 2 (RGB без альфа-канала).


Возможно, мне придется использовать http://docs.oracle.com/javase/7/docs/api/java/awt/image/ColorModel.html


person Shadow Template    schedule 28.11.2013    source источник


Ответы (2)


@haraldK указал в правильном направлении. Я предоставляю некоторый рабочий код из "PNGReader" из "icafe" библиотеки изображений Java.

if(bitsPerPixel == 16) {
    if(interlace_method==NON_INTERLACED)
       spixels = generate16BitRGBPixels(compr_data, false);
    else {
       spixels = generate16BitRGBInterlacedPixels(compr_data, false);
               }
    int[] off = {0, 1, 2}; //band offset, we have 3 bands
    int numOfBands = 3;
    boolean hasAlpha = false;
    int trans = Transparency.OPAQUE;
    int[] nBits = {16, 16, 16}; 
    if(alpha != null) { // Deal with single color transparency
       off = new int[] {0, 1, 2, 3}; //band offset, we have 4 bands
       numOfBands = 4;
       hasAlpha = true;
       trans = Transparency.TRANSLUCENT;
       nBits = new int[] {16, 16, 16, 16};                      
    }
    db = new DataBufferUShort(spixels, spixels.length);
    raster = Raster.createInterleavedRaster(db, width, height, width*numOfBands, numOfBands, off, null);
    cm = new ComponentColorModel(colorSpace, nBits, hasAlpha, false, trans, DataBuffer.TYPE_USHORT);
}
return new BufferedImage(cm, raster, false, null);

Вот метод generate16BitRGBPixels():

private short[] generate16BitRGBPixels(byte[] compr_data, boolean fullAlpha) throws Exception {
     //
     int bytesPerPixel = 0;
     byte[] pixBytes;

     if (fullAlpha)
         bytesPerPixel = 8;
     else 
         bytesPerPixel = 6;

     bytesPerScanLine = width*bytesPerPixel;         

     // Now inflate the data.
     pixBytes = new byte[height * bytesPerScanLine];

     // Wrap an InflaterInputStream with a bufferedInputStream to speed up reading
     BufferedInputStream bis = new BufferedInputStream(new InflaterInputStream(new ByteArrayInputStream(compr_data)));

     apply_defilter(bis, pixBytes, height, bytesPerPixel, bytesPerScanLine);

     short[] spixels = null;

     if(alpha != null) { // Deal with single color transparency
         spixels = new short[width*height*4];
         short redMask = (short)((alpha[1]&0xff)|(alpha[0]&0xff)<<8);
         short greenMask = (short)((alpha[3]&0xff)|(alpha[2]&0xff)<<8);;
         short blueMask = (short)((alpha[5]&0xff)|(alpha[4]&0xff)<<8);

         for(int i = 0, index = 0; i < pixBytes.length; index += 4) {
             short red = (short)((pixBytes[i++]&0xff)<<8|(pixBytes[i++]&0xff));
             short green = (short)((pixBytes[i++]&0xff)<<8|(pixBytes[i++]&0xff));
             short blue = (short)((pixBytes[i++]&0xff)<<8|(pixBytes[i++]&0xff));
             spixels[index] = red;
             spixels[index + 1] = green;
             spixels[index + 2] = blue;
             if(spixels[index] == redMask && spixels[index + 1] == greenMask && spixels[index + 2] == blueMask) {
                 spixels[index + 3] = (short)0x0000;                               
             } else {
                 spixels[index + 3] = (short)0xffff;
             }
         }
     } else
         spixels = ArrayUtils.toShortArray(pixBytes, true);

     return spixels;         
 }

и метод ArrayUtils.toShortArray():

public static short[] toShortArray(byte[] data, int offset, int len, boolean bigEndian) {

    ByteBuffer byteBuffer = ByteBuffer.wrap(data, offset, len);

    if (bigEndian) {
        byteBuffer.order(ByteOrder.BIG_ENDIAN);
    } else {
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    }

    ShortBuffer shortBuf = byteBuffer.asShortBuffer();
    short[] array = new short[shortBuf.remaining()];
    shortBuf.get(array);

    return array;
}
person dragon66    schedule 29.12.2014

Невозможно создать изображение с 16 битами на выборку (или 48 битами на пиксель), используя BufferedImage.TYPE_... (такой константы нет). TYPE_USHORT_565_RGB создает изображение с 16 битами на пиксель с образцами 5 (красный), 6 (зеленый) и 5 ​​(синий) бит соответственно. Я думаю, что эти значения USHORT RGB остались с тех времен, когда некоторые компьютеры имели опцию 16-битного дисплея (он же «Тысячи цветов»).

Что вам нужно сделать, чтобы фактически создать изображение с 16 битами на выборку, это:

ColorModel cm;
WritableRaster raster;

BufferedImage result = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);

Растр создается из буфера данных типа DataBufferUShort либо с 3 банками и BandedSampleModel с 3 каналами, либо с использованием одного банка и PixelInterleavedSampleModel с pixelStride из 3, scanLineStride из 3 * width и bandOffsets {0, 1, 2}.

Вот полный образец с использованием модели с чередованием образцов:

ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB)
ColorModel cm = new ComponentColorModel(sRGB, false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_USHORT, w, h, 3, null);
BufferedImage rgb = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);

PS: с открытым буфером данных вы можете получить прямой доступ к short образцам, чтобы управлять пикселями. Это намного быстрее, чем использование BufferedImage.getRGB(...)/setRGB(...), и сохранит исходную точность 16 бит на выборку. BufferedImage.getRGB(...) преобразует значения пикселей в 32-битные пиксели/8 бит на выборку и, таким образом, теряет дополнительную точность.

person Harald K    schedule 28.11.2013
comment
Спасибо за ответ. Я изучу эти классы и дам вам знать. - person Shadow Template; 28.11.2013
comment
У меня проблемы с созданием ColorModel и WritableRaster. Какой тип ColorModel я должен использовать? ComponentColorModel или я могу установить для него значение null? И как мне создать WritableRaster? Я использовал PixelInterleavedSampleModel(DataBuffer.TYPE_USHORT, img.getWidth(), img.getHeight(), 3, 3*img.getWidth(), bandOffset), как вы сказали. Возможно, проблемы в DataBufferUShort, который я передаю методу createWritableRaster. - person Shadow Template; 03.12.2013