Last active
May 8, 2022 11:37
-
-
Save onnowhere/5b717daeba899ff4dc606add12d6b2b5 to your computer and use it in GitHub Desktop.
[Image to Particles] Quick tool that generates a function that displays particles for an input image using local coordinates. Drop images in the `images` folder in the same directory as the script. You'll also need to `pip install opencv-python`. Edit generation options at the bottom of the file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cv2 | |
import os | |
class ImageToParticle: | |
def __init__(self, image_file, resolution=(40, 40), scale=0.25, max_size=5, replace_transparent=[], animation=0, commands=[], show_display=False): | |
self.image_file = image_file # Source file | |
self.resolution = resolution # Shrinks image to fit within x,y dimensions without changing aspect ratio (1 pixel = 1 particle) | |
self.scale = scale # Scale the size of image displayed in game | |
self.max_size = max_size # Maximum particle size, alpha scales this if replace_transparent is not defined | |
self.replace_transparent = replace_transparent # RGB value to replace 0 alpha with | |
self.animation = animation # Animation type, 0 - Lines from bottom, 1 - Spiral from center | |
self.commands = commands # Additional commands to add to start of each function | |
self.show_display = show_display # Display resized image for debugging | |
def create_path(self, filepath): | |
if not os.path.exists(filepath): | |
try: | |
os.makedirs(filepath) | |
except OSError as exc: # Guard against race condition | |
if exc.errno != errno.EEXIST: | |
raise | |
def create_file(self, filename, contents): | |
self.create_path(os.path.dirname(filename)) | |
with open(filename, 'w', encoding='utf-8') as f: | |
f.writelines(contents) | |
def create_empty_function_folder(self, paths): | |
self.create_path(paths["functions"]) | |
def get_color(self, pixel): | |
if len(pixel) > 3: | |
if pixel[3] == 0.0 and len(self.replace_transparent) == 3: | |
# Replace full transparency with value | |
return "{0} {1} {2} {3}".format(self.replace_transparent[0], self.replace_transparent[1], self.replace_transparent[2], self.max_size) | |
elif len(self.replace_transparent) != 3: | |
return "{0} {1} {2} {3}".format(round(pixel[0]/256, 3), round(pixel[1]/256, 3), round(pixel[2]/256, 3), round(self.max_size * pixel[3]/256, 3)) | |
return "{0} {1} {2} {3}".format(round(pixel[0]/256, 3), round(pixel[1]/256, 3), round(pixel[2]/256, 3), self.max_size) | |
def get_position(self, row, col): | |
return "^{0} ^{1} ^0".format(round(row*self.scale, 3), round(col*self.scale, 3)) | |
def generate_particle_command(self, image, row, col): | |
pixel = image[image.shape[0] - 1 - col, image.shape[1] - 1 - row] | |
if len(pixel) > 3: | |
color = self.get_color([pixel[2], pixel[1], pixel[0], pixel[3]]) | |
else: | |
color = self.get_color([pixel[2], pixel[1], pixel[0]]) | |
position = self.get_position(row - image.shape[1]/2, col - image.shape[0]/2) | |
return 'particle dust {0} {1} 0 0 0 0 0 force'.format(color, position) | |
def generate(self): | |
image_prefix = "image_" | |
datapack_name = image_prefix + os.path.basename(os.path.splitext(self.image_file)[0]).lower().replace(" ","_") | |
paths = { | |
"functions": "particle_images" | |
} | |
self.create_empty_function_folder(paths) | |
image = cv2.imread(self.image_file, cv2.IMREAD_UNCHANGED) | |
function_contents = [] | |
image_size = [image.shape[0], image.shape[1]] | |
if image_size[0] > self.resolution[0]: | |
scale = self.resolution[0] / image_size[0] | |
image_size[1] = int(image_size[1] * scale) | |
image_size[0] = self.resolution[0] | |
if image_size[1] > self.resolution[1]: | |
scale = self.resolution[1] / image_size[1] | |
image_size[0] = int(image_size[0] * scale) | |
image_size[1] = self.resolution[1] | |
image = cv2.resize(image, (image_size[1], image_size[0])) | |
output = [] | |
output += self.commands | |
if self.animation == 0: | |
# Lines from bottom | |
for col in range(image.shape[0]): | |
for row in range(image.shape[1]): | |
output.append(self.generate_particle_command(image, row, col)) | |
elif self.animation == 1: | |
# Spiral from center | |
offset = [0, 0] | |
direction = [0, -1] | |
while abs(offset[0]) < image.shape[0]//2 or abs(offset[1]) < image.shape[1]//2: | |
if offset[0] == offset[1] or (offset[0] < 0 and offset[0] == -offset[1]) or (offset[0] > 0 and offset[0] == 1 - offset[1]): | |
direction = [-direction[1], direction[0]] | |
offset[0] += direction[0] | |
offset[1] += direction[1] | |
if abs(offset[0]) < image.shape[0]//2 and abs(offset[1]) < image.shape[1]//2: | |
position = [offset[0] + image.shape[0]//2, offset[1] + image.shape[1]//2] | |
output.append(self.generate_particle_command(image, position[1], position[0])) | |
output = '\n'.join(output) | |
function_contents.append(output) | |
if self.show_display: | |
cv2.imshow('image',image) | |
cv2.waitKey(0) | |
self.create_file(os.path.join(paths["functions"], "{0}.mcfunction".format(datapack_name)), '\n'.join(function_contents)) | |
if __name__ == '__main__': | |
for file in os.listdir("images"): | |
# With 0 alpha replacement, particles won't scale | |
#image = ImageToParticle(os.path.join("images", file), resolution=(40, 40), scale=0.8, max_size=5, replace_transparent=[1.0, 1.0, 1.0], animation=1, commands=[], show_display=False) | |
# Without 0 alpha replacement, scales particle size by alpha | |
image = ImageToParticle(os.path.join("images", file), resolution=(40, 40), scale=0.8, max_size=5, animation=1, commands=[], show_display=False) | |
image.generate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment