Palette assets improved.

This commit is contained in:
2025-09-01 20:07:12 -05:00
parent 4541d5219b
commit 3e61d6f84d
18 changed files with 161 additions and 120 deletions

View File

@@ -86,18 +86,16 @@ target_include_directories(${DUSK_TARGET_NAME} PUBLIC
) )
# Build assets # Build assets
string(JOIN "," DUSK_ASSETS_ARGUMENTS ${DUSK_ASSETS})
add_custom_target(DUSK_ASSETS_BUILT ALL add_custom_target(DUSK_ASSETS_BUILT ALL
COMMAND COMMAND
${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assetstool/assets.py ${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assetstool/main.py
--assets ${DUSK_ASSETS_DIR} --assets ${DUSK_ASSETS_DIR}
--build-type wad --build-type wad
--output-assets ${DUSK_BUILT_ASSETS_DIR} --output-assets ${DUSK_BUILT_ASSETS_DIR}
--output-file ${DUSK_BUILD_DIR}/dusk.dsk --output-file ${DUSK_BUILD_DIR}/dusk.dsk
--output-headers ${DUSK_GENERATED_HEADERS_DIR}/asset/assetbundle.h --output-headers ${DUSK_GENERATED_HEADERS_DIR}/asset/assetbundle.h
--input ${DUSK_ASSETS_ARGUMENTS} --headers-dir ${DUSK_GENERATED_HEADERS_DIR}
COMMENT --input ${DUSK_ASSETS}
"Creating assets build directory ${DUSK_ASSETS}"
) )
add_dependencies(${DUSK_TARGET_NAME} DUSK_ASSETS_BUILT) add_dependencies(${DUSK_TARGET_NAME} DUSK_ASSETS_BUILT)

View File

@@ -31,7 +31,9 @@ def savePalette(pixels, outputFilePath):
with open(outputFilePath, "wb") as f: with open(outputFilePath, "wb") as f:
f.write(buf) f.write(buf)
def processPalette(assetPath): def processPalette(asset):
assetPath = asset['path']
# Process the image file # Process the image file
print(f"Processing palette: {assetPath}") print(f"Processing palette: {assetPath}")
@@ -58,7 +60,9 @@ def processPalette(assetPath):
return palette return palette
def processImage(assetPath): def processImage(asset):
assetPath = asset['path']
print(f"Processing image: {assetPath}") print(f"Processing image: {assetPath}")
# Load the image # Load the image

View File

@@ -5,7 +5,9 @@ from args import args
from constants import ASSET_FILE_NAME_MAX_LENGTH from constants import ASSET_FILE_NAME_MAX_LENGTH
from processimage import processPalette, processImage from processimage import processPalette, processImage
def processTileset(assetPath): def processTileset(asset):
assetPath = asset['path']
# Process the tileset file # Process the tileset file
print(f"Processing tileset: {assetPath}") print(f"Processing tileset: {assetPath}")

View File

@@ -3,6 +3,7 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
add_asset(PALETTE first.palette.png test0) add_asset(PALETTE first.palette.png)
add_asset(TILESET entities.tsx test test3)
add_asset(IMAGE minogram_6x10.png test2) # add_asset(TILESET entities.tsx)
# add_asset(IMAGE minogram_6x10.png)

View File

@@ -36,7 +36,6 @@ add_subdirectory(error)
add_subdirectory(input) add_subdirectory(input)
# add_subdirectory(locale) # add_subdirectory(locale)
add_subdirectory(rpg) add_subdirectory(rpg)
add_subdirectory(scene)
add_subdirectory(thread) add_subdirectory(thread)
add_subdirectory(time) add_subdirectory(time)
add_subdirectory(util) add_subdirectory(util)

View File

@@ -8,12 +8,12 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
display.c display.c
camera.c camera.c
palette.c
) )
# Subdirectories # Subdirectories
add_subdirectory(framebuffer) add_subdirectory(framebuffer)
add_subdirectory(mesh) add_subdirectory(mesh)
add_subdirectory(palette)
add_subdirectory(texture) add_subdirectory(texture)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(spritebatch) add_subdirectory(spritebatch)

View File

@@ -1,19 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "palette.h"
#include "assert/assert.h"
palette_t PALETTE[PALETTE_COUNT_MAX];
uint8_t PALETTE_COUNT = 0;
void paletteAssetLoadCallback(void *data) {
assertNotNull(data, "Data cannot be NULL.");
uint8_t index = *((uint8_t*)data);
assertTrue(index < PALETTE_COUNT_MAX, "Palette index out of bounds.");
}

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
} palette_t;
#define PALETTE_COUNT_MAX 8
extern palette_t PALETTE[PALETTE_COUNT_MAX];
extern uint8_t PALETTE_COUNT;
/**
* Palette asset load callback.
*
* @param data The data passed to the callback, should be a uint8_t* index
* into the PALETTE array.
*/
void paletteAssetLoadCallback(void *data);

