Рубрики
Без рубрики

Использование Python для создания более 10 000 уникальных 8-битных световых мечей

Python отлично подходит для ряда вещей. Он поддерживает 1,4% Интернета, НАСА много использует! И ты С … Tagged с Python, Python3, ImageProcessing, Starwars.

Python отлично подходит для ряда вещей. Он способствует 1,4% Интернета , НАСА много использует! И вы можете использовать его в Создать искусство Анкет В честь День Звездных войн Я хотел создать программу, которая сгенерировала световые мечь и написал в Твиттере их один раз в день.

TL; DR

Я создал компьютерную программу, которая генерирует уникальный световой меч, сделанный из четырех разных частей (лезвие, рукоятка, племена и кнопка) и твиты в Твиттере раз в день вместе с некоторыми статистическими данными о световом мете.

У Люка ненадолго был красный световой меч

Начиная

Общая предпосылка этого сценария довольно проста. Он случайным образом выбирает четыре части светового меча, а затем собирает их вместе. Для достижения этого я использовал онлайн -инструмент под названием Пискель Чтобы генерировать все 8-битные детали. В то время как Пискель обычно используется для создания анимированных спрайтов для веб-сайтов, это один из лучших 8-битных художественных редакторов. Моя любимая особенность – это возможность зеркала линий. Это упрощает процесс изготовления симметричных световых мечей.

Как только я создал базовый набор частей, положите их в изображения каталог. Этот каталог имеет 5 папок, по одной для каждой части и выходной папки ( Световые мечты ). Это позволяет мне сохранить все организованное, а также помогает позже при генерации световых мечей.

images/
    blades/
        b1.png
        b2.png
    hilts/
        h1.png
        h2.png
    buttons/
        u1.png
    pommels/
        p1.png
    lightsabers/
        h1b2u1p1.png

Чтобы сделать все это, я установил три пакета Python. Я использую Подушка (Версия 7.1.1) Для всей обработки изображений, Tweepy (Версия 3.8.0), чтобы отправить твиты и Numpy (версия 1.18.3), чтобы сделать некоторую цветовую обработку, которую я буду обсуждать позже. В целом это очень простые требования.

# requirements.txt
numpy==1.18.3
Pillow==7.1.1
tweepy==3.8.0

Первая версия этого сценария была действительно такой простой. Позвоните Generate_lightSaber и это все. Далее я расскажу о том, как я сгенерировал Начальный световой меч (только лезвие и рукоять), тогда как я добавил кнопки и Pommels И наконец, как я твитнул финальное изображение Анкет

# lightsaber.py
import os, random
from pathlib import Path, PurePath
# This is actually Pillow but it's API is backwards compatible with the older PIL
from PIL import Image

def generate_lightsaber():
    # Will talk about this more next

if __name__ == " __main__":
    generate_lightsaber()

Добавление лезвий и рук

В моем первоначальном дизайне у меня были только лезвия и хлебы, так что я начал. Моя первая цель состояла в том, чтобы получить единый лезвие, а один рукоятник объединился и правильно выстроился на одном изображении. Я впервые добавил код, чтобы принести уникальную часть светового меча на основе приведенной выше макета каталога.

# lightsaber.py
# Set some constants to keep the code clean and consistent
IMAGE_PATH = Path('../images')
BLADE_PATH = IMAGE_PATH / 'blades'
HILT_PATH = IMAGE_PATH / 'hilts'
OUTPUT_PATH = IMAGE_PATH / 'lightsabers'

def fetch_lightsaber_parts():
    hilt = Path(f"{HILT_PATH}/{random.choice(os.listdir(HILT_PATH))}")
    blade = Path(f"{BLADE_PATH}/{random.choice(os.listdir(BLADE_PATH))}")

    return (hilt, blade)

Во -первых, у нас есть os.listdir () , который возвращает список имен файлов в данном каталоге. Список находится в произвольном порядке, и он не включает в себя специальные записи ». и «..» даже если они присутствуют в каталоге. Однако он включает в себя любые папки или специальные файлы (например, .ds_store в OS X), поэтому убедитесь, что ваша папка включает только файлы изображений. Мы передаем это имя файла в random.choice который выберет случайное имя файла. Наконец, мы создаем полный путь файла и возвращаем его как для рукоятью, так и для лезвия.

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

# lightsaber.py
def generate_lightsaber():
    blade_path, hilt_path = fetch_lightsaber_parts()

    # Open the images using Pillow
    blade = Image.open(blade_path, 'r')
    hilt = Image.open(hilt_path, 'r')

    # Open the output image. Twitter displays the entire image if it's 1024x512
    output = Image.new("RGB", (1024, 512), (255, 255, 255))

    # Paste the blade and hilt onto the output image. 
    output.paste(blade, blade_offset, mask=blade)
    output.paste(hilt, hilt_offset, mask=hilt)

    # Save the output image to disk
    img.save("{}/{}.png".format(OUTPUT_PATH, output_filename))

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

