2015-10-16 2 views
0

При работе над Java-приложением, которое требует отрисовки спрайтов, я думал, что вместо того, чтобы загружать файл .png или .jpg в качестве Image или BufferedImage, я мог бы загрузить массив byte[] содержащие индексы для цветовой палитры (16 цветов на палитре, поэтому два пикселя на byte), а затем визуализируйте это.Быстрое рендеринг байтов на холст с цветовой палитрой

Метод, который я в настоящее время формирует BufferedImage из byte[] массива и цветовой палитры при инициализации, принимая дополнительное время для инициализации, но работает ровно после того, как та, которая работает хорошо, но есть только 4 спрайты в программе до сих пор. Я волнуюсь, что, когда есть более 100 спрайтов, их хранение как BufferedImages будет слишком облагаться налогом. И не только это означало бы 1 BufferedImage за спрайт, но на самом деле 1 изображение для каждой комбинации спрайтов и палитр, которые я хотел бы использовать.

Эта функция создает BufferedImage:

protected BufferedImage genImage(ColorPalette cp, int width, int height){ //Function to generate BufferedImage to render from the byte[] 
    BufferedImage ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); //Create the Image to return 
    for(int j=0; j<height; j++){ //Run a for loop for each pixel 
     for(int i=0; i<width; i++){ 
      int index = (j * width + i)/2; //Get the index of the needed byte 
      int value = image[index] & 0x00ff; //Convert to "unsigned byte", or int 
      byte thing; //declare actual color index as byte 
      if(i % 2 == 0)thing = (byte)((value & 0b11110000) >>> 4); //If it's an even index(since it starts with 0, this includes the 1st one), get the first 4 bits of the value 
      else thing = (byte)(value & 0b00001111); //If it's odd, get the last four bits 
      ret.setRGB(i, j, cp.getColor(thing & 0x00ff).getRGB()); //Set the pixel in the image to the value in the Color Palette 
     } 
    } 
    return ret; 
} 

И это один фактически делает его на экран:

public void render(Graphics g, int x, int y){ //Graphics to render to and x/y coords 
    g.drawImage(texture, x, y, TILE_WIDTH, TILE_HEIGHT, null); //Render it 
} 

Я экспериментировал с другим методом, который делает из byte[] непосредственно ж/о необходимость в BufferedImage, которая теоретически должна была бы сэкономить память, избегая использования BufferedImage для каждого спрайта, но в итоге она была очень и очень медленной. Потребовалось несколько секунд, чтобы сделать каждый кадр не более 25 спрайтов для рендеринга на экране! Обратите внимание, что g - объект Graphics.

private void drawSquare(int x, int y, int scale, Color c){ //Draw each "pixel" to scale 
    if(g == null){ //If null, quit 
     return; 
    } 
    g.setColor(c); //Set the color 
    for(int i=x; i<x+scale; i++){ //Loop through each pixel 
     if(i<0)continue; 
     for(int j=y; j<y+scale; j++){ 
      if(j<0)continue; 
      g.fillRect(x, y, scale, scale); //Fill the rect to make the "pixel" 
     } 
    } 
} 

public void drawBytes(byte[] image, int x, int y, int width, int height, int scale, ColorPalette palette){ //Draw a byte[] image with given byte[], x/y coords, width/height, scale, and color palette 
    if(image.length < width * height/2){ //If the image is too small, exit 
     return; 
    } 
    for(int j=0; j<height; j++){ //Loop through each pixel 
     for(int i=0; i<width; i++){ 
      int index = (j * width + i)/2; //Get index 
      int value = image[index]; //get the byte 
      byte thing; //get the high or low value depending on even/odd 
      if(i % 2 == 0)thing = (byte)((value & 0b11110000) >>> 4); 
      else thing = (byte)(value & 0b00001111); 
      drawSquare((int)(x + scale * i), (int)(y + scale * j), scale, palette.getColor(thing)); //draw the pixel 
     } 
    } 
} 

Так есть более эффективный способ сделать эти byte[] массивы без необходимости BufferedImage-х? Или будет действительно не проблематично иметь несколько сотен BufferdImage, загруженных в память?