View File

@@ -6,5 +6,4 @@
# Sources # Sources
target_sources(${DUSK_TARGET_NAME} target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
scenetest.c
) )

View File

@@ -6,8 +6,9 @@
*/ */
#pragma once #pragma once
#include "display/texture/texture.h" #include "display/color.h"
extern texture_t test; typedef struct {
const uint8_t colorCount;
void sceneTestAdd(void); const color_t *colors;
} palette_t;

View File

@@ -13,9 +13,6 @@
#include "asset/asset.h" #include "asset/asset.h"
#include "rpg/rpg.h" #include "rpg/rpg.h"
#include "display/palette.h"
#include "scene/test/scenetest.h"
engine_t ENGINE; engine_t ENGINE;
errorret_t engineInit(void) { errorret_t engineInit(void) {
@@ -29,12 +26,6 @@ errorret_t engineInit(void) {
errorChain(displayInit()); errorChain(displayInit());
rpgInit(); rpgInit();
uint8_t slot = 0;
assetLoad("entities.dpi", paletteAssetLoadCallback, &slot);
if(ASSET.state == ASSET_STATE_ERROR) errorChain(ASSET.error);
sceneTestAdd();
errorOk(); errorOk();
} }

View File

@@ -1,12 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
)
# Subdirs
add_subdirectory(test)

View File

@@ -1,15 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scenetest.h"
#include "display/camera.h"
#include "display/mesh/quad.h"
texture_t test;
void sceneTestAdd(void) {
}

View File

@@ -5,10 +5,10 @@
# Function that adds an asset to be compiled # Function that adds an asset to be compiled
function(add_asset ASSET_TYPE ASSET_PATH) function(add_asset ASSET_TYPE ASSET_PATH)
message(STATUS "Adding asset: ${ASSET_PATH} (type: ${ASSET_TYPE})")
message(STATUS " Options: ${ARGN}")
set(FULL_ASSET_PATH "${CMAKE_CURRENT_LIST_DIR}/${ASSET_PATH}") set(FULL_ASSET_PATH "${CMAKE_CURRENT_LIST_DIR}/${ASSET_PATH}")
list(APPEND DUSK_ASSETS ${FULL_ASSET_PATH}) string(JOIN "%" ASSETS_ARGS ${ARGN})
list(APPEND DUSK_ASSETS
"${ASSET_TYPE}#${FULL_ASSET_PATH}#${ASSETS_ARGS}$"
)
set(DUSK_ASSETS ${DUSK_ASSETS} CACHE INTERNAL ${DUSK_CACHE_TARGET}) set(DUSK_ASSETS ${DUSK_ASSETS} CACHE INTERNAL ${DUSK_CACHE_TARGET})
endfunction() endfunction()

View File

@@ -6,15 +6,44 @@ import sys
parser = argparse.ArgumentParser(description="Generate chunk header files") parser = argparse.ArgumentParser(description="Generate chunk header files")
parser.add_argument('--assets', required=True, help='Dir to output built assets') parser.add_argument('--assets', required=True, help='Dir to output built assets')
parser.add_argument('--build-type', choices=['wad', 'header'], default='raw', help='Type of build to perform') parser.add_argument('--build-type', choices=['wad', 'header'], default='raw', help='Type of build to perform')
parser.add_argument('--output-file', help='Output file for built assets (required for wad build)') parser.add_argument('--output-file', required=True, help='Output file for built assets (required for wad build)')
parser.add_argument('--headers-dir', required=True, help='Directory to output individual asset headers (required for header build)')
parser.add_argument('--output-headers', help='Output header file for built assets (required for header build)') parser.add_argument('--output-headers', help='Output header file for built assets (required for header build)')
parser.add_argument('--output-assets', help='Output directory for built assets (required for raw build)') parser.add_argument('--output-assets', required=True, help='Output directory for built assets')
parser.add_argument('--input', required=True, help='Input assets to process', nargs='+') parser.add_argument('--input', required=True, help='Input assets to process', nargs='+')
args = parser.parse_args() args = parser.parse_args()
inputAssets = [] inputAssets = []
for inputArg in args.input: for inputArg in args.input:
inputAssets.extend(inputArg.split(',')) files = inputArg.split('$')
for file in files:
if str(file).strip() == '':
continue
pieces = file.split('#')
if len(pieces) < 2:
print(f"Error: Invalid input asset format '{file}'. Expected format: type#path[#option1%option2...]")
sys.exit(1)
options = {}
if len(pieces) > 2:
optionParts = pieces[2].split('%')
for part in optionParts:
partSplit = part.split('=')
if len(partSplit) < 1:
continue
if len(partSplit) == 2:
options[partSplit[0]] = partSplit[1]
else:
options[partSplit[0]] = True
inputAssets.append({
'type': pieces[0],
'path': pieces[1],
'options': options
})
if not inputAssets: if not inputAssets:
print("Error: No input assets provided.") print("Error: No input assets provided.")

