Привет, Хабр.

Пару месяцев назад мой товарищ предложил создать коллекцию NFT и загрузить на opensea.io, идея мне показалась интересной и … Задача:

  1. сгенерировать 10k уникальных изображений (1500X1500 px)

  2. автоматически выложить их на сайт 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

Спасибо за внимание и не судите строго. Это первая публикация, хотя я давний читатель.

Комментарии (22)


  1. APXEOLOG
    25.06.2022 23:46
    +15

    Выглядит как попытка разрекламировать свои бесполезные NFT.

    Про алгоритмическую ценность Вашего скрипта говорить нет смысла, но можно хотя бы было написать красиво - не зря же Вы Котлин брали.


    1. GilgulA Автор
      27.06.2022 07:41
      -1

      Абсолютно не было цели рекламы, я могу удалить это все 1 нажатием на кнопку.

      Так же без претензии на особую ценность. Может быть, кому-то будет интересно.


  1. fire_engel
    25.06.2022 23:57
    +17

    здорово, конечно, но зачем об этом писать? это ультра-элементарная задача, а nft -- скам


    1. GilgulA Автор
      27.06.2022 08:27

      Ну это для вас ультра-элементарно а для кого то нет, может быть , кому то поможет, покажите мне документацию по этому вопросу, как работать с изображениями на котлине подробненько, дайте ссылку.

      Да все, что происходит с НФТ. В основном обман, но это не значит, что остальной 1 процент умрёт. Да, ценность этого занятия близка к коллекционированию марок. Кому от этого плохо?


      1. Nbx
        27.06.2022 17:06
        +1

        Кому от этого плохо?
        Наверное тем кого на этом обманули.


  1. Andrus_Trash
    26.06.2022 10:08
    +3

    Хотя бы сами арты были качественные. Или исполнение, например, сгенеренные собственно натрененной нейросетью красивые психоделические абстракции. а так - пустая трата электроэнергии. Коровка.


    1. GilgulA Автор
      27.06.2022 08:13

      Странно, вроде бы написал "без претензии на художественную ценность", это так, шутка юмора. Статья была написана с целью сэкономить кому-нибудь, возможно время.


  1. Solovej
    26.06.2022 16:49

    А по моему опыту opensea.io всегда выбрасывает Каптчу.


    1. GilgulA Автор
      27.06.2022 07:46

      С IP Великобритании или Венгрии, нет, не всегда.


  1. lexx59
    26.06.2022 21:56
    +2

    NFT = купи значок, что ты дурачок


    1. abcolut
      27.06.2022 07:57

      Что вас побуждает писать подобные комментарии? Ведь они злые, и не несут ни какой пользы?


      1. lexx59
        27.06.2022 10:14

        Что вас побуждает писать подобные комментарии? Ведь они злые, и не несут ни какой пользы?


      1. Nbx
        27.06.2022 17:08

        Очень полезный комментарий. Повышает уровень информированности в области цифрового мошенничества.


  1. osmanpasha
    27.06.2022 07:44
    +1

    Качество кода ужасное


    1. GilgulA Автор
      27.06.2022 08:04
      -4

      Вам шашечки или ехать? ))

      Я же написал в конце переписывать не вижу смысла. Потому что и так работает.

      Если кто-то страдает перфекционизмом, но это его проблемы.


      1. Borz
        27.06.2022 09:03
        +1

        какой посыл у статьи для читателя? Показать "смотри чё умею!"? Или поделиться кодом? Потому что статья уровня лытдыбр и явно не для хабра


        1. GilgulA Автор
          27.06.2022 09:10
          -3

          Ещё раз повторюсь, смысл статьи сэкономить кому-то время. Если вы считаете, что есть где хорошо описано, дайте ссылку


  1. GilgulA Автор
    27.06.2022 14:10
    -1

    Удивляют негативные комментарии. Прежде чем такое писать. Дайте ссылочку в интернете. Где все описано подробненько по этому вопросу, а потом уже что-то там...


    1. Borz
      27.06.2022 14:57

      статья несколько сумбурная. Есть часть про генерацию коллекции NFT "из одной картинки". Есть часть про "автоматическую" загрузку NFT на opensea. И "вскользь" упоминается, что для обхода капчи можно использовать VPN
      При этом, озвучено, что это одна задача, хотя это две задачи

      ЗЫ: сам ни статью ни автора не минусил, если что...


      1. GilgulA Автор
        27.06.2022 16:19

        Что не так?

        Ну, как бы результат продемонстрирован ..... Без каких либо коммерческих целей.

        С капчами. Любыми можно конечно бороться. Но это уже другая статья. Если мы снимаем картинку, можем её анализировать. Только это более серьёзная работа, в данном случае шкурка не стоит выделки


    1. osmanpasha
      28.06.2022 04:31

      А чего удивляться? Качество статьи может объективно оценить только выборка читателей, а мнение автора по поводу его работы будет предвзятое и необъективное.

      На что ссылочки вы хотите? На то, как генерировать картинки с помощью стандартного API jdk? Дак миллион таких статей, начиная с официальной документации, да и задача-то довольно простая. Более того, BufferedImage умеет нормально работать с прозрачностью, так что единственная неочевидная проблема, которая описана в статье, вызвана неумением использовать выбранный инструмент. А больше ничего в статье и нет.


      1. GilgulA Автор
        28.06.2022 06:51
        -1

        Есть люди, которые написали "спасибо"

        С картинками никогда дел не имел, поэтому могу чего-то не знать .