git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

commit 0eb91a20849503e83c34f95aa2b9d1fb0e8b9fc7
parent 02b6ac03dd373d7a237cb342dab4f16981416f2c
Author: Toxa <56631470+untoxa@users.noreply.github.com>
Date:   Fri, 24 Jun 2022 15:49:35 +0300

Merge pull request #385 from michel-iwaniec/nes_logo_example

NES logo example
Diffstat:
Mgbdk-lib/examples/cross-platform/logo/Makefile13+++++++------
Mgbdk-lib/examples/cross-platform/logo/Makefile.targets5+++++
Agbdk-lib/examples/cross-platform/logo/res/NES/GBDK_2020_logo.png0
Agbdk-lib/examples/cross-platform/logo/res/NES/GBDK_2020_logo.png.meta2++
Mgbdk-lib/examples/cross-platform/logo/src/main.c21++++++++++++++++++++-
Mgbdk-lib/include/nes/nes.h51++++++++++++++++++++++++++++++++-------------------
Agbdk-lib/include/nes/rgb_to_nes_macro.h70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgbdk-lib/libc/targets/mos6502/nes/Makefile3++-
Mgbdk-lib/libc/targets/mos6502/nes/crt0.s2+-
Mgbdk-lib/libc/targets/mos6502/nes/fill_rect_bk.s2++
Mgbdk-lib/libc/targets/mos6502/nes/global.s1+
Agbdk-lib/libc/targets/mos6502/nes/nes_palettes.s138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/libc/targets/mos6502/nes/set_bk_attributes.s70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/libc/targets/mos6502/nes/set_bk_ts.s68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-support/nespal/nespal.py220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-support/nespal/palettes/palgen.pal0
Mgbdk-support/png2asset/Makefile2+-
Mgbdk-support/png2asset/png2asset.cpp272++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Agbdk-support/png2asset/rgb_to_nes_lut.cpp67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
19 files changed, 894 insertions(+), 113 deletions(-)

