javascript

Создаю новое направление в изобразительном искусстве — программный LLM ART

  • среда, 9 апреля 2025 г. в 00:00:13
https://habr.com/ru/articles/898670/

Мы посмотрим и на Корбена Далласа и Лилу из фильма «Пятый элемент», на сюжет из фильма «Люди в черном» и другие интересные арты.

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

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

Но это пассивное поведение.

И тут возникает вопрос. А что, если заставить модель проявить себя в активном действии, творческой самореализации? Пока не будем говорить о самых последних версиях GPT-4o и Gemini 2.0 Flash, которые нативно поддерживают непосредственную генерацию изображения по запросу, так же как и текст.

Приведу близкую аналогию. Допустим, мы художнику завязываем глаза, даем в руки кисть и краски и формулируем задание, согласно которому он должен на холсте всё это изобразить максимально точно.

То же самое мы можем проделать с LLM, дав инструкцию нарисовать заданный сюжет, а вместо кисти и красок будет код. Например, HTML5 и API canvas.

Насколько хорошо развит канал преобразования внутренних визуальных представлений в программную реализацию и представление на экране?

Некоторое время назад я уже писал статью на аналогичную тему «LLM и ее невозможный ASCII art», в которой обычная LLM создавала изображения в виде ASCII art, но делала это с очень большим трудом за несколько последовательных шагов улучшения.
Я предполагаю, что современные мультимодальные модели должны в разы, если не на порядок лучше рисовать на canvas.

Выясняется, что среди моделей есть как аутсайдеры, провалившие задание, так и гении, впрочем, как и среди людей. А одна из них, по сравнению с другими, ну просто Леонардо да Винчи.

Начинаем эксперимент

Deepseek и Qwen не справились.
GPT-4o не справились.
Llama 3.2 90B Vision Instruct не справились.
Gemini-2.0-Flash не справились.
ChatGPT не справились.
OpenAI o1-mini не справились.
Grok-2 не справились.
Claude 3.5 Haiku не справились.

Gemini-2.5-Pro подаёт хорошие надежды и переходит в разряд лидеров.

Но лучшая модель, безусловный лидер, оправдавший мои самые смелые надежды, это оказалась Claude 3.5 sonnet.
Её творчество и будет предметом данной статьи. Модель использовалась без опции доступа в интернет.

Самое впечатляющее в этих артах даже не само изображение, а его динамика. Да, на изображениях происходит процесс изменения композиции со временем, всё движется, меняется, перемещается и трансформируется в соответствии с сюжетом. Это, своего рода, короткие видеосюжеты, созданные программно по определённому замыслу, в котором есть выраженная идея.

Первый арт

Промпт:

Напиши HTML-код, который рисует изображение Малдера и Скалли из фильма «Секретные материалы» на canvas размером 1200x600 пикселей. Рисунок создается программно с использованием только нативного JavaScript и Canvas, без внешних библиотек и изображений.

Полученный код изображает агентов Малдера и Скалли на фоне звездного неба, по которому проплывает красивая летающая тарелка. Малдер держит в руке пистолет, а Скалли светит фонариком в разные стороны.

Важно отметить, что арты я создал не одним промптом сразу. Первый промпт создаёт начальный полный арт, который может быть не столь совершенным и детальным, но является хорошей основой для других шагов, направленных на совершенствование этого арта.

Поэтому вторым промптом я усовершенствовал Малдера и Скалли.

Корректировка арта.

У нас имеется следующий HTML код, рисующий Малдера и Скалли из фильма «Секретные материалы».(Здесь код функций рисования Малдера и Скалли).
Сделай Малдера и Скалли более реалистичными, детальными и красивыми.

Все картинки кликабельны.

После этого арт стал значительно более детальным и красивым.
Обратите внимание на прорисованные черты лица и мельчайшие детали персонажей.
Модель адекватно изменяет и детализирует код в соответствии с инструкциями в промптах. Таким методом, в принципе, можно создавать очень впечатляющие арты, насыщенные деталями и динамическими элементами и сценами.
Значения отдельных параметров кода я немного подправил вручную для исправления мелких огрехов.
Вместе с усовершенствованием Малдера и Скалли модель самостоятельно улучшила фон, добавив клубящийся туман и ночную луну.

В конце концов, и знаменитые художники порой многократно дорисовывали и перерисовывали детали своих картин. Например, Илья Репин дописывал свои картины, выставленные прямо в Третьяковской галерее, а Константин Савицкий дорисовал медведицу и трёх медвежат на картине Ивана Шишкина «Утро в сосновом лесу».

Посмотреть арт в динамике в песочнице. (на страничке нужно выбрать — Yes, proceed to preview).

