Postscript: 1/29 0:20
Do you use lofi hip hop radio as background music?
Do you watch voiceroid videos? I like nikko-ken
Also, it seems that PNGTuber is popular
I'm always thinking about whether there is a format that is a little easier to handle, such as looping animations where only the avatar or part of the video moves.
As for the avatar, there was a similar solution, so I'll introduce the article.
https://note.com/lilish_works/n/n956ef62ea2e9
In this prototype, I used photoshop to export PNGs for each layer and write the coordinates into a json file, and then used pygame to output a player that plays those images.
This time, we used the Zundamon character from Mr. Sakamoto Duck.
https://www.pixiv.net/artworks/92641351
Thank you
A group of files exported from Photoshop
Demo played with pygames
You can see that the layers are being recreated in the demo.
{“canvasWidth”:1082,“canvasHeight”:1650,“layers”:
[
{“fileName”:“Path to file”,
“x”: X coordinate from the top left of the canvas,“y”: Y coordinate from the top left of the canvas,
“width“: width of image,”height“: height of image},
{”fileName“:”01.png“,”x“:258,”y“:109,”width“:549,”height":1473},
{“fileName”:“02.png”,“x”:460,“y”:474,“width”:66,“height”:61},
{“fileName”:“03.png”,“x”:466,“y”:496,“width”:58,“height”:16},
{“fileName”:“04.png”,“x”:480,“y”:487,“width”:33,“height”:39},
{“fileName”:“05.png”,“x”:366,“y”:351,“width”:250,“height”:107},
{“fileName”:“06.png”,“x”:383,“y”:305,“width”:216,“height”:18}
]
}
The json is output as shown above
Next, I'm thinking of packing them as a single texture to make them easier to handle.
I've also extended the json format.
I'll use pillow to pack the textures.
pip install pillow
When packing, I'll also export the json.
At this time, I'll save the x and y coordinates from when the original sequential pngs were loaded separately.
Packed images
The json that is output
{
“canvasWidth“: 1082,
”canvasHeight“: 1650,
”layers“: [
{
”fileName“: ‘01.png’,
”x“: 0,
”y“: 0,
”width“: 549,
”height“: 1473,
”basePosition_x“: 258,
”basePosition_y": 109
},
{
“fileName”: “02.png”,
“x”: 549,
“y”: 0,
“width”: 66,
“height”: 61,
“basePosition_x”: 460,
“basePosition_y”: 474
},
{
“fileName”: “03.png”,
“x”: 615,
“y”: 0,
“width“: 58,
”height“: 16,
”basePosition_x“: 466,
”basePosition_y“: 496
},
{
”fileName“: ‘04.png’,
”x“: 673,
”y“: 0,
”width“: 33,
”height“: 39,
”basePosition_x": 480,
“basePosition_y“: 487
},
{
”fileName“: ‘05.png’,
”x“: 706,
”y“: 0,
”width“: 250,
”height“: 107,
”basePosition_x“: 366,
”basePosition_y“: 351
},
{
”fileName": ‘06.png’,
“x“: 0,
”y“: 1473,
”width“: 216,
”height“: 18,
”basePosition_x“: 383,
”basePosition_y": 305
}
]
}
Next, I want to animate it
I'm having trouble
- I can't get the layer name in photoshop jsx, so I get an error, so I'm making it a sequential number
If you know how to do this, please leave a comment!
If you know of any similar solutions, please let me know!
Extra
The following code was output by ChatGPT
It's just a demo, but if it's of any use
photoshop script
// Photoshop script: Output PNG for each layer and save position in JSON (map not used)
#target photoshop
try {
// Let the user select the output folder
var outputFolder = Folder.selectDialog(“Please select the output folder.”);
if (!outputFolder) {
alert(“The output folder was not selected.”);
throw new Error(“The output folder was not selected.”);
}
// Array to store JSON data
var layerData = [];
// Get the current document
var doc = app.activeDocument;
// Get the document canvas size
var canvasWidth = doc.width.as(“px”);
var canvasHeight = doc.height.as(“px”);
// Set the options for saving as PNG
function saveAsPNG(file) {
var pngOptions = new PNGSaveOptions();
pngOptions.interlaced = false;
doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
}
// Alternative function for checking arrays
function isArray(obj) {
return Object.prototype.toString.call(obj) === “[object Array]”;
}
// Function to manually construct a JSON string
function buildJSON(data) {
var json = “{”;
for (var key in data) {
if (data.hasOwnProperty(key)) {
var value = data[key];
if (typeof value === “string”) {
value = '“‘ + value + ’”'; // Enclose strings in double quotes
} else if (typeof value === “object”) {
if (isArray(value)) {
var arrayItems = [];
for (var i = 0; i < value.length; i++) {
arrayItems.push(buildJSON(value[i])); // Recursively process array elements
}
value = “[” + arrayItems.join(“,”) + “]”;
} else {
value = buildJSON(value); // Recursively process objects
}
}
json += '“‘ + key + ’”:' + value + “,”;
}
}
json = json.replace(/,$/, “”); // Remove the last comma
json += “}”;
return json;
}
// Function to export layers individually (process in reverse order)
function processLayers(layerSet, parentX, parentY, layerIndex) {
for (var i = layerSet.layers.length - 1; i >= 0; i--) { // Process from bottom layer
var layer = layerSet.layers[i];
// If the layer is a layer set, process recursively
if (layer.typename === “LayerSet”) {
layerIndex = processLayers(layer, parentX, parentY, layerIndex);
} else {
if (!layer.visible) continue; // Skip hidden layers
// Get the layer's position
var bounds = layer.bounds;
var x = bounds[0].as(“px”) + parentX;
var y = bounds[1].as(“px”) + parentY;
// Duplicate the layer and create a new document
app.activeDocument.activeLayer = layer;
layer.copy();
var tempDoc = app.documents.add(layer.bounds[2] - layer.bounds[0], layer.bounds[3] - layer.bounds[1], doc.resolution, “TempDoc”, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
tempDoc.paste();
// Create file names with sequential numbers
var fileName = (“00” + layerIndex).slice(-2) + “.png”;
var filePath = new File(outputFolder + “/” + fileName);
// Save as PNG
saveAsPNG(filePath);
// Add to JSON data
layerData.push({
fileName: fileName,
x: x,
y: y,
width: tempDoc.width.as(“px”),
height: tempDoc.height.as(“px”)
});
// Close temporary document
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
// Increment the index for the next sequential number
layerIndex++;
}
}
return layerIndex;
}
// Process the layers (the sequential index starts from 1)
processLayers(doc, 0, 0, 1);
// Save as a JSON file
if (layerData.length > 0) {
var jsonFile = new File(outputFolder + “/layer_data.json”);
jsonFile.open(“w”);
// Manually construct JSON data
var outputData = {
canvasWidth: canvasWidth,
canvasHeight: canvasHeight,
layers: layerData
};
jsonFile.write(buildJSON(outputData)); // Use the manual JSON construction function
jsonFile.close();
alert(“Layer export completed!”);
} else {
alert(“There were no processed layers.”);
}
} catch (e) {
alert("An error occurred: ” + e.message);
}
pygame playback section
Additions
A texture packing button and a json switching button have been added.
pip install pygame
import pygame
import json
from PIL import Image
# Default JSON file
current_json_file = “layer_data.json”
# Load JSON file
with open(current_json_file, “r”) as file:
data = json.load(file)
# Get canvas size
canvas_width = data[“canvasWidth”]
canvas_height = data[“canvasHeight”]
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((canvas_width, canvas_height))
pygame.display.set_caption(“Layer Drawing”)
# Dictionary to manage layer visibility
layer_visibility = {layer[“fileName”]: True for layer in data[“layers”]}
# Initialize font
font = pygame.font.Font(None, 24)
# Create texture function
def create_texture():
texture = Image.new(“RGBA”, (canvas_width, canvas_height), (255, 255, 255, 0))
texture_layers = []
# Manage the current placement coordinates
next_x = 0
next_y = 0
row_height = 0
for layer in data[“layers”]:
if layer_visibility[layer[“fileName”]]:
img = Image.open(layer[“fileName”])
img = img.resize((layer[“width”], layer[“height”]))
# If a new line is needed
if next_x + layer[“width”] > canvas_width:
next_x = 0
next_y += row_height
row_height = 0
# Add the image to be placed
texture.paste(img, (next_x, next_y), img)
texture_layers.append({
“fileName”: layer[“fileName”],
“x”: next_x,
“y”: next_y,
“width”: layer[“width”],
“height”: layer[“height”],
“basePosition_x“: layer[”x“],
”basePosition_y“: layer[”y“]
})
# Calculate the next position
next_x += layer[”width“]
row_height = max(row_height, layer[”height“])
texture.save(”texture.png“)
print(”Texture image 'texture.png' has been created!")
# Save the texture-compatible JSON
texture_json = {
“canvasWidth”: canvas_width,
“canvasHeight”: canvas_height,
“layers”: texture_layers
}
with open(“texture.json”, “w”) as json_file:
json.dump(texture_json, json_file, indent=4)
print(“Texture JSON ‘texture.json’ has been created!”)
# Function to switch JSON files
def switch_json(file_name):
global data, layer_visibility, canvas_width, canvas_height, screen, current_json_file
current_json_file = file_name
with open(file_name, “r”) as file:
data = json.load(file)
canvas_width = data[“canvasWidth”]
canvas_height = data[“canvasHeight”]
screen = pygame.display.set_mode((canvas_width, canvas_height))
layer_visibility = {layer[“fileName”]: True for layer in data[“layers”]}
print(f"‘{file_name}’ has been loaded!
# Main loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Switch display on/off with mouse click
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
button_height = 30
# Layer switching button
for index, layer in enumerate(data[“layers”]):
button_y = index * button_height
if 10 <= x <= 200 and button_y <= y <= button_y + button_height:
layer_visibility[layer[“fileName”]] = not layer_visibility[layer[“fileName”]]
# Texture creation button
texture_button_y = len(data[“layers”]) * button_height + 10
if 10 <= x <= 200 and texture_button_y <= y <= texture_button_y + button_height:
create_texture()
# JSON switching button
switch_button_y = texture_button_y + 40
if 10 <= x <= 200 and switch_button_y <= y <= switch_button_y + button_height:
if current_json_file == “layer_data.json”:
switch_json(“texture.json”)
else:
switch_json(“layer_data.json”)
screen.fill((255, 255, 255)) # Set the background to white
# Draw the layers
for layer in data[“layers”]:
if current_json_file == “texture.json”:
if layer_visibility[layer[“fileName”]]:
img = pygame.image.load(“texture.png”)
cropped_img = img.subsurface(pygame.Rect(layer[“x”], layer[“y”], layer[“width”], layer[“height”]))
screen.blit(cropped_img, (layer[“basePosition_x”], layer[“basePosition_y”]))
elif layer_visibility[layer[“fileName”]]:
img = pygame.image.load(layer[“fileName”]) # Load PNG file
img = pygame.transform.scale(img, (layer[“width”], layer[“height”]))
screen.blit(img, (layer[“x”], layer[“y”]))
# Draw UI buttons
for index, layer in enumerate(data[“layers”]):
button_y = index * 30
color = (0, 200, 0) if layer_visibility[layer[“fileName”]] else (200, 0, 0)
pygame.draw.rect(screen, color, (10, button_y, 190, 30))
text = font.render(layer[“fileName”], True, (255, 255, 255))
screen.blit(text, (15, button_y + 5))
# Texture creation button
texture_button_y = len(data[“layers”]) * 30 + 10
pygame.draw.rect(screen, (0, 0, 200), (10, texture_button_y, 190, 30))
texture_text = font.render(“Create Texture”, True, (255, 255, 255))
screen.blit(texture_text, (15, texture_button_y + 5))
# JSON switching button
switch_button_y = texture_button_y + 40
pygame.draw.rect(screen, (200, 200, 0), (10, switch_button_y, 190, 30))
if current_json_file == “layer_data.json”:
switch_text = font.render(“Switch to Texture JSON”, True, (0, 0, 0))
else:
switch_text = font.render(“Switch to Layer Data JSON”, True, (0, 0, 0))
screen.blit(switch_text, (15, switch_button_y + 5))
pygame.display.flip()
pygame.quit()
pygame It looks pretty good
python drawing is really convenient
Top comments (0)