diff --git a/gbdk-lib/examples/cross-platform/logo/Makefile b/gbdk-lib/examples/cross-platform/logo/Makefile @@ -7,19 +7,20 @@ PNG2ASSETS = $(GBDK_HOME)bin/png2asset # Set platforms to build here, spaced separated. (These are in the separate Makefile.targets) # They can also be built/cleaned individually: "make gg" and "make gg-clean" # Possible are: gb gbc pocket megaduck sms gg -TARGETS=gb gbc pocket megaduck sms gg +TARGETS=gb gbc pocket megaduck sms gg nes # Configure platform specific LCC flags here: -LCCFLAGS_gb = -Wl-yt0x1B # Set an MBC for banking (1B-ROM+MBC5+RAM+BATT) -LCCFLAGS_pocket = -Wl-yt0x1B # Usually the same as required for .gb -LCCFLAGS_duck = -Wl-yt0x1B # Usually the same as required for .gb -LCCFLAGS_gbc = -Wl-yt0x1B -Wm-yc # Same as .gb with: -Wm-yc (gb & gbc) or Wm-yC (gbc exclusive) +LCCFLAGS_gb = -Wl-yt0x1B -autobank # Set an MBC for banking (1B-ROM+MBC5+RAM+BATT) +LCCFLAGS_pocket = -Wl-yt0x1B -autobank # Usually the same as required for .gb +LCCFLAGS_duck = -Wl-yt0x1B -autobank # Usually the same as required for .gb +LCCFLAGS_gbc = -Wl-yt0x1B -Wm-yc -autobank # Same as .gb with: -Wm-yc (gb & gbc) or Wm-yC (gbc exclusive) LCCFLAGS_sms = LCCFLAGS_gg = +LCCFLAGS_nes = LCCFLAGS += $(LCCFLAGS_$(EXT)) # This adds the current platform specific LCC Flags -LCCFLAGS += -Wl-j -Wm-yoA -Wm-ya4 -autobank -Wb-ext=.rel -Wb-v # MBC + Autobanking related flags +LCCFLAGS += -Wl-j -Wm-yoA -Wm-ya4 -Wb-ext=.rel -Wb-v # MBC + Autobanking related flags # LCCFLAGS += -debug # Uncomment to enable debug output # LCCFLAGS += -v # Uncomment for lcc verbose output diff --git a/gbdk-lib/examples/cross-platform/logo/Makefile.targets b/gbdk-lib/examples/cross-platform/logo/Makefile.targets @@ -48,3 +48,8 @@ gg-clean: gg: ${MAKE} build-target PORT=z80 PLAT=gg EXT=gg TYP=SEGA + +nes-clean: + ${MAKE} clean-target EXT=nes +nes: + ${MAKE} build-target PORT=mos6502 PLAT=nes EXT=nes TYP=NES diff --git a/gbdk-lib/examples/cross-platform/logo/res/NES/GBDK_2020_logo.png b/gbdk-lib/examples/cross-platform/logo/res/NES/GBDK_2020_logo.png Binary files differ. diff --git a/gbdk-lib/examples/cross-platform/logo/res/NES/GBDK_2020_logo.png.meta b/gbdk-lib/examples/cross-platform/logo/res/NES/GBDK_2020_logo.png.meta @@ -0,0 +1 @@ +-map -use_nes_attributes -use_nes_colors -noflip -bpp 2 -max_palettes 4 -pack_mode gb + diff --git a/gbdk-lib/examples/cross-platform/logo/src/main.c b/gbdk-lib/examples/cross-platform/logo/src/main.c @@ -11,7 +11,9 @@ void main() { if (_cpu == CGB_TYPE) { set_bkg_palette(BKGF_CGB_PAL0, GBDK_2020_logo_PALETTE_COUNT, GBDK_2020_logo_palettes); } -#endif +#elif defined(SYSTEM_NES) + set_bkg_palette(0, GBDK_2020_logo_PALETTE_COUNT, GBDK_2020_logo_palettes); +#endif set_native_tile_data(0, GBDK_2020_logo_TILE_COUNT, GBDK_2020_logo_tiles); #if defined(SYSTEM_CGB) if (_cpu == CGB_TYPE) { @@ -23,12 +25,29 @@ void main() { GBDK_2020_logo_map_attributes); VBK_REG = VBK_TILES; } +#elif defined(SYSTEM_NES) + // Make sure attribute coordinates are rounded to 4 + // TODO: Round to 2 once support for odd attribute coordinates / dimensions is in place + set_bkg_attributes((((DEVICE_SCREEN_WIDTH - (GBDK_2020_logo_WIDTH >> 3)) >> 1) & 0xFC) >> 1, + (((DEVICE_SCREEN_HEIGHT - (GBDK_2020_logo_HEIGHT >> 3)) >> 1) & 0xFC) >> 1, + GBDK_2020_logo_MAP_ATTRIBUTES_WIDTH, + GBDK_2020_logo_MAP_ATTRIBUTES_HEIGHT, + GBDK_2020_logo_map_attributes); #endif +#if defined(SYSTEM_NES) + // Make sure tile coordinates are rounded to 4, to match attribute table + set_tile_map(((DEVICE_SCREEN_WIDTH - (GBDK_2020_logo_WIDTH >> 3)) >> 1) & 0xFC, + ((DEVICE_SCREEN_HEIGHT - (GBDK_2020_logo_HEIGHT >> 3)) >> 1) & 0xFC, + GBDK_2020_logo_WIDTH >> 3, + GBDK_2020_logo_HEIGHT >> 3, + GBDK_2020_logo_map); +#else set_tile_map((DEVICE_SCREEN_WIDTH - (GBDK_2020_logo_WIDTH >> 3)) >> 1, (DEVICE_SCREEN_HEIGHT - (GBDK_2020_logo_HEIGHT >> 3)) >> 1, GBDK_2020_logo_WIDTH >> 3, GBDK_2020_logo_HEIGHT >> 3, GBDK_2020_logo_map); +#endif SHOW_BKG; DISPLAY_ON; } diff --git a/gbdk-lib/include/nes/nes.h b/gbdk-lib/include/nes/nes.h @@ -8,37 +8,49 @@ #include <stdint.h> #include <gbdk/version.h> #include <nes/hardware.h> +#include <nes/rgb_to_nes_macro.h> #define NINTENDO_ENTERTAINMENT_SYSTEM #ifdef SEGA #undef SEGA #endif -#define RGB(r,g,b) ((r) | ((g) << 2) | ((b) << 4)) -#define RGB8(r,g,b) (((r) >> 6) | (((g) >> 6) << 2) | (((b) >> 6) << 4)) -#define RGBHTML(RGB24bit) (((RGB24bit) >> 22) | ((((RGB24bit) & 0xFFFF) >> 14) << 2) | ((((RGB24bit) & 0xFF) >> 6) << 4)) +#define RGB(r,g,b) RGB_TO_NES(((r) | ((g) << 2) | ((b) << 4))) +#define RGB8(r,g,b) RGB_TO_NES((((r) >> 6) | (((g) >> 6) << 2) | (((b) >> 6) << 4))) +#define RGBHTML(RGB24bit) RGB_TO_NES((((RGB24bit) >> 22) | ((((RGB24bit) & 0xFFFF) >> 14) << 2) | ((((RGB24bit) & 0xFF) >> 6) << 4))) /** Common colors based on the EGA default palette. + * + * Manually entered from https://www.nesdev.org/wiki/PPU_palettes#RGBI + * */ -#define RGB_RED RGB( 3, 0, 0) -#define RGB_DARKRED RGB( 2, 0, 0) -#define RGB_GREEN RGB( 0, 3, 0) -#define RGB_DARKGREEN RGB( 0, 2, 0) -#define RGB_BLUE RGB( 0, 0, 3) -#define RGB_DARKBLUE RGB( 0, 0, 2) -#define RGB_YELLOW RGB( 3, 3, 0) -#define RGB_DARKYELLOW RGB( 2, 2, 0) -#define RGB_CYAN RGB( 0, 3, 3) -#define RGB_AQUA RGB( 3, 1, 2) -#define RGB_PINK RGB( 3, 0, 3) -#define RGB_PURPLE RGB( 2, 0, 2) -#define RGB_BLACK RGB( 0, 0, 0) -#define RGB_DARKGRAY RGB( 1, 1, 1) -#define RGB_LIGHTGRAY RGB( 2, 2, 2) -#define RGB_WHITE RGB( 3, 3, 3) +#define RGB_RED 0x16 // EGA12 +#define RGB_DARKRED 0x06 // EGA4 +#define RGB_GREEN 0x2A // EGA10 +#define RGB_DARKGREEN 0x1A // EGA2 +#define RGB_BLUE 0x12 // EGA9 +#define RGB_DARKBLUE 0x02 // EGA1 +#define RGB_YELLOW 0x28 // EGA14 +#define RGB_DARKYELLOW 0x18 // EGA6 +#define RGB_CYAN 0x2C // EGA11 +#define RGB_AQUA 0x1C // EGA3 +#define RGB_PINK 0x24 // EGA13 +#define RGB_PURPLE 0x14 // EGA5 +#define RGB_BLACK 0x0F // EGA0 +#define RGB_DARKGRAY 0x00 // EGA8 +#define RGB_LIGHTGRAY 0x10 // EGA7 +#define RGB_WHITE 0x30 // EGA15 typedef uint8_t palette_color_t; +void set_bkg_palette(uint8_t first_palette, uint8_t nb_palettes, palette_color_t *rgb_data) OLDCALL; + +void set_sprite_palette(uint8_t first_palette, uint8_t nb_palettes, palette_color_t *rgb_data) OLDCALL; + +void set_bkg_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rgb_data) OLDCALL; + +void set_sprite_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rgb_data) OLDCALL; + /** Joypad bits. A logical OR of these is used in the wait_pad and joypad functions. For example, to see if the B button is pressed @@ -461,6 +473,7 @@ void set_bkg_1bpp_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data void set_bkg_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles) OLDCALL; #define set_tile_map set_bkg_tiles +void set_bkg_attributes(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes) OLDCALL; extern uint8_t _map_tile_offset; diff --git a/gbdk-lib/include/nes/rgb_to_nes_macro.h b/gbdk-lib/include/nes/rgb_to_nes_macro.h @@ -0,0 +1,70 @@ +// File auto-generated file by nespal.py +#ifndef __RGB_TO_NES_MACRO_H__ +#define __RGB_TO_NES_MACRO_H__ +#define RGB_TO_NES(c) \ + (c == 0x00) ? 0x1D : \ + (c == 0x01) ? 0x06 : \ + (c == 0x02) ? 0x17 : \ + (c == 0x03) ? 0x16 : \ + (c == 0x04) ? 0x19 : \ + (c == 0x05) ? 0x18 : \ + (c == 0x06) ? 0x17 : \ + (c == 0x07) ? 0x27 : \ + (c == 0x08) ? 0x2A : \ + (c == 0x09) ? 0x29 : \ + (c == 0x0A) ? 0x28 : \ + (c == 0x0B) ? 0x27 : \ + (c == 0x0C) ? 0x2A : \ + (c == 0x0D) ? 0x29 : \ + (c == 0x0E) ? 0x29 : \ + (c == 0x0F) ? 0x28 : \ + (c == 0x10) ? 0x01 : \ + (c == 0x11) ? 0x04 : \ + (c == 0x12) ? 0x15 : \ + (c == 0x13) ? 0x15 : \ + (c == 0x14) ? 0x1C : \ + (c == 0x15) ? 0x00 : \ + (c == 0x16) ? 0x15 : \ + (c == 0x17) ? 0x26 : \ + (c == 0x18) ? 0x2B : \ + (c == 0x19) ? 0x2A : \ + (c == 0x1A) ? 0x10 : \ + (c == 0x1B) ? 0x26 : \ + (c == 0x1C) ? 0x2B : \ + (c == 0x1D) ? 0x2A : \ + (c == 0x1E) ? 0x39 : \ + (c == 0x1F) ? 0x38 : \ + (c == 0x20) ? 0x02 : \ + (c == 0x21) ? 0x13 : \ + (c == 0x22) ? 0x14 : \ + (c == 0x23) ? 0x14 : \ + (c == 0x24) ? 0x11 : \ + (c == 0x25) ? 0x13 : \ + (c == 0x26) ? 0x10 : \ + (c == 0x27) ? 0x25 : \ + (c == 0x28) ? 0x2C : \ + (c == 0x29) ? 0x10 : \ + (c == 0x2A) ? 0x3D : \ + (c == 0x2B) ? 0x36 : \ + (c == 0x2C) ? 0x2C : \ + (c == 0x2D) ? 0x3B : \ + (c == 0x2E) ? 0x3A : \ + (c == 0x2F) ? 0x37 : \ + (c == 0x30) ? 0x12 : \ + (c == 0x31) ? 0x13 : \ + (c == 0x32) ? 0x14 : \ + (c == 0x33) ? 0x24 : \ + (c == 0x34) ? 0x12 : \ + (c == 0x35) ? 0x22 : \ + (c == 0x36) ? 0x23 : \ + (c == 0x37) ? 0x24 : \ + (c == 0x38) ? 0x21 : \ + (c == 0x39) ? 0x22 : \ + (c == 0x3A) ? 0x32 : \ + (c == 0x3B) ? 0x34 : \ + (c == 0x3C) ? 0x2C : \ + (c == 0x3D) ? 0x3C : \ + (c == 0x3E) ? 0x3C : \ + (c == 0x3F) ? 0x20 : \ + 0xFF // out-of-range value - set to 0xFF +#endif diff --git a/gbdk-lib/libc/targets/mos6502/nes/Makefile b/gbdk-lib/libc/targets/mos6502/nes/Makefile @@ -10,7 +10,8 @@ CSRC = crlf.c ASSRC = f_ibm_full.s f_ibm_sh.s f_italic.s f_min.s f_spect.s \ font.s font_color.s set_data.s set_1bit_data.s color.s mode.s \ metasprites.s metasprites_hide.s metasprites_hide_spr.s \ - fill_rect_bk.s \ + set_bk_ts.s fill_rect_bk.s set_bk_attributes.s \ + nes_palettes.s \ pad.s pad_ex.s \ crt0.s diff --git a/gbdk-lib/libc/targets/mos6502/nes/crt0.s b/gbdk-lib/libc/targets/mos6502/nes/crt0.s @@ -43,7 +43,7 @@ __current_bank:: .ds 1 _sys_time:: .ds 2 _shadow_PPUCTRL:: .ds 1 _shadow_PPUMASK:: .ds 1 -__crt0_paletteShadow: .ds 25 +__crt0_paletteShadow:: .ds 25 __crt0_spritePageValid: .ds 1 __crt0_NMI_Done: .ds 1 __crt0_NMI_insideNMI: .ds 1 diff --git a/gbdk-lib/libc/targets/mos6502/nes/fill_rect_bk.s b/gbdk-lib/libc/targets/mos6502/nes/fill_rect_bk.s @@ -14,6 +14,8 @@ _fill_bkg_rect:: .define .width "_fill_bkg_rect_PARM_3" .define .height "_fill_bkg_rect_PARM_4" .define .tile "_fill_bkg_rect_PARM_5" + sta *.xpos + stx *.ypos ldy *.height 1$: lda #0 diff --git a/gbdk-lib/libc/targets/mos6502/nes/global.s b/gbdk-lib/libc/targets/mos6502/nes/global.s @@ -92,6 +92,7 @@ .globl _shadow_PPUCTRL, _shadow_PPUMASK .globl _bkg_scroll_x, _bkg_scroll_y + .globl __crt0_paletteShadow ;; Identity table for register-to-register-adds and bankswitching .globl .identity, _identity diff --git a/gbdk-lib/libc/targets/mos6502/nes/nes_palettes.s b/gbdk-lib/libc/targets/mos6502/nes/nes_palettes.s @@ -0,0 +1,138 @@ + .include "global.s" + + .area OSEG (PAG, OVR) + _set_bkg_palette_PARM_3:: .ds 2 + .num_entries: .ds 1 + .define _set_sprite_palette_PARM_3 "_set_bkg_palette_PARM_3" + .define _set_bkg_palette_entry_PARM_3 "_set_bkg_palette_PARM_3" + .define _set_sprite_palette_entry_PARM_3 "_set_bkg_palette_PARM_3" + .define .src "_set_bkg_palette_PARM_3" + + .area _HOME + +; void set_bkg_palette(uint8_t first_palette, uint8_t nb_palettes, palette_color_t *rgb_data) OLDCALL; +_set_bkg_palette:: + pha + txa + asl + asl + sta *.num_entries + pla + asl + asl + tax + jmp .set_palette_impl + +; void set_sprite_palette(uint8_t first_palette, uint8_t nb_palettes, palette_color_t *rgb_data) OLDCALL; +_set_sprite_palette:: + pha + txa + asl + asl + sta *.num_entries + pla + asl + asl + tax + adc #16 + ; jmp .set_palette_impl + +.set_palette_impl: + ldy #0 +2$: + ; skip mirror entries + lda .paletteShadowLUT,x + bmi 1$ + stx *.tmp + tax + lda [*.src],y + sta *__crt0_paletteShadow,x + ldx *.tmp +1$: + inx + iny + cpy *.num_entries + bne 2$ + rts + +; void set_bkg_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rgb_data) OLDCALL; +_set_bkg_palette_entry:: + ; Ignore entry 0 (unified background color) for all palettes except first + cpx #0 + bne 1$ + cmp #0 + beq 2$ + rts +1$: + dex + asl + asl + clc + adc .identity,x + tax + lda *.src + sta *__crt0_paletteShadow+1,x + rts +2$: + ; Set UBC + lda *.src + sta *__crt0_paletteShadow + rts + +; void set_sprite_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rgb_data) OLDCALL; +_set_sprite_palette_entry:: + ; Ignore entry 0 (transparency encoding) + cpx #0 + bne 1$ + rts +1$: + dex + asl + asl + clc + adc .identity,x + tax + lda *.src + sta *__crt0_paletteShadow+13,x + rts + +.paletteShadowLUT: + .db 0x00 ; UBC + .db 0x01 + .db 0x02 + .db 0x03 + ; + .db 0xFF ; Skip BG1 entry 0 + .db 0x04 + .db 0x05 + .db 0x06 + ; + .db 0xFF ; Skip BG2 entry 0 + .db 0x07 + .db 0x08 + .db 0x09 + ; + .db 0xFF ; Skip BG3 entry 0 + .db 0x0A + .db 0x0B + .db 0x0C + ; + .db 0xFF ; Skip SPR0 entry 0 + .db 0x0D + .db 0x0E + .db 0x0F + ; + .db 0xFF ; Skip SPR1 entry 0 + .db 0x10 + .db 0x11 + .db 0x12 + ; + .db 0xFF ; Skip SPR2 entry 0 + .db 0x13 + .db 0x14 + .db 0x15 + ; + .db 0xFF ; Skip SPR3 entry 0 + .db 0x16 + .db 0x17 + .db 0x18 diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_bk_attributes.s b/gbdk-lib/libc/targets/mos6502/nes/set_bk_attributes.s @@ -0,0 +1,70 @@ + .include "global.s" + + .area OSEG (PAG, OVR) + _set_bkg_attributes_PARM_3:: .ds 1 + _set_bkg_attributes_PARM_4:: .ds 1 + _set_bkg_attributes_PARM_5:: .ds 2 + .xpos: .ds 1 + .ypos: .ds 1 + .num_rows: .ds 1 + .src_tiles: .ds 2 + + .area _HOME + +_set_bkg_attributes:: + ; TODO: Switch to use vram transfer buffer when screen not blanked + .define .width "_set_bkg_attributes_PARM_3" + .define .height "_set_bkg_attributes_PARM_4" + .define .tiles "_set_bkg_attributes_PARM_5" + lsr ; Only support even-numbered attribute coordinates for now. + sta *.xpos + stx *.ypos + lsr *.ypos ; Only support even-numbered attribute coordinates for now. + lda .tiles + sta *.src_tiles + lda .tiles+1 + sta *.src_tiles+1 + lda *.height + lsr ; Only support even-numbered attribute dimensions for now. + sta *.num_rows +1$: + lda #0 + sta *.tmp+1 + lda *.ypos + asl + rol *.tmp+1 + asl + rol *.tmp+1 + asl + rol *.tmp+1 + ora *.xpos + ora #0xC0 + sta *.tmp + ; + lda *.tmp+1 + ora #0x23 + sta PPUADDR + lda *.tmp + sta PPUADDR + lda *.width + lsr ; Only support even-numbered attribute dimensions for now. + tax + ldy #0 +2$: + lda [*.src_tiles],y + iny + sta PPUDATA + dex + bne 2$ + ; .src_tiles += y + tya + clc + adc *.src_tiles + sta *.src_tiles + lda #0 + adc *.src_tiles+1 + sta *.src_tiles+1 + inc *.ypos + dec *.num_rows + bne 1$ + rts diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_bk_ts.s b/gbdk-lib/libc/targets/mos6502/nes/set_bk_ts.s @@ -0,0 +1,68 @@ + .include "global.s" + + .area OSEG (PAG, OVR) + _set_bkg_tiles_PARM_3:: .ds 1 + _set_bkg_tiles_PARM_4:: .ds 1 + _set_bkg_tiles_PARM_5:: .ds 2 + .xpos: .ds 1 + .ypos: .ds 1 + .num_rows: .ds 1 + .src_tiles: .ds 2 + + .area _HOME + +_set_bkg_tiles:: + ; TODO: Switch to use vram transfer buffer when screen not blanked + .define .width "_set_bkg_tiles_PARM_3" + .define .height "_set_bkg_tiles_PARM_4" + .define .tiles "_set_bkg_tiles_PARM_5" + sta *.xpos + stx *.ypos + lda .tiles + sta *.src_tiles + lda .tiles+1 + sta *.src_tiles+1 + lda *.height + sta *.num_rows +1$: + lda #0 + sta *.tmp+1 + lda *.ypos + asl + rol *.tmp+1 + asl + rol *.tmp+1 + asl + rol *.tmp+1 + asl + rol *.tmp+1 + asl + rol *.tmp+1 + ora *.xpos + sta *.tmp + ; + lda *.tmp+1 + ora #0x20 + sta PPUADDR + lda *.tmp + sta PPUADDR + ldx *.width + ldy #0 +2$: + lda [*.src_tiles],y + iny + sta PPUDATA + dex + bne 2$ + ; .src_tiles += y + tya + clc + adc *.src_tiles + sta *.src_tiles + lda #0 + adc *.src_tiles+1 + sta *.src_tiles+1 + inc *.ypos + dec *.num_rows + bne 1$ + rts diff --git a/gbdk-support/nespal/nespal.py b/gbdk-support/nespal/nespal.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +import sys +import argparse +from operator import itemgetter +from pathlib import Path + +from typing import Optional, Tuple, List, Sequence, Dict, Set, NewType + + +def get_ppu_color_name(ppu_color: int) -> str: + """ + Returns a human-readable name for a PPU color + + Naming convention from https://www.nesdev.org/wiki/PPU_palettes#Color_names + + With following simplifications: + 'Dark gray' -> 'Gray' + 'Light gray or silver' -> 'Silver' + + :param ppu_color: 6-bit NES PPU color + :return: Human-readable name for PPU color + """ + ppu_hue_names = [ + 'Gray', + 'Azure', + 'Blue', + 'Violet', + 'Magenta', + 'Rose', + 'Red', + 'Orange', + 'Yellow', + 'Chartreuse', + 'Green', + 'Spring', + 'Cyan' + ] + ppu_luma_names = [ + 'Dark', + 'Medium', + 'Light', + 'Pale' + ] + c = ppu_color & 0x3F + # Special-cases for black/white/grays + if c in [0x20, 0x30]: + return 'White' + elif c in [0x3D]: + return 'Silver' + elif c in [0x00, 0x2D]: + return 'Gray' + elif c in [0x1D, 0x0E, 0x1E, 0x2E, 0x3E, 0x0F, 0x1F, 0x2F, 0x3F]: + return 'Black' + elif c == 0x0D: + return 'BlackerThanBlack' + # All normal hues + ppu_hue_name = ppu_hue_names[c & 0xF] + ppu_luma_name = ppu_luma_names[(c >> 4) & 0x3] + return f'{ppu_luma_name}-{ppu_hue_name}' + + +def get_script_directory() -> Path: + """ + Return path to current scripts directory. + (or the executable if built with pyinstaller) + + :return: Path to directory of this script + """ + if getattr(sys, 'frozen', False): + return Path(sys.executable).parent + # or a script file (e.g. `.py` / `.pyw`) + elif __file__: + return Path(__file__).parent + + +def nes_closest_palette_entry(rgb: Tuple[int, int, int], NESPaletteRGB: List[Tuple[int, int, int]]) -> int: + def dist2(rgbA, rgbB): + return sum((rgbA[i] - rgbB[i])**2 for i in range(3)) + minIndex = min(enumerate([dist2(rgb, nprgb) for i, nprgb in enumerate(NESPaletteRGB)]), key=itemgetter(1))[0] + return minIndex + + +def to_triplets(data: List[int]) -> List[Tuple[int, int, int]]: + """ + Convert a linear list into a triplet list + + :param data: List formatted as [0, 1, 2 ... N, N+1, N+2] + :return: Nested list formatted as [[0, 1, 2] ... [N, N+1, N+2]] + """ + assert(len(data) % 3 == 0) + length = len(data) // 3 + data_triplets = [] + for i in range(length): + data_triplets.append((data[3 * i + 0], data[3 * i + 1], data[3 * i + 2])) + return data_triplets + + +def map_to_PPU_colors(target_colors: List[int], rgb_palette_nes: List[int], invalid_colors: List[int]) -> Tuple[List[int], List[int]]: + """ + Creates a mapping from target colors in RGB format to NES PPU colors + + :param target_colors: List of length N, with target colors in RGB format + :param rgb_palette_nes: List of length 64, giving RGB values for NES colors + :return: List of length N with each entry indicating the best NES color + """ + target_colors = to_triplets(target_colors) + rgb_palette_nes = to_triplets(rgb_palette_nes) + # Set invalid_colors to large value to prevent being picked + rgb_palette_nes = rgb_palette_nes[:] + for c in invalid_colors: + rgb_palette_nes[c] = (1000000, 1000000, 1000000) + nes_palette_colors = [] + for rgb_color in target_colors: + # print(rgb_color) + closest_palette_entry = nes_closest_palette_entry(rgb_color, rgb_palette_nes) + nes_palette_colors.append(closest_palette_entry) + return nes_palette_colors + + +def get_pal_file_path(pal_file_path: str) -> Path: + """ + Convert a string path to Pathlib path. + If None, use default path and print warning message. + If file is missing, print error. + + :param pal_file_path: path to .pal file + :return: path as pathlib Path + """ + default_pal_file_path = get_script_directory() / 'palettes' / 'palgen.pal' + if pal_file_path is None: + print(f'WARNING: Palette file not specified - falling back to default palette file {str(default_pal_file_path)}') + return default_pal_file_path + elif not Path(pal_file_path).exists(): + print(f'WARNING: Palette file {str(Path(pal_file_path))} does not exist - falling back to default palette file {str(default_pal_file_path)}') + return default_pal_file_path + else: + return Path(pal_file_path) + + +def write_c_lut(lut: List[int], filename: Path): + """ + Writes NES color mapping as a C-array rgb_to_nes + + :param lut: Lookup table of 64 values, indexed as BBGGRR + :param filename: Source text file to write + """ + with open(filename, 'wt') as f: + print('// File auto-generated file by nespal.py', file=f) + print(f'unsigned char rgb_to_nes[{len(lut)}] = {{', file=f) + for i, c in enumerate(lut): + r = ((i >> 0) & 0x3) + g = ((i >> 2) & 0x3) + b = ((i >> 4) & 0x3) + print(f' 0x{c:0{2}X}, // RGB({r},{g},{b}) -> {c:0{2}X} ({get_ppu_color_name(c)})', file=f) + print(f'}};', file=f) + + +def write_c_macro(lut: List[int], filename: Path): + """ + Writes NES color mapping as a C-macro RGB_TO_NES(c) + + :param lut: Lookup table of 64 values, indexed as BBGGRR + :param filename: Source text file to write + """ + with open(filename, 'wt') as f: + print('// File auto-generated file by nespal.py', file=f) + print('#ifndef __RGB_TO_NES_MACRO_H__', file=f) + print('#define __RGB_TO_NES_MACRO_H__', file=f) + print(f'#define RGB_TO_NES(c) \\', file=f) + for i, c in enumerate(lut): + print(f' (c == 0x{i:0{2}X}) ? 0x{c:0{2}X} : \\', file=f) + print(f' 0xFF // out-of-range value - set to 0xFF', file=f) + print('#endif', file=f) + + +def main(palette_file: Path, + invalid_colors: List[int], + output_c_lut_path: Path, + output_c_macro_path: Path) -> int: + # Read NES palette mapping file + with open(palette_file, 'rb') as f: + nes_palette = f.read(192) + # Use a 6-bit RGB palette in BBGGRR format + gbdk_palette = [] + for i in range(64): + r = (((i >> 0) & 0x3) / 3.0) * 255.0 + g = (((i >> 2) & 0x3) / 3.0) * 255.0 + b = (((i >> 4) & 0x3) / 3.0) * 255.0 + gbdk_palette.append(r) + gbdk_palette.append(g) + gbdk_palette.append(b) + nes_palette_colors = map_to_PPU_colors(gbdk_palette, nes_palette, invalid_colors) + if output_c_lut_path is not None: + write_c_lut(nes_palette_colors, output_c_lut_path) + if output_c_macro_path is not None: + write_c_macro(nes_palette_colors, output_c_macro_path) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=f'Generates lookup tables to convert RGB to NES PPU compatible colors.') + parser.add_argument('--pal_file', type=str, + default=None, + help='Binary 192-byte file specifying a particular NES palette flavor.') + parser.add_argument('--output_c_lut', type=str, + default='rgb_to_nes_lut.c', + help='Output .c file with generated rgb_to_nes lookup table.') + parser.add_argument('--output_c_macro', type=str, + default='rgb_to_nes_macro.h', + help='Output .h file with generated RGB_TO_NES C macro.') + parser.add_argument('--invalid_colors', type=str, + nargs='+', + default=['0D', '0E', '1E', '2E', '3E', '0F', '1F', '2F', '3F'], + help='Specify which NES PPU colors should be considered invalid.') + args = parser.parse_args() + # Call main program + rc = main(get_pal_file_path(args.pal_file), + [int(s, 16) for s in args.invalid_colors], + Path(args.output_c_lut), + Path(args.output_c_macro)) + sys.exit(rc) diff --git a/gbdk-support/nespal/palettes/palgen.pal b/gbdk-support/nespal/palettes/palgen.pal Binary files differ. diff --git a/gbdk-support/png2asset/Makefile b/gbdk-support/png2asset/Makefile @@ -41,7 +41,7 @@ endif TARGET = png2asset -SRCS := lodepng.cpp png2asset.cpp +SRCS := lodepng.cpp png2asset.cpp rgb_to_nes_lut.cpp OBJS := $(SRCS:%.cpp=%.o) $(TARGET): $(OBJS) diff --git a/gbdk-support/png2asset/png2asset.cpp b/gbdk-support/png2asset/png2asset.cpp @@ -25,6 +25,13 @@ enum { bool export_as_map = false; bool use_map_attributes = false; +bool use_2x2_map_attributes = false; +bool pack_map_attributes = false; +bool convert_rgb_to_nes = false; +size_t map_attributes_width = 0; +size_t map_attributes_height = 0; +size_t map_attributes_packed_width = 0; +size_t map_attributes_packed_height = 0; size_t colors_per_pal; // Number of colors per palette (ex: CGB has 4 colors per palette x 8 palettes total) int tile_w; int tile_h; @@ -32,6 +39,8 @@ int sprite_mode; int bpp = 2; unsigned int tile_origin = 0; // Default to no tile index offset +extern unsigned char rgb_to_nes[64]; + struct Tile { vector< unsigned char > data; @@ -446,6 +455,130 @@ unsigned int PaletteCountApplyMaxLimit(unsigned int max_palettes, unsigned int c return cur_palette_size; } +// +// Builds palettes and palette-per-tile (attributes) for an image +// +// Inputs: +// image32 : Input image in RGBA32 format +// palettes : Palettes for image. Will be added-to / merged with new palettes. +// half_resolution : If true, attributes will follow a constraint of every 2x2 cell having the same attribute. +// +// Returns: array of attributes. This always has *per-tile* dimensions, even when half_resolution is true. +// +int* BuildPalettesAndAttributes(const PNGImage& image32, vector< SetPal >& palettes, bool half_resolution) +{ + int* palettes_per_tile = new int[(image32.w / tile_w) * (image32.h / tile_h)]; + int sx = half_resolution ? 2 : 1; + int sy = half_resolution ? 2 : 1; + for (unsigned int y = 0; y < image32.h; y += tile_h * sy) + { + for (unsigned int x = 0; x < image32.w; x += tile_w * sx) + { + //Get palette colors on (x, y, tile_w, tile_h) + SetPal pal = GetPaletteColors(image32, (x / sx) * sx, (y / sy) * sy, sx * tile_w, sy * tile_h); + if (pal.size() > colors_per_pal) + { + printf("Error: more than %d colors found in tile at x:%d, y:%d of size w:%d, h:%d\n", + (unsigned int)colors_per_pal, + (x / sx) * sx, + (y / sy) * sy, + sx * tile_w, + sy * tile_h); + delete[] palettes_per_tile; + return nullptr; + } + + //Check if it matches any palettes or create a new one + size_t i; + for (i = 0; i < palettes.size(); ++i) + { + //Try to merge this palette with any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) + SetPal merged(palettes[i]); + merged.insert(pal.begin(), pal.end()); + if (merged.size() <= colors_per_pal) + { + if (palettes[i].size() <= colors_per_pal) + palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) + break; //Found palette + } + } + + if (i == palettes.size()) + { + //Palette not found, add a new one + palettes.push_back(pal); + } + // Assign single or multiple entries in palettes_per_tile, to keep it independent of + // of half_resolution parameter + int dx = ((x / tile_w) / sx) * sx; + int dy = ((y / tile_h) / sy) * sy; + int w = (image32.w / tile_w); + for (int yy = 0; yy < sy; yy++) + { + for (int xx = 0; xx < sx; xx++) + { + palettes_per_tile[(dy + yy) * w + dx + xx] = i; + } + } + } + } + return palettes_per_tile; +} + +unsigned char GetMapAttribute(size_t x, size_t y) +{ + if (x < map_attributes_width && y < map_attributes_height) + return map_attributes[y * map_attributes_width + x]; + else + return 0; +} + +void ReduceMapAttributes2x2(const vector< SetPal >& palettes) +{ + size_t w = (map_attributes_width + 1) / 2; + size_t h = (map_attributes_height + 1) / 2; + vector< unsigned char > map_attributes_2x2; + map_attributes_2x2.resize(w * h); + for (size_t y = 0; y < h; y++) + { + for (size_t x = 0; x < w; x++) + { + // Use only Top-left attribute, ignoring the other three as they should now be identical + map_attributes_2x2[y * w + x] = GetMapAttribute(2 * x, 2 * y); + } + } + // Overwrite old attributes + map_attributes_width = w; + map_attributes_height = h; + map_attributes = map_attributes_2x2; +} + +// +// Pack map attributes +// (NES has packs multiple 2-bit entries into one byte) +// +void PackMapAttributes() +{ + vector< unsigned char > map_attributes_packed; + map_attributes_packed_width = (map_attributes_width + 1) / 2; + map_attributes_packed_height = (map_attributes_width + 1) / 2; + map_attributes_packed.resize(map_attributes_packed_width * map_attributes_packed_height); + for (size_t y = 0; y < map_attributes_packed_height; y++) + { + for (size_t x = 0; x < map_attributes_packed_width; x++) + { + unsigned char a_tl = GetMapAttribute(2 * x + 0, 2 * y + 0); + unsigned char a_tr = GetMapAttribute(2 * x + 1, 2 * y + 0); + unsigned char a_bl = GetMapAttribute(2 * x + 0, 2 * y + 1); + unsigned char a_br = GetMapAttribute(2 * x + 1, 2 * y + 1); + unsigned char packed_bits = (a_br << 6) | (a_bl << 4) | (a_tr << 2) | (a_tl << 0); + map_attributes_packed[map_attributes_packed_width * y + x] = packed_bits; + } + } + // Overwrite old attributes + map_attributes = map_attributes_packed; +} + bool GetSourceTileset(bool keep_palette_order, unsigned int max_palettes, vector< SetPal >& palettes) { lodepng::State sourceTilesetState; @@ -497,45 +630,7 @@ bool GetSourceTileset(bool keep_palette_order, unsigned int max_palettes, vector return false; } - - int* palettes_per_tile = new int[(image32.w / tile_w) * (image32.h / tile_h)]; - - for (unsigned int y = 0; y < image32.h; y += tile_h) - { - for (unsigned int x = 0; x < image32.w; x += tile_w) - { - //Get palette colors on (x, y, tile_w, tile_h) - SetPal pal = GetPaletteColors(image32, x, y, tile_w, tile_h); - if (pal.size() > colors_per_pal) - { - printf("Error: more than %d colors found in tile at x:%d, y:%d of size w:%d, h:%d\n", (unsigned int)colors_per_pal, x, y, tile_w, tile_h); - return false; - } - - //Check if it matches any palettes or create a new one - size_t i; - for (i = 0; i < palettes.size(); ++i) - { - //Try to merge this palette with any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) - SetPal merged(palettes[i]); - merged.insert(pal.begin(), pal.end()); - if (merged.size() <= colors_per_pal) - { - if (palettes[i].size() <= colors_per_pal) - palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) - break; //Found palette - } - } - - if (i == palettes.size()) - { - //Palette not found, add a new one - palettes.push_back(pal); - } - - palettes_per_tile[(y / tile_h) * (image32.w / tile_w) + (x / tile_w)] = i; - } - }; + int* palettes_per_tile = BuildPalettesAndAttributes(image32, palettes, use_2x2_map_attributes); //Create the indexed image source_tileset_image.data.clear(); @@ -642,6 +737,8 @@ int main(int argc, char* argv[]) printf("-noflip disable tile flip\n"); printf("-map Export as map (tileset + bg)\n"); printf("-use_map_attributes Use CGB BG Map attributes\n"); + printf("-use_nes_attributes Use NES BG Map attributes\n"); + printf("-use_nes_colors Convert RGB color values to NES PPU colors\n"); printf("-use_structs Group the exported info into structs (default: false) (used by ZGB Game Engine)\n"); printf("-bpp bits per pixel: 1, 2, 4 (default: 2)\n"); printf("-max_palettes max number of palettes allowed (default: 8)\n"); @@ -753,6 +850,16 @@ int main(int argc, char* argv[]) { use_map_attributes = true; } + else if (!strcmp(argv[i], "-use_nes_attributes")) + { + use_map_attributes = true; + use_2x2_map_attributes = true; + pack_map_attributes = true; + } + else if (!strcmp(argv[i], "-use_nes_colors")) + { + convert_rgb_to_nes = true; + } else if(!strcmp(argv[i], "-use_structs")) { use_structs = true; @@ -929,43 +1036,7 @@ int main(int argc, char* argv[]) return 1; } - int* palettes_per_tile = new int[(image32.w / tile_w) * (image32.h /tile_h)]; - for(unsigned int y = 0; y < image32.h; y += tile_h) - { - for(unsigned int x = 0; x < image32.w; x += tile_w) - { - //Get palette colors on (x, y, 8, tile_h) - SetPal pal = GetPaletteColors(image32, x, y, tile_w, tile_h); - if(pal.size() > colors_per_pal) - { - printf("Error: more than %d colors found in tile at x:%d, y:%d of size w:%d, h:%d\n", (unsigned int)colors_per_pal, x, y, tile_w, tile_h); - return 1; - } - - //Check if it matches any palettes or create a new one - size_t i; - for(i = 0; i < palettes.size(); ++i) - { - //Try to merge this palette wit any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) - SetPal merged(palettes[i]); - merged.insert(pal.begin(), pal.end()); - if(merged.size() <= colors_per_pal) - { - if(palettes[i].size() <= colors_per_pal) - palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) - break; //Found palette - } - } - - if(i == palettes.size()) - { - //Palette not found, add a new one - palettes.push_back(pal); - } - - palettes_per_tile[(y / tile_h) * (image32.w / tile_w) + (x / tile_w)] = i; - } - } + int* palettes_per_tile = BuildPalettesAndAttributes(image32, palettes, use_2x2_map_attributes); //Create the indexed image image.data.clear(); @@ -1033,6 +1104,25 @@ int main(int argc, char* argv[]) } } } + map_attributes_width = image.w / 8; + map_attributes_height = image.h / 8; + // Optionally perform 2x2 reduction on attributes (NES attribute table has this format) + if(use_2x2_map_attributes) + { + // NES attribute map dimensions are half-resolution + ReduceMapAttributes2x2(palettes); + } + // Optionally pack map attributes into NES PPU format + if (pack_map_attributes) + { + PackMapAttributes(); + } + else + { + // Use original attribute dimensions for packed + map_attributes_packed_width = map_attributes_width; + map_attributes_packed_height = map_attributes_height; + } //Output .h FILE FILE* file; @@ -1091,7 +1181,15 @@ int main(int argc, char* argv[]) fprintf(file, "%s_map_attributes\n", data_name.c_str()); else fprintf(file, "0\n"); - + + if (use_map_attributes) + { + fprintf(file, "#define %s_MAP_ATTRIBUTES_WIDTH %d\n", data_name.c_str(), (int)map_attributes_width); + fprintf(file, "#define %s_MAP_ATTRIBUTES_HEIGHT %d\n", data_name.c_str(), (int)map_attributes_height); + fprintf(file, "#define %s_MAP_ATTRIBUTES_PACKED_WIDTH %d\n", data_name.c_str(), (int)map_attributes_packed_width); + fprintf(file, "#define %s_MAP_ATTRIBUTES_PACKED_HEIGHT %d\n", data_name.c_str(), (int)map_attributes_packed_height); + } + if(use_structs) { fprintf(file, "#define %s_TILE_PALS ", data_name.c_str()); @@ -1181,7 +1279,13 @@ int main(int argc, char* argv[]) unsigned char* pal_ptr = &image.palette[i * (colors_per_pal * RGBA32_SZ)]; for(int c = 0; c < (int)colors_per_pal; ++ c, pal_ptr += RGBA32_SZ) { - fprintf(file, "RGB8(%3d,%3d,%3d)", pal_ptr[0], pal_ptr[1], pal_ptr[2]); + size_t rgb222 = (((pal_ptr[2] >> 6) & 0x3) << 4) | + (((pal_ptr[1] >> 6) & 0x3) << 2) | + (((pal_ptr[0] >> 6) & 0x3) << 0); + if (convert_rgb_to_nes) + fprintf(file, "0x%0X", rgb_to_nes[rgb222]); + else + fprintf(file, "RGB8(%3d,%3d,%3d)", pal_ptr[0], pal_ptr[1], pal_ptr[2]); if(c != (int)colors_per_pal - 1) fprintf(file, ", "); // Line break every 4 color entries, to keep line width down @@ -1330,23 +1434,23 @@ int main(int argc, char* argv[]) fprintf(file, "\n"); fprintf(file, "const unsigned char %s_map_attributes[%d] = {\n", data_name.c_str(), (unsigned int)map_attributes.size()); if (output_transposed) { - for (size_t i = 0; i < line_size; ++i) + for (size_t i = 0; i < map_attributes_packed_width; ++i) { fprintf(file, "\t"); - for (size_t j = 0; j < image.h / 8; ++j) + for (size_t j = 0; j < map_attributes_packed_height; ++j) { - fprintf(file, "0x%02x,", map_attributes[j * line_size + i]); + fprintf(file, "0x%02x,", map_attributes[j * map_attributes_packed_width + i]); } fprintf(file, "\n"); } } else { - for (size_t j = 0; j < image.h / 8; ++j) + for (size_t j = 0; j < map_attributes_packed_height; ++j) { fprintf(file, "\t"); - for (size_t i = 0; i < line_size; ++i) + for (size_t i = 0; i < map_attributes_packed_width; ++i) { - fprintf(file, "0x%02x,", map_attributes[j * line_size + i]); + fprintf(file, "0x%02x,", map_attributes[j * map_attributes_packed_width + i]); } fprintf(file, "\n"); } diff --git a/gbdk-support/png2asset/rgb_to_nes_lut.cpp b/gbdk-support/png2asset/rgb_to_nes_lut.cpp @@ -0,0 +1,67 @@ +// File auto-generated file by nespal.py +unsigned char rgb_to_nes[64] = { + 0x1D, // RGB(0,0,0) -> 1D (Black) + 0x06, // RGB(1,0,0) -> 06 (Dark-Red) + 0x17, // RGB(2,0,0) -> 17 (Medium-Orange) + 0x16, // RGB(3,0,0) -> 16 (Medium-Red) + 0x19, // RGB(0,1,0) -> 19 (Medium-Chartreuse) + 0x18, // RGB(1,1,0) -> 18 (Medium-Yellow) + 0x17, // RGB(2,1,0) -> 17 (Medium-Orange) + 0x27, // RGB(3,1,0) -> 27 (Light-Orange) + 0x2A, // RGB(0,2,0) -> 2A (Light-Green) + 0x29, // RGB(1,2,0) -> 29 (Light-Chartreuse) + 0x28, // RGB(2,2,0) -> 28 (Light-Yellow) + 0x27, // RGB(3,2,0) -> 27 (Light-Orange) + 0x2A, // RGB(0,3,0) -> 2A (Light-Green) + 0x29, // RGB(1,3,0) -> 29 (Light-Chartreuse) + 0x29, // RGB(2,3,0) -> 29 (Light-Chartreuse) + 0x28, // RGB(3,3,0) -> 28 (Light-Yellow) + 0x01, // RGB(0,0,1) -> 01 (Dark-Azure) + 0x04, // RGB(1,0,1) -> 04 (Dark-Magenta) + 0x15, // RGB(2,0,1) -> 15 (Medium-Rose) + 0x15, // RGB(3,0,1) -> 15 (Medium-Rose) + 0x1C, // RGB(0,1,1) -> 1C (Medium-Cyan) + 0x00, // RGB(1,1,1) -> 00 (Gray) + 0x15, // RGB(2,1,1) -> 15 (Medium-Rose) + 0x26, // RGB(3,1,1) -> 26 (Light-Red) + 0x2B, // RGB(0,2,1) -> 2B (Light-Spring) + 0x2A, // RGB(1,2,1) -> 2A (Light-Green) + 0x10, // RGB(2,2,1) -> 10 (Medium-Gray) + 0x26, // RGB(3,2,1) -> 26 (Light-Red) + 0x2B, // RGB(0,3,1) -> 2B (Light-Spring) + 0x2A, // RGB(1,3,1) -> 2A (Light-Green) + 0x39, // RGB(2,3,1) -> 39 (Pale-Chartreuse) + 0x38, // RGB(3,3,1) -> 38 (Pale-Yellow) + 0x02, // RGB(0,0,2) -> 02 (Dark-Blue) + 0x13, // RGB(1,0,2) -> 13 (Medium-Violet) + 0x14, // RGB(2,0,2) -> 14 (Medium-Magenta) + 0x14, // RGB(3,0,2) -> 14 (Medium-Magenta) + 0x11, // RGB(0,1,2) -> 11 (Medium-Azure) + 0x13, // RGB(1,1,2) -> 13 (Medium-Violet) + 0x10, // RGB(2,1,2) -> 10 (Medium-Gray) + 0x25, // RGB(3,1,2) -> 25 (Light-Rose) + 0x2C, // RGB(0,2,2) -> 2C (Light-Cyan) + 0x10, // RGB(1,2,2) -> 10 (Medium-Gray) + 0x3D, // RGB(2,2,2) -> 3D (Silver) + 0x36, // RGB(3,2,2) -> 36 (Pale-Red) + 0x2C, // RGB(0,3,2) -> 2C (Light-Cyan) + 0x3B, // RGB(1,3,2) -> 3B (Pale-Spring) + 0x3A, // RGB(2,3,2) -> 3A (Pale-Green) + 0x37, // RGB(3,3,2) -> 37 (Pale-Orange) + 0x12, // RGB(0,0,3) -> 12 (Medium-Blue) + 0x13, // RGB(1,0,3) -> 13 (Medium-Violet) + 0x14, // RGB(2,0,3) -> 14 (Medium-Magenta) + 0x24, // RGB(3,0,3) -> 24 (Light-Magenta) + 0x12, // RGB(0,1,3) -> 12 (Medium-Blue) + 0x22, // RGB(1,1,3) -> 22 (Light-Blue) + 0x23, // RGB(2,1,3) -> 23 (Light-Violet) + 0x24, // RGB(3,1,3) -> 24 (Light-Magenta) + 0x21, // RGB(0,2,3) -> 21 (Light-Azure) + 0x22, // RGB(1,2,3) -> 22 (Light-Blue) + 0x32, // RGB(2,2,3) -> 32 (Pale-Blue) + 0x34, // RGB(3,2,3) -> 34 (Pale-Magenta) + 0x2C, // RGB(0,3,3) -> 2C (Light-Cyan) + 0x3C, // RGB(1,3,3) -> 3C (Pale-Cyan) + 0x3C, // RGB(2,3,3) -> 3C (Pale-Cyan) + 0x20, // RGB(3,3,3) -> 20 (White) +};

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.