Convert short[] into a grayscale image

2019-01-15 09:32发布

I am writing a Buddhabrot fractal generator using aparapi. I got the OpenCL part of it to work, resulting in a single-dimension array that represents each pixel. I have the dimensions of the final image as final ints, and have written code to get the index of arbitrary points in that array. I want to save this as an image and I'm trying to use BufferedImage with TYPE_USHORT_GRAY. Here's what I have so far:

    BufferedImage image=new BufferedImage(VERTICAL_PIXELS, HORIZONTAL_PIXELS, BufferedImage.TYPE_USHORT_GRAY);
    for(int i=0; i<VERTICAL_PIXELS; i++)
        for(int k=0; k<HORIZONTAL_PIXELS; k++)
            image.setRGB(k, i, normalized[getArrayIndex(k,i,HORIZONTAL_PIXELS)]);

The problem is, I don't know what to set the RGB as. What do I need to do?

2条回答
叼着烟拽天下
2楼-- · 2019-01-15 09:54

For reference, this example shows how two different BufferedImage types interpret the same 16 bit data. You can mouse over the images to see the pixel values.

Addendum: To elaborate on the word interpret, note that setRGB() tries to find the closest match to the specified value in the given ColorModel.

BufferedImageTest

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

/** @see http://stackoverflow.com/questions/8765004 */
public class BufferedImageTest extends JPanel {

    private static final int SIZE = 256;
    private static final Random r = new Random();
    private final BufferedImage image;

    public BufferedImageTest(int type) {
        image = new BufferedImage(SIZE, SIZE, type);
        this.setPreferredSize(new Dimension(SIZE, SIZE));
        for (int row = 0; row < SIZE; row++) {
            for (int col = 0; col < SIZE; col++) {
                image.setRGB(col, row, 0xff00 << 16 | row << 8 | col);
            }
        }
        this.addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                Point p = e.getPoint();
                int x = p.x * SIZE / getWidth();
                int y = p.y * SIZE / getHeight();
                int c = image.getRGB(x, y);
                setToolTipText(x + "," + y + ": "
                    + String.format("%08X", c));
            }
        });
    }

    @Override
    protected void paintComponent(Graphics g) {
        g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
    }

    static private void display() {
        JFrame f = new JFrame("BufferedImageTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridLayout(1, 0));
        f.add(new BufferedImageTest(BufferedImage.TYPE_INT_ARGB));
        f.add(new BufferedImageTest(BufferedImage.TYPE_USHORT_GRAY));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                display();
            }
        });
    }
}
查看更多
Bombasti
3楼-- · 2019-01-15 10:00

The problem here is that setRGB() wants an 0xRRGGBB color value. BufferedImage likes to pretend that the image is RGB, no matter what the data is stored as. You can actually get at the internal DataBufferShort (with getTile(0, 0).getDataBuffer()), but it can be tricky to figure out how it is laid out.

If you already have your pixels in a short[], a simpler solution might be to copy them into an int[] instead an jam it into a MemoryImageSource:

int[] buffer = /* pixels */;

ColorModel model = new ComponentColorModel(
   ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] { 16 }, 
   false, true, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);

Image image = Toolkit.getDefaultToolkit().createImage(
   new MemoryImageSource(VERTICAL_PIXELS, HORIZONTAL_PIXELS, 
                         model, buffer, 0, VERTICAL_PIXELS));

The advantage of this approach is that you control the underlying pixel array. You could make changes to that array and call newPixels() on your MemoryImageSource, and it would update live. It also gives you complete power to define your own palette other than grayscale:

int[] cmap = new int[65536];
for(int i = 0; i < 65536; ++i) {

    cmap[i] = (((i % 10000) * 256 / 10000) << 16) 
            | (((i % 20000) * 256 / 20000) << 8)
            | (((i % 40000) * 256 / 40000) << 0);
}
ColorModel model = new IndexColorModel(16, 65536, cmap, 0, false, -1, DataBuffer.TYPE_USHORT);

This approach works fine if you just want to display the image on the screen:

JFrame frame = new JFrame();
frame.getContentPane().add(new JLabel(new ImageIcon(image)));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);

However, if you wanted to write it out to a file and preserve the one-short-per-pixel format (say, to load into Matlab) then you're out of luck. The best you can do is to paint it into a BufferedImage and save that with ImageIO, which will save as RGB.

If you definitely need a BufferedImage at the end, another approach is to apply the color palette yourself, calculate the RGB values, and then copy them into the image:

short[] data = /* your data */;
int[] cmap = /* as above */;
int[] rgb = new int[data.length];

for(int i = i; i < rgb.length; ++i) {
   rgb[i] = cmap[data[i]];
}

BufferedImage image = new BufferedImage(
   VERTICAL_PIXELS, HORIZONTAL_PIXELS, 
   BufferedImage.TYPE_INT_RGB);

image.setRGB(0, 0, VERTICAL_PIXELS, HORIZONTAL_PIXELS,
   pixels, 0, VERTICAL_PIXELS);
查看更多
登录 后发表回答