Привет, Хабр.
Пару месяцев назад мой товарищ предложил создать коллекцию NFT и загрузить на opensea.io, идея мне показалась интересной и … Задача:
сгенерировать 10k уникальных изображений (1500X1500 px)
автоматически выложить их на сайт opensea.io
Итак:
1. Немного профильтровав интернет (candy-machine и пр.) я решил что лучше и проще написать самому. среда - IntelliJ IDEA , KOTLIN.
Суть понятна : есть слои, которые нужно рандомно выбирая накладывать друг на друга.
Сколько нужно слоёв для 10k уникальных (не повторяющихся) изображений?
Из теории вероятностей я припоминаю, что будет «пересечение» с высокой вероятностью из выборки вариантов в 30 раз больше — т.е. из 300k.
Быстренько на Inkscape я сделал «корову» (в детстве это рисовали), без претензии на «искусство» (ибо художественных дарований не имею, и это всё-таки тест) - 20 картинок фон, и 11 слоёв по 7 картинок = 20*7^11= 39546534860 вариантов )) более чем достаточно для уникальности. Приводить картинки и описывать подробно смысла не вижу.
Теперь код.
Создаём массивы, считываем и загружаем в них файлы картинок
import java.awt.Color import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO import kotlin.random.Random fun main() { val sloj0 = arrayOfNulls<BufferedImage>(99);val sloj1 = arrayOfNulls<BufferedImage>(99); val sloj2 = arrayOfNulls<BufferedImage>(99) val sloj3 = arrayOfNulls<BufferedImage>(99) ; val sloj4 = arrayOfNulls<BufferedImage>(99) ; val sloj5 = arrayOfNulls<BufferedImage>(99) val sloj6 = arrayOfNulls<BufferedImage>(99) ; val sloj7 = arrayOfNulls<BufferedImage>(99) ; val sloj8 = arrayOfNulls<BufferedImage>(99) val sloj9 = arrayOfNulls<BufferedImage>(99) ; val sloj10 = arrayOfNulls<BufferedImage>(99) ; val sloj11 = arrayOfNulls<BufferedImage>(99) // файлы изображений val file01 = File("D:\\NFT\\0\\1.png"); val file02 = File("D:\\NFT\\0\\2.png"); val file03 = File("D:\\NFT\\0\\3.png"); val file04 = File("D:\\NFT\\0\\4.png"); val file05 = File("D:\\NFT\\0\\5.png"); val file06 = File("D:\\NFT\\0\\6.png"); val file07 = File("D:\\NFT\\0\\7.png"); val file08 = File("D:\\NFT\\0\\8.png"); val file09 = File("D:\\NFT\\0\\9.png"); val file010 = File("D:\\NFT\\0\\10.png"); val file011 = File("D:\\NFT\\0\\11.png"); val file012 = File("D:\\NFT\\0\\12.png"); val file013 = File("D:\\NFT\\0\\13.png"); val file014 = File("D:\\NFT\\0\\14.png"); .................. val file111 = File("D:\\NFT\\11\\1.png"); val file112 = File("D:\\NFT\\11\\2.png"); val file113 = File("D:\\NFT\\11\\3.png"); val file114 = File("D:\\NFT\\11\\4.png"); val file115 = File("D:\\NFT\\11\\5.png"); val file116 = File("D:\\NFT\\11\\6.png"); val file117 = File("D:\\NFT\\11\\7.png"); // в массивы sloj0 [1]= ImageIO.read(file01);sloj0 [2]= ImageIO.read(file02);sloj0 [3]= ImageIO.read(file03);sloj0 [4]= ImageIO.read(file04);sloj0 [5]= ImageIO.read(file05); sloj0 [6]= ImageIO.read(file06);sloj0 [7]= ImageIO.read(file07); sloj0 [8]= ImageIO.read(file08);sloj0 [9]= ImageIO.read(file09); sloj0 [10]= ImageIO.read(file010); sloj0 [11]= ImageIO.read(file011);sloj0 [12]= ImageIO.read(file012);sloj0 [13]= ImageIO.read(file013);sloj0 [14]= ImageIO.read(file014);sloj0 [15]= ImageIO.read(file015); sloj0 [16]= ImageIO.read(file06);sloj0 [17]= ImageIO.read(file017);sloj0 [18]= ImageIO.read(file018);sloj0 [19]= ImageIO.read(file019); sloj0 [20]= ImageIO.read(file020); ..................... sloj11 [1]= ImageIO.read(file111);sloj11 [2]= ImageIO.read(file112);sloj11 [3]= ImageIO.read(file113);sloj11 [4]= ImageIO.read(file114);sloj11 [5]= ImageIO.read(file115); sloj11 [6]= ImageIO.read(file116);sloj11 [7]= ImageIO.read(file117);
Создаем пустое изображение размером 1500Х1500 пикселей:
val width1 = 1500 val height1 = 1500 val resultPng1 = BufferedImage(width1, height1, BufferedImage.TYPE_INT_RGB)
Начало цикла (10000 картинок) и перенос фона (рандомный выбор из 20):
for (x1 in 1..10000) { // перенос фона var go0 = Random.nextInt(1, 20) for (x in 0 until 1500) { for (y in 0 until 1500) { sloj0 [go0]?.let { Color(it.getRGB(x, y)).getRGB() }?.let { resultPng1.setRGB(x, y, i}t) } } }
1 слой (рандомный выбор из 7):
// 1 слой var go1 = Random.nextInt(1, 7) for (x in 0 until 1500) { for (y in 0 until 1500) { // цвет текущего пикселя val color1 = sloj1 [go1] .let { it?.let { it1 -> Color(it1.getRGB(x, y)) } } // каналы этого цвета val red1: Int? = color1?.getRed() val green1: Int? = color1?.getGreen() val blue1: Int? = color1?.getBlue() val grey1 = ((green1?.plus(blue1!!))?.plus(red1!!)) if (grey1 != null) { if( grey1>0) { //устанавливаем этот цвет в текущий пиксель результирующего изображения sloj1 [go1]?.let { Color(it.getRGB(x, y)).getRGB() }?.let { resultPng1.setRGB(x, y, it) } } } } }
Тут есть момент. Поскольку при преобразовании прозрачный «цвет» будет как RGB(0,0,0) черный, его исключаем при наложении слоя. Можно, конечно было бы что-то при думать с переносом (0,0,0) но проще его просто не использовать, разницы между (0,0,0) и (1,1,1) никто не увидит.
Дальше аналогично.
Путь, полное имя результирующего файла и сохраняем:
var xyz1="D:\\NFT\\result1\\MerryCowF #" +x1 +"." +"png" //путь и полное имя файла // Сохраняем результат в новый файл val output1 = File(xyz1) ImageIO.write(resultPng1, "png", output1) }
С кодом всё.
2. Загрузка на opensea
Используя макрос для записи мыши и клавиатуры MiniMouseMacro.exe, сделал цикл.
В Exel 2 столбика, имя (+1 перетаскиванием угла ячейки) и «цена» (скопировал несколько ячеек, и вставка в выделенный столбик) и папка с картинками чтобы кликом была вверху следующая картинка.
Однако opensea при слишком быстрой загрузке выбрасывает капчу, не прогружает иногда странички, выдаёт «Oops, something went wrong» …
VPN помогает, с капчой можно было бы побороться, но шкурка выделки не стоит (проще немного притормозить цикл).
Всё до конца не загружал … для тестовой работы достаточно.
Понимаю код можно было сделать проще и лучше, ну как есть, смысла переделывать я не вижу ( и так работает).
И, поскольку, мой товарищ (который взял бы на себя художественную часть) утратил интерес к теме, буду рад предложениям о каком то взаимодействии — e-mail manlib@yandex.ru …
Спасибо за внимание и не судите строго. Это первая публикация, хотя я давний читатель.
