BufferedImage — рисуем пиксельное сердце

Всем привет. Сегодня будет маленькая заметка. Сидя поздним вечером дома и листая youtube я увидел, что много кто делает пиксельные игры, рисует пиксельный арт. Это сейчас в тренде. Вот и подумал, а почему бы мне не сделать такое же для блога?

Не буду долго расписывать про то, как работает генерация графики в Java, или про то, почему мое решение не лучшее. Просто расскажу как сделать пиксельное сердце.

BufferedImage и imageIO

Все что надо знать — это то, что BufferedImage по сути отвечает за формирование изображения, а ImageIO — за запись и чтение. Поэтому сначала набросаем метод по записи картинки

fun writeImage(img: BufferedImage, file: String) {
    val imagethread = Thread(Runnable {
            ImageIO.write(img, File(file).extension, File(file))
        })
    try {
        imagethread.start()
    } catch (ex: Exception) {
        ex.printStackTrace()
        imagethread.interrupt()
    }
}

Тут все просто. На вход принимаем два параметра — нашу пиксельную картинку и путь к файлу. ImageIO блокирующий метод, поэтому тут он в отдельном потоке. В случае ошибки мы выбрасываем исключение и тормозим поток. Теперь дело за самим формированием изображения.

fun drawPixel(x:Int, y:Int, red:Int, green:Int, blue: Int, image: BufferedImage) {
    image.setRGB(x, y, Color(red,green,blue).rgb)
}

Метод drawPixel как следует из названия рисует пиксель по координате (x, y) с заданным цветом — red, green. blue. Для записи информации о пикселе используем метод setRGB у BufferedImage, куда в качестве цвета передаем Color() с нашими цветами.

Напомню, что цвет по каждому каналу можно указывать в диапазоне от 0 до 255. Теперь напишем метод, который будет закрашивать небольшую область, имитируя большой размер пикселя.

fun drawTile(startX: Int, startY: Int, size: Int, red: Int, green: Int, blue: Int, image: BufferedImage) {
    for (posX in startX until startX+size) {
        for (posY in startY until startY+size) {
            drawPixel(posX,posY,red,green,blue,image)
        }
    }
}

Функция drawTile принимает параметры — начальную позицию, размер плитки, три цвета и изображение, куда это будет отрисовываться. В цикле мы проходимся по координатам (X, Y) и рисуем наш большой пиксель с помощью drawPixel, согласно указанному размеру плитки.

Рисуем пиксельное сердце

Настало время для главного метода. В него мы передаем двумерный массив, в котором:

  • вместо цифры 1 рисуется красный цвет
  • если встречается 2, то красим в темно-красный
  • вместо тройки — будет фиолетовый
  • а цифра 4 дает нам белый цвет
fun drawIcon(pixels: Array<List<Int>>, image: BufferedImage) {
    pixels.forEachIndexed { posY, rowElement ->
        rowElement.forEachIndexed { posX, colElement ->
            when(colElement) {
                1 -> drawTile(posX*10,posY*10,10,255,2,0, image) // red
                2 -> drawTile(posX*10,posY*10,10,156,25,31, image) // dark red
                3 -> drawTile(posX*10,posY*10,10,255,255,255, image) // violet
                else -> drawTile(posX*10,posY*10,10,23,0,44, image) // white
            }        
        }
    }
}

Здесь я задал фиксированный размер плитки в 10 пикселей вот тут — posX*10, но можно добавить его в виде параметра в метод drawIcon.

Проходимся по каждому массиву в подмассиве, и закрашиваем все поочередно. Теперь осталось составить массив из цифр

// prints pixel heart
val map = arrayOf(
    listOf(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),
    listOf(0,0,0,1,1,1,0,0,0,1,2,2,0,0,0),
    listOf(0,0,1,3,3,1,1,0,1,1,1,2,2,0,0),
    listOf(0,1,3,3,1,1,1,1,1,1,1,1,2,2,0),
    listOf(0,1,3,1,1,1,1,1,1,1,1,1,2,2,0),
    listOf(0,1,1,1,1,1,1,1,1,1,1,1,2,2,0),
    listOf(0,1,1,1,1,1,1,1,1,1,1,1,2,2,0),
    listOf(0,0,1,1,1,1,1,1,1,1,1,2,2,0,0),
    listOf(0,0,0,1,1,1,1,1,1,1,2,2,0,0,0),
    listOf(0,0,0,0,1,1,1,1,1,2,2,0,0,0,0),
    listOf(0,0,0,0,0,1,1,1,2,2,0,0,0,0,0),
    listOf(0,0,0,0,0,0,1,2,2,0,0,0,0,0,0),
    listOf(0,0,0,0,0,0,0,2,0,0,0,0,0,0,0),
    listOf(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),
)

Затем создать наше изображение, вызвать метод отрисовки и сохранить его на компьютере или в любом другом месте.

val heartImage = BufferedImage(150,150,TYPE_INT_RGB)
drawIcon(map,heartImage)
writeImage(heartImage,"D:/heart.bmp")

pixel graphic heart kotlin

Улучшения

Я специально не стал брать Graphics2D, потому что для моей задачи это через чур. Также вы можете заметить, я использовал тип TYPE_INT_RGB, но есть TYPE_INT_ARGB, тогда появится еще прозрачность. Еще можно сделать чтение массива цифр из файла, чтобы не вводить вручную в редакторе кода. Цвет закрашивания тоже можно выбрать свой, или сделать к примеру 7 штук — по цветам радуги.

Размер большого пикселя(плитки) можно пропорционально привязать к размеру создаваемого изображения. В общем, была бы фантазия, а сделать можно все что душе угодно!

Исходник можно найти по ссылке на github

Вывод

Вот таким нехитрым способом можно рисовать pixel графику и иконки. Надеюсь, статья была чем-то полезна для Вас! В следующий раз мы напишем редактор для пиксельной графики, добавим эффектов и еще много чего.

Всем пока!

Оставить ответ