Преобразование short[] в изображение в градациях серого

Я пишу генератор фракталов Буддаброта, используя апарапи. Я заставил часть OpenCL работать, в результате чего получился одномерный массив, представляющий каждый пиксель. У меня есть размеры конечного изображения в виде конечных целых чисел, и я написал код для получения индекса произвольных точек в этом массиве. Я хочу сохранить это как изображение и пытаюсь использовать 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. Что мне нужно сделать?


person Shawn Walton    schedule 06.01.2012    source источник


Ответы (2)


Проблема здесь в том, что setRGB() хочет значение цвета 0xRRGGBB. BufferedImage любит делать вид, что изображение RGB, независимо от того, как хранятся данные. На самом деле вы можете добраться до внутреннего DataBufferShortgetTile(0, 0).getDataBuffer()), но может быть сложно понять, как он устроен.

Если у вас уже есть пиксели в 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));

Преимущество этого подхода в том, что вы управляете базовым массивом пикселей. Вы можете внести изменения в этот массив и вызвать newPixels() на своем MemoryImageSource, и он будет обновляться в реальном времени. Это также дает вам полную возможность определить свою собственную палитру, отличную от оттенков серого:

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);
person Russell Zahniser    schedule 07.01.2012
comment
Я сделал это, а затем понял, что мой код рисования — это FUBAR с исключениями ArrayOutOfBounds повсюду. Хотя, глядя на это, кажется, что это будет работать нормально. Все, что мне нужно сделать, это вывести его в формате .png, что я и сделал (надеюсь) успешно. Я отмечу это как правильное, потому что кажется, что это будет работать нормально. - person Shawn Walton; 07.01.2012
comment
Когда это помещает пиксели в изображение, они рисуются вертикальными линиями, которые начинаются слева и идут вправо, или горизонтальными, которые начинаются сверху и идут вниз? Потому что в моем частично работающем коде изображение повернуто на 90 градусов по часовой стрелке, и я не думаю, что это ошибка рисунка. - person Shawn Walton; 07.01.2012
comment
Неважно! Мне пришлось изменить 200 в части MemoryImageSource на HORIZONTAL_PIXELS. Это отлично работает, спасибо! - person Shawn Walton; 07.01.2012
comment
Извините за это - я работал с изображением фиксированного размера 200x200, а затем попытался включить ваши константы, но пропустил одно. - person Russell Zahniser; 07.01.2012
comment
Я сделал это, и это прекрасно работает. Проблема в том, что для рисования больших (2000x2000) изображений с помощью img.getGraphics().drawImage(image, 0, 0, null) требуется почти вечность; чтобы их можно было сохранить с помощью ImageIO. Есть ли более быстрый способ сделать это? - person Shawn Walton; 08.01.2012
comment
В этом коде я рисую в BufferedImage, чтобы сохранить его, извините. - person Shawn Walton; 08.01.2012
comment
Я ожидал, что сохранение займет намного больше времени, чем копирование изображения в память. Есть потенциально более быстрый способ избежать копирования — я добавлю его выше. - person Russell Zahniser; 08.01.2012
comment
Я думаю, что VERTICAL_PIXELS и HORIZONTAL_PIXELS перевернуты. Сначала его ширина, затем высота. - person dgimenes; 06.02.2014

Для справки, в этом примере показано, как два разных типа 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 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();
            }
        });
    }
}
person trashgod    schedule 07.01.2012
comment
С технической точки зрения, они не показывают одни и те же 16-битные данные — они показывают одни и те же значения RGB при принудительном переводе в оттенки серого USHORT или RGB. Это связано с тем, что setRGB() выполняет преобразование цвета в палитру изображения, а не просто записывает значение пикселя. Если бы он записал значение пикселя, значение в градациях серого было бы белым внизу. - person Russell Zahniser; 07.01.2012