Здесь, для примера, код функция рисования агента Малдера
function drawMulder() {
	ctx.save();
	ctx.translate(400, 500);

	// Цвета для Малдера
	const skinTone = "#e0c8b0";
	const suitColor = "#333340";
	const hairColor = "#3a2d1a";

	// Ноги (под телом для правильного перекрытия)
	ctx.fillStyle = suitColor;
	// Левая нога
	ctx.beginPath();
	ctx.moveTo(-20, -30);
	ctx.lineTo(-25, 40);
	ctx.lineTo(-5, 40);
	ctx.lineTo(-7, -30);
	ctx.closePath();
	ctx.fill();

	// Правая нога
	ctx.beginPath();
	ctx.moveTo(7, -30);
	ctx.lineTo(5, 40);
	ctx.lineTo(25, 40);
	ctx.lineTo(20, -30);
	ctx.closePath();
	ctx.fill();

	// Обувь
	ctx.fillStyle = "#111";
	// Левый ботинок
	ctx.beginPath();
	ctx.moveTo(-25, 40);
	ctx.lineTo(-30, 45);
	ctx.lineTo(-5, 45);
	ctx.lineTo(-5, 40);
	ctx.closePath();
	ctx.fill();

	// Правый ботинок
	ctx.beginPath();
	ctx.moveTo(5, 40);
	ctx.lineTo(5, 45);
	ctx.lineTo(30, 45);
	ctx.lineTo(25, 40);
	ctx.closePath();
	ctx.fill();

	// Тело (пиджак)
	ctx.fillStyle = suitColor;
	ctx.beginPath();
	ctx.moveTo(-5, -110);
	ctx.lineTo(-22, -100);
	ctx.lineTo(-24, -30);
	ctx.lineTo(24, -30);
	ctx.lineTo(25, -98);
	ctx.lineTo(5, -110);
	ctx.closePath();
	ctx.fill();

	// Белая рубашка и галстук
	ctx.fillStyle = "#fff";
	ctx.beginPath();
	ctx.moveTo(-10, -100);
	ctx.lineTo(-5, -30);
	ctx.lineTo(5, -30);
	ctx.lineTo(10, -100);
	ctx.closePath();
	ctx.fill();

	// Галстук
	ctx.fillStyle = "#a32f2f";
	ctx.beginPath();
	ctx.moveTo(0, -100);
	ctx.lineTo(-4, -30);
	ctx.lineTo(0, -35);
	ctx.lineTo(4, -30);
	ctx.closePath();
	ctx.fill();

	// Левая рука
	ctx.fillStyle = suitColor;
	ctx.beginPath();
	ctx.moveTo(-20, -100);
	ctx.lineTo(-45, -50);
	ctx.lineTo(-40, -40);
	ctx.lineTo(-15, -80);
	ctx.closePath();
	ctx.fill();

	// Левая кисть
	ctx.fillStyle = skinTone;
	ctx.beginPath();
	ctx.moveTo(-45, -50);
	ctx.lineTo(-50, -35);
	ctx.lineTo(-40, -35);
	ctx.lineTo(-40, -40);
	ctx.closePath();
	ctx.fill();

	// Правая рука с пистолетом
	ctx.save();
	const armAngle = Math.sin(frameCount * 0.02) * 0.1;
	ctx.translate(17, -80);
	ctx.rotate(Math.PI / 4 + armAngle*2);

	// Рукав
	ctx.fillStyle = suitColor;
	ctx.beginPath();
	ctx.moveTo(-10, -18);
	ctx.lineTo(30, -8);
	ctx.lineTo(32, 5);
	ctx.lineTo(-5, -3);
	ctx.closePath();
	ctx.fill();

	// Манжет
	ctx.fillStyle = "#fff";
	ctx.beginPath();
	ctx.moveTo(25, -6);
	ctx.lineTo(35, -4);
	ctx.lineTo(36, 0);
	ctx.lineTo(26, -2);
	ctx.closePath();
	ctx.fill();

	// Кисть правой руки
	ctx.fillStyle = skinTone;
	ctx.beginPath();
	ctx.moveTo(30, -4);
	ctx.lineTo(38, -2);
	ctx.lineTo(40, 3);
	ctx.lineTo(32, 1);
	ctx.closePath();
	ctx.fill();

	// Пистолет
	ctx.fillStyle = "#111";
	// Основная часть пистолета
	ctx.beginPath();
	ctx.moveTo(38, -2);
	ctx.lineTo(65, 0);
	ctx.lineTo(65, -5);
	ctx.lineTo(35, -7);
	ctx.closePath();
	ctx.fill();

	// Рукоять пистолета
	ctx.beginPath();
	ctx.moveTo(42, -3);
	ctx.lineTo(42, 5);
	ctx.lineTo(48, 5);
	ctx.lineTo(48, -2);
	ctx.closePath();
	ctx.fill();

	// Спусковой крючок
	ctx.beginPath();
	ctx.moveTo(48, 0);
	ctx.lineTo(50, 2);
	ctx.lineTo(52, 0);
	ctx.closePath();
	ctx.fill();

	ctx.restore();

	// Голова
	ctx.fillStyle = skinTone;
	ctx.beginPath();
	ctx.ellipse(0, -130, 16, 20, 0, 0, Math.PI * 2);
	ctx.fill();

	// Волосы
	ctx.fillStyle = hairColor;
	ctx.beginPath();
	ctx.moveTo(-20, -150);
	ctx.quadraticCurveTo(15, -157, 20, -142);
	ctx.lineTo(-17, -135);
	ctx.quadraticCurveTo(-10, -100, -25, -130);

	ctx.closePath();
	ctx.fill();

	// Детали лица
	// Глаза
	ctx.fillStyle = "#fff";
	ctx.beginPath();
	ctx.ellipse(-6, -131, 5, 2, 0, 0, Math.PI * 2);
	ctx.fill();

	ctx.beginPath();
	ctx.ellipse(8, -131, 5, 2, 0, 0, Math.PI * 2);
	ctx.fill();

	// Зрачки
	ctx.fillStyle = "#222";
	ctx.beginPath();
	ctx.ellipse(-6, -131, 2, 2, 0, 0, Math.PI * 2);
	ctx.fill();

	ctx.beginPath();
	ctx.ellipse(8, -131, 2, 2, 0, 0, Math.PI * 2);
	ctx.fill();

	// Брови
	ctx.strokeStyle = hairColor;
	ctx.lineWidth = 1.5;
	ctx.beginPath();
	ctx.moveTo(-12, -136);
	ctx.quadraticCurveTo(-6, -139, -2, -136);
	ctx.stroke();

	ctx.beginPath();
	ctx.moveTo(13, -136);
	ctx.quadraticCurveTo(6, -139, 4, -136);
	ctx.stroke();

	// Нос
	ctx.strokeStyle = "#aa8877";
	ctx.lineWidth = 1;
	ctx.beginPath();
	ctx.moveTo(0, -131);
	ctx.lineTo(0, -123);
	ctx.lineTo(3, -120);
	ctx.stroke();

	// Рот
	ctx.beginPath();
	ctx.moveTo(-5, -118);
	ctx.quadraticCurveTo(0, -117, 6, -118);
	ctx.stroke();

	ctx.restore();
}