EDIT: Я также пробовал использовать методы no-BufferedImage, но с g как один большой BufferedImage, которому все визуализируется, и затем отображается на Canvas. Основное различие заключается в том, что g.fillRect(... в этом методе изменен на g.setRGB(..., но он был так же медленным.

EDIT: Образы, с которыми я имею дело, - 16x16 и 32x32 пикселя.

+0

BufferedImage занимает очень мало памяти, кроме растровых данных (массив байтов). Я сомневаюсь, что вы заметили какую-либо разницу в этой оптимизации. – VGR

ответ

0

Если использование памяти является вашей главной задачей, я бы использовал BufferedImage s с IndexColorModel (TYPE_BYTE_BINARY). Это идеально отразило бы ваши byte[] image и ColorPalette, и потерять очень мало памяти. Они также будут достаточно быстры, чтобы рисовать.

Этот подход будет использовать около 1/8th памяти, используемой первоначального использованием TYPE_INT_RGBBufferedImage с, потому что мы сохранить 4 бита на пиксель, вместо 32 бита (AN int составляет 32 бит) на пиксель (плюс некоторые накладные расходы для палитры, конечно).

public static void main(String[] args) { 
    byte[] palette = new byte[16 * 3]; // 16 color palette without alpha 
    byte[] pixels = new byte[(16 * 16 * 4)/8]; // 16 * 16 * 4 bit 

    Random random = new Random(); // For test purposes, just fill arrays with random data 
    random.nextBytes(palette); 
    random.nextBytes(pixels); 

    // Create ColorModel & Raster from palette and pixels 
    IndexColorModel cm = new IndexColorModel(4, 16, palette, 0, false, -1); // -1 for no transparency 
    DataBufferByte buffer = new DataBufferByte(pixels, pixels.length); 
    WritableRaster raster = Raster.createPackedRaster(buffer, 16, 16, 4, null); 

    // Create BufferedImage from CM and Raster 
    final BufferedImage image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); 

    System.out.println("image: " + image); // "image: [email protected]: type = 12 ..." 

    SwingUtilities.invokeLater(new Runnable() { 
     @Override 
     public void run() { 
      JFrame frame = new JFrame("Foo"); 
      frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 
      frame.add(new JLabel(new ImageIcon(image))); 
      frame.pack(); 
      frame.setVisible(true); 
     } 
    }); 
} 

Приведенный выше код будет создавать полностью непрозрачным (Transparency.OPAQUE) изображения, которые будут занимать весь блок размером 16 × 16 пикселя.

Если вы хотите, чтобы прозрачная прозрачность (Transparency.BITMASK) была прозрачной, все пиксели либо полностью непрозрачны, либо просто прозрачны, просто измените последний параметр в IndexColorModel на индекс палитры, который вы хотите полностью прозрачно.

int transparentIndex = ...; 
IndexColorModel cm = new IndexColorModel(4, 16, palette, 0, false, transparentIndex); 
// ...everything else as above 

Это позволит вашим спрайтам иметь любую форму, которую вы хотите.

Если вы хотите получить полупрозрачные пиксели (Transparency.TRANSLUCENT), пиксели могут быть полупрозрачными, вы также можете получить это. Затем вам нужно будет изменить массив palette на 16 * 4 записей и включить образец для альфа-значения в качестве 4-го образца для каждой записи (в четыре раза). Затем вызовите конструктор IndexColorModel с последним параметром, установленным в true (hasAlpha):

byte[] palette = new byte[16 * 4]; // 16 color palette with alpha (translucency) 

// ... 

IndexColorModel cm = new IndexColorModel(4, 16, palette, 0, true); // true for palette with alpha samples 
// ...everything else as above 

Это позволит более плавные градиенты между прозрачными и непрозрачными частями спрайтов. Но только с 16 цветами в палитре у вас не будет много записей для прозрачности.

Обратите внимание, что можно повторно использовать Raster с и IndexColorModel сек здесь, во всех приведенных выше примерах, чтобы сохранить дальше памяти для изображений с использованием той же палитры, или даже изображения, используя одни и те же данные изображения с различными палитрами , Тем не менее, есть одно предостережение, то есть разделение изображений на растрах будет «живыми» взглядами друг друга, поэтому, если вы внесете какие-либо изменения в один, вы измените их все. Но если ваши изображения никогда не будут изменены, вы можете воспользоваться этим фактом.

Сказанное выше - это компромисс между сохранением памяти и «разумной» производительностью. Если важна производительность (т. Е. Кадров в секунду), просто игнорируйте использование памяти и создавайте BufferedImage s, которые совместимы с вашей графической картой/встроенной компоновкой пикселя OS. Вы можете сделать это, используя component.createCompatibleImage(...) (где component - подкласс JComponent) или gfxConfig.createCompatibleImage(...) (где gfxConfig является GraphicsConfiguration получен локальный GraphicsEnvironment).

+0

Есть ли способ сделать это, но разрешить прозрачные пиксели? – user2649681

+0

PS: В конструкторе 'IndexColorModel' произошла ошибка. Последний параметр должен быть '-1' для прозрачности. – haraldK

+0

См. Обновление прозрачности. – haraldK

Смежные вопросы