Вставьте изображения на вывод, вы увидите два аргумента, которые я еще не показал, _offset и Маска Анкет Смещение используется для определения того, где части размещаются на выходном изображении. Когда вы поймете это неправильно, могут произойти забавные вещи.

Самый крошечный из лезвий

Чтобы рассчитать смещение, где изображение помещается, мне нужно получить и сохранить все размеры изображений. Затем мы рассчитываем среднюю позицию по ширине (output_w - blade_w)//2 Это выравнивает его горизонтально на изображении. Это верно как для лезвия, так и для руководителя.

Чтобы позиционировать лезвие и рукоять вертикально, требуется немного больше математики. Для рукояти мы берем высоту выходного изображения (512px) и вычитаем высоту изображения рукояти (от 70 до 120 пикселей в зависимости от конструкции). Это заставляет рукоять начинаться внизу изображения.

# lightsaber.py
output_w, output_h = output.size
blade_w, blade_h = blade.size
hilt_w, hilt_h = hilt.size

blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)
hilt_offset = (output_h - hilt_h, (output_w - hilt_w) // 2)

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

Используя массив выше в качестве примера, если бы у нас был световой меж шириной 1PX и высотой 2PX (1PX, 1PX -лезвие), математика была бы.

blade_offset = ((4 - 1 - 1), (5 - 1) // 2 ) # (2, 2)
hilt_offset = ((4 - 1), (5 - 1) // 2) # (3, 2)

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

Добавление смещений рукояти и дополнительную информацию

При расчете смещения лезвия вы, возможно, заметили переменную hilt_offset Анкет В некоторых дизайнах та часть, где лезвие выходит из рукояти, не является началом светового меча. В этих случаях мне нужно было начать лезвие, чтобы начать «ниже», где начинается рукоять. Зная, что мне нужно сделать это, но также хранить метаданные на уклонах, лезвиях, кнопках и популярности в будущем, я создал файл manifest.py для хранения этих значений по умолчанию.

# manifest.py
MANIFEST = {
    "hilt": {
        "h1": {
            "offsets": {
                "blade": 5
            }
        }
    }
}

# lightsaber.py
from manifest import MANIFEST

def get_hilt_offset(hilt):
    return MANIFEST['hilt'][hilt]['offsets']['blade']

def generate_lightsaber():
    ...
    hilt_offset = get_hilt_offset(hilt)
    blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)

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

Сила должна держать эту вещь вместе

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

Добавление кнопок

Получив линию лопастей и вырубков, я понял, что достиг 10 000 уникальных световых мечей только с лезвиями и руками, которые мне нужно было бы разработать по 100 по 100 (всего 200). Мои 8-битные художественные навыки просто не в этом. Добавляя кнопки, я мог бы сократить уникальные конструкции, необходимые только 22 каждая (всего 66). Кнопки были добавлены к конечному изображению точно так же, как и лезвия.

# lightsaber.py
# Set some constants to keep the code clean and consistent
IMAGE_PATH = Path('../images')
BLADE_PATH = IMAGE_PATH / 'blades'
HILT_PATH = IMAGE_PATH / 'hilts'
BUTTON_PATH = IMAGE_PATH / 'buttons'
OUTPUT_PATH = IMAGE_PATH / 'lightsabers'

def fetch_lightsaber_parts():
    hilt = Path(f"{HILT_PATH}/{random.choice(os.listdir(HILT_PATH))}")
    blade = Path(f"{BLADE_PATH}/{random.choice(os.listdir(BLADE_PATH))}")
    button = Path(f"{BUTTON_PATH}/{random.choice(os.listdir(BUTTON_PATH))}")

    return (hilt, blade, button)

def generate_lightsaber():
    blade_path, hilt_path, button_path = fetch_lightsaber_parts()

    # Open the images using Pillow
    blade = Image.open(blade_path, 'r')
    hilt = Image.open(hilt_path, 'r')
    button = Image.open(button_path, 'r')

    # Open the output image. Twitter displays the entire image if it's 1024x512
    output = Image.new("RGB", (1024, 512), (255, 255, 255))

    output_w, output_h = output.size
    blade_w, blade_h = blade.size
    hilt_w, hilt_h = hilt.size
    button_w, button_h = button.size

    hilt_offset = get_hilt_offset(hilt)
    blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)
    hilt_offset = (output_h - hilt_h, (output_w - hilt_w) // 2)

    # Paste the blade and hilt onto the output image. 
    output.paste(blade, blade_offset, mask=blade)
    output.paste(hilt, hilt_offset, mask=hilt)
    output.paste(button, get_button_offset(hilt), mask=button)

    # Save the output image to disk
    img.save("{}/{}.png".format(OUTPUT_PATH, output_filename))

Основное осложнение с кнопками заключается в том, что их можно помещать в квадрат на рукоять, и этот квадрат отличается для каждого рукояти. Есть несколько разных способов сделать это автоматически (обнаруживать ребра, обнаружить ширину и высоту лезвия и т. Д.), Но для простоты я просто решил вручную рассчитать его для каждого рукояти и сохранить его в манифестном файле. X.

# manifest.py
"hilt": {
    "h1": {
        "offsets": {
            "blade": 0,
            "button": {
                "x": (8, 9),
                "y": (110, 111)
            },
        },
    },
}

# lightsaber.py
def get_button_offset(hilt):
    between_x = MANIFEST['hilt'][hilt]['offsets']['button']['x']
    between_y = MANIFEST['hilt'][hilt]['offsets']['button']['y']

    return (random.randint(between_x[0], between_x[1]), random.randint(between_y[0], between_y[1]))

Добавление Pommels

Еще раз, в изображение были добавлены племени почти так же, как и другие, только на этот раз мне нужно было обновить высоту как лезвия, так и рукоятки, чтобы приспособить Pommel. Сначала это не прошло хорошо.

Это то, что происходит, когда вы получаете смешанные +’и –

# lightsaber.py
# Set some constants to keep the code clean and consistent
IMAGE_PATH = Path('../images')
BLADE_PATH = IMAGE_PATH / 'blades'
HILT_PATH = IMAGE_PATH / 'hilts'
BUTTON_PATH = IMAGE_PATH / 'buttons'
POMMEL_PATH = IMAGE_PATH / 'pommels'
OUTPUT_PATH = IMAGE_PATH / 'lightsabers'

def fetch_lightsaber_parts():
    hilt = Path(f"{HILT_PATH}/{random.choice(os.listdir(HILT_PATH))}")
    blade = Path(f"{BLADE_PATH}/{random.choice(os.listdir(BLADE_PATH))}")
    button = Path(f"{BUTTON_PATH}/{random.choice(os.listdir(BUTTON_PATH))}")
    pommel = Path(f"{POMMEL_PATH}/{random.choice(os.listdir(POMMEL_PATH))}")

    return (hilt, blade, button, pommel)

def generate_lightsaber():
    blade_path, hilt_path, button_path, pommel_path = fetch_lightsaber_parts()

    # Open the images using Pillow
    blade = Image.open(blade_path, 'r')
    hilt = Image.open(hilt_path, 'r')
    button = Image.open(button_path, 'r')
    pommel = Image.open(pommel_path, 'r')

    # Open the output image. Twitter displays the entire image if it's 1024x512
    output = Image.new("RGB", (1024, 512), (255, 255, 255))

    output_w, output_h = output.size
    blade_w, blade_h = blade.size
    hilt_w, hilt_h = hilt.size
    button_w, button_h = button.size
    pommel_w, pommel_h = pommel.size

    pommel_offset = pommel_h
    hilt_offset = get_hilt_offset(hilt_name) - pommel_offset
    button_offset = get_button_offset(hilt_name)  

    blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)
    hilt_offset = (output_h - hilt_h - pommel_offset, (output_w - hilt_w) // 2)
    pommel_offset = (output_h - pommel_h, (output_w - pommel_w) // 2)

    # Paste the blade and hilt onto the output image. 
    output.paste(blade, blade_offset, mask=blade)
    output.paste(hilt, hilt_offset, mask=hilt)
    output.paste(button, get_button_offset(hilt), mask=button)

    # Save the output image to disk
    img.save("{}/{}.png".format(OUTPUT_PATH, output_filename))

Следующим вызовом, с которым я столкнулся с Pommels, было их проектирование, поэтому они выглядели красиво на всех руках. Когда я впервые спроектировал световой меч, я включил и рукоять, и Pommel. Когда я понял, что мне нужно разделить их, чтобы обеспечить более уникальные комбинации, я просто разрезаю старые 8-битные изображения на два. Проблема состоит в том, что цветовые схемы между хильтами и популярностью просто не совпадали.

После того, как я много думал, я решил, что смогу сделать что -то похожее на то, как работают зеленые экраны. Если бы я спроектировал изображения с определенными цветами (в данном случае, и) я мог бы вытащить их, используя подушку и Numpy и заменить их цветами, которые я хочу.

# manifest.py
"hilt": {
    "h1": {
        "offsets": {
            "blade": 0,
            "button": {
                "x": (8, 9),
                "y": (110, 111)
            },
        },
        "colours": {
            "primary": (216,216,216), #d8d8d8
            "secondary": (141,141,141), #8d8d8d
            "tertiary": (180, 97, 19), #b46113
        },
    },
}

# lightsaber.py
import numpy as np

def convert_colours(img, hilt):
    img = img.convert('RGBA')
    data = np.array(img)

    # Grab the pixels which are 100% red, 100% blue and 100% green
    red, green, blue, alpha = data.T
    primary = (red == 255) & (blue == 0) & (green == 0)
    secondary = (red == 0) & (blue == 255) & (green == 0)
    tertiary = (red == 0) & (blue == 0) & (green == 255)

    # Substitute out the colours for the hilt colour scheme
    data[..., :-1][primary.T] = MANIFEST['hilt'][hilt_name]['colours']['primary']
    data[..., :-1][secondary.T] = MANIFEST['hilt'][hilt_name]['colours']['secondary']
    data[..., :-1][tertiary.T] = MANIFEST['hilt'][hilt_name]['colours']['tertiary']

    return Image.fromarray(data)

def generate_lightsaber():
    ... 
    pommel = Image.open(pommel_path, 'r')
    pommel = convert_colours(pommel, hilt_name)
    ...

Как только я это сделал, все было установлено. У Pommels теперь есть правильная цветовая схема в качестве светового меча.

Создание текста случайного твита

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

# manifest.py
"hilt": {
    "h1": {
        "offsets": {
            "blade": 0,
            "button": {
                "x": (8, 9),
                "y": (110, 111)
            },
        },
        "colours": {
            "primary": (216,216,216), #d8d8d8
            "secondary": (141,141,141), #8d8d8d
            "tertiary": (180, 97, 19), #b46113
        },
        "length": 24,
        "materials": "Alloy metal/Salvaged materials"
    },
},
"blade": {
    "b1": {
        "colour": "Red",
        "crystal": ["Ilum crystal", "Ultima Pearl"],
        "type": "Sith"
    },
},
"pommel": {
    "p1": {
        "length": 5,
    },
}

# lightsaber.py
AVERAGE_HILT_LENGTH = 25
AVERAGE_POMMEL_LENGTH = 3
AVERAGE_BLADE_LENGTH = 90

NAMES = ['List', 'of', 'generated', 'names']

def generate_tweet_text(hilt, blade, pommel):
    hilt_details = MANIFEST['hilt'][hilt]
    blade_details = MANIFEST['blade'][blade]
    pommel_details = MANIFEST['pommel'][pommel]

    hilt_length = hilt_details['length']
    pommel_length = pommel_details['length']

    total_length = hilt_length + pommel_length
    average_length = AVERAGE_HILT_LENGTH + AVERAGE_POMMEL_LENGTH
    blade_length = int(AVERAGE_BLADE_LENGTH * (total_length / average_length))

    title = blade_details['type']
    if type(title) is list:
        title = random.choice(title)

    crystal = MANIFEST['blade'][blade]['crystal']
    if type(crystal) is list:
        crystal = random.choice(crystal)

    name = f"{title} {random.choice(NAMES)}"

    tweet = f'''Owner: {name}
Hilt Length: {total_length} cm
Blade Length: {blade_length} cm
Blade Colour: {MANIFEST['blade'][blade]['colour']}
Kyber Crystal: {crystal}

#StarWars
'''

    return tweet

Чтобы сгенерировать уникальные имена, я использовал это Генератор имени Звездных войн Чтобы сгенерировать несколько имен. Затем я случайно выбираю один для использования.

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

consumer_key = os.getenv('CONSUMER_KEY')
consumer_secret = os.getenv('CONSUMER_SECRET')

access_token = os.getenv('ACCESS_TOKEN')
access_token_secret = os.getenv('ACCESS_TOKEN_SECRET')

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)

tweet_text = generate_tweet_text(hilt, blade, pommel)

media = api.media_upload(path)
api.update_status(status=tweet_text, media_ids=[media.media_id,])

Если вы впервые настроите бота в Твиттере, вам нужно Сначала создайте приложение Возьмите свои учетные данные, а затем храните их в переменной среды.

Завершая все это

Теперь, когда все работает правильно, у меня есть уникальный световой меч и разместить его в Twitter, мне просто нужно Добавьте его в Cron бежать каждый день.

12 21 * * * python3 lightsaber.py >> lightsaber.log

Это будет запускать программу каждый день в 21:12. И с этим мы закончили! У нас есть программа Python, которая генерирует уникальный световой меч, каждый день и пишет в Твиттере. Какой джедаи/ситх владеет вашим любимым световым мечом?

Пост Использование Python для создания более 10 000 уникальных 8-битных световых мечей появился первым на Откладывание разработчика Анкет

Оригинал: “https://dev.to/adammckerlie/using-python-to-generate-over-10-000-unique-8-bit-lightsabers-31ae”