Второй арт

Промпт:

Напиши HTML-код, который рисует интересный сюжет из фильма «Люди в черном» на canvas размером 1200x600 пикселей. Рисунок создается программно с использованием только нативного JavaScript и Canvas, без внешних библиотек и изображений.

Полученный код изображает агента в черном и пришельца с миндалевидными глазами на фоне ночного города, в небе которого двигаются стилизованные туманности и галактики. Агент нажимает периодически кнопку на нейрализаторе, происходит знаменитая зелёная вспышка, которая эффектными бликами отражается в больших глазах пришельца и окнах домов ночного города. После чего пришелец впадает в транс и безвольно циклически двигается из стороны в сторону.

Глядя на этот завораживающий процесс, можно бесконечно медитировать и получать удовольствие.
Этот арт тоже получен за 2 шага после корректирующего персонажей промпта.

Посмотреть арт в динамике в песочнице.

Третий арт

Промпт:

Напиши HTML-код, который рисует Корбена Далласа и Лилу из фильма «Пятый элемент» на canvas размером 1200x600 пикселей. Рисунок создается программно с использованием только нативного JavaScript и Canvas, без внешних библиотек и изображений.

Полученный код изображает Корбена Далласа и Лилу рядом с такси на фоне ночного города, в небе которого проносятся другие летающие автомобили. Заметно дыхание персонажей и легкие телодвижения.

Посмотреть арт в динамике в песочнице.

Модель не просто исполняет инструкции промпта. Она придумывает сюжет, создаёт композицию, анимирует персонажей, реализует динамический фон. А также грамотно подбирает цвета и градиенты, применяет реалистичные и согласованные движения и перемещения элементов композиции.

Ну и напоследок, раз уж я сравнил эту модель с Леонардо да Винчи, пусть она попробует нарисовать знаменитый рисунок Леонардо «Витрувианский человек».

Модель подошла творчески и решила поглубже войти в образ и в коде применила такие нюансы, как «эффект выцветания по краям», «небольшие трещины пергамента», а также знаменитые высказывания на латыни типа «homo ad quadratum et circulum», но справа налево, как иногда делал сам Леонардо.
Ну и подпись тоже в обратном порядке. Хороший ход.

Посмотреть арт в песочнице.

P.S. Все HTML-файлы с артами можно скачать архивом здесь и посмотреть локально.