View File

@@ -1,6 +1,7 @@
import sys, os import sys, os
from args import inputAssets, args from args import inputAssets, args
from processasset import processAsset from processasset import processAsset
from processpalette import processPaletteList
from assethelpers import getBuiltAssetsRelativePath from assethelpers import getBuiltAssetsRelativePath
import zipfile import zipfile
@@ -26,6 +27,9 @@ with zipfile.ZipFile(outputFileName, 'w') as zipf:
relativeOutputPath = getBuiltAssetsRelativePath(file) relativeOutputPath = getBuiltAssetsRelativePath(file)
zipf.write(file, arcname=relativeOutputPath) zipf.write(file, arcname=relativeOutputPath)
# Generate additional headers.
processPaletteList()
# Finalize build # Finalize build
if args.build_type == 'header': if args.build_type == 'header':
print("Error: Header build not implemented yet.") print("Error: Header build not implemented yet.")

View File

@@ -1,19 +1,24 @@
from processtileset import processTileset import sys
from processimage import processPalette, processImage # from processtileset import processTileset
# from processimage import processPalette, processImage
from processpalette import processPalette
processedAssets = [] processedAssets = []
def processAsset(assetPath): def processAsset(asset):
if assetPath in processedAssets: if asset['path'] in processedAssets:
return return
processedAssets.append(assetPath) processedAssets.append(asset['path'])
# Handle tiled tilesets # Handle tiled tilesets
if assetPath.endswith('.tsx'): t = asset['type'].lower()
return processTileset(assetPath) if t == 'palette':
elif assetPath.endswith('.png'): return processPalette(asset)
if assetPath.endswith('.palette.png'): # elif t == 'image':
return processPalette(assetPath) # return processImage(asset)
else: # elif t == 'tileset':
return processImage(assetPath) # return processTileset(asset)
else:
print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'")
sys.exit(1)

View File

@@ -0,0 +1,80 @@
import os
from PIL import Image
from args import args
import sys
import datetime
palettes = []
def extractPaletteFromImage(image):
# goes through and finds all unique colors in the image
if image.mode != 'RGBA':
image = image.convert('RGBA')
pixels = list(image.getdata())
uniqueColors = []
for color in pixels:
# We treat alpha 0 as rgba(0,0,0,0) for palette purposes
if color[3] == 0:
color = (0, 0, 0, 0)
if color not in uniqueColors:
uniqueColors.append(color)
return uniqueColors
def processPalette(asset):
print(f"Processing palette: {asset['path']}")
paletteIndex = len(palettes)
image = Image.open(asset['path'])
pixels = extractPaletteFromImage(image)
fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0]
fileNameWithoutPalette = os.path.splitext(fileNameWithoutExt)[0]
# Header
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data = f"// Palette Generated for {asset['path']} at {now}\n"
data += f"#include \"display/palette/palette.h\"\n\n"
data += f"#define PALETTE_{paletteIndex}_COLOR_COUNT {len(pixels)}\n\n"
data += f"static const color_t PALETTE_{paletteIndex}_COLORS[PALETTE_{paletteIndex}_COLOR_COUNT] = {{\n"
for pixel in pixels:
data += f" {{ 0x{pixel[0]:02X}, 0x{pixel[1]:02X}, 0x{pixel[2]:02X}, 0x{pixel[3]:02X} }},\n"
data += f"}};\n\n"
data += f"static const palette_t PALETTE_{paletteIndex} = {{\n"
data += f" .colorCount = PALETTE_{paletteIndex}_COLOR_COUNT,\n"
data += f" .colors = PALETTE_{paletteIndex}_COLORS,\n"
data += f"}};\n"
# Write Header
outputFile = os.path.join(args.headers_dir, "display", "palette", f"palette_{paletteIndex}.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "w") as f:
f.write(data)
palette = {
"paletteIndex": paletteIndex,
"paletteName": fileNameWithoutPalette,
"pixels": pixels,
"headerFile": os.path.relpath(outputFile, args.headers_dir),
"asset": asset,
"files": [ ],# No zippable files.
}
palettes.append(palette)
return palette
def processPaletteList():
data = f"// Auto-generated palette list\n"
for palette in palettes:
data += f"#include \"{palette['headerFile']}\"\n"
data += f"\n"
data += f"#define PALETTE_LIST_COUNT {len(palettes)}\n\n"
data += f"static const palette_t* PALETTE_LIST[PALETTE_LIST_COUNT] = {{\n"
for palette in palettes:
data += f" &PALETTE_{palette['paletteIndex']},\n"
data += f"}};\n"
# Write the palette list to a header file
outputFile = os.path.join(args.headers_dir, "display", "palette", "palettelist.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "w") as f:
f.write(data)