将short[]转换为灰度图像

6

我正在使用aparapi编写一个Buddhabrot分形生成器。我已经让它的OpenCL部分工作了,得到了代表每个像素的单维数组。我已经将最终图像的维度定义为final ints,并编写了获取该数组中任意点索引的代码。我想将其保存为一张图片,尝试使用BufferedImage和TYPE_USHORT_GRAY。以下是我的代码:

    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)]);

问题是,我不知道该将RGB设置为什么。我需要做什么?

2个回答

7
这里的问题在于setRGB()需要一个0xRRGGBB颜色值。无论数据存储方式如何,BufferedImage都喜欢假装图像是RGB格式的。你实际上可以通过getTile(0, 0).getDataBuffer()访问内部的DataBufferShort,但是弄清它的布局可能有些棘手。
如果你已经有了short[]类型的像素,那么一个更简单的解决方案可能是将它们复制到一个int[]中,并将其插入到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));

这种方法的优点是你可以控制底层像素数组。你可以对该数组进行更改,并在MemoryImageSource上调用newPixels(),它会实时更新。它还使您完全有权定义自己的调色板而不仅限于灰度:

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);

如果您只想在屏幕上显示图像,则此方法效果良好:

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

然而,如果您想将其写入文件并保留每像素一个色值的格式(例如,加载到Matlab中),那么就没办法了。最好的方法是将其绘制到BufferedImage中,并使用ImageIO保存为RGB格式。
如果您最终确实需要一个BufferedImage,另一种方法是自己应用颜色调色板,计算RGB值,然后将其复制到图像中:
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);

我已经完成这个任务,但随后发现我的绘图代码出了问题,到处都是数组越界的异常。仔细看了一下,似乎它可以正常运行。我所要做的就是将其输出为 .png 格式,而我已经成功地完成了这项工作。(希望如此)因为它看起来应该是可以正常工作的,所以我会将其标记为正确的。 - Shawn Walton
没事了!我不得不将MemoryImageSource部分的200更改为HORIZONTAL_PIXELS。它运行得很好,谢谢! - Shawn Walton
抱歉,我正在使用一个固定大小的200x200图像,然后我尝试切换到您的常量,但我错过了其中一个。 - Russell Zahniser
那段代码是我在 BufferedImage 上绘制图像以便保存,抱歉。 - Shawn Walton
我认为VERTICAL_PIXELS和HORIZONTAL_PIXELS被颠倒了。它应该是先宽度,然后是高度。 - dgimenes
显示剩余3条评论

3

以下示例展示了两种不同的BufferedImage类型如何解释相同的16位数据。您可以将鼠标悬停在图像上以查看像素值。

补充说明:为了阐明“解释”这个词,注意setRGB()会尝试在给定的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 https://dev59.com/YF_Va4cB1Zd3GeqPPAIn */
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();
            }
        });
    }
}

1
从技术上讲,这些并不显示相同的16位数据 - 当强制转换为USHORT灰度或RGB时,它们显示相同的RGB值。这是因为setRGB()会将颜色转换为图像的调色板 - 它不仅仅写入像素值。如果它写入像素值,灰度值将在底部为白色。 - Russell Zahniser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接