From 3e61d6f84dfa2f2791866a6445ca0c1442df9bea Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 1 Sep 2025 20:07:12 -0500 Subject: [PATCH] Palette assets improved. --- CMakeLists.txt | 8 +- .../tool2}/processimage.py | 8 +- .../tool2}/processtileset.py | 4 +- assets/CMakeLists.txt | 7 +- src/CMakeLists.txt | 1 - src/display/CMakeLists.txt | 2 +- src/display/palette.c | 19 ----- src/display/palette.h | 26 ------ .../test => display/palette}/CMakeLists.txt | 3 +- .../scenetest.h => display/palette/palette.h} | 9 ++- src/engine/engine.c | 9 --- src/scene/CMakeLists.txt | 12 --- src/scene/test/scenetest.c | 15 ---- tools/CMakeLists.txt | 8 +- tools/assetstool/args.py | 37 ++++++++- tools/assetstool/{assets.py => main.py} | 4 + tools/assetstool/processasset.py | 29 ++++--- tools/assetstool/processpalette.py | 80 +++++++++++++++++++ 18 files changed, 161 insertions(+), 120 deletions(-) rename {tools/assetstool => archive/tool2}/processimage.py (97%) rename {tools/assetstool => archive/tool2}/processtileset.py (97%) delete mode 100644 src/display/palette.c delete mode 100644 src/display/palette.h rename src/{scene/test => display/palette}/CMakeLists.txt (90%) rename src/{scene/test/scenetest.h => display/palette/palette.h} (58%) delete mode 100644 src/scene/CMakeLists.txt delete mode 100644 src/scene/test/scenetest.c rename tools/assetstool/{assets.py => main.py} (91%) create mode 100644 tools/assetstool/processpalette.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 70e8aac..e7ace41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,18 +86,16 @@ target_include_directories(${DUSK_TARGET_NAME} PUBLIC ) # Build assets -string(JOIN "," DUSK_ASSETS_ARGUMENTS ${DUSK_ASSETS}) add_custom_target(DUSK_ASSETS_BUILT ALL COMMAND - ${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assetstool/assets.py + ${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assetstool/main.py --assets ${DUSK_ASSETS_DIR} --build-type wad --output-assets ${DUSK_BUILT_ASSETS_DIR} --output-file ${DUSK_BUILD_DIR}/dusk.dsk --output-headers ${DUSK_GENERATED_HEADERS_DIR}/asset/assetbundle.h - --input ${DUSK_ASSETS_ARGUMENTS} - COMMENT - "Creating assets build directory ${DUSK_ASSETS}" + --headers-dir ${DUSK_GENERATED_HEADERS_DIR} + --input ${DUSK_ASSETS} ) add_dependencies(${DUSK_TARGET_NAME} DUSK_ASSETS_BUILT) diff --git a/tools/assetstool/processimage.py b/archive/tool2/processimage.py similarity index 97% rename from tools/assetstool/processimage.py rename to archive/tool2/processimage.py index 0deed0c..f2e3cb9 100644 --- a/tools/assetstool/processimage.py +++ b/archive/tool2/processimage.py @@ -31,7 +31,9 @@ def savePalette(pixels, outputFilePath): with open(outputFilePath, "wb") as f: f.write(buf) -def processPalette(assetPath): +def processPalette(asset): + assetPath = asset['path'] + # Process the image file print(f"Processing palette: {assetPath}") @@ -58,7 +60,9 @@ def processPalette(assetPath): return palette -def processImage(assetPath): +def processImage(asset): + assetPath = asset['path'] + print(f"Processing image: {assetPath}") # Load the image diff --git a/tools/assetstool/processtileset.py b/archive/tool2/processtileset.py similarity index 97% rename from tools/assetstool/processtileset.py rename to archive/tool2/processtileset.py index e92649c..7c58c52 100644 --- a/tools/assetstool/processtileset.py +++ b/archive/tool2/processtileset.py @@ -5,7 +5,9 @@ from args import args from constants import ASSET_FILE_NAME_MAX_LENGTH from processimage import processPalette, processImage -def processTileset(assetPath): +def processTileset(asset): + assetPath = asset['path'] + # Process the tileset file print(f"Processing tileset: {assetPath}") diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index e810f3d..4af287a 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -3,6 +3,7 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(PALETTE first.palette.png test0) -add_asset(TILESET entities.tsx test test3) -add_asset(IMAGE minogram_6x10.png test2) \ No newline at end of file +add_asset(PALETTE first.palette.png) + +# add_asset(TILESET entities.tsx) +# add_asset(IMAGE minogram_6x10.png) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e84eb72..c4c5717 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,7 +36,6 @@ add_subdirectory(error) add_subdirectory(input) # add_subdirectory(locale) add_subdirectory(rpg) -add_subdirectory(scene) add_subdirectory(thread) add_subdirectory(time) add_subdirectory(util) \ No newline at end of file diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index e0978ad..752cf53 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -8,12 +8,12 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE display.c camera.c - palette.c ) # Subdirectories add_subdirectory(framebuffer) add_subdirectory(mesh) +add_subdirectory(palette) add_subdirectory(texture) add_subdirectory(scene) add_subdirectory(spritebatch) diff --git a/src/display/palette.c b/src/display/palette.c deleted file mode 100644 index 3fc29d0..0000000 --- a/src/display/palette.c +++ /dev/null @@ -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."); -} \ No newline at end of file diff --git a/src/display/palette.h b/src/display/palette.h deleted file mode 100644 index bfa17ec..0000000 --- a/src/display/palette.h +++ /dev/null @@ -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); \ No newline at end of file diff --git a/src/scene/test/CMakeLists.txt b/src/display/palette/CMakeLists.txt similarity index 90% rename from src/scene/test/CMakeLists.txt rename to src/display/palette/CMakeLists.txt index 02f9e89..15c3bec 100644 --- a/src/scene/test/CMakeLists.txt +++ b/src/display/palette/CMakeLists.txt @@ -1,10 +1,9 @@ # 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 - scenetest.c ) \ No newline at end of file diff --git a/src/scene/test/scenetest.h b/src/display/palette/palette.h similarity index 58% rename from src/scene/test/scenetest.h rename to src/display/palette/palette.h index 917d5c9..ecce9e8 100644 --- a/src/scene/test/scenetest.h +++ b/src/display/palette/palette.h @@ -6,8 +6,9 @@ */ #pragma once -#include "display/texture/texture.h" +#include "display/color.h" -extern texture_t test; - -void sceneTestAdd(void); \ No newline at end of file +typedef struct { + const uint8_t colorCount; + const color_t *colors; +} palette_t; \ No newline at end of file diff --git a/src/engine/engine.c b/src/engine/engine.c index 1b60bbe..91dfc7e 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -13,9 +13,6 @@ #include "asset/asset.h" #include "rpg/rpg.h" -#include "display/palette.h" -#include "scene/test/scenetest.h" - engine_t ENGINE; errorret_t engineInit(void) { @@ -29,12 +26,6 @@ errorret_t engineInit(void) { errorChain(displayInit()); rpgInit(); - uint8_t slot = 0; - assetLoad("entities.dpi", paletteAssetLoadCallback, &slot); - if(ASSET.state == ASSET_STATE_ERROR) errorChain(ASSET.error); - - sceneTestAdd(); - errorOk(); } diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt deleted file mode 100644 index 2f80ccf..0000000 --- a/src/scene/CMakeLists.txt +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/scene/test/scenetest.c b/src/scene/test/scenetest.c deleted file mode 100644 index 67479dd..0000000 --- a/src/scene/test/scenetest.c +++ /dev/null @@ -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) { -} \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 5cb155a..c03bd04 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -5,10 +5,10 @@ # Function that adds an asset to be compiled 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}") - 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}) endfunction() \ No newline at end of file diff --git a/tools/assetstool/args.py b/tools/assetstool/args.py index 4d38222..92de51c 100644 --- a/tools/assetstool/args.py +++ b/tools/assetstool/args.py @@ -6,16 +6,45 @@ import sys parser = argparse.ArgumentParser(description="Generate chunk header files") 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('--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-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='+') args = parser.parse_args() inputAssets = [] 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: print("Error: No input assets provided.") sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/assets.py b/tools/assetstool/main.py similarity index 91% rename from tools/assetstool/assets.py rename to tools/assetstool/main.py index f1fc386..4465015 100644 --- a/tools/assetstool/assets.py +++ b/tools/assetstool/main.py @@ -1,6 +1,7 @@ import sys, os from args import inputAssets, args from processasset import processAsset +from processpalette import processPaletteList from assethelpers import getBuiltAssetsRelativePath import zipfile @@ -26,6 +27,9 @@ with zipfile.ZipFile(outputFileName, 'w') as zipf: relativeOutputPath = getBuiltAssetsRelativePath(file) zipf.write(file, arcname=relativeOutputPath) +# Generate additional headers. +processPaletteList() + # Finalize build if args.build_type == 'header': print("Error: Header build not implemented yet.") diff --git a/tools/assetstool/processasset.py b/tools/assetstool/processasset.py index 032e8e0..ef40ec2 100644 --- a/tools/assetstool/processasset.py +++ b/tools/assetstool/processasset.py @@ -1,19 +1,24 @@ -from processtileset import processTileset -from processimage import processPalette, processImage +import sys +# from processtileset import processTileset +# from processimage import processPalette, processImage +from processpalette import processPalette processedAssets = [] -def processAsset(assetPath): - if assetPath in processedAssets: +def processAsset(asset): + if asset['path'] in processedAssets: return - processedAssets.append(assetPath) + processedAssets.append(asset['path']) # Handle tiled tilesets - if assetPath.endswith('.tsx'): - return processTileset(assetPath) - elif assetPath.endswith('.png'): - if assetPath.endswith('.palette.png'): - return processPalette(assetPath) - else: - return processImage(assetPath) \ No newline at end of file + t = asset['type'].lower() + if t == 'palette': + return processPalette(asset) + # elif t == 'image': + # return processImage(asset) + # elif t == 'tileset': + # return processTileset(asset) + else: + print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'") + sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/processpalette.py b/tools/assetstool/processpalette.py new file mode 100644 index 0000000..6431098 --- /dev/null +++ b/tools/assetstool/processpalette.py @@ -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) \ No newline at end of file