mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-27 05:22:07 +00:00
Compare commits
3 Commits
v0.6.0-rc1
...
v0.6.0-wel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bffe7eb4de | ||
|
|
cd454d2e9a | ||
|
|
c814a616d6 |
@@ -93,7 +93,7 @@ SpacesInConditionalStatement: false
|
|||||||
SpacesInContainerLiterals: false
|
SpacesInContainerLiterals: false
|
||||||
SpacesInParentheses: false
|
SpacesInParentheses: false
|
||||||
SpacesInSquareBrackets: false
|
SpacesInSquareBrackets: false
|
||||||
Standard: c++17
|
Standard: c++20
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
UseCRLF: false
|
UseCRLF: false
|
||||||
UseTab: ForIndentation
|
UseTab: AlignWithSpaces
|
||||||
|
|||||||
56
.github/actions/doc_postproc.awk
vendored
Executable file
56
.github/actions/doc_postproc.awk
vendored
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/awk -f
|
||||||
|
|
||||||
|
/^\s+<td><b class="Sy">.+<\/b><\/td>$/ {
|
||||||
|
# Assuming that all cells whose contents are bold are heading cells,
|
||||||
|
# use the HTML tag for those
|
||||||
|
sub(/td><b class="Sy"/, "th");
|
||||||
|
sub(/b><\/td/, "th");
|
||||||
|
}
|
||||||
|
|
||||||
|
# The whole page is being generated, so it's not meant to contain any Liquid
|
||||||
|
BEGIN {
|
||||||
|
print "{% raw %}"
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
print "{% endraw %}"
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
in_synopsis = 0
|
||||||
|
}
|
||||||
|
/<table class="Nm">/ {
|
||||||
|
in_synopsis = 1
|
||||||
|
}
|
||||||
|
/<\/table>/ {
|
||||||
|
# Resets synopsis state even when already reset, but whatever
|
||||||
|
in_synopsis = 0
|
||||||
|
}
|
||||||
|
/<code class="Fl">-[a-zA-Z]/ {
|
||||||
|
# Add links to arg descr in synopsis section
|
||||||
|
if (in_synopsis) {
|
||||||
|
while (match($0, /<code class="Fl">-[a-zA-Z]+/)) {
|
||||||
|
# 123456789012345678 -> 18 chars
|
||||||
|
optchars = substr($0, RSTART + 18, RLENGTH - 18)
|
||||||
|
i = length(optchars)
|
||||||
|
while (i) {
|
||||||
|
end = RSTART + 18 + i
|
||||||
|
i -= 1
|
||||||
|
len = i ? 1 : 2
|
||||||
|
$0 = sprintf("%s<a href=\"#%s\">%s</a>%s",
|
||||||
|
substr($0, 0, end - len - 1),
|
||||||
|
substr($0, end - 1, 1),
|
||||||
|
substr($0, end - len, len),
|
||||||
|
substr($0, end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
# Make long opts (defined using `Fl Fl`) into a single tag
|
||||||
|
gsub(/<code class="Fl">-<\/code>\s*<code class="Fl">/, "<code class=\"Fl\">-")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
print
|
||||||
|
}
|
||||||
113
.github/actions/get-pages.sh
vendored
Executable file
113
.github/actions/get-pages.sh
vendored
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 [-h] [-r] <rgbds-www> <version>
|
||||||
|
Copy renders from RGBDS repository to rgbds-www documentation
|
||||||
|
Execute from the root folder of the RGBDS repo, checked out at the desired tag
|
||||||
|
<rgbds-www> : Path to the rgbds-www repository
|
||||||
|
<version> : Version to be copied, such as 'v0.4.1' or 'master'
|
||||||
|
|
||||||
|
-h Display this help message
|
||||||
|
-r Update "latest stable" redirection pages and add a new entry to the index
|
||||||
|
(use for releases, not master)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
is_release=0
|
||||||
|
bad_usage=0
|
||||||
|
while getopts ":hr" opt; do
|
||||||
|
case $opt in
|
||||||
|
r)
|
||||||
|
is_release=1
|
||||||
|
;;
|
||||||
|
h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo "Unknown option '$OPTARG'"
|
||||||
|
bad_usage=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [ $bad_usage -ne 0 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift $(($OPTIND - 1))
|
||||||
|
|
||||||
|
|
||||||
|
declare -A PAGES
|
||||||
|
PAGES=(
|
||||||
|
[rgbasm.1.html]=src/asm/rgbasm.1
|
||||||
|
[rgbasm.5.html]=src/asm/rgbasm.5
|
||||||
|
[rgblink.1.html]=src/link/rgblink.1
|
||||||
|
[rgblink.5.html]=src/link/rgblink.5
|
||||||
|
[rgbfix.1.html]=src/fix/rgbfix.1
|
||||||
|
[rgbgfx.1.html]=src/gfx/rgbgfx.1
|
||||||
|
[rgbds.5.html]=src/rgbds.5
|
||||||
|
[rgbds.7.html]=src/rgbds.7
|
||||||
|
[gbz80.7.html]=src/gbz80.7
|
||||||
|
)
|
||||||
|
WWWPATH="/docs"
|
||||||
|
mkdir -p "$1/_documentation/$2"
|
||||||
|
|
||||||
|
# `mandoc` uses a different format for referring to man pages present in the **current** directory.
|
||||||
|
# We want that format for RGBDS man pages, and the other one for the rest;
|
||||||
|
# we thus need to copy all pages to a temporary directory, and process them there.
|
||||||
|
|
||||||
|
# Copy all pages to current dir
|
||||||
|
cp "${PAGES[@]}" .
|
||||||
|
|
||||||
|
for page in "${!PAGES[@]}"; do
|
||||||
|
stem="${page%.html}"
|
||||||
|
manpage="${stem%.?}(${stem#*.})"
|
||||||
|
descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")"
|
||||||
|
|
||||||
|
cat >"$1/_documentation/$2/$page" <<EOF
|
||||||
|
---
|
||||||
|
layout: doc
|
||||||
|
title: $manpage [$2]
|
||||||
|
description: RGBDS $2 — $descr
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
options=fragment,man='%N.%S;https://linux.die.net/man/%S/%N'
|
||||||
|
if [ $stem = rgbasm.5 ]; then
|
||||||
|
options+=,toc
|
||||||
|
fi
|
||||||
|
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/_documentation/$2/$page"
|
||||||
|
groff -Tpdf -mdoc -wall "${PAGES[$page]##*/}" >"$1/_documentation/$2/$stem.pdf"
|
||||||
|
if [ $is_release -ne 0 ]; then
|
||||||
|
cat - >"$1/_documentation/$page" <<EOF
|
||||||
|
---
|
||||||
|
redirect_to: $WWWPATH/$2/${page%.html}
|
||||||
|
permalink: $WWWPATH/${page%.html}/
|
||||||
|
title: $manpage [latest stable]
|
||||||
|
description: RGBDS latest stable — $descr
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat - >"$1/_documentation/$2/index.html" <<EOF
|
||||||
|
---
|
||||||
|
layout: doc_index
|
||||||
|
permalink: /docs/$2/
|
||||||
|
title: RGBDS online manual [$2]
|
||||||
|
description: RGBDS $2 - Online manual
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
# If making a release, add a new entry right after `master`
|
||||||
|
if [ $is_release -ne 0 ]; then
|
||||||
|
awk '{ print }
|
||||||
|
/"name": "master"/ { print "\t\t{\"name\": \"'$2'\", \"text\": \"'$2'\" }," }
|
||||||
|
' "$1/_data/doc.json" >"$1/_data/doc.json.tmp"
|
||||||
|
mv "$1/_data/doc.json"{.tmp,}
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm "${PAGES[@]##*/}"
|
||||||
10
.github/workflows/create-release-docs.yml
vendored
10
.github/workflows/create-release-docs.yml
vendored
@@ -23,16 +23,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -yq groff zlib1g-dev
|
sudo apt-get install -yq groff zlib1g-dev
|
||||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||||
tar xf mandoc-1.14.6.tar.gz
|
tar xf mandoc-1.14.5.tar.gz
|
||||||
cd mandoc-1.14.6
|
cd mandoc-1.14.5
|
||||||
./configure
|
./configure
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds/man
|
working-directory: rgbds
|
||||||
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
||||||
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} *
|
./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
|
||||||
- name: Push new pages
|
- name: Push new pages
|
||||||
working-directory: rgbds-www
|
working-directory: rgbds-www
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
64
.github/workflows/testing.yml
vendored
64
.github/workflows/testing.yml
vendored
@@ -16,11 +16,6 @@ jobs:
|
|||||||
cc: gcc
|
cc: gcc
|
||||||
- os: macos-11.0
|
- os: macos-11.0
|
||||||
cc: gcc
|
cc: gcc
|
||||||
include:
|
|
||||||
- cc: gcc
|
|
||||||
cxx: g++
|
|
||||||
- cc: clang
|
|
||||||
cxx: clang++
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@@ -33,25 +28,25 @@ jobs:
|
|||||||
# Apple's base version is severely outdated, not even supporting -Wall,
|
# Apple's base version is severely outdated, not even supporting -Wall,
|
||||||
# but it overrides Homebrew's version nonetheless...
|
# but it overrides Homebrew's version nonetheless...
|
||||||
- name: Build & install using Make
|
- name: Build & install using Make
|
||||||
if: matrix.buildsys == 'make'
|
|
||||||
run: |
|
run: |
|
||||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
make develop -j Q= CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
make develop -j Q= CC=${{ matrix.cc }}
|
||||||
sudo make install -j Q=
|
sudo make install -j Q=
|
||||||
|
if: matrix.buildsys == 'make'
|
||||||
- name: Build & install using CMake
|
- name: Build & install using CMake
|
||||||
if: matrix.buildsys == 'cmake'
|
|
||||||
run: |
|
run: |
|
||||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
||||||
cmake --build build -j --verbose
|
cmake --build build -j
|
||||||
cp build/src/rgb{asm,link,fix,gfx} .
|
cp build/src/rgb{asm,link,fix,gfx} .
|
||||||
sudo cmake --install build --verbose
|
sudo cmake --install build
|
||||||
|
if: matrix.buildsys == 'cmake'
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
mkdir bins
|
mkdir bins
|
||||||
cp rgb{asm,link,fix,gfx} bins
|
cp rgb{asm,link,fix,gfx} bins
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
||||||
path: bins
|
path: bins
|
||||||
@@ -76,7 +71,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Get zlib, libpng and bison
|
- name: Get zlib, libpng and bison
|
||||||
run: | # TODO: use an array; remember to update the versions being downloaded, *and* the paths being extracted! (`Move-Item`)
|
run: | # TODO: use an array
|
||||||
$wc = New-Object System.Net.WebClient
|
$wc = New-Object System.Net.WebClient
|
||||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
||||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||||
@@ -100,41 +95,28 @@ jobs:
|
|||||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||||
Move-Item zlib-1.2.12 zlib
|
Move-Item zlib-1.2.12 zlib
|
||||||
Move-Item lpng1637 libpng
|
Move-Item lpng1637 libpng
|
||||||
- uses: actions/cache@v3
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
zbuild
|
|
||||||
pngbuild
|
|
||||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
|
||||||
- name: Build zlib
|
- name: Build zlib
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
cmake --build zbuild --config Release -j
|
cmake --build zbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install zlib
|
|
||||||
run: |
|
|
||||||
cmake --install zbuild
|
cmake --install zbuild
|
||||||
- name: Build libpng
|
- name: Build libpng
|
||||||
run: |
|
run: |
|
||||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
||||||
cmake --build pngbuild --config Release -j
|
cmake --build pngbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install libpng
|
|
||||||
run: |
|
|
||||||
cmake --install pngbuild
|
cmake --install pngbuild
|
||||||
- name: Build Windows binaries
|
- name: Build Windows binaries
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build build --config Release -j --verbose
|
cmake --build build --config Release -j
|
||||||
cmake --install build --verbose --prefix install_dir
|
cmake --install build
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir bins
|
mkdir bins
|
||||||
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
|
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-win${{ matrix.bits }}
|
name: rgbds-canary-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
@@ -142,7 +124,6 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cp bins/* .
|
cp bins/* .
|
||||||
cp bins/*.dll test/gfx
|
|
||||||
test/run-tests.sh
|
test/run-tests.sh
|
||||||
|
|
||||||
windows-xbuild:
|
windows-xbuild:
|
||||||
@@ -169,7 +150,7 @@ jobs:
|
|||||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
./.github/actions/install_deps.sh ${{ matrix.os }}
|
||||||
- name: Install MinGW
|
- name: Install MinGW
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
sudo apt-get install gcc-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
||||||
- name: Install libpng dev headers for MinGW
|
- name: Install libpng dev headers for MinGW
|
||||||
run: |
|
run: |
|
||||||
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||||
@@ -185,21 +166,12 @@ jobs:
|
|||||||
mv rgbgfx bins/rgbgfx.exe
|
mv rgbgfx bins/rgbgfx.exe
|
||||||
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
|
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
|
||||||
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
|
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
|
||||||
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/lib{gcc_s_sjlj-1,stdc++-6}.dll bins; fi
|
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/libgcc_s_sjlj-1.dll bins; fi
|
||||||
mv test/gfx/randtilegen{,.exe}
|
|
||||||
mv test/gfx/rgbgfx_test{,.exe}
|
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
- name: Upload Windows test binaries
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: testing-programs-mingw-win${{ matrix.bits }}
|
|
||||||
path: |
|
|
||||||
test/gfx/randtilegen.exe
|
|
||||||
test/gfx/rgbgfx_test.exe
|
|
||||||
|
|
||||||
windows-xtesting:
|
windows-xtesting:
|
||||||
needs: windows-xbuild
|
needs: windows-xbuild
|
||||||
@@ -211,20 +183,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Retrieve binaries
|
- name: Retrieve binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
- name: Retrieve test binaries
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: testing-programs-mingw-win${{ matrix.bits }}
|
|
||||||
path: test/gfx
|
|
||||||
- name: Extract binaries
|
- name: Extract binaries
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cp bins/* .
|
cp bins/* .
|
||||||
cp bins/*.dll test/gfx
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
31
.github/workflows/update-master-docs.yml
vendored
31
.github/workflows/update-master-docs.yml
vendored
@@ -4,15 +4,16 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- man/gbz80.7
|
- .github/actions/get-pages.sh
|
||||||
- man/rgbds.5
|
- src/gbz80.7
|
||||||
- man/rgbds.7
|
- src/rgbds.5
|
||||||
- man/rgbasm.1
|
- src/rgbds.7
|
||||||
- man/rgbasm.5
|
- src/asm/rgbasm.1
|
||||||
- man/rgblink.1
|
- src/asm/rgbasm.5
|
||||||
- man/rgblink.5
|
- src/link/rgblink.1
|
||||||
- man/rgbfix.1
|
- src/link/rgblink.5
|
||||||
- man/rgbgfx.1
|
- src/fix/rgbfix.1
|
||||||
|
- src/gfx/rgbgfx.1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -35,16 +36,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -yq groff zlib1g-dev
|
sudo apt-get install -yq groff zlib1g-dev
|
||||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||||
tar xf mandoc-1.14.6.tar.gz
|
tar xf mandoc-1.14.5.tar.gz
|
||||||
cd mandoc-1.14.6
|
cd mandoc-1.14.5
|
||||||
./configure
|
./configure
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds/man
|
working-directory: rgbds
|
||||||
run: |
|
run: |
|
||||||
../../rgbds-www/maintainer/man_to_html.sh master *
|
./.github/actions/get-pages.sh ../rgbds-www master
|
||||||
- name: Push new pages
|
- name: Push new pages
|
||||||
working-directory: rgbds-www
|
working-directory: rgbds-www
|
||||||
run: |
|
run: |
|
||||||
@@ -55,7 +56,7 @@ jobs:
|
|||||||
ssh-add ~/.ssh/id_ed25519
|
ssh-add ~/.ssh/id_ed25519
|
||||||
git config --global user.name "GitHub Action"
|
git config --global user.name "GitHub Action"
|
||||||
git config --global user.email "community@gbdev.io"
|
git config --global user.email "community@gbdev.io"
|
||||||
git add -A
|
git add .
|
||||||
git commit -m "Update RGBDS master documentation"
|
git commit -m "Update RGBDS master documentation"
|
||||||
if git remote | grep -q origin; then
|
if git remote | grep -q origin; then
|
||||||
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||||
|
|
||||||
project(rgbds
|
project(rgbds
|
||||||
LANGUAGES C CXX)
|
LANGUAGES C)
|
||||||
|
|
||||||
# get real path of source and binary directories
|
# get real path of source and binary directories
|
||||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||||
@@ -29,18 +29,8 @@ option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
|
|||||||
if(MSVC)
|
if(MSVC)
|
||||||
# MSVC's standard library triggers warning C5105,
|
# MSVC's standard library triggers warning C5105,
|
||||||
# "macro expansion producing 'defined' has undefined behavior"
|
# "macro expansion producing 'defined' has undefined behavior"
|
||||||
add_compile_options(/MP /wd5105)
|
add_compile_options(/std:c11 /W1 /MP /wd5105)
|
||||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
||||||
# Also, CMake appears not to pass the C11-enabling flag, so we must add it manually... but only for C!
|
|
||||||
if(NOT CMAKE_C_FLAGS MATCHES "std:c11") # The flag may already have been injected by an earlier CMake invocation, so don't add it twice
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std:c11" CACHE STRING "Flags used by the C compiler during all build types." FORCE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(SANITIZERS)
|
|
||||||
set(SAN_FLAGS /fsanitize=address)
|
|
||||||
add_compile_options(${SAN_FLAGS})
|
|
||||||
add_link_options(${SAN_FLAGS})
|
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
add_compile_options(-Wall -pedantic)
|
add_compile_options(-Wall -pedantic)
|
||||||
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
|
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
|
||||||
@@ -51,18 +41,18 @@ else()
|
|||||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||||
add_compile_options(${SAN_FLAGS})
|
add_compile_options(${SAN_FLAGS})
|
||||||
add_link_options(${SAN_FLAGS})
|
link_libraries(${SAN_FLAGS})
|
||||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||||
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MORE_WARNINGS)
|
if(MORE_WARNINGS)
|
||||||
add_compile_options(-Werror -Wextra
|
add_compile_options(-Werror -Wextra
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
-Wfloat-equal -Winline -Wlogical-op -Wnested-externs -Wnull-dereference
|
||||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused
|
-Wold-style-definition -Wshift-overflow=2 -Wstrict-overflow=5
|
||||||
|
-Wstrict-prototypes -Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||||
-Wshadow # TODO: -Wshadow=compatible-local ?
|
-Wshadow # TODO: -Wshadow=compatible-local ?
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||||
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
||||||
@@ -87,23 +77,12 @@ else(GIT)
|
|||||||
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
|
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
|
||||||
endif(GIT)
|
endif(GIT)
|
||||||
|
|
||||||
find_package(PkgConfig)
|
|
||||||
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
|
||||||
# fallback to find_package
|
|
||||||
find_package(PNG REQUIRED)
|
|
||||||
else()
|
|
||||||
pkg_check_modules(LIBPNG REQUIRED libpng)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include_directories("${PROJECT_SOURCE_DIR}/include")
|
include_directories("${PROJECT_SOURCE_DIR}/include")
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED True)
|
set(CMAKE_C_STANDARD_REQUIRED True)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(test)
|
|
||||||
|
|
||||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||||
# (You may want to augment it with the options above)
|
# (You may want to augment it with the options above)
|
||||||
@@ -118,19 +97,3 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|||||||
message(CHECK_FAIL "no")
|
message(CHECK_FAIL "no")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(MANDIR "share/man")
|
|
||||||
set(man1 "man/rgbasm.1"
|
|
||||||
"man/rgbfix.1"
|
|
||||||
"man/rgbgfx.1"
|
|
||||||
"man/rgblink.1")
|
|
||||||
set(man5 "man/rgbasm.5"
|
|
||||||
"man/rgblink.5"
|
|
||||||
"man/rgbds.5")
|
|
||||||
set(man7 "man/gbz80.7"
|
|
||||||
"man/rgbds.7")
|
|
||||||
|
|
||||||
foreach(SECTION "man1" "man5" "man7")
|
|
||||||
set(DEST "${MANDIR}/${SECTION}")
|
|
||||||
install(FILES ${${SECTION}} DESTINATION ${DEST})
|
|
||||||
endforeach()
|
|
||||||
|
|||||||
65
Makefile
65
Makefile
@@ -7,7 +7,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
.SUFFIXES: .h .y .c .cpp .o
|
.SUFFIXES: .h .y .c .o
|
||||||
|
|
||||||
# User-defined variables
|
# User-defined variables
|
||||||
|
|
||||||
@@ -34,13 +34,10 @@ WARNFLAGS := -Wall -pedantic
|
|||||||
|
|
||||||
# Overridable CFLAGS
|
# Overridable CFLAGS
|
||||||
CFLAGS ?= -O3 -flto -DNDEBUG
|
CFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
|
||||||
# Non-overridable CFLAGS
|
# Non-overridable CFLAGS
|
||||||
# _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
|
# _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
|
||||||
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
|
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
|
||||||
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
|
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
|
||||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++17 -I include \
|
|
||||||
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
|
|
||||||
# Overridable LDFLAGS
|
# Overridable LDFLAGS
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
# Non-overridable LDFLAGS
|
# Non-overridable LDFLAGS
|
||||||
@@ -105,14 +102,9 @@ rgbfix_obj := \
|
|||||||
src/error.o
|
src/error.o
|
||||||
|
|
||||||
rgbgfx_obj := \
|
rgbgfx_obj := \
|
||||||
|
src/gfx/gb.o \
|
||||||
src/gfx/main.o \
|
src/gfx/main.o \
|
||||||
src/gfx/pal_packing.o \
|
src/gfx/makepng.o \
|
||||||
src/gfx/pal_sorting.o \
|
|
||||||
src/gfx/pal_spec.o \
|
|
||||||
src/gfx/process.o \
|
|
||||||
src/gfx/proto_palette.o \
|
|
||||||
src/gfx/reverse.o \
|
|
||||||
src/gfx/rgba.o \
|
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/error.o
|
src/error.o
|
||||||
|
|
||||||
@@ -126,13 +118,7 @@ rgbfix: ${rgbfix_obj}
|
|||||||
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
||||||
|
|
||||||
rgbgfx: ${rgbgfx_obj}
|
rgbgfx: ${rgbgfx_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS}
|
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCFLAGS} src/version.c ${PNGLDLIBS}
|
||||||
|
|
||||||
test/gfx/randtilegen: test/gfx/randtilegen.c
|
|
||||||
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
|
||||||
|
|
||||||
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
|
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGLDLIBS}
|
|
||||||
|
|
||||||
# Rules to process files
|
# Rules to process files
|
||||||
|
|
||||||
@@ -159,10 +145,7 @@ src/asm/parser.c: src/asm/parser.y
|
|||||||
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
||||||
|
|
||||||
.c.o:
|
.c.o:
|
||||||
$Q${CC} ${REALCFLAGS} -c -o $@ $<
|
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
|
|
||||||
.cpp.o:
|
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
|
||||||
|
|
||||||
# Target used to remove all files generated by other Makefile targets
|
# Target used to remove all files generated by other Makefile targets
|
||||||
|
|
||||||
@@ -174,7 +157,6 @@ clean:
|
|||||||
$Qfind src/ -name "*.o" -exec rm {} \;
|
$Qfind src/ -name "*.o" -exec rm {} \;
|
||||||
$Q${RM} rgbshim.sh
|
$Q${RM} rgbshim.sh
|
||||||
$Q${RM} src/asm/parser.c src/asm/parser.h
|
$Q${RM} src/asm/parser.c src/asm/parser.h
|
||||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
|
||||||
|
|
||||||
# Target used to install the binaries and man pages.
|
# Target used to install the binaries and man pages.
|
||||||
|
|
||||||
@@ -185,15 +167,15 @@ install: all
|
|||||||
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
||||||
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
|
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
|
||||||
$Qinstall -m ${MANMODE} man/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
$Qinstall -m ${MANMODE} src/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
||||||
$Qinstall -m ${MANMODE} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
$Qinstall -m ${MANMODE} src/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
||||||
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
$Qinstall -m ${MANMODE} src/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
||||||
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
$Qinstall -m ${MANMODE} src/asm/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
||||||
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
$Qinstall -m ${MANMODE} src/asm/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
||||||
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
$Qinstall -m ${MANMODE} src/fix/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
||||||
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
$Qinstall -m ${MANMODE} src/link/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
||||||
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
$Qinstall -m ${MANMODE} src/link/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
||||||
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
$Qinstall -m ${MANMODE} src/gfx/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
||||||
|
|
||||||
# Target used to check the coding style of the whole codebase.
|
# Target used to check the coding style of the whole codebase.
|
||||||
# `extern/` is excluded, as it contains external code that should not be patched
|
# `extern/` is excluded, as it contains external code that should not be patched
|
||||||
@@ -232,9 +214,11 @@ checkdiff:
|
|||||||
develop:
|
develop:
|
||||||
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
|
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
-Wfloat-equal -Winline -Wlogical-op -Wnested-externs -Wold-style-definition \
|
||||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused \
|
-Wshift-overflow=2 \
|
||||||
|
-Wstrict-overflow=5 -Wstrict-prototypes -Wundef -Wuninitialized -Wunused \
|
||||||
-Wshadow \
|
-Wshadow \
|
||||||
|
-Wnull-dereference -Wstringop-overflow=4 \
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral \
|
-Wno-format-nonliteral \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
||||||
@@ -245,8 +229,7 @@ develop:
|
|||||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||||
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
|
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
|
||||||
|
|
||||||
# Targets for the project maintainer to easily create Windows exes.
|
# Targets for the project maintainer to easily create Windows exes.
|
||||||
# This is not for Windows users!
|
# This is not for Windows users!
|
||||||
@@ -254,14 +237,12 @@ develop:
|
|||||||
# install instructions instead.
|
# install instructions instead.
|
||||||
|
|
||||||
mingw32:
|
mingw32:
|
||||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
$Q${MAKE} CC=i686-w64-mingw32-gcc BISON=bison \
|
||||||
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
|
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||||
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
|
||||||
|
|
||||||
mingw64:
|
mingw64:
|
||||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
$Q${MAKE} CC=x86_64-w64-mingw32-gcc BISON=bison \
|
||||||
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
|
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||||
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
|
||||||
|
|
||||||
wine-shim:
|
wine-shim:
|
||||||
$Qecho '#!/bin/bash' > rgbshim.sh
|
$Qecho '#!/bin/bash' > rgbshim.sh
|
||||||
|
|||||||
23
README.rst
23
README.rst
@@ -12,10 +12,11 @@ for the Game Boy and Game Boy Color. It consists of:
|
|||||||
This is a fork of the original RGBDS which aims to make the programs more like
|
This is a fork of the original RGBDS which aims to make the programs more like
|
||||||
other UNIX tools.
|
other UNIX tools.
|
||||||
|
|
||||||
This toolchain is maintained `on GitHub <https://github.com/gbdev/rgbds>`__.
|
This toolchain is maintained on `GitHub <https://github.com/rednex/rgbds>`__.
|
||||||
|
|
||||||
The documentation of this toolchain can be viewed online `here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages found in this repository.
|
The documentation of this toolchain can be viewed online
|
||||||
The source code of the website itself is on GitHub as well under the repo `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
|
`here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages
|
||||||
|
found in this repository.
|
||||||
|
|
||||||
If you want to contribute or maintain RGBDS, and have questions regarding the code, its organisation, etc. you can find me `on GBDev <https://gbdev.io/chat>`__ or via mail at ``rgbds at eldred dot fr``.
|
If you want to contribute or maintain RGBDS, and have questions regarding the code, its organisation, etc. you can find me `on GBDev <https://gbdev.io/chat>`__ or via mail at ``rgbds at eldred dot fr``.
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ If you want to contribute or maintain RGBDS, and have questions regarding the co
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The `installation procedure <https://rgbds.gbdev.io/install>`__ is available
|
The `installation procedure <https://rgbds.gbdev.io/install>`__ is available
|
||||||
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/#building-from-source>`__
|
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/source>`__
|
||||||
is possible using ``make`` or ``cmake``; follow the link for more detailed instructions.
|
is possible using ``make`` or ``cmake``; follow the link for more detailed instructions.
|
||||||
|
|
||||||
.. code:: sh
|
.. code:: sh
|
||||||
@@ -56,8 +57,6 @@ The RGBDS source code file structure somewhat resembles the following:
|
|||||||
│ └── ...
|
│ └── ...
|
||||||
├── include/
|
├── include/
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── man/
|
|
||||||
│ └── ...
|
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── asm/
|
│ ├── asm/
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
@@ -97,9 +96,7 @@ The RGBDS source code file structure somewhat resembles the following:
|
|||||||
|
|
||||||
- ``include/`` - header files for each respective C files in `src`.
|
- ``include/`` - header files for each respective C files in `src`.
|
||||||
|
|
||||||
- ``man/`` - manual pages.
|
- ``src/`` - source code and manual pages for RGBDS.
|
||||||
|
|
||||||
- ``src/`` - source code of RGBDS.
|
|
||||||
|
|
||||||
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
|
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
|
||||||
(rgbasm -> ``src/asm/``, for example). ``src/extern/`` contains code imported from external sources.
|
(rgbasm -> ``src/asm/``, for example). ``src/extern/`` contains code imported from external sources.
|
||||||
@@ -129,11 +126,3 @@ The RGBDS source code file structure somewhat resembles the following:
|
|||||||
- 2018, codebase relicensed under the MIT license.
|
- 2018, codebase relicensed under the MIT license.
|
||||||
|
|
||||||
- 2020, repository is moved to the `gbdev <https://github.com/gbdev>`__ organisation. The `rgbds.gbdev.io <https://rgbds.gbdev.io>`__ website serving documentation and downloads is created.
|
- 2020, repository is moved to the `gbdev <https://github.com/gbdev>`__ organisation. The `rgbds.gbdev.io <https://rgbds.gbdev.io>`__ website serving documentation and downloads is created.
|
||||||
|
|
||||||
4. Acknowledgements
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
RGBGFX generates palettes using algorithms found in the paper
|
|
||||||
`"Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items" <http://arxiv.org/abs/1605.00558>`__
|
|
||||||
(`GitHub <https://github.com/pagination-problem/pagination>`__, MIT license),
|
|
||||||
by Aristide Grange, Imed Kacem, and Sébastien Martin.
|
|
||||||
|
|||||||
@@ -11,24 +11,19 @@ _rgbgfx_completions() {
|
|||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
[C]="color-curve:normal"
|
[C]="color-curve:normal"
|
||||||
|
[D]="debug:normal"
|
||||||
|
[h]="horizontal:normal"
|
||||||
[m]="mirror-tiles:normal"
|
[m]="mirror-tiles:normal"
|
||||||
[u]="unique-tiles:normal"
|
[u]="unique-tiles:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[Z]="columns:normal"
|
[f]="fix:normal"
|
||||||
|
[F]="fix-and-save:normal"
|
||||||
[a]="attr-map:*.attrmap"
|
[a]="attr-map:*.attrmap"
|
||||||
[A]="output-attr-map:normal"
|
[A]="output-attr-map:normal"
|
||||||
[b]="base-tiles:unk"
|
|
||||||
[d]="depth:unk"
|
[d]="depth:unk"
|
||||||
[L]="slice:unk"
|
|
||||||
[N]="nb-tiles:unk"
|
|
||||||
[n]="nb-palettes:unk"
|
|
||||||
[o]="output:glob *.2bpp"
|
[o]="output:glob *.2bpp"
|
||||||
[p]="palette:glob *.pal"
|
[p]="palette:glob *.pal"
|
||||||
[P]="output-palette:normal"
|
[P]="output-palette:normal"
|
||||||
[q]="palette-map:glob *.palmap"
|
|
||||||
[Q]="output-palette-map:normal"
|
|
||||||
[r]="reverse:unk"
|
|
||||||
[s]="palette-size:unk"
|
|
||||||
[t]="tilemap:glob *.tilemap"
|
[t]="tilemap:glob *.tilemap"
|
||||||
[T]="output-tilemap:normal"
|
[T]="output-tilemap:normal"
|
||||||
[x]="trim-end:unk"
|
[x]="trim-end:unk"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
declare -A FILES
|
declare -A FILES
|
||||||
while read -r -d '' file; do
|
while read -r -d '' file; do
|
||||||
FILES["$file"]="true"
|
FILES["$file"]="true"
|
||||||
done < <(git diff --name-only -z "$1" HEAD)
|
done < <(git diff --name-only -z $1 HEAD)
|
||||||
|
|
||||||
edited () {
|
edited () {
|
||||||
${FILES["$1"]:-"false"}
|
${FILES["$1"]:-"false"}
|
||||||
@@ -40,13 +40,13 @@ dependency () {
|
|||||||
# Pull requests that edit the first file without the second may be correct,
|
# Pull requests that edit the first file without the second may be correct,
|
||||||
# but are suspicious enough to require review.
|
# but are suspicious enough to require review.
|
||||||
|
|
||||||
dependency include/linkdefs.h man/rgbds.5 \
|
dependency include/linkdefs.h src/rgbds.5 \
|
||||||
"Was the object file format changed?"
|
"Was the object file format changed?"
|
||||||
|
|
||||||
dependency src/asm/parser.y man/rgbasm.5 \
|
dependency src/asm/parser.y src/asm/rgbasm.5 \
|
||||||
"Was the rgbasm grammar changed?"
|
"Was the rgbasm grammar changed?"
|
||||||
|
|
||||||
dependency include/asm/warning.h man/rgbasm.1 \
|
dependency include/asm/warning.h src/asm/rgbasm.1 \
|
||||||
"Were the rgbasm warnings changed?"
|
"Were the rgbasm warnings changed?"
|
||||||
|
|
||||||
dependency src/asm/object.c include/linkdefs.h \
|
dependency src/asm/object.c include/linkdefs.h \
|
||||||
@@ -59,27 +59,27 @@ dependency Makefile CMakeLists.txt \
|
|||||||
dependency Makefile src/CMakeLists.txt \
|
dependency Makefile src/CMakeLists.txt \
|
||||||
"Did the build process change?"
|
"Did the build process change?"
|
||||||
|
|
||||||
dependency src/asm/main.c man/rgbasm.1 \
|
dependency src/asm/main.c src/asm/rgbasm.1 \
|
||||||
"Did the rgbasm CLI change?"
|
"Did the rgbasm CLI change?"
|
||||||
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
|
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
|
||||||
"Did the rgbasm CLI change?"
|
"Did the rgbasm CLI change?"
|
||||||
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
|
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
|
||||||
"Did the rgbasm CLI change?"
|
"Did the rgbasm CLI change?"
|
||||||
dependency src/link/main.c man/rgblink.1 \
|
dependency src/link/main.c src/link/rgblink.1 \
|
||||||
"Did the rgblink CLI change?"
|
"Did the rgblink CLI change?"
|
||||||
dependency src/link/main.c contrib/zsh_compl/_rgblink \
|
dependency src/link/main.c contrib/zsh_compl/_rgblink \
|
||||||
"Did the rgblink CLI change?"
|
"Did the rgblink CLI change?"
|
||||||
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
|
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
|
||||||
"Did the rgblink CLI change?"
|
"Did the rgblink CLI change?"
|
||||||
dependency src/fix/main.c man/rgbfix.1 \
|
dependency src/fix/main.c src/fix/rgbfix.1 \
|
||||||
"Did the rgbfix CLI change?"
|
"Did the rgbfix CLI change?"
|
||||||
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
|
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
|
||||||
"Did the rgbfix CLI change?"
|
"Did the rgbfix CLI change?"
|
||||||
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
|
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
|
||||||
"Did the rgbfix CLI change?"
|
"Did the rgbfix CLI change?"
|
||||||
dependency src/gfx/main.cpp man/rgbgfx.1 \
|
dependency src/gfx/main.c src/gfx/rgbgfx.1 \
|
||||||
"Did the rgbgfx CLI change?"
|
"Did the rgbgfx CLI change?"
|
||||||
dependency src/gfx/main.cpp contrib/zsh_compl/_rgbgfx \
|
dependency src/gfx/main.c contrib/zsh_compl/_rgbgfx \
|
||||||
"Did the rgbgfx CLI change?"
|
"Did the rgbgfx CLI change?"
|
||||||
dependency src/gfx/main.cpp contrib/bash_compl/_rgbgfx.bash \
|
dependency src/gfx/main.c contrib/bash_compl/_rgbgfx.bash \
|
||||||
"Did the rgbgfx CLI change?"
|
"Did the rgbgfx CLI change?"
|
||||||
|
|||||||
@@ -23,51 +23,51 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
STATE=0
|
STATE=0
|
||||||
diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
diff <(xxd $1) <(xxd $2) | while read -r LINE; do
|
||||||
if [[ $STATE -eq 0 ]]; then
|
if [ $STATE -eq 0 ]; then
|
||||||
# Discard first line (line info)
|
# Discard first line (line info)
|
||||||
STATE=1
|
STATE=1
|
||||||
elif [[ "$LINE" = '---' ]]; then
|
elif [ "$LINE" = '---' ]; then
|
||||||
# Separator between files switches states
|
# Separator between files switches states
|
||||||
echo "$LINE"
|
echo $LINE
|
||||||
STATE=3
|
STATE=3
|
||||||
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
||||||
# Line info resets the whole thing
|
# Line info resets the whole thing
|
||||||
STATE=1
|
STATE=1
|
||||||
elif [[ $STATE -eq 1 || $STATE -eq 3 ]]; then
|
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
|
||||||
# Compute the GB address from the ROM offset
|
# Compute the GB address from the ROM offset
|
||||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||||
BANK=$((0x$OFS / 0x4000))
|
BANK=$((0x$OFS / 0x4000))
|
||||||
ADDR=$((0x$OFS % 0x4000 + (BANK != 0) * 0x4000))
|
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
||||||
# Try finding the preceding symbol closest to the diff
|
# Try finding the preceding symbol closest to the diff
|
||||||
if [[ $STATE -eq 1 ]]; then
|
if [ $STATE -eq 1 ]; then
|
||||||
STATE=2
|
STATE=2
|
||||||
SYMFILE=${1%.*}.sym
|
SYMFILE=${1%.*}.sym
|
||||||
else
|
else
|
||||||
STATE=4
|
STATE=4
|
||||||
SYMFILE=${2%.*}.sym
|
SYMFILE=${2%.*}.sym
|
||||||
fi
|
fi
|
||||||
EXTRA=$(if [[ -f "$SYMFILE" ]]; then
|
EXTRA=$(if [ -f "$SYMFILE" ]; then
|
||||||
# Read the sym file for such a symbol
|
# Read the sym file for such a symbol
|
||||||
# Ignore comment lines, only pick matching bank
|
# Ignore comment lines, only pick matching bank
|
||||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
grep -Ei $(printf "^%02x:" $BANK) "$SYMFILE" |
|
||||||
cut -d ';' -f 1 |
|
cut -d ';' -f 1 |
|
||||||
tr -d "\r" |
|
tr -d "\r" |
|
||||||
while read -r SYMADDR SYM; do
|
while read -r SYMADDR SYM; do
|
||||||
SYMADDR=$((0x${SYMADDR#*:}))
|
SYMADDR=$((0x${SYMADDR#*:}))
|
||||||
if [[ $SYMADDR -le $ADDR ]]; then
|
if [ $SYMADDR -le $ADDR ]; then
|
||||||
printf " (%s+%#x)\n" "$SYM" $((ADDR - SYMADDR))
|
printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
|
||||||
fi
|
fi
|
||||||
# TODO: assumes sorted sym files
|
# TODO: assumes sorted sym files
|
||||||
done | tail -n 1
|
done | tail -n 1
|
||||||
fi)
|
fi)
|
||||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
|
||||||
fi
|
fi
|
||||||
if [[ $STATE -eq 2 || $STATE -eq 4 ]]; then
|
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
|
||||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||||
BANK=$((0x$OFS / 0x4000))
|
BANK=$((0x$OFS / 0x4000))
|
||||||
ADDR=$((0x$OFS % 0x4000 + (BANK != 0) * 0x4000))
|
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
||||||
printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
|
printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -15,25 +15,20 @@ local args=(
|
|||||||
|
|
||||||
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
|
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
|
||||||
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||||
|
'(-D --debug)'{-D,--debug}'[Enable debug features]'
|
||||||
|
'(-f --fix -F --fix-and-save)'{-f,--fix}'[Fix input PNG into an indexed image]'
|
||||||
|
'(-f --fix -F --fix-and-save)'{-F,--fix-and-save}'[Like -f but also save CLI params within the PNG]'
|
||||||
|
'(-h --horizontal)'{-h,--horizontal}'[Lay out tiles horizontally instead of vertically]'
|
||||||
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
|
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
|
||||||
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
|
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
|
||||||
'(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p <file>.palmap]'
|
|
||||||
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'
|
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'
|
||||||
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
||||||
{-v,--verbose}'[Enable verbose output]'
|
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||||
'(-h --horizontal -Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
|
||||||
|
|
||||||
'(-a --attr-map -A --output-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
'(-a --attr-map -A --output-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
|
||||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||||
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
|
|
||||||
'(-N --nb-tiles)'{-n,--nb-tiles}'+[Limit number of tiles]:tile count:'
|
|
||||||
'(-n --nb-palettes)'{-n,--nb-palettes}'+[Limit number of palettes]:palette count:'
|
|
||||||
'(-o --output)'{-o,--output}'+[Set output file]:output file:_files'
|
'(-o --output)'{-o,--output}'+[Set output file]:output file:_files'
|
||||||
'(-p --palette -P --output-palette)'{-p,--palette}"+[Output the image's palette in little-endian native RGB555 format]:palette file:_files"
|
'(-p --palette -P --output-palette)'{-p,--palette}"+[Output the image's palette in little-endian native RGB555 format]:palette file:_files"
|
||||||
'(-q --palette-map -Q --output-palette-map)'{-p,--palette-map}"+[Output the image's palette map]:palette map file:_files"
|
|
||||||
'(-r --reverse)'{-r,--reverse}'+[Yield an image from binary data]:image width (in tiles):'
|
|
||||||
'(-s --palette-size)'{-s,--palette-size}'+[Limit palette size]:palette size:'
|
|
||||||
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
|
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
|
||||||
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int32_t fix_Callback_PI(void);
|
||||||
void fix_Print(int32_t i);
|
void fix_Print(int32_t i);
|
||||||
int32_t fix_Sin(int32_t i);
|
int32_t fix_Sin(int32_t i);
|
||||||
int32_t fix_Cos(int32_t i);
|
int32_t fix_Cos(int32_t i);
|
||||||
|
|||||||
@@ -16,9 +16,7 @@
|
|||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
extern bool haltnop;
|
extern bool haltnop;
|
||||||
extern bool warnOnHaltNop;
|
|
||||||
extern bool optimizeLoads;
|
extern bool optimizeLoads;
|
||||||
extern bool warnOnLdOpt;
|
|
||||||
extern bool verbose;
|
extern bool verbose;
|
||||||
extern bool warnings; /* True to enable warnings, false to disable them. */
|
extern bool warnings; /* True to enable warnings, false to disable them. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Allocator adaptor that interposes construct() calls to convert value-initialization
|
|
||||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
|
||||||
* zero out non-class types).
|
|
||||||
* From
|
|
||||||
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DEFAULT_INIT_ALLOC_H
|
|
||||||
#define DEFAULT_INIT_ALLOC_H
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
template<typename T, typename A = std::allocator<T>>
|
|
||||||
class default_init_allocator : public A {
|
|
||||||
using a_t = std::allocator_traits<A>;
|
|
||||||
public:
|
|
||||||
template<typename U>
|
|
||||||
struct rebind {
|
|
||||||
using other = default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
using A::A; // Inherit the allocator's constructors
|
|
||||||
|
|
||||||
template<typename U>
|
|
||||||
void construct(U *ptr) noexcept(std::is_nothrow_default_constructible_v<U>) {
|
|
||||||
::new (static_cast<void *>(ptr)) U;
|
|
||||||
}
|
|
||||||
template<typename U, typename... Args>
|
|
||||||
void construct(U *ptr, Args &&...args) {
|
|
||||||
a_t::construct(static_cast<A &>(*this), ptr, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -12,18 +12,10 @@
|
|||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* RGBDS_ERROR_H */
|
#endif /* RGBDS_ERROR_H */
|
||||||
|
|||||||
8
include/extern/getopt.h
vendored
8
include/extern/getopt.h
vendored
@@ -26,10 +26,6 @@
|
|||||||
#ifndef RGBDS_EXTERN_GETOPT_H
|
#ifndef RGBDS_EXTERN_GETOPT_H
|
||||||
#define RGBDS_EXTERN_GETOPT_H
|
#define RGBDS_EXTERN_GETOPT_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern char *musl_optarg;
|
extern char *musl_optarg;
|
||||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||||
|
|
||||||
@@ -47,8 +43,4 @@ int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
|||||||
#define required_argument 1
|
#define required_argument 1
|
||||||
#define optional_argument 2
|
#define optional_argument 2
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
36
include/gfx/gb.h
Normal file
36
include/gfx/gb.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_GB_H
|
||||||
|
#define RGBDS_GFX_GB_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "gfx/main.h"
|
||||||
|
|
||||||
|
#define XFLIP 0x40
|
||||||
|
#define YFLIP 0x20
|
||||||
|
|
||||||
|
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
|
||||||
|
void output_file(const struct Options *opts, const struct GBImage *gb);
|
||||||
|
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
|
||||||
|
int tile_size);
|
||||||
|
uint8_t reverse_bits(uint8_t b);
|
||||||
|
void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size);
|
||||||
|
void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size);
|
||||||
|
int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
|
||||||
|
int tile_size, int *flags);
|
||||||
|
void create_mapfiles(const struct Options *opts, struct GBImage *gb,
|
||||||
|
struct Mapfile *tilemap, struct Mapfile *attrmap);
|
||||||
|
void output_tilemap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *tilemap);
|
||||||
|
void output_attrmap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *attrmap);
|
||||||
|
void output_palette_file(const struct Options *opts,
|
||||||
|
const struct RawIndexedImage *raw_image);
|
||||||
|
|
||||||
|
#endif
|
||||||
91
include/gfx/main.h
Normal file
91
include/gfx/main.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_MAIN_H
|
||||||
|
#define RGBDS_GFX_MAIN_H
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
bool debug;
|
||||||
|
bool verbose;
|
||||||
|
bool hardfix;
|
||||||
|
bool fix;
|
||||||
|
bool horizontal;
|
||||||
|
bool mirror;
|
||||||
|
bool unique;
|
||||||
|
bool colorcurve;
|
||||||
|
unsigned int trim;
|
||||||
|
char *tilemapfile;
|
||||||
|
bool tilemapout;
|
||||||
|
char *attrmapfile;
|
||||||
|
bool attrmapout;
|
||||||
|
char *palfile;
|
||||||
|
bool palout;
|
||||||
|
char *outfile;
|
||||||
|
char *infile;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RGBColor {
|
||||||
|
uint8_t red;
|
||||||
|
uint8_t green;
|
||||||
|
uint8_t blue;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ImageOptions {
|
||||||
|
bool horizontal;
|
||||||
|
unsigned int trim;
|
||||||
|
char *tilemapfile;
|
||||||
|
bool tilemapout;
|
||||||
|
char *attrmapfile;
|
||||||
|
bool attrmapout;
|
||||||
|
char *palfile;
|
||||||
|
bool palout;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PNGImage {
|
||||||
|
png_struct *png;
|
||||||
|
png_info *info;
|
||||||
|
|
||||||
|
png_byte **data;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
png_byte depth;
|
||||||
|
png_byte type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RawIndexedImage {
|
||||||
|
uint8_t **data;
|
||||||
|
struct RGBColor *palette;
|
||||||
|
int num_colors;
|
||||||
|
unsigned int width;
|
||||||
|
unsigned int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBImage {
|
||||||
|
uint8_t *data;
|
||||||
|
int size;
|
||||||
|
bool horizontal;
|
||||||
|
int trim;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mapfile {
|
||||||
|
uint8_t *data;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int depth, colors;
|
||||||
|
|
||||||
|
#include "gfx/makepng.h"
|
||||||
|
#include "gfx/gb.h"
|
||||||
|
|
||||||
|
#endif /* RGBDS_GFX_MAIN_H */
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_MAIN_HPP
|
|
||||||
#define RGBDS_GFX_MAIN_HPP
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
|
||||||
|
|
||||||
struct Options {
|
|
||||||
uint8_t reversedWidth = 0; // -r, in pixels
|
|
||||||
bool reverse() const { return reversedWidth != 0; }
|
|
||||||
|
|
||||||
bool useColorCurve = false; // -C
|
|
||||||
bool allowMirroring = false; // -m
|
|
||||||
bool allowDedup = false; // -u
|
|
||||||
bool columnMajor = false; // -Z, previously -h
|
|
||||||
uint8_t verbosity = 0; // -v
|
|
||||||
|
|
||||||
std::string attrmap{}; // -a, -A
|
|
||||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
|
||||||
enum {
|
|
||||||
NO_SPEC,
|
|
||||||
EXPLICIT,
|
|
||||||
EMBEDDED,
|
|
||||||
} palSpecType = NO_SPEC; // -c
|
|
||||||
std::vector<std::array<Rgba, 4>> palSpec{};
|
|
||||||
uint8_t bitDepth = 2; // -d
|
|
||||||
struct {
|
|
||||||
uint16_t left;
|
|
||||||
uint16_t top;
|
|
||||||
uint16_t width;
|
|
||||||
uint16_t height;
|
|
||||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
|
||||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
|
||||||
uint8_t nbPalettes = 8; // -n
|
|
||||||
std::string output{}; // -o
|
|
||||||
std::string palettes{}; // -p, -P
|
|
||||||
std::string palmap{}; // -q, -Q
|
|
||||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
|
||||||
std::string tilemap{}; // -t, -T
|
|
||||||
uint64_t trim = 0; // -x
|
|
||||||
|
|
||||||
std::string input{}; // positional arg
|
|
||||||
|
|
||||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
|
||||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
|
||||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
|
||||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
|
||||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
|
||||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
|
||||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
|
||||||
format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
|
||||||
|
|
||||||
mutable bool hasTransparentPixels = false;
|
|
||||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Options options;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints the error count, and exits with failure
|
|
||||||
*/
|
|
||||||
[[noreturn]] void giveUp();
|
|
||||||
/**
|
|
||||||
* Prints a warning, and does not change the error count
|
|
||||||
*/
|
|
||||||
void warning(char const *fmt, ...);
|
|
||||||
/**
|
|
||||||
* Prints an error, and increments the error count
|
|
||||||
*/
|
|
||||||
void error(char const *fmt, ...);
|
|
||||||
/**
|
|
||||||
* Prints a fatal error, increments the error count, and gives up
|
|
||||||
*/
|
|
||||||
[[noreturn]] void fatal(char const *fmt, ...);
|
|
||||||
|
|
||||||
struct Palette {
|
|
||||||
// An array of 4 GBC-native (RGB555) colors
|
|
||||||
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
|
|
||||||
void addColor(uint16_t color);
|
|
||||||
uint8_t indexOf(uint16_t color) const;
|
|
||||||
uint16_t &operator[](size_t index) { return colors[index]; }
|
|
||||||
uint16_t const &operator[](size_t index) const { return colors[index]; }
|
|
||||||
|
|
||||||
decltype(colors)::iterator begin();
|
|
||||||
decltype(colors)::iterator end();
|
|
||||||
decltype(colors)::const_iterator begin() const;
|
|
||||||
decltype(colors)::const_iterator end() const;
|
|
||||||
|
|
||||||
uint8_t size() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template<typename T, T... i>
|
|
||||||
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
|
|
||||||
return std::array{[](uint8_t byte) {
|
|
||||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
|
||||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
|
||||||
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
|
||||||
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
|
||||||
return byte;
|
|
||||||
}(i)...};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
|
||||||
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_MAIN_HPP */
|
|
||||||
21
include/gfx/makepng.h
Normal file
21
include/gfx/makepng.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_PNG_H
|
||||||
|
#define RGBDS_GFX_PNG_H
|
||||||
|
|
||||||
|
#include "gfx/main.h"
|
||||||
|
|
||||||
|
struct RawIndexedImage *input_png_file(const struct Options *opts,
|
||||||
|
struct ImageOptions *png_options);
|
||||||
|
void output_png_file(const struct Options *opts,
|
||||||
|
const struct ImageOptions *png_options,
|
||||||
|
const struct RawIndexedImage *raw_image);
|
||||||
|
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr);
|
||||||
|
|
||||||
|
#endif /* RGBDS_GFX_PNG_H */
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
|
||||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
struct Palette;
|
|
||||||
class ProtoPalette;
|
|
||||||
|
|
||||||
namespace packing {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
|
||||||
*/
|
|
||||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_PACKING_HPP */
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
|
||||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <optional>
|
|
||||||
#include <png.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
|
||||||
|
|
||||||
struct Palette;
|
|
||||||
|
|
||||||
namespace sorting {
|
|
||||||
|
|
||||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
|
||||||
png_byte *palAlpha);
|
|
||||||
void grayscale(std::vector<Palette> &palettes,
|
|
||||||
std::array<std::optional<Rgba>, 0x8001> const &colors);
|
|
||||||
void rgb(std::vector<Palette> &palettes);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_SORTING_HPP */
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
|
||||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const arg);
|
|
||||||
void parseExternalPalSpec(char const *arg);
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_SPEC_HPP */
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_CONVERT_HPP
|
|
||||||
#define RGBDS_GFX_CONVERT_HPP
|
|
||||||
|
|
||||||
void process();
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_CONVERT_HPP */
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
|
||||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
class ProtoPalette {
|
|
||||||
// Up to 4 colors, sorted, and where SIZE_MAX means the slot is empty
|
|
||||||
// (OK because it's not a valid color index)
|
|
||||||
// Sorting is done on the raw numerical values to lessen `compare`'s complexity
|
|
||||||
std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Adds the specified color to the set
|
|
||||||
* Returns false if the set is full
|
|
||||||
*/
|
|
||||||
bool add(uint16_t color);
|
|
||||||
|
|
||||||
enum ComparisonResult {
|
|
||||||
NEITHER,
|
|
||||||
WE_BIGGER,
|
|
||||||
THEY_BIGGER = -1,
|
|
||||||
};
|
|
||||||
ComparisonResult compare(ProtoPalette const &other) const;
|
|
||||||
|
|
||||||
size_t size() const;
|
|
||||||
bool empty() const;
|
|
||||||
|
|
||||||
decltype(_colorIndices)::const_iterator begin() const;
|
|
||||||
decltype(_colorIndices)::const_iterator end() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PROTO_PALETTE_HPP */
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
|
||||||
#define RGBDS_GFX_REVERSE_HPP
|
|
||||||
|
|
||||||
void reverse();
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_REVERSE_HPP */
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_RGBA_HPP
|
|
||||||
#define RGBDS_GFX_RGBA_HPP
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
struct Rgba {
|
|
||||||
uint8_t red;
|
|
||||||
uint8_t green;
|
|
||||||
uint8_t blue;
|
|
||||||
uint8_t alpha;
|
|
||||||
|
|
||||||
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
|
||||||
: red(r), green(g), blue(b), alpha(a) {}
|
|
||||||
/**
|
|
||||||
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
|
||||||
*/
|
|
||||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
|
||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
|
||||||
|
|
||||||
static constexpr Rgba fromCGBColor(uint16_t cgbColor) {
|
|
||||||
constexpr auto _5to8 = [](uint8_t fiveBpp) -> uint8_t {
|
|
||||||
fiveBpp &= 0b11111; // For caller's convenience
|
|
||||||
return fiveBpp << 3 | fiveBpp >> 2;
|
|
||||||
};
|
|
||||||
return {_5to8(cgbColor), _5to8(cgbColor >> 5), _5to8(cgbColor >> 10),
|
|
||||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
|
||||||
* representation
|
|
||||||
*/
|
|
||||||
uint32_t toCSS() const {
|
|
||||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
|
||||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
|
||||||
}
|
|
||||||
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
|
||||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
|
||||||
*/
|
|
||||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
|
||||||
|
|
||||||
static constexpr uint8_t transparency_threshold = 0x10;
|
|
||||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
|
||||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
|
||||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
|
||||||
/**
|
|
||||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
|
||||||
*/
|
|
||||||
uint16_t cgbColor() const;
|
|
||||||
|
|
||||||
bool isGray() const { return red == green && green == blue; }
|
|
||||||
uint8_t grayIndex() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_RGBA_HPP */
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
#define attr_(...)
|
#define attr_(...)
|
||||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||||
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
|
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
|
||||||
static inline _Noreturn void unreachable_(void) {}
|
static inline _Noreturn unreachable_(void) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Use builtins whenever possible, and shim them otherwise
|
// Use builtins whenever possible, and shim them otherwise
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_ITERTOOLS_HPP
|
|
||||||
#define RGBDS_ITERTOOLS_HPP
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
template<typename... Ts>
|
|
||||||
static inline void report() {
|
|
||||||
puts(__PRETTY_FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
|
||||||
// We also assume that all iterators have the same length.
|
|
||||||
template<typename... Iters>
|
|
||||||
class Zip {
|
|
||||||
std::tuple<Iters...> _iters;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Zip(std::tuple<Iters...> &&iters) : _iters(iters) {}
|
|
||||||
|
|
||||||
Zip &operator++() {
|
|
||||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator*() const {
|
|
||||||
return std::apply([](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); },
|
|
||||||
_iters);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend auto operator==(Zip const &lhs, Zip const &rhs) {
|
|
||||||
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
|
|
||||||
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template<typename... Containers>
|
|
||||||
class ZipContainer {
|
|
||||||
std::tuple<Containers...> _containers;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ZipContainer(Containers &&...containers)
|
|
||||||
: _containers(std::forward<Containers>(containers)...) {}
|
|
||||||
|
|
||||||
auto begin() {
|
|
||||||
return Zip(std::apply(
|
|
||||||
[](auto &&...containers) {
|
|
||||||
using std::begin;
|
|
||||||
return std::make_tuple(begin(containers)...);
|
|
||||||
},
|
|
||||||
_containers));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end() {
|
|
||||||
return Zip(std::apply(
|
|
||||||
[](auto &&...containers) {
|
|
||||||
using std::end;
|
|
||||||
return std::make_tuple(end(containers)...);
|
|
||||||
},
|
|
||||||
_containers));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
|
||||||
template<typename T>
|
|
||||||
using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T,
|
|
||||||
std::remove_cv_t<std::remove_reference_t<T>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the same number of iterations as the first container's iterator!
|
|
||||||
*/
|
|
||||||
template<typename... Containers>
|
|
||||||
static constexpr auto zip(Containers &&...cs) {
|
|
||||||
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* RGBDS_ITERTOOLS_HPP */
|
|
||||||
@@ -46,14 +46,12 @@
|
|||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* MSVC doesn't support `[static N]` for array arguments from C99 or C11 */
|
/* MSVC doesn't support `[static N]` for array arguments from C99 */
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define MIN_NB_ELMS(N)
|
# define MIN_NB_ELMS(N)
|
||||||
# define ARR_QUALS(...)
|
|
||||||
# define NONNULL(ptr) *ptr
|
# define NONNULL(ptr) *ptr
|
||||||
#else
|
#else
|
||||||
# define MIN_NB_ELMS(N) static (N)
|
# define MIN_NB_ELMS(N) static (N)
|
||||||
# define ARR_QUALS(...) __VA_ARGS__
|
|
||||||
# define NONNULL(ptr) ptr[static 1]
|
# define NONNULL(ptr) ptr[static 1]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,10 @@
|
|||||||
#ifndef EXTERN_VERSION_H
|
#ifndef EXTERN_VERSION_H
|
||||||
#define EXTERN_VERSION_H
|
#define EXTERN_VERSION_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 6
|
#define PACKAGE_VERSION_MINOR 5
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 2
|
||||||
#define PACKAGE_VERSION_RC 1
|
|
||||||
|
|
||||||
char const *get_package_version_string(void);
|
char const *get_package_version_string(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* EXTERN_VERSION_H */
|
#endif /* EXTERN_VERSION_H */
|
||||||
|
|||||||
591
man/rgbgfx.1
591
man/rgbgfx.1
@@ -1,591 +0,0 @@
|
|||||||
'\" e
|
|
||||||
.\"
|
|
||||||
.\" This file is part of RGBDS.
|
|
||||||
.\"
|
|
||||||
.\" Copyright (c) 2013-2021, stag019 and RGBDS contributors.
|
|
||||||
.\"
|
|
||||||
.\" SPDX-License-Identifier: MIT
|
|
||||||
.\"
|
|
||||||
.Dd March 28, 2021
|
|
||||||
.Dt RGBGFX 1
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm rgbgfx
|
|
||||||
.Nd Game Boy graphics converter
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm
|
|
||||||
.Op Fl r Ar stride
|
|
||||||
.Op Fl CmuVZ
|
|
||||||
.Op Fl v Op Fl v No ...
|
|
||||||
.Op Fl a Ar attrmap | Fl A
|
|
||||||
.Op Fl b Ar base_ids
|
|
||||||
.Op Fl c Ar color_spec
|
|
||||||
.Op Fl d Ar depth
|
|
||||||
.Op Fl L Ar slice
|
|
||||||
.Op Fl N Ar nb_tiles
|
|
||||||
.Op Fl n Ar nb_pals
|
|
||||||
.Op Fl o Ar out_file
|
|
||||||
.Op Fl p Ar pal_file | Fl P
|
|
||||||
.Op Fl q Ar pal_map | Fl Q
|
|
||||||
.Op Fl s Ar nb_colors
|
|
||||||
.Op Fl t Ar tilemap | Fl T
|
|
||||||
.Op Fl x Ar quantity
|
|
||||||
.Ar file
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
The
|
|
||||||
.Nm
|
|
||||||
program converts PNG images into data suitable for display on the Game Boy and Game Boy Color, or vice-versa.
|
|
||||||
.Pp
|
|
||||||
The main function of
|
|
||||||
.Nm
|
|
||||||
is to divide the input PNG into 8\[tmu]8 pixel
|
|
||||||
.Em squares ,
|
|
||||||
convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file.
|
|
||||||
It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below.
|
|
||||||
.Sh ARGUMENTS
|
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
|
||||||
.Fl Fl verb
|
|
||||||
is
|
|
||||||
.Fl Fl verbose ,
|
|
||||||
but
|
|
||||||
.Fl Fl ver
|
|
||||||
is invalid because it could also be
|
|
||||||
.Fl Fl version .
|
|
||||||
.Pp
|
|
||||||
.Nm
|
|
||||||
accepts decimal, binary, and hexadecimal numbers in option arguments.
|
|
||||||
Decimal numbers are written as usual; binary numbers must be prefixed with either
|
|
||||||
.Ql %
|
|
||||||
or
|
|
||||||
.Ql 0b ,
|
|
||||||
and hexadecimal numbers must be prefixed with either
|
|
||||||
.Ql $
|
|
||||||
(which will likely need escaping or quoting to avoid being interpreted by the shell), or
|
|
||||||
.Ql 0x .
|
|
||||||
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
|
||||||
All of these are equivalent:
|
|
||||||
.Ql 42 ,
|
|
||||||
.Ql 042 ,
|
|
||||||
.Ql 0b00101010 ,
|
|
||||||
.Ql 0B101010 ,
|
|
||||||
.Ql 0x2A ,
|
|
||||||
.Ql 0X2A ,
|
|
||||||
.Ql 0x2a .
|
|
||||||
.Pp
|
|
||||||
The following options are accepted:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
|
||||||
Generate an attribute map, which is a file containing tile
|
|
||||||
.Dq attributes .
|
|
||||||
For each square of the input image, its corresponding attribute map byte contains the mirroring bits (if
|
|
||||||
.Fl m
|
|
||||||
was specified), the bank bit
|
|
||||||
.Pq see Fl N ,
|
|
||||||
and the palette index.
|
|
||||||
See
|
|
||||||
.Lk https://gbdev.io/pandocs/Tile_Maps#bg-map-attributes-cgb-mode-only Pan Docs
|
|
||||||
for the individual bytes' format.
|
|
||||||
The output is written just like the tile map (see
|
|
||||||
.Fl t ) ,
|
|
||||||
follows the same order
|
|
||||||
.Pq Fl Z ,
|
|
||||||
and has the same size.
|
|
||||||
.It Fl A , Fl Fl output-attr-map
|
|
||||||
Same as
|
|
||||||
.Fl a Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .attrmap .
|
|
||||||
.It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids
|
|
||||||
Set the base IDs for tile map output.
|
|
||||||
.Ar base_ids
|
|
||||||
should be one or two numbers between 0 and 255, separated by a comma; they are for bank 0 and bank 1 respectively.
|
|
||||||
Both default to 0.
|
|
||||||
.It Fl C , Fl Fl color-curve
|
|
||||||
When generating palettes, use a color curve mimicking the Game Boy Color's screen.
|
|
||||||
The resulting colors may look closer to the input image's
|
|
||||||
.Sy on hardware and accurate emulators .
|
|
||||||
.It Fl c Ar color_spec , Fl Fl colors Ar color_spec
|
|
||||||
Use the specified color palettes instead of having
|
|
||||||
.Nm
|
|
||||||
automatically determine some.
|
|
||||||
.Ar color_spec
|
|
||||||
can be one of the following:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Sy inline palette spec
|
|
||||||
If
|
|
||||||
.Ar color_spec
|
|
||||||
begins with a hash character
|
|
||||||
.Ql # ,
|
|
||||||
it is treated as an inline palette specification.
|
|
||||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
|
||||||
Colors in are accepted either as
|
|
||||||
.Ql #rgb
|
|
||||||
or
|
|
||||||
.Ql #rrggbb
|
|
||||||
format.
|
|
||||||
Palettes must be separated by a colon or semicolon (the latter may require quoting to avoid special handling by the shell), and spaces are allowed around colons, semicolons and commas; trailing commas and semicolons are allowed.
|
|
||||||
See
|
|
||||||
.Sx EXAMPLES
|
|
||||||
for an example of an inline palette specification.
|
|
||||||
.It Sy embedded palette spec
|
|
||||||
If
|
|
||||||
.Ar color_spec
|
|
||||||
is the case-insensitive word
|
|
||||||
.Cm embedded ,
|
|
||||||
then the first four colors of the input PNG's embedded palette are used.
|
|
||||||
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
|
|
||||||
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
|
|
||||||
.It Sy external palette spec
|
|
||||||
Otherwise,
|
|
||||||
.Ar color_spec
|
|
||||||
is assumed to be an external palette specification.
|
|
||||||
The expected format is
|
|
||||||
.Ql format:path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is a path to a file, which will be processed according to the
|
|
||||||
.Ar format .
|
|
||||||
See
|
|
||||||
.Sx PALETTE SPECIFICATION FORMATS
|
|
||||||
for a list of formats and their descriptions.
|
|
||||||
.El
|
|
||||||
.It Fl d Ar depth , Fl Fl depth Ar depth
|
|
||||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
|
||||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
|
||||||
.It Fl L Ar slice , Fl Fl slice Ar slice
|
|
||||||
Only process a given rectangle of the image.
|
|
||||||
This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually.
|
|
||||||
The default is to process the whole image as-is.
|
|
||||||
.Pp
|
|
||||||
.Ar slice
|
|
||||||
must be two number pairs, separated by a colon.
|
|
||||||
The numbers must be separated by commas; space is allowed around all punctuation.
|
|
||||||
The first number pair specifies the X and Y coordinates of the top-left pixel that will be processed (anything above it or to its left will be ignored).
|
|
||||||
The second number pair specifies how many tiles to process horizontally and vertically, respectively.
|
|
||||||
.Pp
|
|
||||||
.Sy Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
|
||||||
.It Fl m , Fl Fl mirror-tiles
|
|
||||||
Deduplicate tiles that are mirrors of each other.
|
|
||||||
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
|
||||||
Useful with a tile map and attribute map together to keep track of the duplicated tiles and the dimension(s) mirrored.
|
|
||||||
Implies
|
|
||||||
.Fl u .
|
|
||||||
.It Fl N Ar nb_tiles , Fl Fl nb-tiles Ar nb_tiles
|
|
||||||
Set a maximum number of tiles that can be placed in each VRAM bank.
|
|
||||||
.Ar nb_tiles
|
|
||||||
should be one or two numbers between 0 and 256, separated by a comma; if the latter is omitted, it defaults to 0.
|
|
||||||
Setting either number to 0 prevents any tiles from being output in that bank.
|
|
||||||
.Pp
|
|
||||||
If more tiles are generated than can fit in the two banks combined,
|
|
||||||
.Nm
|
|
||||||
will abort.
|
|
||||||
If
|
|
||||||
.Fl N
|
|
||||||
is not specified, no limit will be set on the amount of tiles placed in bank 0, and tiles will not be placed in bank 1.
|
|
||||||
.It Fl n Ar nb_pals , Fl Fl nb-palettes Ar nb_pals
|
|
||||||
Abort if more than
|
|
||||||
.Ar nb_pals
|
|
||||||
palettes are generated.
|
|
||||||
This may not be more than 256.
|
|
||||||
.Pp
|
|
||||||
Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data unless relying on a palette map
|
|
||||||
.Pq see Fl q .
|
|
||||||
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
|
||||||
Output the tile data in native 2bpp format or in 1bpp
|
|
||||||
.Pq depending on Fl d
|
|
||||||
to this file.
|
|
||||||
.It Fl p Ar pal_file , Fl Fl palette Ar pal_file
|
|
||||||
Output the image's palette set to this file.
|
|
||||||
.It Fl P , Fl Fl output-palette
|
|
||||||
Same as
|
|
||||||
.Fl p Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .pal .
|
|
||||||
.It Fl q Ar pal_file , Fl Fl palette-map Ar pal_file
|
|
||||||
Output the image's palette map to this file.
|
|
||||||
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
|
|
||||||
.It Fl Q , Fl Fl output-palette-map
|
|
||||||
Same as
|
|
||||||
.Fl q Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .palmap .
|
|
||||||
.It Fl r Ar width , Fl Fl reverse Ar width
|
|
||||||
Switches
|
|
||||||
.Nm
|
|
||||||
into
|
|
||||||
.Dq Sy reverse
|
|
||||||
mode.
|
|
||||||
In this mode, instead of converting a PNG image into Game Boy data,
|
|
||||||
.Nm
|
|
||||||
will attempt to reverse the process, and render Game Boy data into an image.
|
|
||||||
See
|
|
||||||
.Sx REVERSE MODE
|
|
||||||
below for details.
|
|
||||||
.Pp
|
|
||||||
.Ar width
|
|
||||||
is the image's width, in tiles
|
|
||||||
.Pq including any margins specified by Fl L .
|
|
||||||
.It Fl s Ar nb_colors , Fl Fl palette-size Ar nb_colors
|
|
||||||
Specify how many colors each palette contains, including the transparent one if any.
|
|
||||||
.Ar nb_colors
|
|
||||||
cannot be more than
|
|
||||||
.Ql 1 << Ar depth
|
|
||||||
.Pq see Fl d .
|
|
||||||
.It Fl t Ar tilemap , Fl Fl tilemap Ar tilemap
|
|
||||||
Generate a file of tile indices.
|
|
||||||
For each square of the input image, its corresponding tile map byte contains the index of the associated tile in the tile data file.
|
|
||||||
The IDs wrap around from 255 back to 0, and do not include the bank bit; use
|
|
||||||
.Fl a
|
|
||||||
for that.
|
|
||||||
Useful in combination with
|
|
||||||
.Fl u
|
|
||||||
and/or
|
|
||||||
.Fl m
|
|
||||||
to keep track of duplicate tiles.
|
|
||||||
.It Fl T , Fl Fl output-tilemap
|
|
||||||
Same as
|
|
||||||
.Fl t Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .tilemap .
|
|
||||||
.It Fl u , Fl Fl unique-tiles
|
|
||||||
Deduplicate identical tiles, and omit the duplicates from the tile data file.
|
|
||||||
Useful with a tile map
|
|
||||||
.Pq see Fl t
|
|
||||||
to keep track of the duplicated tiles.
|
|
||||||
.Pp
|
|
||||||
Note that if this option is enabled, no guarantee is made on the order in which tiles are output; while it
|
|
||||||
.Em should
|
|
||||||
be consistent across identical runs of a given
|
|
||||||
.Nm
|
|
||||||
release, the same is not true for different releases.
|
|
||||||
.It Fl V , Fl Fl version
|
|
||||||
Print the version of the program and exit.
|
|
||||||
.It Fl v , Fl Fl verbose
|
|
||||||
Be verbose.
|
|
||||||
The verbosity level is increased by one each time the flag is specified, with each level including the previous:
|
|
||||||
.Bl -enum -width 2n -compact
|
|
||||||
.It
|
|
||||||
.Nm
|
|
||||||
prints out its configuration before doing anything.
|
|
||||||
.It
|
|
||||||
A generic message is printed before doing most actions.
|
|
||||||
.It
|
|
||||||
Some of the actions' intermediate results are printed.
|
|
||||||
.It
|
|
||||||
Some internal debug printing is enabled.
|
|
||||||
.El
|
|
||||||
The verbosity level does not go past 6.
|
|
||||||
.Pp
|
|
||||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
|
||||||
.It Fl x Ar quantity , Fl Fl trim-end Ar quantity
|
|
||||||
Do not output the last
|
|
||||||
.Ar quantity
|
|
||||||
tiles to the tile data file; no other output is affected.
|
|
||||||
This is useful for trimming
|
|
||||||
.Dq filler
|
|
||||||
/ blank squares at the end of an image.
|
|
||||||
If fewer than
|
|
||||||
.Ar quantity
|
|
||||||
tiles would have been emitted, the file will be empty.
|
|
||||||
.Pp
|
|
||||||
Note that this is done
|
|
||||||
.Em after
|
|
||||||
deduplication if
|
|
||||||
.Fl u
|
|
||||||
was enabled, so you probably don't want to use this option in combination with
|
|
||||||
.Fl u .
|
|
||||||
Note also that the tiles that don't get output will not count towards
|
|
||||||
.Fl N Ap s
|
|
||||||
limit.
|
|
||||||
.It Fl Z , Fl Fl columns
|
|
||||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
|
||||||
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
|
||||||
.El
|
|
||||||
.Ss At-files
|
|
||||||
In a given project, many images are to be converted with different flags.
|
|
||||||
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile / build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
|
|
||||||
.Pp
|
|
||||||
To avoid these drawbacks,
|
|
||||||
.Nm
|
|
||||||
supports
|
|
||||||
.Dq at-files :
|
|
||||||
any command-line argument that begins with an at sign
|
|
||||||
.Pq Ql @
|
|
||||||
is interpreted as one.
|
|
||||||
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
|
|
||||||
At-files can be stored right next to the corresponding image, for example.
|
|
||||||
.Pp
|
|
||||||
Since the contents of at-files are interpreted by
|
|
||||||
.Nm ,
|
|
||||||
.Sy no shell processing is performed ;
|
|
||||||
for example, shell variables are not expanded
|
|
||||||
.Ql ( $PWD ,
|
|
||||||
.Ql %WINDIR% ,
|
|
||||||
etc.).
|
|
||||||
In at-files, lines that are empty or contain only whitespace are ignored; lines that begin with a hash sign
|
|
||||||
.Pq Ql # ,
|
|
||||||
optionally preceded by whitespace, are considered comments and also ignored.
|
|
||||||
Each line can contain any number of arguments, which are separated by whitespace.
|
|
||||||
.Pq \&No quoting feature to prevent this is provided.
|
|
||||||
.Pp
|
|
||||||
Note that this special meaning given to arguments has less precedence than option arguments, and that the standard
|
|
||||||
.Ql --
|
|
||||||
to stop option processing also disables at-file processing.
|
|
||||||
For example, the following command line processes
|
|
||||||
.Ql @tilesets/town.png ,
|
|
||||||
outputs tile data to
|
|
||||||
.Ql @tilesets/town.2bpp ,
|
|
||||||
and reads command-line options from
|
|
||||||
.Ql tilesets/town.flags
|
|
||||||
then
|
|
||||||
.Ql tilesets.flags :
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
|
|
||||||
.Pp
|
|
||||||
At-files can also specify the input image directly, and call for more at-files, both using the regular syntax.
|
|
||||||
Note that while
|
|
||||||
.Ql --
|
|
||||||
can be used in an at-file (with identical semantics), it is only effective inside of it\(emnormal option processing continues in the parent scope.
|
|
||||||
.Sh PALETTE SPECIFICATION FORMATS
|
|
||||||
The following formats are supported:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Sy act
|
|
||||||
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table .
|
|
||||||
.It Sy aco
|
|
||||||
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch .
|
|
||||||
.It Sy psp
|
|
||||||
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
If you wish for another format to be supported, please open an issue (see
|
|
||||||
.Sx BUGS
|
|
||||||
below) or contact us, and supply a few sample files.
|
|
||||||
.Sh PALETTE GENERATION
|
|
||||||
.Nm
|
|
||||||
must generate palettes from the colors in the input image, unless
|
|
||||||
.Fl c
|
|
||||||
was used; in that case, the provided palettes will be used.
|
|
||||||
.Sy If the order of colors in the palettes is important to you ,
|
|
||||||
for example because you want to use palette swaps, please use
|
|
||||||
.Fl c
|
|
||||||
to specify the palette explicitly.
|
|
||||||
.Pp
|
|
||||||
First, if the image contains
|
|
||||||
.Em any
|
|
||||||
transparent pixel, color #0 of
|
|
||||||
.Em all
|
|
||||||
palettes will be allocated to it.
|
|
||||||
This is done
|
|
||||||
.Sy even if palettes were explicitly specified using Fl c ;
|
|
||||||
then the specification only covers color #1 onwards.
|
|
||||||
.Pq If you do not want this, ask your image editor to remove the alpha channel.
|
|
||||||
.Pp
|
|
||||||
After generating palettes,
|
|
||||||
.Nm
|
|
||||||
sorts colors within those palettes using the following rules:
|
|
||||||
.EQ
|
|
||||||
delim $$
|
|
||||||
.EN
|
|
||||||
.Bl -bullet -offset indent
|
|
||||||
.It
|
|
||||||
If the PNG file internally contains a palette (often dubbed an
|
|
||||||
.Dq indexed
|
|
||||||
PNG), then colors in each output palette will be sorted according to their order in the PNG's palette.
|
|
||||||
Any unused entries will be ignored, and only the first entry is considered if there are any duplicates.
|
|
||||||
.Po If you want a given color to appear more than once, or an unused color to appear at all, you should specify the palettes explicitly instead using Fl c ;
|
|
||||||
.Fl c Cm embedded
|
|
||||||
may be appropriate.
|
|
||||||
.Pc
|
|
||||||
.It
|
|
||||||
Otherwise, if the PNG only contains shades of gray, they will be categorized into as many
|
|
||||||
.Dq bins
|
|
||||||
as there are colors per palette, and the palette is set to these bins.
|
|
||||||
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
|
||||||
If two distinct grays end up in the same bin, the RGB method is used instead.
|
|
||||||
.Pp
|
|
||||||
Be careful that
|
|
||||||
.Nm
|
|
||||||
is picky about what it considers
|
|
||||||
.Dq grays :
|
|
||||||
the red, green, and blue components of each color must
|
|
||||||
.Em all
|
|
||||||
be
|
|
||||||
.Em exactly
|
|
||||||
the same.
|
|
||||||
.It
|
|
||||||
If none of the above apply, colors are sorted from lightest to darkest.
|
|
||||||
The definition of luminance that
|
|
||||||
.Nm
|
|
||||||
uses is
|
|
||||||
.Do
|
|
||||||
$2126 times red + 7152 times green + 722 times blue$
|
|
||||||
.Dc .
|
|
||||||
.El
|
|
||||||
.EQ
|
|
||||||
delim off
|
|
||||||
.EN
|
|
||||||
.Pp
|
|
||||||
Note that the
|
|
||||||
.Dq indexed
|
|
||||||
behavior depends on an internal detail of how the PNG is saved, specifically its
|
|
||||||
.Ql PLTE
|
|
||||||
chunk.
|
|
||||||
Since few image editors (such as GIMP) expose that detail, this behavior is only kept for compatibility and should be considered deprecated.
|
|
||||||
.Sh OUTPUT FILES
|
|
||||||
All files output by
|
|
||||||
.Nm
|
|
||||||
are binary files, and designed to follow the Game Boy and Game Boy Color's native formats.
|
|
||||||
What follows is succinct descriptions of those formats, including
|
|
||||||
.Nm Ns -specific
|
|
||||||
details.
|
|
||||||
For more complete, beginner-friendly descriptions of the native formats with illustrations, please check out
|
|
||||||
.Lk https://gbdev.io/pandocs/Rendering Pan Docs .
|
|
||||||
.Ss Tile data
|
|
||||||
Tile data is output like a binary dump of VRAM, with no padding between tiles.
|
|
||||||
Each tile is 16 bytes, 2 per row of 8 pixels; the bits of color IDs are split into each byte
|
|
||||||
.Pq or Dq bitplane .
|
|
||||||
The leftmost pixel's color ID is stored in the two bytes' most significant bits, and the rightmost pixel's color ID in their least significant bits.
|
|
||||||
.Pp
|
|
||||||
When the bit depth
|
|
||||||
.Pq Fl d
|
|
||||||
is set to 1, the most significant bitplane (second byte) of each row, being all zeros, is simply not output.
|
|
||||||
.Ss Palette data
|
|
||||||
Palette data is output like a dump of palette memory.
|
|
||||||
Each color is written as GBC-native little-endian RGB555, with the unused bit 15 set to 0.
|
|
||||||
There is no padding between colors, nor between palettes; however, empty colors in the palettes are output as 0xFFFF.
|
|
||||||
.EQ
|
|
||||||
delim $$
|
|
||||||
.EN
|
|
||||||
For example, if 5 palettes are generated with
|
|
||||||
.Fl s Cm 4 ,
|
|
||||||
the palette data file will be $2 times 4 times 5 = 40$ bytes long, even if some palettes contain less than 3 colors.
|
|
||||||
.EQ
|
|
||||||
delim off
|
|
||||||
.EN
|
|
||||||
Note that
|
|
||||||
.Fl n
|
|
||||||
only puts a limit on the amount of palettes, but does not fix this file's size.
|
|
||||||
.Ss Tile map data
|
|
||||||
A tile map is an array of tile IDs, with one byte per tile ID.
|
|
||||||
The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it
|
|
||||||
.Pq with Fl Z ;
|
|
||||||
and so on, continuing in the same direction.
|
|
||||||
Rows / columns (respectively) are stored consecutively, with no padding.
|
|
||||||
.Ss Attribute map data
|
|
||||||
Attribute maps mirror the format of tile maps, like on the GBC, especially the order in which bytes are output.
|
|
||||||
The contents of individual bytes follows the GBC's native format:
|
|
||||||
.Bl -column "Bit 2\(en0" "Background Palette number"
|
|
||||||
.It Bit 7 Ta BG-to-OAM Priority Ta Set to 0
|
|
||||||
.It Bit 6 Ta Vertical Flip Ta 0=Normal, 1=Mirror vertically
|
|
||||||
.It Bit 5 Ta Horizontal Flip Ta 0=Normal, 1=Mirror horizontally
|
|
||||||
.It Bit 4 Ta Not used Ta Set to 0
|
|
||||||
.It Bit 3 Ta Tile VRAM Bank number Ta 0=Bank 0, 1=Bank 1
|
|
||||||
.It Bit 2\(en0 Ta Background Palette number Ta BGP0-7
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
Note that if more than 8 palettes are used, only the lowest 3 bits of the palette ID are output.
|
|
||||||
.Sh REVERSE MODE
|
|
||||||
.Nm
|
|
||||||
can produce a PNG image from valid data.
|
|
||||||
This may be useful for ripping graphics, recovering lost source images, etc.
|
|
||||||
An important caveat on that last one, though: the conversion process is
|
|
||||||
.Sy lossy
|
|
||||||
both ways, so the
|
|
||||||
.Do reversed Dc image won't be perfectly identical to the original\(embut it should be close to a Game Boy's output .
|
|
||||||
.Pq Keep in mind that many of consoles output different colors, so there is no true reference rendering.
|
|
||||||
.Pp
|
|
||||||
When using reverse mode, make sure to pass the same flags that were given when generating the data, especially
|
|
||||||
.Fl C , d , N , s , x ,
|
|
||||||
and
|
|
||||||
.Fl Z .
|
|
||||||
.Do Sx At-files Dc may help with this .
|
|
||||||
.Nm
|
|
||||||
will warn about any inconsistencies it detects.
|
|
||||||
.Pp
|
|
||||||
Files that are normally outputs
|
|
||||||
.Pq Fl a , p , t
|
|
||||||
become inputs, and
|
|
||||||
.Ar file
|
|
||||||
will be written to instead of read from, and thus needs not exist beforehand.
|
|
||||||
Any of these inputs not passed is assumed to be some default:
|
|
||||||
.Bl -column "attribute map"
|
|
||||||
.It palettes Ta Unspecified palette data makes
|
|
||||||
.Nm
|
|
||||||
assume DMG (monochrome Game Boy) mode: a single palette of 4 grays.
|
|
||||||
It is possible to pass palettes using
|
|
||||||
.Fl c
|
|
||||||
instead of
|
|
||||||
.Fl p .
|
|
||||||
.It tile data Ta Tile data must be provided, as there is no reasonable assumption to fall back on.
|
|
||||||
.It tile map Ta A missing tile map makes
|
|
||||||
.Nm
|
|
||||||
assume that tiles were not deduplicated, and should be laid out in the order they are stored.
|
|
||||||
.It attribute map Ta Without an attribute map,
|
|
||||||
.Nm
|
|
||||||
assumes that no tiles were mirrored.
|
|
||||||
.El
|
|
||||||
.Sh NOTES
|
|
||||||
Some flags have had their functionality removed.
|
|
||||||
.Fl D , f ,
|
|
||||||
and
|
|
||||||
.Fl F
|
|
||||||
are now ignored, and
|
|
||||||
.Fl h
|
|
||||||
is an alias for the new (and less confusingly named)
|
|
||||||
.Fl Z .
|
|
||||||
These will be removed and/or repurposed in future versions of
|
|
||||||
.Nm ,
|
|
||||||
so relying on them is not recommended.
|
|
||||||
The same applies to the corresponding long options.
|
|
||||||
.Pp
|
|
||||||
If you are curious, you may find out that palette generation is an NP-complete problem, so
|
|
||||||
.Nm
|
|
||||||
does not attempt to find the optimal solution, but instead to find a good one in a reasonable amount of time.
|
|
||||||
It is possible to compute the optimal solution externally (using a solver, for example), and then provide it to
|
|
||||||
.Nm
|
|
||||||
via
|
|
||||||
.Fl c .
|
|
||||||
.Sh EXAMPLES
|
|
||||||
The following will only validate the PNG (check its size, that all tiles have a suitable amount of colors, etc.), but output nothing:
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx src/res/maps/overworld/tileset.png
|
|
||||||
.Pp
|
|
||||||
The following will convert the image using the two given palettes (and only those), and store the generated 2bpp tile data in
|
|
||||||
.Ql tileset.2bpp ,
|
|
||||||
and the attribute map in
|
|
||||||
.Ql tileset.attrmap .
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx -c '#ffffff,#8d05de, #dc7905,#000000 ; #fff,#8d05de, #7e0000 \&, #000' -A -o tileset.2bpp tileset.png
|
|
||||||
.Pp
|
|
||||||
TODO: more examples.
|
|
||||||
.Sh BUGS
|
|
||||||
Please report bugs and mistakes in this man page on
|
|
||||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
|
||||||
Bug reports and feature requests about RGBDS are also welcome!
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr rgbds 7 ,
|
|
||||||
.Xr rgbasm 1 ,
|
|
||||||
.Xr rgblink 1 ,
|
|
||||||
.Xr rgbfix 1 ,
|
|
||||||
.Xr gbz80 7
|
|
||||||
.Pp
|
|
||||||
The Game Boy hardware reference
|
|
||||||
.Lk https://gbdev.io/pandocs/Rendering.html Pan Docs ,
|
|
||||||
particularly the section about graphics.
|
|
||||||
.Sh HISTORY
|
|
||||||
.Nm
|
|
||||||
was originally created by
|
|
||||||
.An stag019
|
|
||||||
to be included in RGBDS.
|
|
||||||
It was later rewritten by
|
|
||||||
.An ISSOtm ,
|
|
||||||
and is now maintained by a number of contributors at
|
|
||||||
.Lk https://github.com/gbdev/rgbds .
|
|
||||||
@@ -14,6 +14,14 @@ set(common_src
|
|||||||
"_version.c"
|
"_version.c"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
||||||
|
# fallback to find_package
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
|
else()
|
||||||
|
pkg_check_modules(LIBPNG REQUIRED libpng)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(BISON REQUIRED)
|
find_package(BISON REQUIRED)
|
||||||
set(BISON_FLAGS "-Wall")
|
set(BISON_FLAGS "-Wall")
|
||||||
# Set sompe optimization flags on versions that support them
|
# Set sompe optimization flags on versions that support them
|
||||||
@@ -62,16 +70,9 @@ set(rgbfix_src
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(rgbgfx_src
|
set(rgbgfx_src
|
||||||
"gfx/main.cpp"
|
"gfx/gb.c"
|
||||||
"gfx/pal_packing.cpp"
|
"gfx/main.c"
|
||||||
"gfx/pal_sorting.cpp"
|
"gfx/makepng.c"
|
||||||
"gfx/pal_spec.cpp"
|
|
||||||
"gfx/process.cpp"
|
|
||||||
"gfx/proto_palette.cpp"
|
|
||||||
"gfx/reverse.cpp"
|
|
||||||
"gfx/rgba.cpp"
|
|
||||||
"extern/getopt.c"
|
|
||||||
"error.c"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(rgblink_src
|
set(rgblink_src
|
||||||
@@ -96,6 +97,22 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
|||||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
set(MANDIR "share/man")
|
||||||
|
set(man1 "asm/rgbasm.1"
|
||||||
|
"fix/rgbfix.1"
|
||||||
|
"gfx/rgbgfx.1"
|
||||||
|
"link/rgblink.1")
|
||||||
|
set(man5 "asm/rgbasm.5"
|
||||||
|
"link/rgblink.5"
|
||||||
|
"rgbds.5")
|
||||||
|
set(man7 "gbz80.7"
|
||||||
|
"rgbds.7")
|
||||||
|
|
||||||
|
foreach(SECTION "man1" "man5" "man7")
|
||||||
|
set(DEST "${MANDIR}/${SECTION}")
|
||||||
|
install(FILES ${${SECTION}} DESTINATION ${DEST})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
if(LIBPNG_FOUND) # pkg-config
|
if(LIBPNG_FOUND) # pkg-config
|
||||||
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
||||||
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
||||||
|
|||||||
@@ -30,6 +30,16 @@
|
|||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the _PI symbol value
|
||||||
|
*/
|
||||||
|
int32_t fix_Callback_PI(void)
|
||||||
|
{
|
||||||
|
warning(WARNING_OBSOLETE, "`_PI` is deprecated; use 3.14159\n");
|
||||||
|
|
||||||
|
return double2fix(M_PI);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Print a fixed point value
|
* Print a fixed point value
|
||||||
*/
|
*/
|
||||||
|
|||||||
105
src/asm/lexer.c
105
src/asm/lexer.c
@@ -109,7 +109,7 @@ static struct KeywordMapping {
|
|||||||
{"DEC", T_Z80_DEC},
|
{"DEC", T_Z80_DEC},
|
||||||
{"DI", T_Z80_DI},
|
{"DI", T_Z80_DI},
|
||||||
{"EI", T_Z80_EI},
|
{"EI", T_Z80_EI},
|
||||||
{"HALT", T_Z80_HALT},
|
{"HALT✋", T_Z80_HALT},
|
||||||
{"INC", T_Z80_INC},
|
{"INC", T_Z80_INC},
|
||||||
{"JP", T_Z80_JP},
|
{"JP", T_Z80_JP},
|
||||||
{"JR", T_Z80_JR},
|
{"JR", T_Z80_JR},
|
||||||
@@ -118,8 +118,9 @@ static struct KeywordMapping {
|
|||||||
{"LDD", T_Z80_LDD},
|
{"LDD", T_Z80_LDD},
|
||||||
{"LDIO", T_Z80_LDH},
|
{"LDIO", T_Z80_LDH},
|
||||||
{"LDH", T_Z80_LDH},
|
{"LDH", T_Z80_LDH},
|
||||||
{"NOP", T_Z80_NOP},
|
{"NOPE", T_Z80_NOP},
|
||||||
{"OR", T_Z80_OR},
|
{"OR", T_Z80_OR},
|
||||||
|
{"OWO", T_OWO},
|
||||||
{"POP", T_Z80_POP},
|
{"POP", T_Z80_POP},
|
||||||
{"PUSH", T_Z80_PUSH},
|
{"PUSH", T_Z80_PUSH},
|
||||||
{"RES", T_Z80_RES},
|
{"RES", T_Z80_RES},
|
||||||
@@ -140,7 +141,7 @@ static struct KeywordMapping {
|
|||||||
{"SLA", T_Z80_SLA},
|
{"SLA", T_Z80_SLA},
|
||||||
{"SRA", T_Z80_SRA},
|
{"SRA", T_Z80_SRA},
|
||||||
{"SRL", T_Z80_SRL},
|
{"SRL", T_Z80_SRL},
|
||||||
{"STOP", T_Z80_STOP},
|
{"STOP!!🛑", T_Z80_STOP},
|
||||||
{"SUB", T_Z80_SUB},
|
{"SUB", T_Z80_SUB},
|
||||||
{"SWAP", T_Z80_SWAP},
|
{"SWAP", T_Z80_SWAP},
|
||||||
{"XOR", T_Z80_XOR},
|
{"XOR", T_Z80_XOR},
|
||||||
@@ -148,24 +149,29 @@ static struct KeywordMapping {
|
|||||||
{"NZ", T_CC_NZ},
|
{"NZ", T_CC_NZ},
|
||||||
{"Z", T_CC_Z},
|
{"Z", T_CC_Z},
|
||||||
{"NC", T_CC_NC},
|
{"NC", T_CC_NC},
|
||||||
/* Handled after as T_TOKEN_C */
|
{"C", T_CC_C},
|
||||||
/* { "C", T_CC_C }, */
|
|
||||||
|
|
||||||
{"AF", T_MODE_AF},
|
{"•̀A•́)𝓕𝓾𝓬𝓴", T_MODE_AF},
|
||||||
{"BC", T_MODE_BC},
|
// {"BC", T_MODE_BC},
|
||||||
{"DE", T_MODE_DE},
|
// {"DE", T_MODE_DE},
|
||||||
{"HL", T_MODE_HL},
|
{"н∠(", T_MODE_HL_START},
|
||||||
{"SP", T_MODE_SP},
|
{"SP", T_MODE_SP},
|
||||||
{"HLD", T_MODE_HL_DEC},
|
{"н∠( ᐛ 」∠)_👁", T_MODE_HL_DEC},
|
||||||
{"HLI", T_MODE_HL_INC},
|
{"н∠( ᐛ 」∠)_👎", T_MODE_HL_INC},
|
||||||
|
|
||||||
{"A", T_TOKEN_A},
|
// HACK: normally this is surrounded by parens, but this is annoying to special-case,
|
||||||
{"B", T_TOKEN_B},
|
// so we use cooperation from the parser.
|
||||||
{"C", T_TOKEN_C},
|
{"•̀A•́", T_TOKEN_A},
|
||||||
{"D", T_TOKEN_D},
|
// {"=B", T_TOKEN_B}, HACK: This begins with a non-identifier character, so we'll cheat
|
||||||
{"E", T_TOKEN_E},
|
{"♥(˘⌣˘", T_TOKEN_C}, // HACK: same for "C" after the space & closing paren
|
||||||
{"H", T_TOKEN_H},
|
// {";D", T_TOKEN_D}, HACK: also needs to be special-cased. God I feel dirty.
|
||||||
{"L", T_TOKEN_L},
|
{"(´ε`", T_TOKEN_E},
|
||||||
|
{"♡", T_TOKEN_E_HEART},
|
||||||
|
{"н", T_TOKEN_H},
|
||||||
|
{"∠(", T_TOKEN_L_ARM},
|
||||||
|
{"ᐛ", T_TOKEN_L_FACE},
|
||||||
|
{"」∠", T_TOKEN_L_BODY},
|
||||||
|
{"_", T_TOKEN_L_LEG},
|
||||||
|
|
||||||
{"DEF", T_OP_DEF},
|
{"DEF", T_OP_DEF},
|
||||||
|
|
||||||
@@ -212,6 +218,10 @@ static struct KeywordMapping {
|
|||||||
{"INCLUDE", T_POP_INCLUDE},
|
{"INCLUDE", T_POP_INCLUDE},
|
||||||
{"PRINT", T_POP_PRINT},
|
{"PRINT", T_POP_PRINT},
|
||||||
{"PRINTLN", T_POP_PRINTLN},
|
{"PRINTLN", T_POP_PRINTLN},
|
||||||
|
{"PRINTT", T_POP_PRINTT},
|
||||||
|
{"PRINTI", T_POP_PRINTI},
|
||||||
|
{"PRINTV", T_POP_PRINTV},
|
||||||
|
{"PRINTF", T_POP_PRINTF},
|
||||||
{"EXPORT", T_POP_EXPORT},
|
{"EXPORT", T_POP_EXPORT},
|
||||||
{"DS", T_POP_DS},
|
{"DS", T_POP_DS},
|
||||||
{"DB", T_POP_DB},
|
{"DB", T_POP_DB},
|
||||||
@@ -574,16 +584,16 @@ struct KeywordDictNode {
|
|||||||
* In turn, this allows greatly simplifying checking an index into this array,
|
* In turn, this allows greatly simplifying checking an index into this array,
|
||||||
* which should help speed up the lexer.
|
* which should help speed up the lexer.
|
||||||
*/
|
*/
|
||||||
uint16_t children[0x60 - ' '];
|
uint16_t children[256]; // HACK: we "support" UTF-8 as input now
|
||||||
struct KeywordMapping const *keyword;
|
struct KeywordMapping const *keyword;
|
||||||
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
||||||
} keywordDict[365] = {0}; /* Make sure to keep this correct when adding keywords! */
|
} keywordDict[690] = {0}; /* Nice */
|
||||||
|
|
||||||
/* Convert a char into its index into the dict */
|
/* Convert a char into its index into the dict */
|
||||||
static uint8_t dictIndex(char c)
|
static uint8_t dictIndex(char c)
|
||||||
{
|
{
|
||||||
/* Translate uppercase to lowercase (roughly) */
|
/* Translate uppercase to lowercase (roughly) */
|
||||||
if (c > 0x60)
|
if (c > 0x60 && c < 0x80)
|
||||||
c = c - ('a' - 'A');
|
c = c - ('a' - 'A');
|
||||||
return c - ' ';
|
return c - ' ';
|
||||||
}
|
}
|
||||||
@@ -605,8 +615,9 @@ void lexer_Init(void)
|
|||||||
|
|
||||||
/* Walk the dictionary, creating intermediate nodes for the keyword */
|
/* Walk the dictionary, creating intermediate nodes for the keyword */
|
||||||
for (char const *ptr = keywords[i].name; *ptr; ptr++) {
|
for (char const *ptr = keywords[i].name; *ptr; ptr++) {
|
||||||
|
unsigned char index = (unsigned char)*ptr - ' ';
|
||||||
/* We should be able to assume all entries are well-formed */
|
/* We should be able to assume all entries are well-formed */
|
||||||
if (keywordDict[nodeID].children[*ptr - ' '] == 0) {
|
if (keywordDict[nodeID].children[index] == 0) {
|
||||||
/*
|
/*
|
||||||
* If this gets tripped up, set the size of keywordDict to
|
* If this gets tripped up, set the size of keywordDict to
|
||||||
* something high, compile with `-DPRINT_NODE_COUNT` (see below),
|
* something high, compile with `-DPRINT_NODE_COUNT` (see below),
|
||||||
@@ -615,10 +626,10 @@ void lexer_Init(void)
|
|||||||
assert(usedNodes < sizeof(keywordDict) / sizeof(*keywordDict));
|
assert(usedNodes < sizeof(keywordDict) / sizeof(*keywordDict));
|
||||||
|
|
||||||
/* There is no node at that location, grab one from the pool */
|
/* There is no node at that location, grab one from the pool */
|
||||||
keywordDict[nodeID].children[*ptr - ' '] = usedNodes;
|
keywordDict[nodeID].children[index] = usedNodes;
|
||||||
usedNodes++;
|
usedNodes++;
|
||||||
}
|
}
|
||||||
nodeID = keywordDict[nodeID].children[*ptr - ' '];
|
nodeID = keywordDict[nodeID].children[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This assumes that no two keywords have the same name */
|
/* This assumes that no two keywords have the same name */
|
||||||
@@ -1285,12 +1296,16 @@ static uint32_t readGfxConstant(void)
|
|||||||
static bool startsIdentifier(int c)
|
static bool startsIdentifier(int c)
|
||||||
{
|
{
|
||||||
// Anonymous labels internally start with '!'
|
// Anonymous labels internally start with '!'
|
||||||
return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_';
|
return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_' || c >= 0x80 || c == '(';
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool continuesIdentifier(int c)
|
static bool continuesIdentifier(int c)
|
||||||
{
|
{
|
||||||
return startsIdentifier(c) || (c <= '9' && c >= '0') || c == '#' || c == '@';
|
// April Fools HACK: allow UTF-8 :D
|
||||||
|
// This would normally be quite unsafe (hello, RTL control codes?),
|
||||||
|
// but since this is for a joke I'll also make the code a joke
|
||||||
|
// Also, hi if you're reading this!
|
||||||
|
return startsIdentifier(c) || (c <= '9' && c >= '0') || c == '#' || c == '@' || c == '!';
|
||||||
}
|
}
|
||||||
|
|
||||||
static int readIdentifier(char firstChar)
|
static int readIdentifier(char firstChar)
|
||||||
@@ -1770,6 +1785,10 @@ static int yylex_NORMAL(void)
|
|||||||
/* Ignore whitespace and comments */
|
/* Ignore whitespace and comments */
|
||||||
|
|
||||||
case ';':
|
case ';':
|
||||||
|
if (peek() == 'D') {
|
||||||
|
shiftChar();
|
||||||
|
return T_TOKEN_D;
|
||||||
|
}
|
||||||
discardComment();
|
discardComment();
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
case ' ':
|
case ' ':
|
||||||
@@ -1790,8 +1809,6 @@ static int yylex_NORMAL(void)
|
|||||||
return T_LBRACK;
|
return T_LBRACK;
|
||||||
case ']':
|
case ']':
|
||||||
return T_RBRACK;
|
return T_RBRACK;
|
||||||
case '(':
|
|
||||||
return T_LPAREN;
|
|
||||||
case ')':
|
case ')':
|
||||||
return T_RPAREN;
|
return T_RPAREN;
|
||||||
case ',':
|
case ',':
|
||||||
@@ -1859,9 +1876,14 @@ static int yylex_NORMAL(void)
|
|||||||
return T_OP_XOR;
|
return T_OP_XOR;
|
||||||
|
|
||||||
case '=': /* Either assignment or EQ */
|
case '=': /* Either assignment or EQ */
|
||||||
if (peek() == '=') {
|
switch (peek()) {
|
||||||
|
case '=':
|
||||||
shiftChar();
|
shiftChar();
|
||||||
return T_OP_LOGICEQU;
|
return T_OP_LOGICEQU;
|
||||||
|
case 'b':
|
||||||
|
case 'B':
|
||||||
|
shiftChar();
|
||||||
|
return T_TOKEN_B;
|
||||||
}
|
}
|
||||||
return T_POP_EQUAL;
|
return T_POP_EQUAL;
|
||||||
|
|
||||||
@@ -2000,6 +2022,12 @@ static int yylex_NORMAL(void)
|
|||||||
|
|
||||||
/* Handle identifiers... or report garbage characters */
|
/* Handle identifiers... or report garbage characters */
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
if (peek() != (unsigned char)"´"[0]) {
|
||||||
|
return T_LPAREN;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (startsIdentifier(c)) {
|
if (startsIdentifier(c)) {
|
||||||
int tokenType = readIdentifier(c);
|
int tokenType = readIdentifier(c);
|
||||||
@@ -2052,23 +2080,9 @@ static int yylex_RAW(void)
|
|||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
/* Trim left whitespace (stops at a block comment) */
|
/* Trim left whitespace (stops at a block comment or line continuation) */
|
||||||
for (;;) {
|
while (isWhitespace(peek()))
|
||||||
c = peek();
|
|
||||||
if (isWhitespace(c)) {
|
|
||||||
shiftChar();
|
shiftChar();
|
||||||
} else if (c == '\\') {
|
|
||||||
shiftChar();
|
|
||||||
c = peek();
|
|
||||||
// If not a line continuation, handle as a normal char
|
|
||||||
if (!isWhitespace(c) && c != '\n' && c != '\r')
|
|
||||||
goto backslash;
|
|
||||||
// Line continuations count as "whitespace"
|
|
||||||
readLineContinuation();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
c = peek();
|
c = peek();
|
||||||
@@ -2117,7 +2131,6 @@ static int yylex_RAW(void)
|
|||||||
shiftChar();
|
shiftChar();
|
||||||
c = peek();
|
c = peek();
|
||||||
|
|
||||||
backslash:
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case ',': /* Escapes only valid inside a macro arg */
|
case ',': /* Escapes only valid inside a macro arg */
|
||||||
case '(':
|
case '(':
|
||||||
|
|||||||
@@ -59,9 +59,7 @@ bool generatePhonyDeps;
|
|||||||
char *targetFileName;
|
char *targetFileName;
|
||||||
|
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool warnOnHaltNop;
|
|
||||||
bool optimizeLoads;
|
bool optimizeLoads;
|
||||||
bool warnOnLdOpt;
|
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool warnings; /* True to enable warnings, false to disable them. */
|
bool warnings; /* True to enable warnings, false to disable them. */
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ static char *make_escape(char const *str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Short options */
|
/* Short options */
|
||||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w";
|
static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
|
||||||
|
|
||||||
/* Variables for the long-only options */
|
/* Variables for the long-only options */
|
||||||
static int depType; /* Variants of `-M` */
|
static int depType; /* Variants of `-M` */
|
||||||
@@ -106,11 +104,9 @@ static struct option const longopts[] = {
|
|||||||
{ "define", required_argument, NULL, 'D' },
|
{ "define", required_argument, NULL, 'D' },
|
||||||
{ "export-all", no_argument, NULL, 'E' },
|
{ "export-all", no_argument, NULL, 'E' },
|
||||||
{ "gfx-chars", required_argument, NULL, 'g' },
|
{ "gfx-chars", required_argument, NULL, 'g' },
|
||||||
{ "nop-after-halt", no_argument, NULL, 'H' },
|
|
||||||
{ "halt-without-nop", no_argument, NULL, 'h' },
|
{ "halt-without-nop", no_argument, NULL, 'h' },
|
||||||
{ "include", required_argument, NULL, 'i' },
|
{ "include", required_argument, NULL, 'i' },
|
||||||
{ "preserve-ld", no_argument, NULL, 'L' },
|
{ "preserve-ld", no_argument, NULL, 'L' },
|
||||||
{ "auto-ldh", no_argument, NULL, 'l' },
|
|
||||||
{ "dependfile", required_argument, NULL, 'M' },
|
{ "dependfile", required_argument, NULL, 'M' },
|
||||||
{ "MG", no_argument, &depType, 'G' },
|
{ "MG", no_argument, &depType, 'G' },
|
||||||
{ "MP", no_argument, &depType, 'P' },
|
{ "MP", no_argument, &depType, 'P' },
|
||||||
@@ -146,6 +142,9 @@ static void print_usage(void)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
#if YYDEBUG
|
||||||
|
yydebug = 1;
|
||||||
|
#endif
|
||||||
int ch;
|
int ch;
|
||||||
char *ep;
|
char *ep;
|
||||||
|
|
||||||
@@ -174,10 +173,8 @@ int main(int argc, char *argv[])
|
|||||||
opt_B("01");
|
opt_B("01");
|
||||||
opt_G("0123");
|
opt_G("0123");
|
||||||
opt_P(0);
|
opt_P(0);
|
||||||
haltnop = true;
|
|
||||||
warnOnHaltNop = true;
|
|
||||||
optimizeLoads = true;
|
optimizeLoads = true;
|
||||||
warnOnLdOpt = true;
|
haltnop = true;
|
||||||
verbose = false;
|
verbose = false;
|
||||||
warnings = true;
|
warnings = true;
|
||||||
sym_SetExportAll(false);
|
sym_SetExportAll(false);
|
||||||
@@ -215,14 +212,7 @@ int main(int argc, char *argv[])
|
|||||||
errx("Must specify exactly 4 characters for option 'g'");
|
errx("Must specify exactly 4 characters for option 'g'");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'H':
|
|
||||||
if (!haltnop)
|
|
||||||
errx("`-H` and `-h` don't make sense together");
|
|
||||||
warnOnHaltNop = false;
|
|
||||||
break;
|
|
||||||
case 'h':
|
case 'h':
|
||||||
if (!warnOnHaltNop)
|
|
||||||
errx("`-H` and `-h` don't make sense together");
|
|
||||||
haltnop = false;
|
haltnop = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -231,15 +221,8 @@ int main(int argc, char *argv[])
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
if (!warnOnLdOpt)
|
|
||||||
errx("`-L` and `-l` don't make sense together");
|
|
||||||
optimizeLoads = false;
|
optimizeLoads = false;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
|
||||||
if (!optimizeLoads)
|
|
||||||
errx("`-L` and `-l` don't make sense together");
|
|
||||||
warnOnLdOpt = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (!strcmp("-", musl_optarg))
|
if (!strcmp("-", musl_optarg))
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ struct OptStackEntry {
|
|||||||
char gbgfx[4];
|
char gbgfx[4];
|
||||||
int32_t fillByte;
|
int32_t fillByte;
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool warnOnHaltNop;
|
|
||||||
bool optimizeLoads;
|
bool optimizeLoads;
|
||||||
bool warnOnLdOpt;
|
|
||||||
bool warningsAreErrors;
|
bool warningsAreErrors;
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
// Don't be confused: we use the size of the **global variable** `warningStates`!
|
// Don't be confused: we use the size of the **global variable** `warningStates`!
|
||||||
@@ -50,11 +48,6 @@ void opt_R(size_t newDepth)
|
|||||||
lexer_CheckRecursionDepth();
|
lexer_CheckRecursionDepth();
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_H(bool warn)
|
|
||||||
{
|
|
||||||
warnOnHaltNop = warn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_h(bool halt)
|
void opt_h(bool halt)
|
||||||
{
|
{
|
||||||
haltnop = halt;
|
haltnop = halt;
|
||||||
@@ -65,11 +58,6 @@ void opt_L(bool optimize)
|
|||||||
optimizeLoads = optimize;
|
optimizeLoads = optimize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_l(bool warn)
|
|
||||||
{
|
|
||||||
warnOnLdOpt = warn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_W(char *flag)
|
void opt_W(char *flag)
|
||||||
{
|
{
|
||||||
processWarningFlag(flag);
|
processWarningFlag(flag);
|
||||||
@@ -130,13 +118,6 @@ void opt_Parse(char *s)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'H':
|
|
||||||
if (s[1] == '\0')
|
|
||||||
opt_H(false);
|
|
||||||
else
|
|
||||||
error("Option 'H' does not take an argument\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
if (s[1] == '\0')
|
if (s[1] == '\0')
|
||||||
opt_h(false);
|
opt_h(false);
|
||||||
@@ -151,13 +132,6 @@ void opt_Parse(char *s)
|
|||||||
error("Option 'L' does not take an argument\n");
|
error("Option 'L' does not take an argument\n");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l':
|
|
||||||
if (s[1] == '\0')
|
|
||||||
opt_l(false);
|
|
||||||
else
|
|
||||||
error("Option 'l' does not take an argument\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
if (strlen(&s[1]) > 0)
|
if (strlen(&s[1]) > 0)
|
||||||
opt_W(&s[1]);
|
opt_W(&s[1]);
|
||||||
@@ -212,10 +186,8 @@ void opt_Push(void)
|
|||||||
entry->fillByte = fillByte; // Pulled from section.h
|
entry->fillByte = fillByte; // Pulled from section.h
|
||||||
|
|
||||||
entry->haltnop = haltnop; // Pulled from main.h
|
entry->haltnop = haltnop; // Pulled from main.h
|
||||||
entry->warnOnHaltNop = warnOnHaltNop;
|
|
||||||
|
|
||||||
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
|
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
|
||||||
entry->warnOnLdOpt = warnOnLdOpt;
|
|
||||||
|
|
||||||
// Both of these pulled from warning.h
|
// Both of these pulled from warning.h
|
||||||
entry->warningsAreErrors = warningsAreErrors;
|
entry->warningsAreErrors = warningsAreErrors;
|
||||||
@@ -237,10 +209,8 @@ void opt_Pop(void)
|
|||||||
opt_B(entry->binary);
|
opt_B(entry->binary);
|
||||||
opt_G(entry->gbgfx);
|
opt_G(entry->gbgfx);
|
||||||
opt_P(entry->fillByte);
|
opt_P(entry->fillByte);
|
||||||
opt_H(entry->warnOnHaltNop);
|
|
||||||
opt_h(entry->haltnop);
|
opt_h(entry->haltnop);
|
||||||
opt_L(entry->optimizeLoads);
|
opt_L(entry->optimizeLoads);
|
||||||
opt_l(entry->warnOnLdOpt);
|
|
||||||
|
|
||||||
// opt_W does not apply a whole warning state; it processes one flag string
|
// opt_W does not apply a whole warning state; it processes one flag string
|
||||||
warningsAreErrors = entry->warningsAreErrors;
|
warningsAreErrors = entry->warningsAreErrors;
|
||||||
|
|||||||
103
src/asm/parser.y
103
src/asm/parser.y
@@ -599,6 +599,7 @@ enum {
|
|||||||
|
|
||||||
%token T_POP_INCLUDE "INCLUDE"
|
%token T_POP_INCLUDE "INCLUDE"
|
||||||
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
|
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
|
||||||
|
%token T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI"
|
||||||
%token T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC"
|
%token T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC"
|
||||||
%token T_POP_EXPORT "EXPORT"
|
%token T_POP_EXPORT "EXPORT"
|
||||||
%token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
|
%token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
|
||||||
@@ -645,31 +646,32 @@ enum {
|
|||||||
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
|
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
|
||||||
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
|
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
|
||||||
%token T_Z80_EI "ei"
|
%token T_Z80_EI "ei"
|
||||||
%token T_Z80_HALT "halt"
|
%token T_Z80_HALT "halt✋"
|
||||||
%token T_Z80_INC "inc"
|
%token T_Z80_INC "inc"
|
||||||
%token T_Z80_JP "jp" T_Z80_JR "jr"
|
%token T_Z80_JP "jp" T_Z80_JR "jr"
|
||||||
%token T_Z80_LD "ld"
|
%token T_Z80_LD "ld"
|
||||||
%token T_Z80_LDI "ldi"
|
%token T_Z80_LDI "ldi"
|
||||||
%token T_Z80_LDD "ldd"
|
%token T_Z80_LDD "ldd"
|
||||||
%token T_Z80_LDH "ldh"
|
%token T_Z80_LDH "ldh"
|
||||||
%token T_Z80_NOP "nop"
|
%token T_Z80_NOP "nope"
|
||||||
%token T_Z80_OR "or"
|
%token T_Z80_OR "or"
|
||||||
|
%token T_OWO "owo"
|
||||||
%token T_Z80_POP "pop" T_Z80_PUSH "push"
|
%token T_Z80_POP "pop" T_Z80_PUSH "push"
|
||||||
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
|
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
|
||||||
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
|
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
|
||||||
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
|
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
|
||||||
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_STOP "stop"
|
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_STOP "stop!!🛑"
|
||||||
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
|
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
|
||||||
%token T_Z80_SWAP "swap"
|
%token T_Z80_SWAP "swap"
|
||||||
%token T_Z80_XOR "xor"
|
%token T_Z80_XOR "xor"
|
||||||
|
|
||||||
%token T_TOKEN_A "a"
|
%token T_TOKEN_A "( •̀A•́)" T_TOKEN_F "𝓕𝓾𝓬𝓴"
|
||||||
%token T_TOKEN_B "b" T_TOKEN_C "c"
|
%token T_TOKEN_B "=B" T_TOKEN_C "♥(˘⌣˘ C)"
|
||||||
%token T_TOKEN_D "d" T_TOKEN_E "e"
|
%token T_TOKEN_D ";D" T_TOKEN_E "(´ε` )♡" T_TOKEN_E_HEART "(´ε` )♡"
|
||||||
%token T_TOKEN_H "h" T_TOKEN_L "l"
|
%token T_TOKEN_H "н" T_TOKEN_L_ARM "∠( ᐛ 」∠)_" T_TOKEN_L_FACE "∠( ᐛ 」∠)_" T_TOKEN_L_BODY "∠( ᐛ 」∠)_" T_TOKEN_L_LEG "∠( ᐛ 」∠)_"
|
||||||
%token T_MODE_AF "af" T_MODE_BC "bc" T_MODE_DE "de" T_MODE_SP "sp"
|
%token T_MODE_AF "af" /* T_MODE_BC "bc" T_MODE_DE "de" */ T_MODE_SP "sp"
|
||||||
%token T_MODE_HL "hl" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+"
|
%token T_MODE_HL_START "н∠( ᐛ 」∠)_" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+"
|
||||||
%token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" // There is no T_CC_C, only T_TOKEN_C
|
%token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" T_CC_C "c"
|
||||||
|
|
||||||
%type <constValue> reg_r
|
%type <constValue> reg_r
|
||||||
%type <constValue> reg_ss
|
%type <constValue> reg_ss
|
||||||
@@ -855,7 +857,7 @@ macroargs : %empty {
|
|||||||
|
|
||||||
/* These commands start with a T_LABEL. */
|
/* These commands start with a T_LABEL. */
|
||||||
assignment_directive : equ
|
assignment_directive : equ
|
||||||
| assignment
|
| set
|
||||||
| rb
|
| rb
|
||||||
| rw
|
| rw
|
||||||
| rl
|
| rl
|
||||||
@@ -865,6 +867,10 @@ assignment_directive : equ
|
|||||||
directive : endc
|
directive : endc
|
||||||
| print
|
| print
|
||||||
| println
|
| println
|
||||||
|
| printf
|
||||||
|
| printt
|
||||||
|
| printv
|
||||||
|
| printi
|
||||||
| export
|
| export
|
||||||
| db
|
| db
|
||||||
| dw
|
| dw
|
||||||
@@ -922,8 +928,12 @@ compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; }
|
|||||||
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
|
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
|
||||||
;
|
;
|
||||||
|
|
||||||
assignment : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
|
set : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
|
||||||
| T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
|
| T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
|
||||||
|
| T_LABEL T_POP_SET const {
|
||||||
|
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
|
||||||
|
sym_AddVar($1, $3);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
|
equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
|
||||||
@@ -1165,6 +1175,14 @@ def_set : def_id T_POP_EQUAL const { sym_AddVar($1, $3); }
|
|||||||
| redef_id T_POP_EQUAL const { sym_AddVar($1, $3); }
|
| redef_id T_POP_EQUAL const { sym_AddVar($1, $3); }
|
||||||
| def_id compoundeq const { compoundAssignment($1, $2, $3); }
|
| def_id compoundeq const { compoundAssignment($1, $2, $3); }
|
||||||
| redef_id compoundeq const { compoundAssignment($1, $2, $3); }
|
| redef_id compoundeq const { compoundAssignment($1, $2, $3); }
|
||||||
|
| def_id T_POP_SET const {
|
||||||
|
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
|
||||||
|
sym_AddVar($1, $3);
|
||||||
|
}
|
||||||
|
| redef_id T_POP_SET const {
|
||||||
|
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
|
||||||
|
sym_AddVar($1, $3);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
def_rb : def_id T_POP_RB rs_uconst {
|
def_rb : def_id T_POP_RB rs_uconst {
|
||||||
@@ -1278,6 +1296,30 @@ print_expr : const_no_str { printf("$%" PRIX32, $1); }
|
|||||||
| string { printf("%s", $1); }
|
| string { printf("%s", $1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
printt : T_POP_PRINTT string {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTT` is deprecated; use `PRINT`\n");
|
||||||
|
printf("%s", $2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
printv : T_POP_PRINTV const {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTV` is deprecated; use `PRINT`\n");
|
||||||
|
printf("$%" PRIX32, $2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
printi : T_POP_PRINTI const {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTI` is deprecated; use `PRINT` with `STRFMT` \"%%d\"\n");
|
||||||
|
printf("%" PRId32, $2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
printf : T_POP_PRINTF const {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTF` is deprecated; use `PRINT` with `STRFMT` \"%%f\"\n");
|
||||||
|
fix_Print($2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
const_3bit : const {
|
const_3bit : const {
|
||||||
int32_t value = $1;
|
int32_t value = $1;
|
||||||
|
|
||||||
@@ -1728,6 +1770,7 @@ cpu_command : z80_adc
|
|||||||
| z80_sub
|
| z80_sub
|
||||||
| z80_swap
|
| z80_swap
|
||||||
| z80_xor
|
| z80_xor
|
||||||
|
| T_OWO { fatalerror("*BONK* go to horny jail\n"); }
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_adc : T_Z80_ADC op_a_n {
|
z80_adc : T_Z80_ADC op_a_n {
|
||||||
@@ -1801,14 +1844,9 @@ z80_ei : T_Z80_EI { sect_AbsByte(0xFB); }
|
|||||||
|
|
||||||
z80_halt : T_Z80_HALT {
|
z80_halt : T_Z80_HALT {
|
||||||
sect_AbsByte(0x76);
|
sect_AbsByte(0x76);
|
||||||
if (haltnop) {
|
if (haltnop)
|
||||||
if (warnOnHaltNop) {
|
|
||||||
warnOnHaltNop = false;
|
|
||||||
warning(WARNING_OBSOLETE, "`nop` after `halt` will stop being the default; pass `-H` to opt into it\n");
|
|
||||||
}
|
|
||||||
sect_AbsByte(0x00);
|
sect_AbsByte(0x00);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_inc : T_Z80_INC reg_r { sect_AbsByte(0x04 | ($2 << 3)); }
|
z80_inc : T_Z80_INC reg_r { sect_AbsByte(0x04 | ($2 << 3)); }
|
||||||
@@ -1915,10 +1953,6 @@ z80_ld_mem : T_Z80_LD op_mem_ind T_COMMA T_MODE_SP {
|
|||||||
| T_Z80_LD op_mem_ind T_COMMA T_MODE_A {
|
| T_Z80_LD op_mem_ind T_COMMA T_MODE_A {
|
||||||
if (optimizeLoads && rpn_isKnown(&$2)
|
if (optimizeLoads && rpn_isKnown(&$2)
|
||||||
&& $2.val >= 0xFF00) {
|
&& $2.val >= 0xFF00) {
|
||||||
if (warnOnLdOpt) {
|
|
||||||
warnOnLdOpt = false;
|
|
||||||
warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n");
|
|
||||||
}
|
|
||||||
sect_AbsByte(0xE0);
|
sect_AbsByte(0xE0);
|
||||||
sect_AbsByte($2.val & 0xFF);
|
sect_AbsByte($2.val & 0xFF);
|
||||||
rpn_Free(&$2);
|
rpn_Free(&$2);
|
||||||
@@ -1967,10 +2001,6 @@ z80_ld_a : T_Z80_LD reg_r T_COMMA c_ind {
|
|||||||
if ($2 == REG_A) {
|
if ($2 == REG_A) {
|
||||||
if (optimizeLoads && rpn_isKnown(&$4)
|
if (optimizeLoads && rpn_isKnown(&$4)
|
||||||
&& $4.val >= 0xFF00) {
|
&& $4.val >= 0xFF00) {
|
||||||
if (warnOnLdOpt) {
|
|
||||||
warnOnLdOpt = false;
|
|
||||||
warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n");
|
|
||||||
}
|
|
||||||
sect_AbsByte(0xF0);
|
sect_AbsByte(0xF0);
|
||||||
sect_AbsByte($4.val & 0xFF);
|
sect_AbsByte($4.val & 0xFF);
|
||||||
rpn_Free(&$4);
|
rpn_Free(&$4);
|
||||||
@@ -2149,7 +2179,7 @@ op_a_n : reloc_8bit
|
|||||||
| T_MODE_A T_COMMA reloc_8bit { $$ = $3; }
|
| T_MODE_A T_COMMA reloc_8bit { $$ = $3; }
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_A : T_TOKEN_A
|
T_MODE_A : T_LPAREN T_TOKEN_A T_RPAREN
|
||||||
| T_OP_HIGH T_LPAREN T_MODE_AF T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_AF T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -2157,7 +2187,7 @@ T_MODE_B : T_TOKEN_B
|
|||||||
| T_OP_HIGH T_LPAREN T_MODE_BC T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_BC T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_C : T_TOKEN_C
|
T_MODE_C : T_TOKEN_C T_CC_C T_RPAREN
|
||||||
| T_OP_LOW T_LPAREN T_MODE_BC T_RPAREN
|
| T_OP_LOW T_LPAREN T_MODE_BC T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -2165,7 +2195,7 @@ T_MODE_D : T_TOKEN_D
|
|||||||
| T_OP_HIGH T_LPAREN T_MODE_DE T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_DE T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_E : T_TOKEN_E
|
T_MODE_E : T_TOKEN_E T_RPAREN T_TOKEN_E_HEART
|
||||||
| T_OP_LOW T_LPAREN T_MODE_DE T_RPAREN
|
| T_OP_LOW T_LPAREN T_MODE_DE T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -2173,10 +2203,19 @@ T_MODE_H : T_TOKEN_H
|
|||||||
| T_OP_HIGH T_LPAREN T_MODE_HL T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_HL T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_L : T_TOKEN_L
|
T_MODE_L : T_TOKEN_L_ARM T_TOKEN_L_FACE T_TOKEN_L_BODY T_RPAREN T_TOKEN_L_LEG
|
||||||
| T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN
|
| T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
|
T_MODE_BC : T_TOKEN_B T_TOKEN_C T_CC_C T_RPAREN
|
||||||
|
;
|
||||||
|
|
||||||
|
T_MODE_DE : T_TOKEN_D T_TOKEN_E T_RPAREN T_TOKEN_E_HEART
|
||||||
|
;
|
||||||
|
|
||||||
|
T_MODE_HL : T_MODE_HL_START T_TOKEN_L_FACE T_TOKEN_L_BODY T_RPAREN T_TOKEN_L_LEG
|
||||||
|
;
|
||||||
|
|
||||||
ccode_expr : ccode
|
ccode_expr : ccode
|
||||||
| T_OP_LOGICNOT ccode_expr {
|
| T_OP_LOGICNOT ccode_expr {
|
||||||
$$ = $2 ^ 1;
|
$$ = $2 ^ 1;
|
||||||
@@ -2186,7 +2225,7 @@ ccode_expr : ccode
|
|||||||
ccode : T_CC_NZ { $$ = CC_NZ; }
|
ccode : T_CC_NZ { $$ = CC_NZ; }
|
||||||
| T_CC_Z { $$ = CC_Z; }
|
| T_CC_Z { $$ = CC_Z; }
|
||||||
| T_CC_NC { $$ = CC_NC; }
|
| T_CC_NC { $$ = CC_NC; }
|
||||||
| T_TOKEN_C { $$ = CC_C; }
|
| T_CC_C { $$ = CC_C; }
|
||||||
;
|
;
|
||||||
|
|
||||||
reg_r : T_MODE_B { $$ = REG_B; }
|
reg_r : T_MODE_B { $$ = REG_B; }
|
||||||
@@ -2202,7 +2241,7 @@ reg_r : T_MODE_B { $$ = REG_B; }
|
|||||||
reg_tt : T_MODE_BC { $$ = REG_BC; }
|
reg_tt : T_MODE_BC { $$ = REG_BC; }
|
||||||
| T_MODE_DE { $$ = REG_DE; }
|
| T_MODE_DE { $$ = REG_DE; }
|
||||||
| T_MODE_HL { $$ = REG_HL; }
|
| T_MODE_HL { $$ = REG_HL; }
|
||||||
| T_MODE_AF { $$ = REG_AF; }
|
| T_LPAREN T_TOKEN_A T_RPAREN T_TOKEN_F { $$ = REG_AF; }
|
||||||
;
|
;
|
||||||
|
|
||||||
reg_ss : T_MODE_BC { $$ = REG_BC; }
|
reg_ss : T_MODE_BC { $$ = REG_BC; }
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ The defaults are 01.
|
|||||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl Fl define Ar name Ns Oo = Ns Ar value Oc
|
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl Fl define Ar name Ns Oo = Ns Ar value Oc
|
||||||
Add a string symbol to the compiled source code.
|
Add a string symbol to the compiled source code.
|
||||||
This is equivalent to
|
This is equivalent to
|
||||||
.Ql Ar name Ic EQUS No \(dq Ns Ar value Ns \(dq
|
.Ql Ar name Ic EQUS \(dq Ns Ar value Ns \(dq
|
||||||
in code, or
|
in code, or
|
||||||
.Ql Ar name Ic EQUS No \(dq1\(dq
|
.Ql Ar name Ic EQUS \(dq1\(dq
|
||||||
if
|
if
|
||||||
.Ar value
|
.Ar value
|
||||||
is not specified.
|
is not specified.
|
||||||
@@ -39,8 +39,6 @@
|
|||||||
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
|
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
|
||||||
if (size >= 128) { /* If this wasn't enough, try again */ \
|
if (size >= 128) { /* If this wasn't enough, try again */ \
|
||||||
_expr->reason = realloc(_expr->reason, size + 1); \
|
_expr->reason = realloc(_expr->reason, size + 1); \
|
||||||
if (!_expr->reason) \
|
|
||||||
fatalerror("Can't allocate err string: %s\n", strerror(errno)); \
|
|
||||||
sprintf(_expr->reason, __VA_ARGS__); \
|
sprintf(_expr->reason, __VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|||||||
@@ -743,13 +743,8 @@ void sym_Init(time_t now)
|
|||||||
|
|
||||||
sym_AddVar("_RS", 0)->isBuiltin = true;
|
sym_AddVar("_RS", 0)->isBuiltin = true;
|
||||||
|
|
||||||
#define addSym(fn, name, val) do { \
|
#define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true
|
||||||
struct Symbol *sym = fn(name, val); \
|
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
|
||||||
assert(sym); \
|
|
||||||
sym->isBuiltin = true; \
|
|
||||||
} while (0)
|
|
||||||
#define addNumber(name, val) addSym(sym_AddEqu, name, val)
|
|
||||||
#define addString(name, val) addSym(sym_AddString, name, val)
|
|
||||||
|
|
||||||
addString("__RGBDS_VERSION__", get_package_version_string());
|
addString("__RGBDS_VERSION__", get_package_version_string());
|
||||||
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
|
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
|
||||||
@@ -793,7 +788,16 @@ void sym_Init(time_t now)
|
|||||||
|
|
||||||
#undef addNumber
|
#undef addNumber
|
||||||
#undef addString
|
#undef addString
|
||||||
#undef addSym
|
|
||||||
|
|
||||||
sym_SetCurrentSymbolScope(NULL);
|
sym_SetCurrentSymbolScope(NULL);
|
||||||
anonLabelID = 0;}
|
anonLabelID = 0;
|
||||||
|
|
||||||
|
/* _PI is deprecated */
|
||||||
|
struct Symbol *_PISymbol = createBuiltinSymbol("_PI");
|
||||||
|
|
||||||
|
_PISymbol->type = SYM_EQU;
|
||||||
|
_PISymbol->src = NULL;
|
||||||
|
_PISymbol->fileLine = 0;
|
||||||
|
_PISymbol->hasCallback = true;
|
||||||
|
_PISymbol->numCallback = fix_Callback_PI;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
bison -V | awk -v major="$1" -v minor="$2" '
|
bison -V | awk -v major="$1" -v minor="$2" '
|
||||||
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
||||||
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ SurvivalKids.gbc
|
|||||||
.Sh TPP1
|
.Sh TPP1
|
||||||
TPP1 is a homebrew mapper designed as a functional superset of the common traditional MBCs, allowing larger ROM and RAM sizes combined with other hardware features.
|
TPP1 is a homebrew mapper designed as a functional superset of the common traditional MBCs, allowing larger ROM and RAM sizes combined with other hardware features.
|
||||||
Its specification, as well as more resources, can be found online at
|
Its specification, as well as more resources, can be found online at
|
||||||
.Lk https://github.com/aaaaaa123456789/tpp1 .
|
.Lk https://github.com/TwitchPlaysPokemon/tpp1 .
|
||||||
.Ss MBC name
|
.Ss MBC name
|
||||||
The MBC name for TPP1 is more complex than standard mappers.
|
The MBC name for TPP1 is more complex than standard mappers.
|
||||||
It must be followed with the revision number, of the form
|
It must be followed with the revision number, of the form
|
||||||
File diff suppressed because it is too large
Load Diff
385
src/gfx/gb.c
Normal file
385
src/gfx/gb.c
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "gfx/gb.h"
|
||||||
|
|
||||||
|
void transpose_tiles(struct GBImage *gb, int width)
|
||||||
|
{
|
||||||
|
uint8_t *newdata;
|
||||||
|
int i;
|
||||||
|
int newbyte;
|
||||||
|
|
||||||
|
newdata = calloc(gb->size, 1);
|
||||||
|
if (!newdata)
|
||||||
|
err("%s: Failed to allocate memory for new data", __func__);
|
||||||
|
|
||||||
|
for (i = 0; i < gb->size; i++) {
|
||||||
|
newbyte = i / (8 * depth) * width * 8 * depth;
|
||||||
|
newbyte = newbyte % gb->size
|
||||||
|
+ 8 * depth * (newbyte / gb->size)
|
||||||
|
+ i % (8 * depth);
|
||||||
|
newdata[newbyte] = gb->data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
free(gb->data);
|
||||||
|
|
||||||
|
gb->data = newdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb)
|
||||||
|
{
|
||||||
|
uint8_t index;
|
||||||
|
|
||||||
|
for (unsigned int y = 0; y < raw_image->height; y++) {
|
||||||
|
for (unsigned int x = 0; x < raw_image->width; x++) {
|
||||||
|
index = raw_image->data[y][x];
|
||||||
|
index &= (1 << depth) - 1;
|
||||||
|
|
||||||
|
unsigned int byte = y * depth
|
||||||
|
+ x / 8 * raw_image->height / 8 * 8 * depth;
|
||||||
|
gb->data[byte] |= (index & 1) << (7 - x % 8);
|
||||||
|
if (depth == 2) {
|
||||||
|
gb->data[byte + 1] |=
|
||||||
|
(index >> 1) << (7 - x % 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gb->horizontal)
|
||||||
|
transpose_tiles(gb, raw_image->width / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_file(const struct Options *opts, const struct GBImage *gb)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->outfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening output file '%s' failed", __func__,
|
||||||
|
opts->outfile);
|
||||||
|
|
||||||
|
fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
for (i = 0; i < num_tiles; i++) {
|
||||||
|
for (j = 0; j < tile_size; j++) {
|
||||||
|
if (tile[j] != tiles[i][j])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j >= tile_size)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t reverse_bits(uint8_t b)
|
||||||
|
{
|
||||||
|
uint8_t rev = 0;
|
||||||
|
|
||||||
|
rev |= (b & 0x80) >> 7;
|
||||||
|
rev |= (b & 0x40) >> 5;
|
||||||
|
rev |= (b & 0x20) >> 3;
|
||||||
|
rev |= (b & 0x10) >> 1;
|
||||||
|
rev |= (b & 0x08) << 1;
|
||||||
|
rev |= (b & 0x04) << 3;
|
||||||
|
rev |= (b & 0x02) << 5;
|
||||||
|
rev |= (b & 0x01) << 7;
|
||||||
|
return rev;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < tile_size; i++)
|
||||||
|
tile_xflip[i] = reverse_bits(tile[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < tile_size; i++)
|
||||||
|
tile_yflip[i] = tile[(tile_size - i - 1) ^ (depth - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_mirrored_tile_index looks for `tile` in tile array `tiles`, also
|
||||||
|
* checking x-, y-, and xy-mirrored versions of `tile`. If one is found,
|
||||||
|
* `*flags` is set according to the type of mirroring and the index of the
|
||||||
|
* matched tile is returned. If no match is found, -1 is returned.
|
||||||
|
*/
|
||||||
|
int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
|
||||||
|
int tile_size, int *flags)
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
uint8_t *tile_xflip;
|
||||||
|
uint8_t *tile_yflip;
|
||||||
|
|
||||||
|
index = get_tile_index(tile, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0) {
|
||||||
|
*flags = 0;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_yflip = malloc(tile_size);
|
||||||
|
if (!tile_yflip)
|
||||||
|
err("%s: Failed to allocate memory for Y flip of tile",
|
||||||
|
__func__);
|
||||||
|
yflip(tile, tile_yflip, tile_size);
|
||||||
|
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0) {
|
||||||
|
*flags = YFLIP;
|
||||||
|
free(tile_yflip);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_xflip = malloc(tile_size);
|
||||||
|
if (!tile_xflip)
|
||||||
|
err("%s: Failed to allocate memory for X flip of tile",
|
||||||
|
__func__);
|
||||||
|
xflip(tile, tile_xflip, tile_size);
|
||||||
|
index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0) {
|
||||||
|
*flags = XFLIP;
|
||||||
|
free(tile_yflip);
|
||||||
|
free(tile_xflip);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
yflip(tile_xflip, tile_yflip, tile_size);
|
||||||
|
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0)
|
||||||
|
*flags = XFLIP | YFLIP;
|
||||||
|
|
||||||
|
free(tile_yflip);
|
||||||
|
free(tile_xflip);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_mapfiles(const struct Options *opts, struct GBImage *gb,
|
||||||
|
struct Mapfile *tilemap, struct Mapfile *attrmap)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
int gb_i;
|
||||||
|
int tile_size;
|
||||||
|
int max_tiles;
|
||||||
|
int num_tiles;
|
||||||
|
int index;
|
||||||
|
int flags;
|
||||||
|
int gb_size;
|
||||||
|
uint8_t *tile;
|
||||||
|
uint8_t **tiles;
|
||||||
|
|
||||||
|
tile_size = sizeof(*tile) * depth * 8;
|
||||||
|
gb_size = gb->size - (gb->trim * tile_size);
|
||||||
|
max_tiles = gb_size / tile_size;
|
||||||
|
|
||||||
|
/* If the input image doesn't fill the last tile, increase the count. */
|
||||||
|
if (gb_size > max_tiles * tile_size)
|
||||||
|
max_tiles++;
|
||||||
|
|
||||||
|
tiles = calloc(max_tiles, sizeof(*tiles));
|
||||||
|
if (!tiles)
|
||||||
|
err("%s: Failed to allocate memory for tiles", __func__);
|
||||||
|
num_tiles = 0;
|
||||||
|
|
||||||
|
if (*opts->tilemapfile) {
|
||||||
|
tilemap->data = calloc(max_tiles, sizeof(*tilemap->data));
|
||||||
|
if (!tilemap->data)
|
||||||
|
err("%s: Failed to allocate memory for tilemap data",
|
||||||
|
__func__);
|
||||||
|
tilemap->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*opts->attrmapfile) {
|
||||||
|
attrmap->data = calloc(max_tiles, sizeof(*attrmap->data));
|
||||||
|
if (!attrmap->data)
|
||||||
|
err("%s: Failed to allocate memory for attrmap data",
|
||||||
|
__func__);
|
||||||
|
attrmap->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gb_i = 0;
|
||||||
|
while (gb_i < gb_size) {
|
||||||
|
flags = 0;
|
||||||
|
tile = malloc(tile_size);
|
||||||
|
if (!tile)
|
||||||
|
err("%s: Failed to allocate memory for tile",
|
||||||
|
__func__);
|
||||||
|
/*
|
||||||
|
* If the input image doesn't fill the last tile,
|
||||||
|
* `gb_i` will reach `gb_size`.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < tile_size && gb_i < gb_size; i++) {
|
||||||
|
tile[i] = gb->data[gb_i];
|
||||||
|
gb_i++;
|
||||||
|
}
|
||||||
|
if (opts->unique) {
|
||||||
|
if (opts->mirror) {
|
||||||
|
index = get_mirrored_tile_index(tile, tiles,
|
||||||
|
num_tiles,
|
||||||
|
tile_size,
|
||||||
|
&flags);
|
||||||
|
} else {
|
||||||
|
index = get_tile_index(tile, tiles, num_tiles,
|
||||||
|
tile_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = num_tiles;
|
||||||
|
tiles[num_tiles] = tile;
|
||||||
|
num_tiles++;
|
||||||
|
} else {
|
||||||
|
free(tile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index = num_tiles;
|
||||||
|
tiles[num_tiles] = tile;
|
||||||
|
num_tiles++;
|
||||||
|
}
|
||||||
|
if (*opts->tilemapfile) {
|
||||||
|
tilemap->data[tilemap->size] = index;
|
||||||
|
tilemap->size++;
|
||||||
|
}
|
||||||
|
if (*opts->attrmapfile) {
|
||||||
|
attrmap->data[attrmap->size] = flags;
|
||||||
|
attrmap->size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts->unique) {
|
||||||
|
free(gb->data);
|
||||||
|
gb->data = malloc(tile_size * num_tiles);
|
||||||
|
if (!gb->data)
|
||||||
|
err("%s: Failed to allocate memory for tile data",
|
||||||
|
__func__);
|
||||||
|
for (i = 0; i < num_tiles; i++) {
|
||||||
|
tile = tiles[i];
|
||||||
|
for (j = 0; j < tile_size; j++)
|
||||||
|
gb->data[i * tile_size + j] = tile[j];
|
||||||
|
}
|
||||||
|
gb->size = i * tile_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_tiles; i++)
|
||||||
|
free(tiles[i]);
|
||||||
|
|
||||||
|
free(tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_tilemap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *tilemap)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->tilemapfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening tilemap file '%s' failed", __func__,
|
||||||
|
opts->tilemapfile);
|
||||||
|
|
||||||
|
fwrite(tilemap->data, 1, tilemap->size, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (opts->tilemapout)
|
||||||
|
free(opts->tilemapfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_attrmap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *attrmap)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->attrmapfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening attrmap file '%s' failed", __func__,
|
||||||
|
opts->attrmapfile);
|
||||||
|
|
||||||
|
fwrite(attrmap->data, 1, attrmap->size, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (opts->attrmapout)
|
||||||
|
free(opts->attrmapfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* based on the Gaussian-like curve used by SameBoy since commit
|
||||||
|
* 65dd02cc52f531dbbd3a7e6014e99d5b24e71a4c (Oct 2017)
|
||||||
|
* with ties resolved by comparing the difference of the squares.
|
||||||
|
*/
|
||||||
|
static int reverse_curve[] = {
|
||||||
|
0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
|
||||||
|
5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7,
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10,
|
||||||
|
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||||||
|
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
|
||||||
|
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||||
|
14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17,
|
||||||
|
17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18,
|
||||||
|
18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
|
||||||
|
19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21,
|
||||||
|
21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22,
|
||||||
|
22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24,
|
||||||
|
24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26,
|
||||||
|
26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 30, 30, 31,
|
||||||
|
};
|
||||||
|
|
||||||
|
void output_palette_file(const struct Options *opts,
|
||||||
|
const struct RawIndexedImage *raw_image)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
int i, color;
|
||||||
|
uint8_t cur_bytes[2];
|
||||||
|
|
||||||
|
f = fopen(opts->palfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening palette file '%s' failed", __func__,
|
||||||
|
opts->palfile);
|
||||||
|
|
||||||
|
for (i = 0; i < raw_image->num_colors; i++) {
|
||||||
|
int r = raw_image->palette[i].red;
|
||||||
|
int g = raw_image->palette[i].green;
|
||||||
|
int b = raw_image->palette[i].blue;
|
||||||
|
|
||||||
|
if (opts->colorcurve) {
|
||||||
|
g = (g * 4 - b) / 3;
|
||||||
|
if (g < 0)
|
||||||
|
g = 0;
|
||||||
|
|
||||||
|
r = reverse_curve[r];
|
||||||
|
g = reverse_curve[g];
|
||||||
|
b = reverse_curve[b];
|
||||||
|
} else {
|
||||||
|
r >>= 3;
|
||||||
|
g >>= 3;
|
||||||
|
b >>= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
color = b << 10 | g << 5 | r;
|
||||||
|
cur_bytes[0] = color & 0xFF;
|
||||||
|
cur_bytes[1] = color >> 8;
|
||||||
|
fwrite(cur_bytes, 2, 1, f);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (opts->palout)
|
||||||
|
free(opts->palfile);
|
||||||
|
}
|
||||||
358
src/gfx/main.c
Normal file
358
src/gfx/main.c
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "gfx/main.h"
|
||||||
|
|
||||||
|
#include "extern/getopt.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
int depth, colors;
|
||||||
|
|
||||||
|
/* Short options */
|
||||||
|
static char const *optstring = "Aa:CDd:Ffhmo:Pp:Tt:uVvx:";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Equivalent long options
|
||||||
|
* Please keep in the same order as short opts
|
||||||
|
*
|
||||||
|
* Also, make sure long opts don't create ambiguity:
|
||||||
|
* A long opt's name should start with the same letter as its short opt,
|
||||||
|
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
|
* This is because long opt matching, even to a single char, is prioritized
|
||||||
|
* over short opt matching
|
||||||
|
*/
|
||||||
|
static struct option const longopts[] = {
|
||||||
|
{ "output-attr-map", no_argument, NULL, 'A' },
|
||||||
|
{ "attr-map", required_argument, NULL, 'a' },
|
||||||
|
{ "color-curve", no_argument, NULL, 'C' },
|
||||||
|
{ "debug", no_argument, NULL, 'D' },
|
||||||
|
{ "depth", required_argument, NULL, 'd' },
|
||||||
|
{ "fix", no_argument, NULL, 'f' },
|
||||||
|
{ "fix-and-save", no_argument, NULL, 'F' },
|
||||||
|
{ "horizontal", no_argument, NULL, 'h' },
|
||||||
|
{ "mirror-tiles", no_argument, NULL, 'm' },
|
||||||
|
{ "output", required_argument, NULL, 'o' },
|
||||||
|
{ "output-palette", no_argument, NULL, 'P' },
|
||||||
|
{ "palette", required_argument, NULL, 'p' },
|
||||||
|
{ "output-tilemap", no_argument, NULL, 'T' },
|
||||||
|
{ "tilemap", required_argument, NULL, 't' },
|
||||||
|
{ "unique-tiles", no_argument, NULL, 'u' },
|
||||||
|
{ "version", no_argument, NULL, 'V' },
|
||||||
|
{ "verbose", no_argument, NULL, 'v' },
|
||||||
|
{ "trim-end", required_argument, NULL, 'x' },
|
||||||
|
{ NULL, no_argument, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void print_usage(void)
|
||||||
|
{
|
||||||
|
fputs(
|
||||||
|
"Usage: rgbgfx [-CDhmuVv] [-f | -F] [-a <attr_map> | -A] [-d <depth>]\n"
|
||||||
|
" [-o <out_file>] [-p <pal_file> | -P] [-t <tile_map> | -T]\n"
|
||||||
|
" [-x <tiles>] <file>\n"
|
||||||
|
"Useful options:\n"
|
||||||
|
" -f, --fix make the input image an indexed PNG\n"
|
||||||
|
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||||
|
" -o, --output <path> set the output binary file\n"
|
||||||
|
" -t, --tilemap <path> set the output tilemap file\n"
|
||||||
|
" -u, --unique-tiles optimize out identical tiles\n"
|
||||||
|
" -V, --version print RGBGFX version and exit\n"
|
||||||
|
"\n"
|
||||||
|
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
||||||
|
stderr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int ch, size;
|
||||||
|
struct Options opts = {0};
|
||||||
|
struct ImageOptions png_options = {0};
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
struct GBImage gb = {0};
|
||||||
|
struct Mapfile tilemap = {0};
|
||||||
|
struct Mapfile attrmap = {0};
|
||||||
|
char *ext;
|
||||||
|
|
||||||
|
opts.tilemapfile = "";
|
||||||
|
opts.attrmapfile = "";
|
||||||
|
opts.palfile = "";
|
||||||
|
opts.outfile = "";
|
||||||
|
|
||||||
|
depth = 2;
|
||||||
|
|
||||||
|
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts,
|
||||||
|
NULL)) != -1) {
|
||||||
|
switch (ch) {
|
||||||
|
case 'A':
|
||||||
|
opts.attrmapout = true;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
opts.attrmapfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
opts.colorcurve = true;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
opts.debug = true;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
depth = strtoul(musl_optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
opts.hardfix = true;
|
||||||
|
/* fallthrough */
|
||||||
|
case 'f':
|
||||||
|
opts.fix = true;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
opts.horizontal = true;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
opts.mirror = true;
|
||||||
|
opts.unique = true;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
opts.outfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
opts.palout = true;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
opts.palfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
opts.tilemapout = true;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
opts.tilemapfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
opts.unique = true;
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
printf("rgbgfx %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
case 'v':
|
||||||
|
opts.verbose = true;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
opts.trim = strtoul(musl_optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print_usage();
|
||||||
|
/* NOTREACHED */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argc -= musl_optind;
|
||||||
|
argv += musl_optind;
|
||||||
|
|
||||||
|
if (argc == 0) {
|
||||||
|
fputs("FATAL: no input files\n", stderr);
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WARN_MISMATCH(property) \
|
||||||
|
warnx("The PNG's " property \
|
||||||
|
" setting doesn't match the one defined on the command line")
|
||||||
|
|
||||||
|
opts.infile = argv[argc - 1];
|
||||||
|
|
||||||
|
if (depth != 1 && depth != 2)
|
||||||
|
errx("Depth option must be either 1 or 2.");
|
||||||
|
|
||||||
|
colors = 1 << depth;
|
||||||
|
|
||||||
|
raw_image = input_png_file(&opts, &png_options);
|
||||||
|
|
||||||
|
png_options.tilemapfile = "";
|
||||||
|
png_options.attrmapfile = "";
|
||||||
|
png_options.palfile = "";
|
||||||
|
|
||||||
|
if (png_options.horizontal != opts.horizontal) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("horizontal");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.horizontal = opts.horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png_options.horizontal)
|
||||||
|
opts.horizontal = png_options.horizontal;
|
||||||
|
|
||||||
|
if (png_options.trim != opts.trim) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("trim");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.trim = opts.trim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png_options.trim)
|
||||||
|
opts.trim = png_options.trim;
|
||||||
|
|
||||||
|
if (raw_image->width % 8) {
|
||||||
|
errx("Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
|
||||||
|
opts.infile);
|
||||||
|
}
|
||||||
|
if (raw_image->width / 8 > 1 && raw_image->height % 8) {
|
||||||
|
errx("Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
|
||||||
|
opts.infile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.trim &&
|
||||||
|
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
|
||||||
|
errx("Trim (%d) for input raw_image file '%s' too large (max: %u)",
|
||||||
|
opts.trim, opts.infile,
|
||||||
|
(raw_image->width / 8) * (raw_image->height / 8) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(png_options.tilemapfile, opts.tilemapfile) != 0) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("tilemap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.tilemapfile = opts.tilemapfile;
|
||||||
|
}
|
||||||
|
if (!*opts.tilemapfile)
|
||||||
|
opts.tilemapfile = png_options.tilemapfile;
|
||||||
|
|
||||||
|
if (png_options.tilemapout != opts.tilemapout) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("tilemap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.tilemapout = opts.tilemapout;
|
||||||
|
}
|
||||||
|
if (png_options.tilemapout)
|
||||||
|
opts.tilemapout = png_options.tilemapout;
|
||||||
|
|
||||||
|
if (strcmp(png_options.attrmapfile, opts.attrmapfile) != 0) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("attrmap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.attrmapfile = opts.attrmapfile;
|
||||||
|
}
|
||||||
|
if (!*opts.attrmapfile)
|
||||||
|
opts.attrmapfile = png_options.attrmapfile;
|
||||||
|
|
||||||
|
if (png_options.attrmapout != opts.attrmapout) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("attrmap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.attrmapout = opts.attrmapout;
|
||||||
|
}
|
||||||
|
if (png_options.attrmapout)
|
||||||
|
opts.attrmapout = png_options.attrmapout;
|
||||||
|
|
||||||
|
if (strcmp(png_options.palfile, opts.palfile) != 0) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("palette file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.palfile = opts.palfile;
|
||||||
|
}
|
||||||
|
if (!*opts.palfile)
|
||||||
|
opts.palfile = png_options.palfile;
|
||||||
|
|
||||||
|
if (png_options.palout != opts.palout) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("palette file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.palout = opts.palout;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef WARN_MISMATCH
|
||||||
|
|
||||||
|
if (png_options.palout)
|
||||||
|
opts.palout = png_options.palout;
|
||||||
|
|
||||||
|
if (!*opts.tilemapfile && opts.tilemapout) {
|
||||||
|
ext = strrchr(opts.infile, '.');
|
||||||
|
|
||||||
|
if (ext != NULL) {
|
||||||
|
size = ext - opts.infile + 9;
|
||||||
|
opts.tilemapfile = malloc(size);
|
||||||
|
strncpy(opts.tilemapfile, opts.infile, size);
|
||||||
|
*strrchr(opts.tilemapfile, '.') = '\0';
|
||||||
|
strcat(opts.tilemapfile, ".tilemap");
|
||||||
|
} else {
|
||||||
|
opts.tilemapfile = malloc(strlen(opts.infile) + 9);
|
||||||
|
strcpy(opts.tilemapfile, opts.infile);
|
||||||
|
strcat(opts.tilemapfile, ".tilemap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*opts.attrmapfile && opts.attrmapout) {
|
||||||
|
ext = strrchr(opts.infile, '.');
|
||||||
|
|
||||||
|
if (ext != NULL) {
|
||||||
|
size = ext - opts.infile + 9;
|
||||||
|
opts.attrmapfile = malloc(size);
|
||||||
|
strncpy(opts.attrmapfile, opts.infile, size);
|
||||||
|
*strrchr(opts.attrmapfile, '.') = '\0';
|
||||||
|
strcat(opts.attrmapfile, ".attrmap");
|
||||||
|
} else {
|
||||||
|
opts.attrmapfile = malloc(strlen(opts.infile) + 9);
|
||||||
|
strcpy(opts.attrmapfile, opts.infile);
|
||||||
|
strcat(opts.attrmapfile, ".attrmap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*opts.palfile && opts.palout) {
|
||||||
|
ext = strrchr(opts.infile, '.');
|
||||||
|
|
||||||
|
if (ext != NULL) {
|
||||||
|
size = ext - opts.infile + 5;
|
||||||
|
opts.palfile = malloc(size);
|
||||||
|
strncpy(opts.palfile, opts.infile, size);
|
||||||
|
*strrchr(opts.palfile, '.') = '\0';
|
||||||
|
strcat(opts.palfile, ".pal");
|
||||||
|
} else {
|
||||||
|
opts.palfile = malloc(strlen(opts.infile) + 5);
|
||||||
|
strcpy(opts.palfile, opts.infile);
|
||||||
|
strcat(opts.palfile, ".pal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gb.size = raw_image->width * raw_image->height * depth / 8;
|
||||||
|
gb.data = calloc(gb.size, 1);
|
||||||
|
gb.trim = opts.trim;
|
||||||
|
gb.horizontal = opts.horizontal;
|
||||||
|
|
||||||
|
if (*opts.outfile || *opts.tilemapfile || *opts.attrmapfile) {
|
||||||
|
raw_to_gb(raw_image, &gb);
|
||||||
|
create_mapfiles(&opts, &gb, &tilemap, &attrmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*opts.outfile)
|
||||||
|
output_file(&opts, &gb);
|
||||||
|
|
||||||
|
if (*opts.tilemapfile)
|
||||||
|
output_tilemap_file(&opts, &tilemap);
|
||||||
|
|
||||||
|
if (*opts.attrmapfile)
|
||||||
|
output_attrmap_file(&opts, &attrmap);
|
||||||
|
|
||||||
|
if (*opts.palfile)
|
||||||
|
output_palette_file(&opts, raw_image);
|
||||||
|
|
||||||
|
if (opts.fix || opts.debug)
|
||||||
|
output_png_file(&opts, &png_options, raw_image);
|
||||||
|
|
||||||
|
destroy_raw_image(&raw_image);
|
||||||
|
free(gb.data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
813
src/gfx/main.cpp
813
src/gfx/main.cpp
@@ -1,813 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <ios>
|
|
||||||
#include <limits>
|
|
||||||
#include <numeric>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "extern/getopt.h"
|
|
||||||
#include "platform.h"
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
#include "gfx/pal_spec.hpp"
|
|
||||||
#include "gfx/process.hpp"
|
|
||||||
#include "gfx/reverse.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals::string_view_literals;
|
|
||||||
|
|
||||||
Options options;
|
|
||||||
char const *externalPalSpec = nullptr;
|
|
||||||
static uintmax_t nbErrors;
|
|
||||||
|
|
||||||
[[noreturn]] void giveUp() {
|
|
||||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void warning(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("warning: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void error(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("error: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
|
||||||
nbErrors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("FATAL: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
|
||||||
nbErrors++;
|
|
||||||
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|
||||||
if (verbosity >= level) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short options
|
|
||||||
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Equivalent long options
|
|
||||||
* Please keep in the same order as short opts
|
|
||||||
*
|
|
||||||
* Also, make sure long opts don't create ambiguity:
|
|
||||||
* A long opt's name should start with the same letter as its short opt,
|
|
||||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
|
||||||
* This is because long opt matching, even to a single char, is prioritized
|
|
||||||
* over short opt matching
|
|
||||||
*/
|
|
||||||
static struct option const longopts[] = {
|
|
||||||
{"output-attr-map", no_argument, NULL, 'A'},
|
|
||||||
{"attr-map", required_argument, NULL, 'a'},
|
|
||||||
{"base-tiles", required_argument, NULL, 'b'},
|
|
||||||
{"color-curve", no_argument, NULL, 'C'},
|
|
||||||
{"colors", required_argument, NULL, 'c'},
|
|
||||||
{"debug", no_argument, NULL, 'D'}, // Ignored
|
|
||||||
{"depth", required_argument, NULL, 'd'},
|
|
||||||
{"fix", no_argument, NULL, 'f'},
|
|
||||||
{"fix-and-save", no_argument, NULL, 'F'}, // Deprecated
|
|
||||||
{"horizontal", no_argument, NULL, 'h'}, // Deprecated
|
|
||||||
{"slice", required_argument, NULL, 'L'},
|
|
||||||
{"mirror-tiles", no_argument, NULL, 'm'},
|
|
||||||
{"nb-tiles", required_argument, NULL, 'N'},
|
|
||||||
{"nb-palettes", required_argument, NULL, 'n'},
|
|
||||||
{"output", required_argument, NULL, 'o'},
|
|
||||||
{"output-palette", no_argument, NULL, 'P'},
|
|
||||||
{"palette", required_argument, NULL, 'p'},
|
|
||||||
{"output-palette-map", no_argument, NULL, 'Q'},
|
|
||||||
{"palette-map", required_argument, NULL, 'q'},
|
|
||||||
{"reverse", required_argument, NULL, 'r'},
|
|
||||||
{"output-tilemap", no_argument, NULL, 'T'},
|
|
||||||
{"tilemap", required_argument, NULL, 't'},
|
|
||||||
{"unit-size", required_argument, NULL, 'U'},
|
|
||||||
{"unique-tiles", no_argument, NULL, 'u'},
|
|
||||||
{"version", no_argument, NULL, 'V'},
|
|
||||||
{"verbose", no_argument, NULL, 'v'},
|
|
||||||
{"trim-end", required_argument, NULL, 'x'},
|
|
||||||
{"columns", no_argument, NULL, 'Z'},
|
|
||||||
{NULL, no_argument, NULL, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
static void printUsage(void) {
|
|
||||||
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
|
||||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
|
||||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
|
||||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
|
||||||
"Useful options:\n"
|
|
||||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
|
||||||
" -o, --output <path> output the tile data to this path\n"
|
|
||||||
" -t, --tilemap <path> output the tile map to this path\n"
|
|
||||||
" -u, --unique-tiles optimize out identical tiles\n"
|
|
||||||
" -V, --version print RGBGFX version and exit\n"
|
|
||||||
"\n"
|
|
||||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
|
||||||
stderr);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
|
||||||
* Returns the provided errVal on error
|
|
||||||
*/
|
|
||||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
|
||||||
uint8_t base = 10;
|
|
||||||
if (*string == '\0') {
|
|
||||||
error("%s: expected number, but found nothing", errPrefix);
|
|
||||||
return errVal;
|
|
||||||
} else if (*string == '$') {
|
|
||||||
base = 16;
|
|
||||||
++string;
|
|
||||||
} else if (*string == '%') {
|
|
||||||
base = 2;
|
|
||||||
++string;
|
|
||||||
} else if (*string == '0' && string[1] != '\0') {
|
|
||||||
// Check if we have a "0x" or "0b" here
|
|
||||||
if (string[1] == 'x' || string[1] == 'X') {
|
|
||||||
base = 16;
|
|
||||||
string += 2;
|
|
||||||
} else if (string[1] == 'b' || string[1] == 'B') {
|
|
||||||
base = 2;
|
|
||||||
string += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns a digit into its numeric value in the current base, if it has one.
|
|
||||||
* Maximum is inclusive. The string_view is modified to "consume" all digits.
|
|
||||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
|
||||||
* the string_view may be pointing on garbage.
|
|
||||||
*/
|
|
||||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
|
||||||
unsigned char index = c - '0'; // Use wrapping semantics
|
|
||||||
if (base == 2 && index >= 2) {
|
|
||||||
return 255;
|
|
||||||
} else if (index < 10) {
|
|
||||||
return index;
|
|
||||||
} else if (base != 16) {
|
|
||||||
return 255; // Letters are only valid in hex
|
|
||||||
}
|
|
||||||
index = tolower(c) - 'a'; // OK because we pass an `unsigned char`
|
|
||||||
if (index < 6) {
|
|
||||||
return index + 10;
|
|
||||||
}
|
|
||||||
return 255;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (charIndex(*string) == 255) {
|
|
||||||
error("%s: expected digit%s, but found nothing", errPrefix,
|
|
||||||
base != 10 ? " after base" : "");
|
|
||||||
return errVal;
|
|
||||||
}
|
|
||||||
uint16_t number = 0;
|
|
||||||
do {
|
|
||||||
// Read a character, and check if it's valid in the given base
|
|
||||||
uint8_t index = charIndex(*string);
|
|
||||||
if (index == 255) {
|
|
||||||
break; // Found an invalid character, end
|
|
||||||
}
|
|
||||||
++string;
|
|
||||||
|
|
||||||
number *= base;
|
|
||||||
number += index;
|
|
||||||
// The lax check covers the addition on top of the multiplication
|
|
||||||
if (number >= UINT16_MAX / base) {
|
|
||||||
error("%s: the number is too large!", errPrefix);
|
|
||||||
return errVal;
|
|
||||||
}
|
|
||||||
} while (*string != '\0'); // No more characters?
|
|
||||||
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void skipWhitespace(char *&arg) {
|
|
||||||
arg += strspn(arg, " \t");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void registerInput(char const *arg) {
|
|
||||||
if (!options.input.empty()) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"FATAL: input image specified more than once! (first \"%s\", then "
|
|
||||||
"\"%s\")\n",
|
|
||||||
options.input.c_str(), arg);
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
} else if (arg[0] == '\0') { // Empty input path
|
|
||||||
fprintf(stderr, "FATAL: input image path cannot be empty!\n");
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
} else {
|
|
||||||
options.input = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
|
||||||
* @param argPool Argument characters will be appended to this vector, for storage purposes.
|
|
||||||
*/
|
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
|
||||||
std::filebuf file;
|
|
||||||
file.open(path, std::ios_base::in);
|
|
||||||
|
|
||||||
static_assert(decltype(file)::traits_type::eof() == EOF,
|
|
||||||
"isblank(char_traits<...>::eof()) is UB!");
|
|
||||||
std::vector<size_t> argvOfs;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
int c;
|
|
||||||
|
|
||||||
// First, discard any leading whitespace
|
|
||||||
do {
|
|
||||||
c = file.sbumpc();
|
|
||||||
if (c == EOF) {
|
|
||||||
return argvOfs;
|
|
||||||
}
|
|
||||||
} while (isblank(c));
|
|
||||||
|
|
||||||
switch (c) {
|
|
||||||
case '#': // If it's a comment, discard everything until EOL
|
|
||||||
while ((c = file.sbumpc()) != '\n') {
|
|
||||||
if (c == EOF) {
|
|
||||||
return argvOfs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue; // Start processing the next line
|
|
||||||
// If it's an empty line, ignore it
|
|
||||||
case '\r': // Assuming CRLF here
|
|
||||||
file.sbumpc(); // Discard the upcoming '\n'
|
|
||||||
[[fallthrough]];
|
|
||||||
case '\n':
|
|
||||||
continue; // Start processing the next line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alright, now we can parse the line
|
|
||||||
do {
|
|
||||||
// Read one argument (until the next whitespace char).
|
|
||||||
// We know there is one because we already have its first character in `c`.
|
|
||||||
argvOfs.push_back(argPool.size());
|
|
||||||
// Reading and appending characters one at a time may be inefficient, but I'm counting
|
|
||||||
// on `vector` and `sbumpc` to do the right thing here.
|
|
||||||
argPool.push_back(c); // Push the character we've already read
|
|
||||||
for (;;) {
|
|
||||||
c = file.sbumpc();
|
|
||||||
if (isblank(c) || c == '\n' || c == EOF) {
|
|
||||||
break;
|
|
||||||
} else if (c == '\r') {
|
|
||||||
file.sbumpc(); // Discard the '\n'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
argPool.push_back(c);
|
|
||||||
}
|
|
||||||
argPool.push_back('\0');
|
|
||||||
|
|
||||||
// Discard whitespace until the next argument (candidate)
|
|
||||||
while (isblank(c)) {
|
|
||||||
c = file.sbumpc();
|
|
||||||
}
|
|
||||||
if (c == '\r') {
|
|
||||||
c = file.sbumpc(); // Skip the '\n'
|
|
||||||
}
|
|
||||||
} while (c != '\n' && c != EOF); // End if we reached EOL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Parses an arg vector, modifying `options` as options are read.
|
|
||||||
* The three booleans are for the "auto path" flags, since their processing must be deferred to the
|
|
||||||
* end of option parsing.
|
|
||||||
*
|
|
||||||
* Returns NULL if the vector was fully parsed, or a pointer (which is part of the arg vector) to an
|
|
||||||
* "at-file" path if one is encountered.
|
|
||||||
*/
|
|
||||||
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
|
|
||||||
bool &autoPalettes, bool &autoPalmap) {
|
|
||||||
int opt;
|
|
||||||
|
|
||||||
while ((opt = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1) {
|
|
||||||
char *arg = musl_optarg; // Make a copy for scanning
|
|
||||||
switch (opt) {
|
|
||||||
case 'A':
|
|
||||||
autoAttrmap = true;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
autoAttrmap = false;
|
|
||||||
options.attrmap = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
options.baseTileIDs[0] = parseNumber(arg, "Bank 0 base tile ID", 0);
|
|
||||||
if (options.baseTileIDs[0] >= 256) {
|
|
||||||
error("Bank 0 base tile ID must be below 256");
|
|
||||||
}
|
|
||||||
if (*arg == '\0') {
|
|
||||||
options.baseTileIDs[1] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg; // Skip comma
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.baseTileIDs[1] = parseNumber(arg, "Bank 1 base tile ID", 0);
|
|
||||||
if (options.baseTileIDs[1] >= 256) {
|
|
||||||
error("Bank 1 base tile ID must be below 256");
|
|
||||||
}
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'C':
|
|
||||||
options.useColorCurve = true;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
if (musl_optarg[0] == '#') {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
parseInlinePalSpec(musl_optarg);
|
|
||||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
|
||||||
// Use PLTE, error out if missing
|
|
||||||
options.palSpecType = Options::EMBEDDED;
|
|
||||||
} else {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
// Can't parse the file yet, as "flat" color collections need to know the palette
|
|
||||||
// size to be split; thus, we defer that
|
|
||||||
// TODO: this does not validate the `fmt` part of any external spec but the last
|
|
||||||
// one, but I guess that's okay
|
|
||||||
externalPalSpec = musl_optarg;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
options.bitDepth = parseNumber(arg, "Bit depth", 2);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Bit depth (-b) argument must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
|
||||||
error("Bit depth must be 1 or 2, not %" PRIu8);
|
|
||||||
options.bitDepth = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
|
|
||||||
if (options.inputSlice.left > INT16_MAX) {
|
|
||||||
error("Input slice left coordinate is out of range!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Missing comma after left coordinate in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate");
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ':') {
|
|
||||||
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.inputSlice.width = parseNumber(arg, "Input slice width");
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (options.inputSlice.width == 0) {
|
|
||||||
error("Input slice width may not be 0!");
|
|
||||||
}
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Missing comma after width in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.inputSlice.height = parseNumber(arg, "Input slice height");
|
|
||||||
if (options.inputSlice.height == 0) {
|
|
||||||
error("Input slice height may not be 0!");
|
|
||||||
}
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
options.allowMirroring = true;
|
|
||||||
[[fallthrough]]; // Imply `-u`
|
|
||||||
case 'u':
|
|
||||||
options.allowDedup = true;
|
|
||||||
break;
|
|
||||||
case 'N':
|
|
||||||
options.maxNbTiles[0] = parseNumber(arg, "Number of tiles in bank 0", 256);
|
|
||||||
if (options.maxNbTiles[0] > 256) {
|
|
||||||
error("Bank 0 cannot contain more than 256 tiles");
|
|
||||||
}
|
|
||||||
if (*arg == '\0') {
|
|
||||||
options.maxNbTiles[1] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg; // Skip comma
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.maxNbTiles[1] = parseNumber(arg, "Number of tiles in bank 1", 256);
|
|
||||||
if (options.maxNbTiles[1] > 256) {
|
|
||||||
error("Bank 1 cannot contain more than 256 tiles");
|
|
||||||
}
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
options.nbPalettes = parseNumber(arg, "Number of palettes", 256);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
if (options.nbPalettes > 256) {
|
|
||||||
error("Number of palettes (-n) must not exceed 256!");
|
|
||||||
} else if (options.nbPalettes == 0) {
|
|
||||||
error("Number of palettes (-n) may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
options.output = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'P':
|
|
||||||
autoPalettes = true;
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
autoPalettes = false;
|
|
||||||
options.palettes = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'Q':
|
|
||||||
autoPalmap = true;
|
|
||||||
break;
|
|
||||||
case 'q':
|
|
||||||
autoPalmap = false;
|
|
||||||
options.palmap = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Reversed image stride (-r) must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
if (options.reversedWidth == 0) {
|
|
||||||
error("Reversed image stride (-r) may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Palette size (-s) must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
if (options.nbColorsPerPal > 4) {
|
|
||||||
error("Palette size (-s) must not exceed 4!");
|
|
||||||
} else if (options.nbColorsPerPal == 0) {
|
|
||||||
error("Palette size (-s) may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'T':
|
|
||||||
autoTilemap = true;
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
autoTilemap = false;
|
|
||||||
options.tilemap = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'V':
|
|
||||||
printf("rgbgfx %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
case 'v':
|
|
||||||
if (options.verbosity < Options::VERB_VVVVVV) {
|
|
||||||
++options.verbosity;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Tile trim (-x) argument must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
warning("`-h` is deprecated, use `-Z` instead");
|
|
||||||
[[fallthrough]];
|
|
||||||
case 'Z':
|
|
||||||
options.columnMajor = true;
|
|
||||||
break;
|
|
||||||
case 1: // Positional argument, requested by leading `-` in opt string
|
|
||||||
if (musl_optarg[0] == '@') {
|
|
||||||
// Instruct the caller to process that at-file
|
|
||||||
return &musl_optarg[1];
|
|
||||||
} else {
|
|
||||||
registerInput(musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'D':
|
|
||||||
case 'F':
|
|
||||||
case 'f':
|
|
||||||
warning("Ignoring retired option `-%c`", opt);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr; // Done processing this argv
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
|
|
||||||
|
|
||||||
struct AtFileStackEntry {
|
|
||||||
int parentInd; // Saved offset into parent argv
|
|
||||||
std::vector<char *> argv; // This context's arg pointer vec
|
|
||||||
std::vector<char> argPool;
|
|
||||||
|
|
||||||
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
|
||||||
: parentInd(parentInd_), argv(argv_) {}
|
|
||||||
};
|
|
||||||
std::vector<AtFileStackEntry> atFileStack;
|
|
||||||
|
|
||||||
int curArgc = argc;
|
|
||||||
char **curArgv = argv;
|
|
||||||
for (;;) {
|
|
||||||
char *atFileName =
|
|
||||||
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
|
|
||||||
if (atFileName) {
|
|
||||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
|
||||||
AtFileStackEntry &stackEntry =
|
|
||||||
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
|
||||||
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
|
||||||
// that; so we must compute the offsets after the pool is fixed
|
|
||||||
auto offsets = readAtFile(&musl_optarg[1], stackEntry.argPool);
|
|
||||||
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
|
||||||
for (size_t ofs : offsets) {
|
|
||||||
stackEntry.argv.push_back(&stackEntry.argPool.data()[ofs]);
|
|
||||||
}
|
|
||||||
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
|
||||||
|
|
||||||
curArgc = stackEntry.argv.size() - 1;
|
|
||||||
curArgv = stackEntry.argv.data();
|
|
||||||
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
|
||||||
continue; // Begin scanning that arg vector
|
|
||||||
}
|
|
||||||
|
|
||||||
if (musl_optind != curArgc) {
|
|
||||||
// This happens if `--` is passed, process the remaining arg(s) as positional
|
|
||||||
assert(musl_optind < curArgc);
|
|
||||||
for (int i = musl_optind; i < curArgc; ++i) {
|
|
||||||
registerInput(argv[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop off the top stack entry, or end parsing if none
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// OK to restore `optind` directly, because `optpos` must be 0 right now.
|
|
||||||
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
|
|
||||||
musl_optind = atFileStack.back().parentInd;
|
|
||||||
atFileStack.pop_back();
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
curArgc = argc;
|
|
||||||
curArgv = argv;
|
|
||||||
} else {
|
|
||||||
auto &vec = atFileStack.back().argv;
|
|
||||||
curArgc = vec.size();
|
|
||||||
curArgv = vec.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.nbColorsPerPal == 0) {
|
|
||||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
|
||||||
} else if (options.nbColorsPerPal > 1u << options.bitDepth) {
|
|
||||||
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth,
|
|
||||||
1u << options.bitDepth, options.nbColorsPerPal);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
|
|
||||||
if (autoOptEnabled) {
|
|
||||||
constexpr std::string_view chars =
|
|
||||||
// Both must start with a dot!
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
|
||||||
"./\\"sv;
|
|
||||||
#else
|
|
||||||
"./"sv;
|
|
||||||
#endif
|
|
||||||
size_t len = options.input.npos;
|
|
||||||
size_t i = options.input.find_last_of(chars);
|
|
||||||
if (i != options.input.npos && options.input[i] == '.') {
|
|
||||||
// We found the last dot, but check if it's part of a stem
|
|
||||||
// (There must be a non-path separator character before it)
|
|
||||||
if (i != 0 && chars.find(options.input[i - 1], 1) == chars.npos) {
|
|
||||||
// We can replace the extension
|
|
||||||
len = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path.assign(options.input, 0, len);
|
|
||||||
path.append(extension);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
|
|
||||||
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
|
||||||
autoOutPath(autoPalettes, options.palettes, ".pal");
|
|
||||||
autoOutPath(autoPalmap, options.palmap, ".palmap");
|
|
||||||
|
|
||||||
// Execute deferred external pal spec parsing, now that all other params are known
|
|
||||||
if (externalPalSpec) {
|
|
||||||
parseExternalPalSpec(externalPalSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_CFG) {
|
|
||||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_VVVVVV) {
|
|
||||||
fputc('\n', stderr);
|
|
||||||
static std::array<uint16_t, 21> gfx{
|
|
||||||
0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE,
|
|
||||||
0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE,
|
|
||||||
};
|
|
||||||
static std::array<char const *, 3> textbox{
|
|
||||||
" ,----------------------------------------.",
|
|
||||||
" | Augh, dimensional interference again?! |",
|
|
||||||
" `----------------------------------------'"};
|
|
||||||
for (size_t i = 0; i < gfx.size(); ++i) {
|
|
||||||
uint16_t row = gfx[i];
|
|
||||||
for (uint8_t _ = 0; _ < 10; ++_) {
|
|
||||||
unsigned char c = row & 1 ? '0' : ' ';
|
|
||||||
fputc(c, stderr);
|
|
||||||
// Double the pixel horizontally, otherwise the aspect ratio looks wrong
|
|
||||||
fputc(c, stderr);
|
|
||||||
row >>= 1;
|
|
||||||
}
|
|
||||||
if (i < textbox.size()) {
|
|
||||||
fputs(textbox[i], stderr);
|
|
||||||
}
|
|
||||||
fputc('\n', stderr);
|
|
||||||
}
|
|
||||||
fputc('\n', stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
|
||||||
if (options.columnMajor)
|
|
||||||
fputs("\tVisit image in column-major order\n", stderr);
|
|
||||||
if (options.allowMirroring)
|
|
||||||
fputs("\tAllow mirroring tiles\n", stderr);
|
|
||||||
if (options.allowDedup)
|
|
||||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
|
||||||
if (options.useColorCurve)
|
|
||||||
fputs("\tUse color curve\n", stderr);
|
|
||||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
|
||||||
if (options.trim != 0)
|
|
||||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
|
||||||
fprintf(stderr, "\tMaximum %" PRIu8 " palettes\n", options.nbPalettes);
|
|
||||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
|
||||||
fprintf(stderr, "\t%s palette spec\n", []() {
|
|
||||||
switch (options.palSpecType) {
|
|
||||||
case Options::NO_SPEC:
|
|
||||||
return "No";
|
|
||||||
case Options::EXPLICIT:
|
|
||||||
return "Explicit";
|
|
||||||
case Options::EMBEDDED:
|
|
||||||
return "Embedded";
|
|
||||||
}
|
|
||||||
return "???";
|
|
||||||
}());
|
|
||||||
if (options.palSpecType == Options::EXPLICIT) {
|
|
||||||
fputs("\t[\n", stderr);
|
|
||||||
for (std::array<Rgba, 4> const &pal : options.palSpec) {
|
|
||||||
fprintf(stderr, "\t\t#%06x, #%06x, #%06x, #%06x,\n", pal[0].toCSS() >> 8,
|
|
||||||
pal[1].toCSS() >> 8, pal[2].toCSS() >> 8, pal[3].toCSS() >> 8);
|
|
||||||
}
|
|
||||||
fputs("\t]\n", stderr);
|
|
||||||
}
|
|
||||||
fprintf(stderr,
|
|
||||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
|
|
||||||
", %" PRIi32 ")\n",
|
|
||||||
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
|
|
||||||
options.inputSlice.top);
|
|
||||||
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
|
||||||
options.baseTileIDs[1]);
|
|
||||||
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
|
||||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
|
||||||
auto printPath = [](char const *name, std::string const &path) {
|
|
||||||
if (!path.empty()) {
|
|
||||||
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
printPath("Input image", options.input);
|
|
||||||
printPath("Output tile data", options.output);
|
|
||||||
printPath("Output tilemap", options.tilemap);
|
|
||||||
printPath("Output attrmap", options.attrmap);
|
|
||||||
printPath("Output palettes", options.palettes);
|
|
||||||
fputs("Ready.\n", stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.input.empty()) {
|
|
||||||
fatal("No input image specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not do anything if option parsing went wrong
|
|
||||||
if (nbErrors) {
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.reverse()) {
|
|
||||||
reverse();
|
|
||||||
} else {
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbErrors) {
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Palette::addColor(uint16_t color) {
|
|
||||||
for (size_t i = 0; true; ++i) {
|
|
||||||
assert(i < colors.size()); // The packing should guarantee this
|
|
||||||
if (colors[i] == color) { // The color is already present
|
|
||||||
break;
|
|
||||||
} else if (colors[i] == UINT16_MAX) { // Empty slot
|
|
||||||
colors[i] = color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ID of the color in the palette, or `size()` if the color is not in
|
|
||||||
*/
|
|
||||||
uint8_t Palette::indexOf(uint16_t color) const {
|
|
||||||
return std::find(colors.begin(), colors.end(), color) - colors.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::begin() -> decltype(colors)::iterator {
|
|
||||||
// Skip the first slot if reserved for transparency
|
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
auto Palette::end() -> decltype(colors)::iterator {
|
|
||||||
return std::find(begin(), colors.end(), UINT16_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
|
||||||
// Skip the first slot if reserved for transparency
|
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
auto Palette::end() const -> decltype(colors)::const_iterator {
|
|
||||||
return std::find(begin(), colors.end(), UINT16_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Palette::size() const {
|
|
||||||
return indexOf(UINT16_MAX);
|
|
||||||
}
|
|
||||||
806
src/gfx/makepng.c
Normal file
806
src/gfx/makepng.c
Normal file
@@ -0,0 +1,806 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "gfx/makepng.h"
|
||||||
|
|
||||||
|
static void initialize_png(struct PNGImage *img, FILE * f);
|
||||||
|
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img);
|
||||||
|
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img);
|
||||||
|
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img);
|
||||||
|
static void get_text(const struct PNGImage *img,
|
||||||
|
struct ImageOptions *png_options);
|
||||||
|
static void set_text(const struct PNGImage *img,
|
||||||
|
const struct ImageOptions *png_options);
|
||||||
|
static void free_png_data(const struct PNGImage *png);
|
||||||
|
|
||||||
|
struct RawIndexedImage *input_png_file(const struct Options *opts,
|
||||||
|
struct ImageOptions *png_options)
|
||||||
|
{
|
||||||
|
struct PNGImage img;
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->infile, "rb");
|
||||||
|
if (!f)
|
||||||
|
err("Opening input png file '%s' failed", opts->infile);
|
||||||
|
|
||||||
|
initialize_png(&img, f);
|
||||||
|
|
||||||
|
if (img.depth != depth) {
|
||||||
|
if (opts->verbose) {
|
||||||
|
warnx("Image bit depth is not %d (is %d).",
|
||||||
|
depth, img.depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (img.type) {
|
||||||
|
case PNG_COLOR_TYPE_PALETTE:
|
||||||
|
raw_image = indexed_png_to_raw(&img); break;
|
||||||
|
case PNG_COLOR_TYPE_GRAY:
|
||||||
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
|
raw_image = grayscale_png_to_raw(&img); break;
|
||||||
|
case PNG_COLOR_TYPE_RGB:
|
||||||
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
|
raw_image = truecolor_png_to_raw(&img); break;
|
||||||
|
default:
|
||||||
|
/* Shouldn't happen, but might as well handle just in case. */
|
||||||
|
errx("Input PNG file is of invalid color type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
get_text(&img, png_options);
|
||||||
|
|
||||||
|
png_destroy_read_struct(&img.png, &img.info, NULL);
|
||||||
|
fclose(f);
|
||||||
|
free_png_data(&img);
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_png_file(const struct Options *opts,
|
||||||
|
const struct ImageOptions *png_options,
|
||||||
|
const struct RawIndexedImage *raw_image)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
char *outfile;
|
||||||
|
struct PNGImage img;
|
||||||
|
png_color *png_palette;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Variable outfile is for debugging purposes. Eventually,
|
||||||
|
* opts.infile will be used directly.
|
||||||
|
*/
|
||||||
|
if (opts->debug) {
|
||||||
|
outfile = malloc(strlen(opts->infile) + 5);
|
||||||
|
if (!outfile)
|
||||||
|
err("%s: Failed to allocate memory for outfile",
|
||||||
|
__func__);
|
||||||
|
strcpy(outfile, opts->infile);
|
||||||
|
strcat(outfile, ".out");
|
||||||
|
} else {
|
||||||
|
outfile = opts->infile;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fopen(outfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("Opening output png file '%s' failed", outfile);
|
||||||
|
|
||||||
|
if (opts->debug)
|
||||||
|
free(outfile);
|
||||||
|
|
||||||
|
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
if (!img.png)
|
||||||
|
errx("Creating png structure failed");
|
||||||
|
|
||||||
|
img.info = png_create_info_struct(img.png);
|
||||||
|
if (!img.info)
|
||||||
|
errx("Creating png info structure failed");
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(img.png)))
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
png_init_io(img.png, f);
|
||||||
|
|
||||||
|
png_set_IHDR(img.png, img.info, raw_image->width, raw_image->height,
|
||||||
|
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
|
||||||
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
|
||||||
|
png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors);
|
||||||
|
if (!png_palette)
|
||||||
|
err("%s: Failed to allocate memory for PNG palette",
|
||||||
|
__func__);
|
||||||
|
for (i = 0; i < raw_image->num_colors; i++) {
|
||||||
|
png_palette[i].red = raw_image->palette[i].red;
|
||||||
|
png_palette[i].green = raw_image->palette[i].green;
|
||||||
|
png_palette[i].blue = raw_image->palette[i].blue;
|
||||||
|
}
|
||||||
|
png_set_PLTE(img.png, img.info, png_palette, raw_image->num_colors);
|
||||||
|
free(png_palette);
|
||||||
|
|
||||||
|
if (opts->fix)
|
||||||
|
set_text(&img, png_options);
|
||||||
|
|
||||||
|
png_write_info(img.png, img.info);
|
||||||
|
|
||||||
|
png_write_image(img.png, (png_byte **) raw_image->data);
|
||||||
|
png_write_end(img.png, NULL);
|
||||||
|
|
||||||
|
png_destroy_write_struct(&img.png, &img.info);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image = *raw_image_ptr_ptr;
|
||||||
|
|
||||||
|
for (unsigned int y = 0; y < raw_image->height; y++)
|
||||||
|
free(raw_image->data[y]);
|
||||||
|
|
||||||
|
free(raw_image->data);
|
||||||
|
free(raw_image->palette);
|
||||||
|
free(raw_image);
|
||||||
|
*raw_image_ptr_ptr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_png(struct PNGImage *img, FILE *f)
|
||||||
|
{
|
||||||
|
img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
if (!img->png)
|
||||||
|
errx("Creating png structure failed");
|
||||||
|
|
||||||
|
img->info = png_create_info_struct(img->png);
|
||||||
|
if (!img->info)
|
||||||
|
errx("Creating png info structure failed");
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(img->png)))
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
png_init_io(img->png, f);
|
||||||
|
|
||||||
|
png_read_info(img->png, img->info);
|
||||||
|
|
||||||
|
img->width = png_get_image_width(img->png, img->info);
|
||||||
|
img->height = png_get_image_height(img->png, img->info);
|
||||||
|
img->depth = png_get_bit_depth(img->png, img->info);
|
||||||
|
img->type = png_get_color_type(img->png, img->info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_png(struct PNGImage *img);
|
||||||
|
static struct RawIndexedImage *create_raw_image(int width, int height,
|
||||||
|
int num_colors);
|
||||||
|
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
|
||||||
|
png_color const *palette, int num_colors);
|
||||||
|
|
||||||
|
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
png_color *palette;
|
||||||
|
int colors_in_PLTE;
|
||||||
|
int colors_in_new_palette;
|
||||||
|
png_byte *trans_alpha;
|
||||||
|
int num_trans;
|
||||||
|
png_color_16 *trans_color;
|
||||||
|
png_color *original_palette;
|
||||||
|
uint8_t *old_to_new_palette;
|
||||||
|
int i, x, y;
|
||||||
|
|
||||||
|
if (img->depth < 8)
|
||||||
|
png_set_packing(img->png);
|
||||||
|
|
||||||
|
png_get_PLTE(img->png, img->info, &palette, &colors_in_PLTE);
|
||||||
|
|
||||||
|
raw_image = create_raw_image(img->width, img->height, colors);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transparent palette entries are removed, and the palette is
|
||||||
|
* collapsed. Transparent pixels are then replaced with palette index 0.
|
||||||
|
* This way, an indexed PNG can contain transparent pixels in *addition*
|
||||||
|
* to 4 normal colors.
|
||||||
|
*/
|
||||||
|
if (png_get_tRNS(img->png, img->info, &trans_alpha, &num_trans,
|
||||||
|
&trans_color)) {
|
||||||
|
original_palette = palette;
|
||||||
|
palette = malloc(sizeof(*palette) * colors_in_PLTE);
|
||||||
|
if (!palette)
|
||||||
|
err("%s: Failed to allocate memory for palette",
|
||||||
|
__func__);
|
||||||
|
colors_in_new_palette = 0;
|
||||||
|
old_to_new_palette = malloc(sizeof(*old_to_new_palette)
|
||||||
|
* colors_in_PLTE);
|
||||||
|
if (!old_to_new_palette)
|
||||||
|
err("%s: Failed to allocate memory for new palette",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
for (i = 0; i < num_trans; i++) {
|
||||||
|
if (trans_alpha[i] == 0) {
|
||||||
|
old_to_new_palette[i] = 0;
|
||||||
|
} else {
|
||||||
|
old_to_new_palette[i] = colors_in_new_palette;
|
||||||
|
palette[colors_in_new_palette++] =
|
||||||
|
original_palette[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = num_trans; i < colors_in_PLTE; i++) {
|
||||||
|
old_to_new_palette[i] = colors_in_new_palette;
|
||||||
|
palette[colors_in_new_palette++] = original_palette[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colors_in_new_palette != colors_in_PLTE) {
|
||||||
|
palette = realloc(palette,
|
||||||
|
sizeof(*palette) *
|
||||||
|
colors_in_new_palette);
|
||||||
|
if (!palette)
|
||||||
|
err("%s: Failed to allocate memory for palette",
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setting and validating palette before reading
|
||||||
|
* allows us to error out *before* doing the data
|
||||||
|
* transformation if the palette is too long.
|
||||||
|
*/
|
||||||
|
set_raw_image_palette(raw_image, palette,
|
||||||
|
colors_in_new_palette);
|
||||||
|
read_png(img);
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
for (x = 0; x < img->width; x++) {
|
||||||
|
raw_image->data[y][x] =
|
||||||
|
old_to_new_palette[img->data[y][x]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(palette);
|
||||||
|
free(old_to_new_palette);
|
||||||
|
} else {
|
||||||
|
set_raw_image_palette(raw_image, palette, colors_in_PLTE);
|
||||||
|
read_png(img);
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
for (x = 0; x < img->width; x++)
|
||||||
|
raw_image->data[y][x] = img->data[y][x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
if (img->depth < 8)
|
||||||
|
png_set_expand_gray_1_2_4_to_8(img->png);
|
||||||
|
|
||||||
|
png_set_gray_to_rgb(img->png);
|
||||||
|
return truecolor_png_to_raw(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgba_png_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors);
|
||||||
|
static struct RawIndexedImage
|
||||||
|
*processed_rgba_png_to_raw(const struct PNGImage *img,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette);
|
||||||
|
|
||||||
|
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
png_color *palette;
|
||||||
|
int colors_in_palette;
|
||||||
|
|
||||||
|
if (img->depth == 16) {
|
||||||
|
#if PNG_LIBPNG_VER >= 10504
|
||||||
|
png_set_scale_16(img->png);
|
||||||
|
#else
|
||||||
|
png_set_strip_16(img->png);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(img->type & PNG_COLOR_MASK_ALPHA)) {
|
||||||
|
if (png_get_valid(img->png, img->info, PNG_INFO_tRNS))
|
||||||
|
png_set_tRNS_to_alpha(img->png);
|
||||||
|
else
|
||||||
|
png_set_add_alpha(img->png, 0xFF, PNG_FILLER_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_png(img);
|
||||||
|
|
||||||
|
rgba_png_palette(img, &palette, &colors_in_palette);
|
||||||
|
raw_image = processed_rgba_png_to_raw(img, palette, colors_in_palette);
|
||||||
|
|
||||||
|
free(palette);
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgba_PLTE_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors);
|
||||||
|
static void rgba_build_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors);
|
||||||
|
|
||||||
|
static void rgba_png_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors)
|
||||||
|
{
|
||||||
|
if (png_get_valid(img->png, img->info, PNG_INFO_PLTE))
|
||||||
|
rgba_PLTE_palette(img, palette_ptr_ptr, num_colors);
|
||||||
|
else
|
||||||
|
rgba_build_palette(img, palette_ptr_ptr, num_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgba_PLTE_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors)
|
||||||
|
{
|
||||||
|
png_get_PLTE(img->png, img->info, palette_ptr_ptr, num_colors);
|
||||||
|
/*
|
||||||
|
* Lets us free the palette manually instead of leaving it to libpng,
|
||||||
|
* which lets us handle a PLTE and a built palette the same way.
|
||||||
|
*/
|
||||||
|
png_data_freer(img->png, img->info,
|
||||||
|
PNG_USER_WILL_FREE_DATA, PNG_FREE_PLTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_built_palette(png_color *palette,
|
||||||
|
png_color const *pixel_color, png_byte alpha,
|
||||||
|
int *num_colors, bool *only_grayscale);
|
||||||
|
static int fit_grayscale_palette(png_color *palette, int *num_colors);
|
||||||
|
static void order_color_palette(png_color *palette, int num_colors);
|
||||||
|
|
||||||
|
static void rgba_build_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors)
|
||||||
|
{
|
||||||
|
png_color *palette;
|
||||||
|
int y, value_index;
|
||||||
|
png_color cur_pixel_color;
|
||||||
|
png_byte cur_alpha;
|
||||||
|
bool only_grayscale = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By filling the palette up with black by default, if the image
|
||||||
|
* doesn't have enough colors, the palette gets padded with black.
|
||||||
|
*/
|
||||||
|
*palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr));
|
||||||
|
if (!*palette_ptr_ptr)
|
||||||
|
err("%s: Failed to allocate memory for palette", __func__);
|
||||||
|
palette = *palette_ptr_ptr;
|
||||||
|
*num_colors = 0;
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
value_index = 0;
|
||||||
|
while (value_index < img->width * 4) {
|
||||||
|
cur_pixel_color.red = img->data[y][value_index++];
|
||||||
|
cur_pixel_color.green = img->data[y][value_index++];
|
||||||
|
cur_pixel_color.blue = img->data[y][value_index++];
|
||||||
|
cur_alpha = img->data[y][value_index++];
|
||||||
|
|
||||||
|
update_built_palette(palette, &cur_pixel_color,
|
||||||
|
cur_alpha,
|
||||||
|
num_colors, &only_grayscale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In order not to count 100% transparent images as grayscale. */
|
||||||
|
only_grayscale = *num_colors ? only_grayscale : false;
|
||||||
|
|
||||||
|
if (!only_grayscale || !fit_grayscale_palette(palette, num_colors))
|
||||||
|
order_color_palette(palette, *num_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_built_palette(png_color *palette,
|
||||||
|
png_color const *pixel_color, png_byte alpha,
|
||||||
|
int *num_colors, bool *only_grayscale)
|
||||||
|
{
|
||||||
|
bool color_exists;
|
||||||
|
png_color cur_palette_color;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transparent pixels don't count toward the palette,
|
||||||
|
* as they'll be replaced with color #0 later.
|
||||||
|
*/
|
||||||
|
if (alpha == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (*only_grayscale && !(pixel_color->red == pixel_color->green &&
|
||||||
|
pixel_color->red == pixel_color->blue)) {
|
||||||
|
*only_grayscale = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_exists = false;
|
||||||
|
for (i = 0; i < *num_colors; i++) {
|
||||||
|
cur_palette_color = palette[i];
|
||||||
|
if (pixel_color->red == cur_palette_color.red &&
|
||||||
|
pixel_color->green == cur_palette_color.green &&
|
||||||
|
pixel_color->blue == cur_palette_color.blue) {
|
||||||
|
color_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!color_exists) {
|
||||||
|
if (*num_colors == colors) {
|
||||||
|
errx("Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
|
||||||
|
depth, colors);
|
||||||
|
}
|
||||||
|
palette[*num_colors] = *pixel_color;
|
||||||
|
(*num_colors)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fit_grayscale_palette(png_color *palette, int *num_colors)
|
||||||
|
{
|
||||||
|
int interval = 256 / colors;
|
||||||
|
png_color *fitted_palette = malloc(sizeof(*fitted_palette) * colors);
|
||||||
|
bool *set_indices = calloc(colors, sizeof(*set_indices));
|
||||||
|
int i, shade_index;
|
||||||
|
|
||||||
|
if (!fitted_palette)
|
||||||
|
err("%s: Failed to allocate memory for palette", __func__);
|
||||||
|
if (!set_indices)
|
||||||
|
err("%s: Failed to allocate memory for indices", __func__);
|
||||||
|
|
||||||
|
fitted_palette[0].red = 0xFF;
|
||||||
|
fitted_palette[0].green = 0xFF;
|
||||||
|
fitted_palette[0].blue = 0xFF;
|
||||||
|
fitted_palette[colors - 1].red = 0;
|
||||||
|
fitted_palette[colors - 1].green = 0;
|
||||||
|
fitted_palette[colors - 1].blue = 0;
|
||||||
|
if (colors == 4) {
|
||||||
|
fitted_palette[1].red = 0xA9;
|
||||||
|
fitted_palette[1].green = 0xA9;
|
||||||
|
fitted_palette[1].blue = 0xA9;
|
||||||
|
fitted_palette[2].red = 0x55;
|
||||||
|
fitted_palette[2].green = 0x55;
|
||||||
|
fitted_palette[2].blue = 0x55;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < *num_colors; i++) {
|
||||||
|
shade_index = colors - 1 - palette[i].red / interval;
|
||||||
|
if (set_indices[shade_index]) {
|
||||||
|
free(fitted_palette);
|
||||||
|
free(set_indices);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fitted_palette[shade_index] = palette[i];
|
||||||
|
set_indices[shade_index] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < colors; i++)
|
||||||
|
palette[i] = fitted_palette[i];
|
||||||
|
|
||||||
|
*num_colors = colors;
|
||||||
|
|
||||||
|
free(fitted_palette);
|
||||||
|
free(set_indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A combined struct is needed to sort csolors in order of luminance. */
|
||||||
|
struct ColorWithLuminance {
|
||||||
|
png_color color;
|
||||||
|
int luminance;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int compare_luminance(void const *a, void const *b)
|
||||||
|
{
|
||||||
|
const struct ColorWithLuminance *x, *y;
|
||||||
|
|
||||||
|
x = (const struct ColorWithLuminance *)a;
|
||||||
|
y = (const struct ColorWithLuminance *)b;
|
||||||
|
|
||||||
|
return y->luminance - x->luminance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void order_color_palette(png_color *palette, int num_colors)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct ColorWithLuminance *palette_with_luminance =
|
||||||
|
malloc(sizeof(*palette_with_luminance) * num_colors);
|
||||||
|
|
||||||
|
if (!palette_with_luminance)
|
||||||
|
err("%s: Failed to allocate memory for palette", __func__);
|
||||||
|
|
||||||
|
for (i = 0; i < num_colors; i++) {
|
||||||
|
/*
|
||||||
|
* Normally this would be done with floats, but since it's only
|
||||||
|
* used for comparison, we might as well use integer math.
|
||||||
|
*/
|
||||||
|
palette_with_luminance[i].color = palette[i];
|
||||||
|
palette_with_luminance[i].luminance = 2126 * palette[i].red +
|
||||||
|
7152 * palette[i].green +
|
||||||
|
722 * palette[i].blue;
|
||||||
|
}
|
||||||
|
qsort(palette_with_luminance, num_colors,
|
||||||
|
sizeof(*palette_with_luminance), compare_luminance);
|
||||||
|
for (i = 0; i < num_colors; i++)
|
||||||
|
palette[i] = palette_with_luminance[i].color;
|
||||||
|
|
||||||
|
free(palette_with_luminance);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
|
||||||
|
const struct PNGImage *img,
|
||||||
|
int *value_index, int x, int y,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette);
|
||||||
|
|
||||||
|
static struct RawIndexedImage
|
||||||
|
*processed_rgba_png_to_raw(const struct PNGImage *img,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
int x, y, value_index;
|
||||||
|
|
||||||
|
raw_image = create_raw_image(img->width, img->height, colors);
|
||||||
|
|
||||||
|
set_raw_image_palette(raw_image, palette, colors_in_palette);
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
x = raw_image->width - 1;
|
||||||
|
value_index = img->width * 4 - 1;
|
||||||
|
|
||||||
|
while (x >= 0) {
|
||||||
|
put_raw_image_pixel(raw_image, img,
|
||||||
|
&value_index, x, y,
|
||||||
|
palette, colors_in_palette);
|
||||||
|
x--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t palette_index_of(png_color const *palette,
|
||||||
|
int num_colors, png_color const *color);
|
||||||
|
|
||||||
|
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
|
||||||
|
const struct PNGImage *img,
|
||||||
|
int *value_index, int x, int y,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette)
|
||||||
|
{
|
||||||
|
png_color pixel_color;
|
||||||
|
png_byte alpha;
|
||||||
|
|
||||||
|
alpha = img->data[y][*value_index];
|
||||||
|
if (alpha == 0) {
|
||||||
|
raw_image->data[y][x] = 0;
|
||||||
|
*value_index -= 4;
|
||||||
|
} else {
|
||||||
|
(*value_index)--;
|
||||||
|
pixel_color.blue = img->data[y][(*value_index)--];
|
||||||
|
pixel_color.green = img->data[y][(*value_index)--];
|
||||||
|
pixel_color.red = img->data[y][(*value_index)--];
|
||||||
|
raw_image->data[y][x] = palette_index_of(palette,
|
||||||
|
colors_in_palette,
|
||||||
|
&pixel_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t palette_index_of(png_color const *palette,
|
||||||
|
int num_colors, png_color const *color)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < num_colors; i++) {
|
||||||
|
if (palette[i].red == color->red &&
|
||||||
|
palette[i].green == color->green &&
|
||||||
|
palette[i].blue == color->blue) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errx("The input PNG file contains colors that don't appear in its embedded palette.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_png(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
int y;
|
||||||
|
|
||||||
|
png_read_update_info(img->png, img->info);
|
||||||
|
|
||||||
|
img->data = malloc(sizeof(*img->data) * img->height);
|
||||||
|
if (!img->data)
|
||||||
|
err("%s: Failed to allocate memory for image data",
|
||||||
|
__func__);
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
|
||||||
|
if (!img->data[y])
|
||||||
|
err("%s: Failed to allocate memory for image data",
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
png_read_image(img->png, img->data);
|
||||||
|
png_read_end(img->png, img->info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct RawIndexedImage *create_raw_image(int width, int height,
|
||||||
|
int num_colors)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
int y;
|
||||||
|
|
||||||
|
raw_image = malloc(sizeof(*raw_image));
|
||||||
|
if (!raw_image)
|
||||||
|
err("%s: Failed to allocate memory for raw image",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
raw_image->width = width;
|
||||||
|
raw_image->height = height;
|
||||||
|
raw_image->num_colors = num_colors;
|
||||||
|
|
||||||
|
raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors);
|
||||||
|
if (!raw_image->palette)
|
||||||
|
err("%s: Failed to allocate memory for raw image palette",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
raw_image->data = malloc(sizeof(*raw_image->data) * height);
|
||||||
|
if (!raw_image->data)
|
||||||
|
err("%s: Failed to allocate memory for raw image data",
|
||||||
|
__func__);
|
||||||
|
for (y = 0; y < height; y++) {
|
||||||
|
raw_image->data[y] = malloc(sizeof(*raw_image->data[y])
|
||||||
|
* width);
|
||||||
|
if (!raw_image->data[y])
|
||||||
|
err("%s: Failed to allocate memory for raw image data",
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
|
||||||
|
png_color const *palette, int num_colors)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (num_colors > raw_image->num_colors) {
|
||||||
|
errx("Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
|
||||||
|
raw_image->num_colors >> 1,
|
||||||
|
num_colors, raw_image->num_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_colors; i++) {
|
||||||
|
raw_image->palette[i].red = palette[i].red;
|
||||||
|
raw_image->palette[i].green = palette[i].green;
|
||||||
|
raw_image->palette[i].blue = palette[i].blue;
|
||||||
|
}
|
||||||
|
for (i = num_colors; i < raw_image->num_colors; i++) {
|
||||||
|
raw_image->palette[i].red = 0;
|
||||||
|
raw_image->palette[i].green = 0;
|
||||||
|
raw_image->palette[i].blue = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_text(const struct PNGImage *img,
|
||||||
|
struct ImageOptions *png_options)
|
||||||
|
{
|
||||||
|
png_text *text;
|
||||||
|
int i, numtxts, numremoved;
|
||||||
|
|
||||||
|
png_get_text(img->png, img->info, &text, &numtxts);
|
||||||
|
for (i = 0; i < numtxts; i++) {
|
||||||
|
if (strcmp(text[i].key, "h") == 0 && !*text[i].text) {
|
||||||
|
png_options->horizontal = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "x") == 0) {
|
||||||
|
png_options->trim = strtoul(text[i].text, NULL, 0);
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "t") == 0) {
|
||||||
|
png_options->tilemapfile = text[i].text;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
|
||||||
|
png_options->tilemapout = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "a") == 0) {
|
||||||
|
png_options->attrmapfile = text[i].text;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "A") == 0 && !*text[i].text) {
|
||||||
|
png_options->attrmapout = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "p") == 0) {
|
||||||
|
png_options->palfile = text[i].text;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "P") == 0 && !*text[i].text) {
|
||||||
|
png_options->palout = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Remove this and simply change the warning function not to warn
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
|
for (i = 0, numremoved = 0; i < numtxts; i++) {
|
||||||
|
if (text[i].key == NULL)
|
||||||
|
numremoved++;
|
||||||
|
|
||||||
|
text[i].key = text[i + numremoved].key;
|
||||||
|
text[i].text = text[i + numremoved].text;
|
||||||
|
text[i].compression = text[i + numremoved].compression;
|
||||||
|
}
|
||||||
|
png_set_text(img->png, img->info, text, numtxts - numremoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_text(const struct PNGImage *img,
|
||||||
|
const struct ImageOptions *png_options)
|
||||||
|
{
|
||||||
|
png_text *text;
|
||||||
|
char buffer[3];
|
||||||
|
|
||||||
|
text = malloc(sizeof(*text));
|
||||||
|
if (!text)
|
||||||
|
err("%s: Failed to allocate memory for PNG text",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
if (png_options->horizontal) {
|
||||||
|
text[0].key = "h";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->trim) {
|
||||||
|
text[0].key = "x";
|
||||||
|
snprintf(buffer, 3, "%d", png_options->trim);
|
||||||
|
text[0].text = buffer;
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (*png_options->tilemapfile) {
|
||||||
|
text[0].key = "t";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->tilemapout) {
|
||||||
|
text[0].key = "T";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (*png_options->attrmapfile) {
|
||||||
|
text[0].key = "a";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->attrmapout) {
|
||||||
|
text[0].key = "A";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (*png_options->palfile) {
|
||||||
|
text[0].key = "p";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->palout) {
|
||||||
|
text[0].key = "P";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_png_data(const struct PNGImage *img)
|
||||||
|
{
|
||||||
|
int y;
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++)
|
||||||
|
free(img->data[y]);
|
||||||
|
|
||||||
|
free(img->data);
|
||||||
|
}
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/pal_packing.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <bitset>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <deque>
|
|
||||||
#include <numeric>
|
|
||||||
#include <optional>
|
|
||||||
#include <queue>
|
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
#include "gfx/proto_palette.hpp"
|
|
||||||
|
|
||||||
using std::swap;
|
|
||||||
|
|
||||||
namespace packing {
|
|
||||||
|
|
||||||
// The solvers here are picked from the paper at http://arxiv.org/abs/1605.00558:
|
|
||||||
// "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"
|
|
||||||
// Their formulation of the problem consists in packing "tiles" into "pages"; here is a
|
|
||||||
// correspondence table for our application of it:
|
|
||||||
// Paper | RGBGFX
|
|
||||||
// ------+-------
|
|
||||||
// Tile | Proto-palette
|
|
||||||
// Page | Palette
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A reference to a proto-palette, and attached attributes for sorting purposes
|
|
||||||
*/
|
|
||||||
struct ProtoPalAttrs {
|
|
||||||
size_t const protoPalIndex;
|
|
||||||
/**
|
|
||||||
* Pages from which we are banned (to prevent infinite loops)
|
|
||||||
* This is dynamic because we wish not to hard-cap the amount of palettes
|
|
||||||
*/
|
|
||||||
std::vector<bool> bannedPages;
|
|
||||||
|
|
||||||
ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
|
|
||||||
bool isBannedFrom(size_t index) const {
|
|
||||||
return index < bannedPages.size() && bannedPages[index];
|
|
||||||
}
|
|
||||||
void banFrom(size_t index) {
|
|
||||||
if (bannedPages.size() <= index) {
|
|
||||||
bannedPages.resize(index + 1);
|
|
||||||
}
|
|
||||||
bannedPages[index] = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of proto-palettes assigned to a palette
|
|
||||||
* Does not contain the actual color indices because we need to be able to remove elements
|
|
||||||
*/
|
|
||||||
class AssignedProtos {
|
|
||||||
// We leave room for emptied slots to avoid copying the structs around on removal
|
|
||||||
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
|
||||||
// For resolving proto-palette indices
|
|
||||||
std::vector<ProtoPalette> const *_protoPals;
|
|
||||||
|
|
||||||
public:
|
|
||||||
template<typename... Ts>
|
|
||||||
AssignedProtos(std::vector<ProtoPalette> const &protoPals, Ts &&...elems)
|
|
||||||
: _assigned{std::forward<Ts>(elems)...}, _protoPals{&protoPals} {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
template<typename Inner, template<typename> typename Constness>
|
|
||||||
class Iter {
|
|
||||||
public:
|
|
||||||
friend class AssignedProtos;
|
|
||||||
// For `iterator_traits`
|
|
||||||
using difference_type = typename std::iterator_traits<Inner>::difference_type;
|
|
||||||
using value_type = ProtoPalAttrs;
|
|
||||||
using pointer = Constness<value_type> *;
|
|
||||||
using reference = Constness<value_type> &;
|
|
||||||
using iterator_category = std::forward_iterator_tag;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Constness<decltype(_assigned)> *_array = nullptr;
|
|
||||||
Inner _iter{};
|
|
||||||
|
|
||||||
Iter(decltype(_array) array, decltype(_iter) &&iter) : _array(array), _iter(iter) {}
|
|
||||||
Iter &skipEmpty() {
|
|
||||||
while (_iter != _array->end() && !_iter->has_value()) {
|
|
||||||
++_iter;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Iter() = default;
|
|
||||||
|
|
||||||
bool operator==(Iter const &other) const { return _iter == other._iter; }
|
|
||||||
bool operator!=(Iter const &other) const { return !(*this == other); }
|
|
||||||
Iter &operator++() {
|
|
||||||
++_iter;
|
|
||||||
skipEmpty();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
Iter operator++(int) {
|
|
||||||
Iter it = *this;
|
|
||||||
++(*this);
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
reference operator*() const {
|
|
||||||
assert((*_iter).has_value());
|
|
||||||
return **_iter;
|
|
||||||
}
|
|
||||||
pointer operator->() const {
|
|
||||||
return &(**this); // Invokes the operator above, not quite a no-op!
|
|
||||||
}
|
|
||||||
|
|
||||||
friend void swap(Iter &lhs, Iter &rhs) {
|
|
||||||
swap(lhs._array, rhs._array);
|
|
||||||
swap(lhs._iter, rhs._iter);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
using iterator = Iter<decltype(_assigned)::iterator, std::remove_const_t>;
|
|
||||||
iterator begin() { return iterator{&_assigned, _assigned.begin()}.skipEmpty(); }
|
|
||||||
iterator end() { return iterator{&_assigned, _assigned.end()}; }
|
|
||||||
using const_iterator = Iter<decltype(_assigned)::const_iterator, std::add_const_t>;
|
|
||||||
const_iterator begin() const {
|
|
||||||
return const_iterator{&_assigned, _assigned.begin()}.skipEmpty();
|
|
||||||
}
|
|
||||||
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
|
||||||
* Args are passed to the `ProtoPalAttrs`'s constructor
|
|
||||||
*/
|
|
||||||
template<typename... Ts>
|
|
||||||
void assign(Ts &&...args) {
|
|
||||||
auto freeSlot = std::find_if_not(
|
|
||||||
_assigned.begin(), _assigned.end(),
|
|
||||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
|
|
||||||
|
|
||||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
|
||||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
|
||||||
} else { // Reuse a free slot
|
|
||||||
freeSlot->emplace(std::forward<Ts>(args)...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void remove(iterator const &iter) {
|
|
||||||
iter._iter->reset(); // This time, we want to access the `optional` itself
|
|
||||||
}
|
|
||||||
void clear() { _assigned.clear(); }
|
|
||||||
|
|
||||||
bool empty() const {
|
|
||||||
return std::find_if(
|
|
||||||
_assigned.begin(), _assigned.end(),
|
|
||||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); })
|
|
||||||
== _assigned.end();
|
|
||||||
}
|
|
||||||
size_t nbProtoPals() const { return std::distance(begin(), end()); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
template<typename Iter>
|
|
||||||
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end,
|
|
||||||
std::vector<ProtoPalette> const &protoPals) {
|
|
||||||
for (; iter != end; ++iter) {
|
|
||||||
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
|
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This function should stay private because it returns a reference to a unique object
|
|
||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
|
||||||
// faster than "back-checking" on every element (O(n²))
|
|
||||||
//
|
|
||||||
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
|
||||||
// performs better:
|
|
||||||
// > So basically you make a priority queue that takes iterators into each of your sets
|
|
||||||
// > (paired with end iterators so you'll know where to stop), and the comparator tests the
|
|
||||||
// > values pointed to by each iterator
|
|
||||||
// > Then each iteration you pop from the queue,
|
|
||||||
// > optionally add one to your count, increment the iterator and push it back into the
|
|
||||||
// > queue if it didn't reach the end
|
|
||||||
// > And you do this until the priority queue is empty
|
|
||||||
static std::unordered_set<uint16_t> colors;
|
|
||||||
|
|
||||||
colors.clear();
|
|
||||||
addUniqueColors(colors, begin(), end(), *_protoPals);
|
|
||||||
return colors;
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Returns the number of distinct colors
|
|
||||||
*/
|
|
||||||
size_t volume() const { return uniqueColors().size(); }
|
|
||||||
bool canFit(ProtoPalette const &protoPal) const {
|
|
||||||
auto &colors = uniqueColors();
|
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
|
||||||
return colors.size() <= options.maxOpaqueColors();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the "relative size" of a proto-palette on this palette
|
|
||||||
*/
|
|
||||||
double relSizeOf(ProtoPalette const &protoPal) const {
|
|
||||||
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
|
|
||||||
double relSize = 0.;
|
|
||||||
for (uint16_t color : protoPal) {
|
|
||||||
auto n = std::count_if(begin(), end(), [this, &color](ProtoPalAttrs const &attrs) {
|
|
||||||
ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex];
|
|
||||||
return std::find(pal.begin(), pal.end(), color) != pal.end();
|
|
||||||
});
|
|
||||||
// NOTE: The paper and the associated code disagree on this: the code has
|
|
||||||
// this `1 +`, whereas the paper does not; its lack causes a division by 0
|
|
||||||
// if the symbol is not found anywhere, so I'm assuming the paper is wrong.
|
|
||||||
relSize += 1. / (1 + n);
|
|
||||||
}
|
|
||||||
return relSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the "relative size" of a set of proto-palettes on this palette
|
|
||||||
*/
|
|
||||||
template<typename Iter>
|
|
||||||
auto combinedVolume(Iter &&begin, Iter const &end,
|
|
||||||
std::vector<ProtoPalette> const &protoPals) const {
|
|
||||||
auto &colors = uniqueColors();
|
|
||||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
|
||||||
return colors.size();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Computes the "relative size" of a set of colors on this palette
|
|
||||||
*/
|
|
||||||
template<typename Iter>
|
|
||||||
auto combinedVolume(Iter &&begin, Iter &&end) const {
|
|
||||||
auto &colors = uniqueColors();
|
|
||||||
colors.insert(std::forward<Iter>(begin), std::forward<Iter>(end));
|
|
||||||
return colors.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void decant(std::vector<AssignedProtos> &assignments,
|
|
||||||
std::vector<ProtoPalette> const &protoPalettes) {
|
|
||||||
// "Decanting" is the process of moving all *things* that can fit in a lower index there
|
|
||||||
auto decantOn = [&assignments](auto const &tryDecanting) {
|
|
||||||
// No need to attempt decanting on palette #0, as there are no palettes to decant to
|
|
||||||
for (size_t from = assignments.size(); --from;) {
|
|
||||||
// Scan all palettes before this one
|
|
||||||
for (size_t to = 0; to < from; ++to) {
|
|
||||||
tryDecanting(assignments[to], assignments[from]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the proto-palette is now empty, remove it
|
|
||||||
// Doing this now reduces the number of iterations performed by later steps
|
|
||||||
// NB: order is intentionally preserved so as not to alter the "decantation"'s
|
|
||||||
// properties
|
|
||||||
// NB: this does mean that the first step might get empty palettes as its input!
|
|
||||||
// NB: this is safe to do because we go towards the beginning of the vector, thereby not
|
|
||||||
// invalidating our iteration (thus, iterators should not be used to drivethe outer
|
|
||||||
// loop)
|
|
||||||
if (assignments[from].empty()) {
|
|
||||||
assignments.erase(assignments.begin() + from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n",
|
|
||||||
assignments.size());
|
|
||||||
|
|
||||||
// Decant on palettes
|
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
|
||||||
// If the entire palettes can be merged, move all of `from`'s proto-palettes
|
|
||||||
if (to.combinedVolume(from.begin(), from.end(), protoPalettes)
|
|
||||||
<= options.maxOpaqueColors()) {
|
|
||||||
for (ProtoPalAttrs &attrs : from) {
|
|
||||||
to.assign(attrs.protoPalIndex);
|
|
||||||
}
|
|
||||||
from.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n",
|
|
||||||
assignments.size());
|
|
||||||
|
|
||||||
// Decant on "components" (= proto-pals sharing colors)
|
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
|
||||||
// We need to iterate on all the "components", which are groups of proto-palettes sharing at
|
|
||||||
// least one color with another proto-palettes in the group.
|
|
||||||
// We do this by adding the first available proto-palette, and then looking for palettes
|
|
||||||
// with common colors. (As an optimization, we know we can skip palettes already scanned.)
|
|
||||||
std::vector<bool> processed(from.nbProtoPals(), false);
|
|
||||||
std::unordered_set<uint16_t> colors;
|
|
||||||
std::vector<size_t> members;
|
|
||||||
while (true) {
|
|
||||||
auto iter = std::find(processed.begin(), processed.end(), true);
|
|
||||||
if (iter == processed.end()) { // Processed everything!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto attrs = from.begin();
|
|
||||||
std::advance(attrs, (iter - processed.begin()));
|
|
||||||
|
|
||||||
// Build up the "component"...
|
|
||||||
colors.clear();
|
|
||||||
members.clear();
|
|
||||||
assert(members.empty()); // Compiler optimization hint
|
|
||||||
do {
|
|
||||||
ProtoPalette const &protoPal = protoPalettes[attrs->protoPalIndex];
|
|
||||||
// If this is the first proto-pal, or if at least one color matches, add it
|
|
||||||
if (members.empty()
|
|
||||||
|| std::find_first_of(colors.begin(), colors.end(), protoPal.begin(),
|
|
||||||
protoPal.end())
|
|
||||||
!= colors.end()) {
|
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
|
||||||
members.push_back(iter - processed.begin());
|
|
||||||
*iter = true; // Mark that proto-pal as processed
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
++attrs;
|
|
||||||
} while (iter != processed.end());
|
|
||||||
|
|
||||||
if (to.combinedVolume(colors.begin(), colors.end()) <= options.maxOpaqueColors()) {
|
|
||||||
// Iterate through the component's proto-palettes, and transfer them
|
|
||||||
auto member = from.begin();
|
|
||||||
size_t curIndex = 0;
|
|
||||||
for (size_t index : members) {
|
|
||||||
std::advance(member, index - curIndex);
|
|
||||||
curIndex = index;
|
|
||||||
to.assign(std::move(*member));
|
|
||||||
from.remove(member); // Removing does not shift elements, so it's cheap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n",
|
|
||||||
assignments.size());
|
|
||||||
|
|
||||||
// Decant on individual proto-palettes
|
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
|
||||||
for (auto iter = from.begin(); iter != from.end(); ++iter) {
|
|
||||||
if (to.canFit(protoPalettes[iter->protoPalIndex])) {
|
|
||||||
to.assign(std::move(*iter));
|
|
||||||
from.remove(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n",
|
|
||||||
assignments.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT,
|
|
||||||
"Paginating palettes using \"overload-and-remove\" strategy...\n");
|
|
||||||
|
|
||||||
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
|
||||||
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
|
|
||||||
sortedProtoPalIDs.clear();
|
|
||||||
for (size_t i = 0; i < protoPalettes.size(); ++i) {
|
|
||||||
sortedProtoPalIDs.insert(
|
|
||||||
std::lower_bound(sortedProtoPalIDs.begin(), sortedProtoPalIDs.end(), i), i);
|
|
||||||
}
|
|
||||||
// Begin with all proto-palettes queued up for insertion
|
|
||||||
std::queue<ProtoPalAttrs> queue(
|
|
||||||
std::deque<ProtoPalAttrs>(sortedProtoPalIDs.begin(), sortedProtoPalIDs.end()));
|
|
||||||
// Begin with no pages
|
|
||||||
std::vector<AssignedProtos> assignments{};
|
|
||||||
|
|
||||||
for (; !queue.empty(); queue.pop()) {
|
|
||||||
ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()`
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "Handling proto-pal %zu\n", attrs.protoPalIndex);
|
|
||||||
|
|
||||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
|
||||||
size_t bestPalIndex = assignments.size();
|
|
||||||
// We're looking for a palette where the proto-palette's relative size is less than
|
|
||||||
// its actual size; so only overwrite the "not found" index on meeting that criterion
|
|
||||||
double bestRelSize = protoPal.size();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
|
||||||
// Skip the page if this one is banned from it
|
|
||||||
if (attrs.isBannedFrom(i)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1,
|
|
||||||
assignments.size(), assignments[i].relSizeOf(protoPal),
|
|
||||||
protoPal.size());
|
|
||||||
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
|
||||||
bestPalIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestPalIndex == assignments.size()) {
|
|
||||||
// Found nowhere to put it, create a new page containing just that one
|
|
||||||
assignments.emplace_back(protoPalettes, std::move(attrs));
|
|
||||||
} else {
|
|
||||||
auto &bestPal = assignments[bestPalIndex];
|
|
||||||
// Add the color to that palette
|
|
||||||
bestPal.assign(std::move(attrs));
|
|
||||||
|
|
||||||
// If this overloads the palette, get it back to normal (if possible)
|
|
||||||
while (bestPal.volume() > options.maxOpaqueColors()) {
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG,
|
|
||||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
|
||||||
bestPalIndex, bestPal.volume(), options.maxOpaqueColors());
|
|
||||||
|
|
||||||
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
|
|
||||||
auto efficiency = [&bestPal](ProtoPalette const &pal) {
|
|
||||||
return pal.size() / bestPal.relSizeOf(pal);
|
|
||||||
};
|
|
||||||
auto [minEfficiencyIter, maxEfficiencyIter] =
|
|
||||||
std::minmax_element(bestPal.begin(), bestPal.end(),
|
|
||||||
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs,
|
|
||||||
ProtoPalAttrs const &rhs) {
|
|
||||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
|
||||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// All efficiencies are identical iff min equals max
|
|
||||||
// TODO: maybe not ideal to re-compute these two?
|
|
||||||
// TODO: yikes for float comparison! I *think* this threshold is OK?
|
|
||||||
if (efficiency(protoPalettes[maxEfficiencyIter->protoPalIndex])
|
|
||||||
- efficiency(protoPalettes[minEfficiencyIter->protoPalIndex])
|
|
||||||
< .001) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the proto-pal with minimal efficiency
|
|
||||||
queue.emplace(std::move(*minEfficiencyIter));
|
|
||||||
queue.back().banFrom(bestPalIndex); // Ban it from this palette
|
|
||||||
bestPal.remove(minEfficiencyIter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with palettes still overloaded, by emptying them
|
|
||||||
for (AssignedProtos &pal : assignments) {
|
|
||||||
if (pal.volume() > options.maxOpaqueColors()) {
|
|
||||||
for (ProtoPalAttrs &attrs : pal) {
|
|
||||||
queue.emplace(std::move(attrs));
|
|
||||||
}
|
|
||||||
pal.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Place back any proto-palettes now in the queue via first-fit
|
|
||||||
while (!queue.empty()) {
|
|
||||||
ProtoPalAttrs const &attrs = queue.front();
|
|
||||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
|
||||||
auto iter =
|
|
||||||
std::find_if(assignments.begin(), assignments.end(),
|
|
||||||
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); });
|
|
||||||
if (iter == assignments.end()) { // No such page, create a new one
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG,
|
|
||||||
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
|
|
||||||
assignments.size(), attrs.protoPalIndex);
|
|
||||||
assignments.emplace_back(protoPalettes, std::move(attrs));
|
|
||||||
} else {
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG,
|
|
||||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
|
||||||
attrs.protoPalIndex, iter - assignments.begin());
|
|
||||||
iter->assign(std::move(attrs));
|
|
||||||
}
|
|
||||||
queue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
|
||||||
for (auto &&assignment : assignments) {
|
|
||||||
fprintf(stderr, "{ ");
|
|
||||||
for (auto &&attrs : assignment) {
|
|
||||||
fprintf(stderr, "[%zu] ", attrs.protoPalIndex);
|
|
||||||
for (auto &&colorIndex : protoPalettes[attrs.protoPalIndex]) {
|
|
||||||
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Decant" the result
|
|
||||||
decant(assignments, protoPalettes);
|
|
||||||
// Note that the result does not contain any empty palettes
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
|
||||||
for (auto &&assignment : assignments) {
|
|
||||||
fprintf(stderr, "{ ");
|
|
||||||
for (auto &&attrs : assignment) {
|
|
||||||
fprintf(stderr, "[%zu] ", attrs.protoPalIndex);
|
|
||||||
for (auto &&colorIndex : protoPalettes[attrs.protoPalIndex]) {
|
|
||||||
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
|
||||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
|
||||||
for (ProtoPalAttrs const &attrs : assignments[i]) {
|
|
||||||
mappings[attrs.protoPalIndex] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {mappings, assignments.size()};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace packing
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
|
|
||||||
#include "gfx/pal_sorting.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <png.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
#include "gfx/process.hpp"
|
|
||||||
|
|
||||||
namespace sorting {
|
|
||||||
|
|
||||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
|
||||||
png_byte *palAlpha) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
|
|
||||||
|
|
||||||
auto pngToRgb = [&palRGB, &palAlpha](int index) {
|
|
||||||
auto const &c = palRGB[index];
|
|
||||||
return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF);
|
|
||||||
};
|
|
||||||
|
|
||||||
// HACK: for compatibility with old versions, add unused colors if:
|
|
||||||
// - there is only one palette, and
|
|
||||||
// - only some of the first N colors are being used
|
|
||||||
if (palettes.size() == 1) {
|
|
||||||
Palette &palette = palettes[0];
|
|
||||||
// Build our candidate array of colors
|
|
||||||
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
for (int i = 0; i < options.maxOpaqueColors(); ++i) {
|
|
||||||
colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the palette only uses those colors
|
|
||||||
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
|
|
||||||
return std::find(colors.begin(), colors.end(), color) != colors.end();
|
|
||||||
})) {
|
|
||||||
if (palette.size() != options.maxOpaqueColors()) {
|
|
||||||
warning("Unused color in PNG embedded palette was re-added; please use `-c "
|
|
||||||
"embedded` to get this in future versions");
|
|
||||||
}
|
|
||||||
// Overwrite the palette, and return with that (it's already sorted)
|
|
||||||
palette.colors = colors;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Palette &pal : palettes) {
|
|
||||||
std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) {
|
|
||||||
// Iterate through the PNG's palette, looking for either of the two
|
|
||||||
for (int i = 0; i < palSize; ++i) {
|
|
||||||
uint16_t color = pngToRgb(i).cgbColor();
|
|
||||||
if (color == Rgba::transparent) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Return whether lhs < rhs
|
|
||||||
if (color == rhs) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (color == lhs) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable_(); // This should not be possible
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void grayscale(std::vector<Palette> &palettes,
|
|
||||||
std::array<std::optional<Rgba>, 0x8001> const &colors) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
|
|
||||||
|
|
||||||
// This method is only applicable if there are at most as many colors as colors per palette, so
|
|
||||||
// we should only have a single palette.
|
|
||||||
assert(palettes.size() == 1);
|
|
||||||
|
|
||||||
Palette &palette = palettes[0];
|
|
||||||
std::fill(palette.colors.begin(), palette.colors.end(), Rgba::transparent);
|
|
||||||
for (auto const &slot : colors) {
|
|
||||||
if (!slot.has_value() || slot->isTransparent()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
palette[slot->grayIndex()] = slot->cgbColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned int legacyLuminance(uint16_t color) {
|
|
||||||
uint8_t red = color & 0b11111;
|
|
||||||
uint8_t green = color >> 5 & 0b11111;
|
|
||||||
uint8_t blue = color >> 10;
|
|
||||||
return 2126 * red + 7152 * green + 722 * blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rgb(std::vector<Palette> &palettes) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n");
|
|
||||||
|
|
||||||
for (Palette &pal : palettes) {
|
|
||||||
std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {
|
|
||||||
return legacyLuminance(lhs) < legacyLuminance(rhs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sorting
|
|
||||||
@@ -1,451 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/pal_spec.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <climits>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <ostream>
|
|
||||||
#include <streambuf>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include "platform.h"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
using namespace std::string_view_literals;
|
|
||||||
|
|
||||||
constexpr uint8_t nibble(char c) {
|
|
||||||
if (c >= 'a') {
|
|
||||||
assert(c <= 'f');
|
|
||||||
return c - 'a' + 10;
|
|
||||||
} else if (c >= 'A') {
|
|
||||||
assert(c <= 'F');
|
|
||||||
return c - 'A' + 10;
|
|
||||||
} else {
|
|
||||||
assert(c >= '0' && c <= '9');
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint8_t toHex(char c1, char c2) {
|
|
||||||
return nibble(c1) * 16 + nibble(c2);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint8_t singleToHex(char c) {
|
|
||||||
return toHex(c, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Str> // Should be std::string or std::string_view
|
|
||||||
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
|
||||||
pos = std::min(str.find_first_not_of(" \t", pos), str.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const rawArg) {
|
|
||||||
// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
|
|
||||||
|
|
||||||
std::string_view arg(rawArg);
|
|
||||||
using size_type = decltype(arg)::size_type;
|
|
||||||
|
|
||||||
auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *fmt,
|
|
||||||
auto &&...args) {
|
|
||||||
(void)arg; // With NDEBUG, `arg` is otherwise not used
|
|
||||||
assert(ofs <= arg.length());
|
|
||||||
assert(len <= arg.length());
|
|
||||||
|
|
||||||
error(fmt, args...);
|
|
||||||
fprintf(stderr,
|
|
||||||
"In inline palette spec: %s\n"
|
|
||||||
" ",
|
|
||||||
rawArg);
|
|
||||||
for (auto i = ofs; i; --i) {
|
|
||||||
putc(' ', stderr);
|
|
||||||
}
|
|
||||||
for (auto i = len; i; --i) {
|
|
||||||
putc('^', stderr);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
};
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
|
|
||||||
|
|
||||||
size_type n = 0; // Index into the argument
|
|
||||||
// TODO: store max `nbColors` ever reached, and compare against palette size later
|
|
||||||
size_t nbColors = 0; // Number of colors in the current palette
|
|
||||||
for (;;) {
|
|
||||||
++n; // Ignore the '#' (checked either by caller or previous loop iteration)
|
|
||||||
|
|
||||||
Rgba &color = options.palSpec.back()[nbColors];
|
|
||||||
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
|
||||||
switch (pos - n) {
|
|
||||||
case 3:
|
|
||||||
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
|
|
||||||
0xFF);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
|
|
||||||
toHex(arg[n + 4], arg[n + 5]), 0xFF);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
parseError(n - 1, 1, "Missing color after '#'");
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
parseError(n, pos - n, "Unknown color specification");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
n = pos;
|
|
||||||
|
|
||||||
// Skip whitespace, if any
|
|
||||||
skipWhitespace(arg, n);
|
|
||||||
|
|
||||||
// Skip comma/semicolon, or end
|
|
||||||
if (n == arg.length()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch (arg[n]) {
|
|
||||||
case ',':
|
|
||||||
++n; // Skip it
|
|
||||||
|
|
||||||
++nbColors;
|
|
||||||
|
|
||||||
// A trailing comma may be followed by a semicolon
|
|
||||||
skipWhitespace(arg, n);
|
|
||||||
if (n == arg.length()) {
|
|
||||||
break;
|
|
||||||
} else if (arg[n] != ';' && arg[n] != ':') {
|
|
||||||
if (nbColors == 4) {
|
|
||||||
parseError(n, 1, "Each palette can only contain up to 4 colors");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
[[fallthrough]];
|
|
||||||
|
|
||||||
case ':':
|
|
||||||
case ';':
|
|
||||||
++n;
|
|
||||||
skipWhitespace(arg, n);
|
|
||||||
|
|
||||||
nbColors = 0; // Start a new palette
|
|
||||||
// Avoid creating a spurious empty palette
|
|
||||||
if (n != arg.length()) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
parseError(n, 1, "Unexpected character, expected ',', ';', or end of argument");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check again to allow trailing a comma/semicolon
|
|
||||||
if (n == arg.length()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (arg[n] != '#') {
|
|
||||||
parseError(n, 1, "Unexpected character, expected '#'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to read some magic bytes from the provided `file`.
|
|
||||||
* Returns whether the magic was correctly read.
|
|
||||||
*/
|
|
||||||
template<size_t n>
|
|
||||||
static bool readMagic(std::filebuf &file, char const *magic) {
|
|
||||||
assert(strlen(magic) == n);
|
|
||||||
|
|
||||||
char magicBuf[n];
|
|
||||||
return file.sgetn(magicBuf, n) == n && memcmp(magicBuf, magic, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like `readMagic`, but automatically determines the size from the string literal's length.
|
|
||||||
// Don't worry if you make a mistake, an `assert`'s got your back!
|
|
||||||
#define READ_MAGIC(file, magic) \
|
|
||||||
readMagic<sizeof(magic) - 1>(file, magic) // Don't count the terminator
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
static T readBE(U const *bytes) {
|
|
||||||
T val = 0;
|
|
||||||
for (size_t i = 0; i < sizeof(val); ++i) {
|
|
||||||
val = val << 8 | static_cast<uint8_t>(bytes[i]);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
|
||||||
*/
|
|
||||||
static void readLine(std::filebuf &file, std::string &buffer) {
|
|
||||||
// TODO: maybe this can be optimized to bulk reads?
|
|
||||||
for (;;) {
|
|
||||||
auto c = file.sbumpc();
|
|
||||||
if (c == std::filebuf::traits_type::eof()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (c == '\n') {
|
|
||||||
// Discard a trailing CRLF
|
|
||||||
if (!buffer.empty() && buffer.back() == '\r') {
|
|
||||||
buffer.pop_back();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
|
|
||||||
/**
|
|
||||||
* Parses the initial part of a string_view, advancing the "read index" as it does
|
|
||||||
*/
|
|
||||||
static uint16_t parseDec(std::string const &str, std::string::size_type &n) {
|
|
||||||
uint32_t value = 0; // Use a larger type to handle overflow more easily
|
|
||||||
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789", n)); n < end; ++n) {
|
|
||||||
value = std::min<uint32_t>(value * 10 + (str[n] - '0'), UINT16_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void parsePSPFile(std::filebuf &file) {
|
|
||||||
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
readLine(file, line);
|
|
||||||
if (line != "JASC-PAL") {
|
|
||||||
error("Palette file does not appear to be a PSP palette file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
line.clear();
|
|
||||||
readLine(file, line);
|
|
||||||
if (line != "0100") {
|
|
||||||
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
line.clear();
|
|
||||||
readLine(file, line);
|
|
||||||
std::string::size_type n = 0;
|
|
||||||
uint16_t nbColors = parseDec(line, n);
|
|
||||||
if (n != line.length()) {
|
|
||||||
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
|
||||||
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
|
||||||
"; ignoring extra",
|
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
|
||||||
line.clear();
|
|
||||||
readLine(file, line);
|
|
||||||
n = 0;
|
|
||||||
|
|
||||||
uint8_t r = parseDec(line, n);
|
|
||||||
skipWhitespace(line, n);
|
|
||||||
if (n == line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
|
||||||
line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t g = parseDec(line, n);
|
|
||||||
if (n == line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
|
||||||
line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
skipWhitespace(line, n);
|
|
||||||
uint8_t b = parseDec(line, n);
|
|
||||||
if (n != line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16
|
|
||||||
" (\"%s\"): trailing characters after blue component",
|
|
||||||
i + 1, line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i % options.nbColorsPerPal == 0) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
}
|
|
||||||
options.palSpec.back()[i % options.nbColorsPerPal] = Rgba(r, g, b, 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseACTFile(std::filebuf &file) {
|
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
|
|
||||||
|
|
||||||
std::array<char, 772> buf;
|
|
||||||
auto len = file.sgetn(buf.data(), buf.size());
|
|
||||||
|
|
||||||
uint16_t nbColors = 256;
|
|
||||||
if (len == 772) {
|
|
||||||
nbColors = readBE<uint16_t>(&buf[768]);
|
|
||||||
// TODO: apparently there is a "transparent color index"? What?
|
|
||||||
if (nbColors > 256 || nbColors == 0) {
|
|
||||||
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (len != 768) {
|
|
||||||
error("Invalid file size for ACT file (expected 768 or 772 bytes, got %zu", len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
|
||||||
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
|
||||||
"; ignoring extra",
|
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
|
|
||||||
char const *ptr = buf.data();
|
|
||||||
size_t colorIdx = 0;
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
|
||||||
Rgba &color = options.palSpec.back()[colorIdx];
|
|
||||||
color = Rgba(ptr[0], ptr[1], ptr[2], 0xFF);
|
|
||||||
|
|
||||||
ptr += 3;
|
|
||||||
++colorIdx;
|
|
||||||
if (colorIdx == options.nbColorsPerPal) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
colorIdx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the spurious empty palette if there is one
|
|
||||||
if (colorIdx == 0) {
|
|
||||||
options.palSpec.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseACOFile(std::filebuf &file) {
|
|
||||||
// rhttps://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
|
|
||||||
// http://www.nomodes.com/aco.html
|
|
||||||
|
|
||||||
char buf[10];
|
|
||||||
|
|
||||||
if (file.sgetn(buf, 2) != 2) {
|
|
||||||
error("Couldn't read ACO file version");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (readBE<uint16_t>(buf) != 1) {
|
|
||||||
error("Palette file does not appear to be an ACO file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.sgetn(buf, 2) != 2) {
|
|
||||||
error("Couldn't read number of colors in palette file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint16_t nbColors = readBE<uint16_t>(buf);
|
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
|
||||||
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
|
||||||
"; ignoring extra",
|
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
|
||||||
if (file.sgetn(buf, 10) != 10) {
|
|
||||||
error("Failed to read color #%" PRIu16 " from palette file", i + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i % options.nbColorsPerPal == 0) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
Rgba &color = options.palSpec.back()[i % options.nbColorsPerPal];
|
|
||||||
uint16_t colorType = readBE<uint16_t>(buf);
|
|
||||||
switch (colorType) {
|
|
||||||
case 0: // RGB
|
|
||||||
color = Rgba(buf[0], buf[2], buf[4], 0xFF);
|
|
||||||
break;
|
|
||||||
case 1: // HSB
|
|
||||||
error("Unsupported color type (HSB) for ACO file");
|
|
||||||
return;
|
|
||||||
case 2: // CMYK
|
|
||||||
error("Unsupported color type (CMYK) for ACO file");
|
|
||||||
return;
|
|
||||||
case 7: // Lab
|
|
||||||
error("Unsupported color type (lab) for ACO file");
|
|
||||||
return;
|
|
||||||
case 8: // Grayscale
|
|
||||||
error("Unsupported color type (grayscale) for ACO file");
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
error("Unknown color type (%" PRIu16 ") for ACO file", colorType);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe scan the v2 data instead (if present)
|
|
||||||
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseExternalPalSpec(char const *arg) {
|
|
||||||
// `fmt:path`, parse the file according to the given format
|
|
||||||
|
|
||||||
// Split both parts, error out if malformed
|
|
||||||
char const *ptr = strchr(arg, ':');
|
|
||||||
if (ptr == nullptr) {
|
|
||||||
error("External palette spec must have format `fmt:path` (missing colon)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char const *path = ptr + 1;
|
|
||||||
|
|
||||||
static std::array parsers{
|
|
||||||
std::tuple{"PSP", &parsePSPFile, std::ios::in },
|
|
||||||
std::tuple{"ACT", &parseACTFile, std::ios::binary},
|
|
||||||
std::tuple{"ACO", &parseACOFile, std::ios::binary},
|
|
||||||
};
|
|
||||||
|
|
||||||
auto iter = std::find_if(parsers.begin(), parsers.end(),
|
|
||||||
[&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
|
||||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
|
||||||
});
|
|
||||||
if (iter == parsers.end()) {
|
|
||||||
error("Unknown external palette format \"%.*s\"",
|
|
||||||
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
|
|
||||||
arg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filebuf file;
|
|
||||||
// Some parsers read the file in text mode, others in binary mode
|
|
||||||
if (!file.open(path, std::ios::in | std::get<2>(*iter))) {
|
|
||||||
error("Failed to open palette file \"%s\"", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::get<1> (*iter)(file);
|
|
||||||
}
|
|
||||||
1114
src/gfx/process.cpp
1114
src/gfx/process.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/proto_palette.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cassert>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
bool ProtoPalette::add(uint16_t color) {
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
// Seek the first slot greater than our color
|
|
||||||
// (A linear search is better because we don't store the array size,
|
|
||||||
// and there are very few slots anyway)
|
|
||||||
while (_colorIndices[i] < color) {
|
|
||||||
++i;
|
|
||||||
if (i == _colorIndices.size())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If we found ourselves, great!
|
|
||||||
if (_colorIndices[i] == color)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Swap entries until the end
|
|
||||||
while (_colorIndices[i] != UINT16_MAX) {
|
|
||||||
std::swap(_colorIndices[i], color);
|
|
||||||
++i;
|
|
||||||
if (i == _colorIndices.size())
|
|
||||||
return false; // Oh well
|
|
||||||
}
|
|
||||||
// Write that last one into the new slot
|
|
||||||
_colorIndices[i] = color;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const {
|
|
||||||
// This works because the sets are sorted numerically
|
|
||||||
assert(std::is_sorted(_colorIndices.begin(), _colorIndices.end()));
|
|
||||||
assert(std::is_sorted(other._colorIndices.begin(), other._colorIndices.end()));
|
|
||||||
|
|
||||||
auto ours = _colorIndices.begin(), theirs = other._colorIndices.begin();
|
|
||||||
bool weBigger = true, theyBigger = true;
|
|
||||||
|
|
||||||
while (ours != _colorIndices.end() && theirs != other._colorIndices.end()) {
|
|
||||||
if (*ours == *theirs) {
|
|
||||||
++ours;
|
|
||||||
++theirs;
|
|
||||||
} else if (*ours < *theirs) {
|
|
||||||
++ours;
|
|
||||||
theyBigger = false;
|
|
||||||
} else { // *ours > *theirs
|
|
||||||
++theirs;
|
|
||||||
weBigger = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
weBigger &= theirs == other._colorIndices.end();
|
|
||||||
theyBigger &= ours == _colorIndices.end();
|
|
||||||
|
|
||||||
return theyBigger ? THEY_BIGGER : (weBigger ? WE_BIGGER : NEITHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ProtoPalette::size() const {
|
|
||||||
return std::distance(begin(), end());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProtoPalette::empty() const {
|
|
||||||
return _colorIndices[0] == UINT16_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ProtoPalette::begin() const -> decltype(_colorIndices)::const_iterator {
|
|
||||||
return _colorIndices.begin();
|
|
||||||
}
|
|
||||||
auto ProtoPalette::end() const -> decltype(_colorIndices)::const_iterator {
|
|
||||||
return std::find(_colorIndices.begin(), _colorIndices.end(), UINT16_MAX);
|
|
||||||
}
|
|
||||||
@@ -1,324 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/reverse.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <optional>
|
|
||||||
#include <png.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <tuple>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
#include "helpers.h"
|
|
||||||
#include "itertools.hpp"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
static DefaultInitVec<uint8_t> readInto(std::string path) {
|
|
||||||
std::filebuf file;
|
|
||||||
file.open(path, std::ios::in | std::ios::binary);
|
|
||||||
DefaultInitVec<uint8_t> data(128 * 16); // Begin with some room pre-allocated
|
|
||||||
|
|
||||||
size_t curSize = 0;
|
|
||||||
for (;;) {
|
|
||||||
size_t oldSize = curSize;
|
|
||||||
curSize = data.size();
|
|
||||||
|
|
||||||
// Fill the new area ([oldSize; curSize[) with bytes
|
|
||||||
size_t nbRead =
|
|
||||||
file.sgetn(reinterpret_cast<char *>(&data.data()[oldSize]), curSize - oldSize);
|
|
||||||
if (nbRead != curSize - oldSize) {
|
|
||||||
// Shrink the vector to discard bytes that weren't read
|
|
||||||
data.resize(oldSize + nbRead);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If the vector has some capacity left, use it; otherwise, double the current size
|
|
||||||
|
|
||||||
// Arbitrary, but if you got a better idea...
|
|
||||||
size_t newSize = oldSize != data.capacity() ? data.capacity() : oldSize * 2;
|
|
||||||
assert(oldSize != newSize);
|
|
||||||
data.resize(newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] static void pngError(png_structp png, char const *msg) {
|
|
||||||
fatal("Error writing reversed image (\"%s\"): %s",
|
|
||||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pngWarning(png_structp png, char const *msg) {
|
|
||||||
warning("While writing reversed image (\"%s\"): %s",
|
|
||||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void writePng(png_structp png, png_bytep data, size_t length) {
|
|
||||||
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png));
|
|
||||||
pngFile.sputn(reinterpret_cast<char *>(data), length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void flushPng(png_structp png) {
|
|
||||||
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png));
|
|
||||||
pngFile.pubsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reverse() {
|
|
||||||
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
|
|
||||||
|
|
||||||
// Check for weird flag combinations
|
|
||||||
|
|
||||||
if (options.output.empty()) {
|
|
||||||
fatal("Tile data must be provided when reversing an image!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.allowDedup && options.tilemap.empty()) {
|
|
||||||
warning("Tile deduplication is enabled, but no tilemap is provided?");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.useColorCurve) {
|
|
||||||
warning("The color curve is not yet supported in reverse mode...");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.inputSlice.left != 0 || options.inputSlice.top != 0
|
|
||||||
|| options.inputSlice.height != 0) {
|
|
||||||
warning("\"Sliced-off\" pixels are ignored in reverse mode");
|
|
||||||
}
|
|
||||||
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
|
|
||||||
warning("Specified input slice width (%" PRIu16
|
|
||||||
") doesn't match provided reversing width (%" PRIu8 " * 8)",
|
|
||||||
options.inputSlice.width, options.reversedWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
|
|
||||||
auto const tiles = readInto(options.output);
|
|
||||||
uint8_t tileSize = 8 * options.bitDepth;
|
|
||||||
if (tiles.size() % tileSize != 0) {
|
|
||||||
fatal("Tile data size must be a multiple of %" PRIu8 " bytes! (Read %zu)", tileSize,
|
|
||||||
tiles.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
|
|
||||||
size_t nbTileInstances = tiles.size() / tileSize + options.trim; // Image size in tiles
|
|
||||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTileInstances);
|
|
||||||
std::optional<DefaultInitVec<uint8_t>> tilemap;
|
|
||||||
if (!options.tilemap.empty()) {
|
|
||||||
tilemap = readInto(options.tilemap);
|
|
||||||
nbTileInstances = tilemap->size();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
|
||||||
warning("Read %zu tiles, more than the limit of %zu + %zu", nbTileInstances,
|
|
||||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t width = options.reversedWidth, height; // In tiles
|
|
||||||
if (nbTileInstances % width != 0) {
|
|
||||||
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
|
||||||
nbTileInstances, width);
|
|
||||||
}
|
|
||||||
height = nbTileInstances / width;
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width,
|
|
||||||
height);
|
|
||||||
|
|
||||||
// TODO: -U
|
|
||||||
|
|
||||||
std::vector<std::array<Rgba, 4>> palettes{
|
|
||||||
{Rgba(0xffffffff), Rgba(0xaaaaaaff), Rgba(0x555555ff), Rgba(0x000000ff)}
|
|
||||||
};
|
|
||||||
if (!options.palettes.empty()) {
|
|
||||||
std::filebuf file;
|
|
||||||
file.open(options.palettes, std::ios::in | std::ios::binary);
|
|
||||||
|
|
||||||
palettes.clear();
|
|
||||||
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors
|
|
||||||
size_t nbRead;
|
|
||||||
do {
|
|
||||||
nbRead = file.sgetn(reinterpret_cast<char *>(buf.data()), buf.size());
|
|
||||||
if (nbRead == buf.size()) {
|
|
||||||
// Expand the colors
|
|
||||||
auto &palette = palettes.emplace_back();
|
|
||||||
std::generate(palette.begin(), palette.begin() + options.nbColorsPerPal,
|
|
||||||
[&buf, i = 0]() mutable {
|
|
||||||
i += 2;
|
|
||||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
|
||||||
});
|
|
||||||
} else if (nbRead != 0) {
|
|
||||||
fatal("Palette data size (%zu) is not a multiple of %zu bytes!\n",
|
|
||||||
palettes.size() * buf.size() + nbRead, buf.size());
|
|
||||||
}
|
|
||||||
} while (nbRead != 0);
|
|
||||||
|
|
||||||
if (palettes.size() > options.nbPalettes) {
|
|
||||||
warning("Read %zu palettes, more than the specified limit of %zu", palettes.size(),
|
|
||||||
options.nbPalettes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefaultInitVec<uint8_t>> attrmap;
|
|
||||||
if (!options.attrmap.empty()) {
|
|
||||||
attrmap = readInto(options.attrmap);
|
|
||||||
if (attrmap->size() != nbTileInstances) {
|
|
||||||
fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(),
|
|
||||||
nbTileInstances);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan through the attributes for inconsistencies
|
|
||||||
// We do this now for two reasons:
|
|
||||||
// 1. Checking those during the main loop is harmful to optimization, and
|
|
||||||
// 2. It clutters the code more, and it's not in great shape to begin with
|
|
||||||
bool bad = false;
|
|
||||||
for (auto attr : *attrmap) {
|
|
||||||
if ((attr & 0b111) > palettes.size()) {
|
|
||||||
error("Referencing palette %u, but there are only %zu!");
|
|
||||||
bad = true;
|
|
||||||
}
|
|
||||||
if (attr & 0x08 && !tilemap) {
|
|
||||||
warning("Tile in bank 1 but no tilemap specified; ignoring the bank bit");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bad) {
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tilemap) {
|
|
||||||
if (attrmap) {
|
|
||||||
for (auto [id, attr] : zip(*tilemap, *attrmap)) {
|
|
||||||
bool bank = attr & 1 << 3;
|
|
||||||
if (id >= options.maxNbTiles[bank]) {
|
|
||||||
warning("Tile #%" PRIu8
|
|
||||||
" was referenced, but the limit for bank %u is %" PRIu16,
|
|
||||||
id, bank, options.maxNbTiles[bank]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (auto id : *tilemap) {
|
|
||||||
if (id >= options.maxNbTiles[0]) {
|
|
||||||
warning("Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16, id,
|
|
||||||
options.maxNbTiles[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefaultInitVec<uint8_t>> palmap;
|
|
||||||
if (!options.palmap.empty()) {
|
|
||||||
palmap = readInto(options.palmap);
|
|
||||||
if (palmap->size() != nbTileInstances) {
|
|
||||||
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(),
|
|
||||||
nbTileInstances);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
|
||||||
std::filebuf pngFile;
|
|
||||||
pngFile.open(options.input, std::ios::out | std::ios::binary);
|
|
||||||
png_structp png = png_create_write_struct(
|
|
||||||
PNG_LIBPNG_VER_STRING,
|
|
||||||
const_cast<png_voidp>(static_cast<void const *>(options.input.c_str())), pngError,
|
|
||||||
pngWarning);
|
|
||||||
if (!png) {
|
|
||||||
fatal("Couldn't create PNG write struct: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
png_infop pngInfo = png_create_info_struct(png);
|
|
||||||
if (!pngInfo) {
|
|
||||||
fatal("Couldn't create PNG info struct: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
|
||||||
|
|
||||||
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
|
||||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
png_write_info(png, pngInfo);
|
|
||||||
|
|
||||||
png_color_8 sbitChunk;
|
|
||||||
sbitChunk.red = 5;
|
|
||||||
sbitChunk.green = 5;
|
|
||||||
sbitChunk.blue = 5;
|
|
||||||
sbitChunk.alpha = 1;
|
|
||||||
png_set_sBIT(png, pngInfo, &sbitChunk);
|
|
||||||
|
|
||||||
constexpr uint8_t SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component)
|
|
||||||
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
|
|
||||||
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
|
||||||
uint8_t * const rowPtrs[8] = {
|
|
||||||
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
|
|
||||||
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
|
|
||||||
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW],
|
|
||||||
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (size_t ty = 0; ty < height; ++ty) {
|
|
||||||
for (size_t tx = 0; tx < width; ++tx) {
|
|
||||||
size_t index = options.columnMajor ? ty + tx * width : ty * width + tx;
|
|
||||||
// By default, a tile is unflipped, in bank 0, and uses palette #0
|
|
||||||
uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00;
|
|
||||||
bool bank = attribute & 0x08;
|
|
||||||
// Get the tile ID at this location
|
|
||||||
uint8_t tileID = index;
|
|
||||||
if (tilemap.has_value()) {
|
|
||||||
tileID =
|
|
||||||
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
|
||||||
}
|
|
||||||
assert(tileID < nbTileInstances); // Should have been checked earlier
|
|
||||||
size_t palID = palmap ? (*palmap)[index] : attribute & 0b111;
|
|
||||||
assert(palID < palettes.size()); // Should be ensured on data read
|
|
||||||
|
|
||||||
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
|
||||||
static std::array<uint8_t, 16> const trimmedTile{
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
};
|
|
||||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
|
||||||
? trimmedTile.data()
|
|
||||||
: &tiles[tileID * tileSize];
|
|
||||||
auto const &palette = palettes[palID];
|
|
||||||
for (uint8_t y = 0; y < 8; ++y) {
|
|
||||||
// If vertically mirrored, fetch the bytes from the other end
|
|
||||||
uint8_t realY = attribute & 0x40 ? 7 - y : y;
|
|
||||||
uint8_t bitplane0 = tileData[realY * 2], bitplane1 = tileData[realY * 2 + 1];
|
|
||||||
if (attribute & 0x20) { // Handle horizontal flip
|
|
||||||
bitplane0 = flipTable[bitplane0];
|
|
||||||
bitplane1 = flipTable[bitplane1];
|
|
||||||
}
|
|
||||||
uint8_t *ptr = &rowPtrs[y][tx * 8 * SIZEOF_PIXEL];
|
|
||||||
for (uint8_t x = 0; x < 8; ++x) {
|
|
||||||
uint8_t bit0 = bitplane0 & 0x80, bit1 = bitplane1 & 0x80;
|
|
||||||
Rgba const &pixel = palette[bit0 >> 7 | bit1 >> 6];
|
|
||||||
*ptr++ = pixel.red;
|
|
||||||
*ptr++ = pixel.green;
|
|
||||||
*ptr++ = pixel.blue;
|
|
||||||
*ptr++ = pixel.alpha;
|
|
||||||
|
|
||||||
// Shift the pixel out
|
|
||||||
bitplane0 <<= 1;
|
|
||||||
bitplane1 <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We never modify the pointers, and neither should libpng, despite the overly lax function
|
|
||||||
// signature.
|
|
||||||
// (AIUI, casting away const-ness is okay as long as you don't actually modify the
|
|
||||||
// pointed-to data)
|
|
||||||
png_write_rows(png, const_cast<png_bytepp>(rowPtrs), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize the write
|
|
||||||
png_write_end(png, pngInfo);
|
|
||||||
|
|
||||||
png_destroy_write_struct(&png, &pngInfo);
|
|
||||||
pngFile.close();
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#include "gfx/rgba.hpp"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "gfx/main.hpp" // options
|
|
||||||
|
|
||||||
/*
|
|
||||||
* based on the Gaussian-like curve used by SameBoy since commit
|
|
||||||
* 65dd02cc52f531dbbd3a7e6014e99d5b24e71a4c (Oct 2017)
|
|
||||||
* with ties resolved by comparing the difference of the squares.
|
|
||||||
*/
|
|
||||||
static std::array<uint8_t, 256> reverse_curve{
|
|
||||||
0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, // These
|
|
||||||
5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, // comments
|
|
||||||
7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, // prevent
|
|
||||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, // clang-format
|
|
||||||
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // from
|
|
||||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, // reflowing
|
|
||||||
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, // these
|
|
||||||
14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // sixteen
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, // 16-item
|
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, // lines,
|
|
||||||
18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // which,
|
|
||||||
19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, // in
|
|
||||||
21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, // my
|
|
||||||
22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, // opinion,
|
|
||||||
24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, // help
|
|
||||||
26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 30, 30, 31, // visualization!
|
|
||||||
};
|
|
||||||
|
|
||||||
uint16_t Rgba::cgbColor() const {
|
|
||||||
if (isTransparent()) {
|
|
||||||
return transparent;
|
|
||||||
}
|
|
||||||
assert(isOpaque());
|
|
||||||
|
|
||||||
uint8_t r = red, g = green, b = blue;
|
|
||||||
if (options.useColorCurve) {
|
|
||||||
g = g * 4 < b ? 0 : (g * 4 - b) / 3;
|
|
||||||
r = reverse_curve[r];
|
|
||||||
g = reverse_curve[g];
|
|
||||||
b = reverse_curve[b];
|
|
||||||
}
|
|
||||||
return (r >> 3) | (g >> 3) << 5 | (b >> 3) << 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Rgba::grayIndex() const {
|
|
||||||
assert(isGray());
|
|
||||||
// Convert from [0; 256[ to [0; maxOpaqueColors[
|
|
||||||
return static_cast<uint16_t>(255 - red) * options.maxOpaqueColors() / 256;
|
|
||||||
}
|
|
||||||
160
src/gfx/rgbgfx.1
Normal file
160
src/gfx/rgbgfx.1
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
.\"
|
||||||
|
.\" This file is part of RGBDS.
|
||||||
|
.\"
|
||||||
|
.\" Copyright (c) 2013-2021, stag019 and RGBDS contributors.
|
||||||
|
.\"
|
||||||
|
.\" SPDX-License-Identifier: MIT
|
||||||
|
.\"
|
||||||
|
.Dd March 28, 2021
|
||||||
|
.Dt RGBGFX 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm rgbgfx
|
||||||
|
.Nd Game Boy graphics converter
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Op Fl CDhmuVv
|
||||||
|
.Op Fl f | Fl F
|
||||||
|
.Op Fl a Ar attrmap | Fl A
|
||||||
|
.Op Fl d Ar depth
|
||||||
|
.Op Fl o Ar out_file
|
||||||
|
.Op Fl p Ar pal_file | Fl P
|
||||||
|
.Op Fl t Ar tilemap | Fl T
|
||||||
|
.Op Fl x Ar tiles
|
||||||
|
.Ar file
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
program converts PNG images into the Nintendo Game Boy's planar tile format.
|
||||||
|
.Pp
|
||||||
|
The resulting colors and their palette indices are determined differently depending on the input PNG file:
|
||||||
|
.Bl -dash -width Ds
|
||||||
|
.It
|
||||||
|
If the file has an embedded palette, that palette's color and order are used.
|
||||||
|
.It
|
||||||
|
If not, and the image only contains shades of gray, rgbgfx maps them to the indices appropriate for each shade.
|
||||||
|
Any undetermined indices are set to respective default shades of gray.
|
||||||
|
For example: if the bit depth is 2 and the image contains light gray and black, they become the second and fourth colors, and the first and third colors get set to default white and dark gray.
|
||||||
|
If the image has multiple shades that map to the same index, the palette is instead determined as if the image had color.
|
||||||
|
.It
|
||||||
|
If the image has color (or the grayscale method failed), the colors are sorted from lightest to darkest.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The input image may not contain more colors than the selected bit depth allows.
|
||||||
|
Transparent pixels are set to palette index 0.
|
||||||
|
.Sh ARGUMENTS
|
||||||
|
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
|
.Fl Fl verb
|
||||||
|
is
|
||||||
|
.Fl Fl verbose ,
|
||||||
|
but
|
||||||
|
.Fl Fl ver
|
||||||
|
is invalid because it could also be
|
||||||
|
.Fl Fl version .
|
||||||
|
The arguments are as follows:
|
||||||
|
.Bl -tag -width Ds
|
||||||
|
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
||||||
|
Generate a file of tile mirroring attributes for OAM or (CGB-only) background tiles.
|
||||||
|
For each tile in the input file, a byte is written representing the dimensions that the associated tile in the output file should be mirrored.
|
||||||
|
Useful in combination with
|
||||||
|
.Fl m
|
||||||
|
to keep track the mirror direction of mirrored duplicate tiles.
|
||||||
|
.It Fl A , Fl Fl output-attr-map
|
||||||
|
Same as
|
||||||
|
.Fl a ,
|
||||||
|
but the attrmap file output name is made by taking the input filename, removing the file extension, and appending
|
||||||
|
.Pa .attrmap .
|
||||||
|
.It Fl C , Fl Fl color-curve
|
||||||
|
Use the color curve of the Game Boy Color when generating palettes.
|
||||||
|
.It Fl D , Fl Fl debug
|
||||||
|
Debug features are enabled.
|
||||||
|
.It Fl d Ar depth , Fl Fl depth Ar depth
|
||||||
|
The bit depth of the output image (either 1 or 2).
|
||||||
|
By default, the bit depth is 2 (two bits per pixel).
|
||||||
|
.It Fl f , Fl Fl fix
|
||||||
|
Fix the input PNG file to be a correctly indexed image.
|
||||||
|
.It Fl F , Fl Fl fix-and-save
|
||||||
|
Same as
|
||||||
|
.Fl f ,
|
||||||
|
but additionally, the supplied command line parameters are saved within the PNG and will be loaded and automatically used next time.
|
||||||
|
.It Fl h , Fl Fl horizontal
|
||||||
|
Lay out tiles in column-major order (column by column), instead of the default row-major order (line by line).
|
||||||
|
Especially useful for "8x16" OBJ mode, if the input image is 16 pixels tall.
|
||||||
|
.It Fl m , Fl Fl mirror-tiles
|
||||||
|
Truncate tiles by checking for tiles that are mirrored versions of others and omitting these from the output file.
|
||||||
|
Useful with tilemaps and attrmaps together to keep track of the duplicated tiles and the dimension mirrored.
|
||||||
|
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
||||||
|
Implies
|
||||||
|
.Fl u .
|
||||||
|
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
||||||
|
The name of the output file.
|
||||||
|
.It Fl p Ar pal_file , Fl Fl palette Ar pal_file
|
||||||
|
Output the image's palette in standard GBC palette format: bytes (8 bytes for two bits per pixel, 4 bytes for one bit per pixel) containing the RGB15 values in little-endian byte order.
|
||||||
|
If the palette contains too few colors, the remaining entries are set to black.
|
||||||
|
.It Fl P , Fl Fl output-palette
|
||||||
|
Same as
|
||||||
|
.Fl p ,
|
||||||
|
but the palette file output name is made by taking the input PNG file's filename, removing the file extension, and appending
|
||||||
|
.Pa .pal .
|
||||||
|
.It Fl t Ar tilemap , Fl Fl tilemap Ar tilemap
|
||||||
|
Generate a file of tile indices.
|
||||||
|
For each tile in the input file, a byte is written representing the index of the associated tile in the output file.
|
||||||
|
Useful in combination with
|
||||||
|
.Fl u
|
||||||
|
or
|
||||||
|
.Fl m
|
||||||
|
to keep track of duplicate tiles.
|
||||||
|
.It Fl T , Fl Fl output-tilemap
|
||||||
|
Same as
|
||||||
|
.Fl t ,
|
||||||
|
but the tilemap file output name is made by taking the input filename, removing the file extension, and appending
|
||||||
|
.Pa .tilemap .
|
||||||
|
.It Fl u , Fl Fl unique-tiles
|
||||||
|
Truncate tiles by checking for tiles that are exact duplicates of others and omitting these from the output file.
|
||||||
|
Useful with tilemaps to keep track of the duplicated tiles.
|
||||||
|
.It Fl V , Fl Fl version
|
||||||
|
Print the version of the program and exit.
|
||||||
|
.It Fl v , Fl Fl verbose
|
||||||
|
Verbose.
|
||||||
|
Print errors when the command line parameters and the parameters in the PNG file don't match.
|
||||||
|
.It Fl x Ar tiles , Fl Fl trim-end Ar tiles
|
||||||
|
Trim the end of the output file by this many tiles.
|
||||||
|
.El
|
||||||
|
.Sh EXAMPLES
|
||||||
|
The following will take a PNG file with a bit depth of 1, 2, or 8, and output planar 2bpp data:
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx -o out.2bpp in.png
|
||||||
|
.Pp
|
||||||
|
The following creates a planar 2bpp file with only unique tiles, and its tilemap
|
||||||
|
.Pa out.tilemap :
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx -T -u -o out.2bpp in.png
|
||||||
|
.Pp
|
||||||
|
The following creates a planar 2bpp file with only unique tiles
|
||||||
|
.Pa accounting for tile mirroring
|
||||||
|
and its associated tilemap
|
||||||
|
.Pa out.tilemap
|
||||||
|
and attrmap
|
||||||
|
.Pa out.attrmap :
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx -A -T -m -o out.2bpp in.png
|
||||||
|
.Pp
|
||||||
|
The following will do nothing:
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx in.png
|
||||||
|
.Sh BUGS
|
||||||
|
Please report bugs on
|
||||||
|
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr rgbds 7 ,
|
||||||
|
.Xr rgbasm 1 ,
|
||||||
|
.Xr rgblink 1 ,
|
||||||
|
.Xr rgbfix 1 ,
|
||||||
|
.Xr gbz80 7
|
||||||
|
.Sh HISTORY
|
||||||
|
.Nm
|
||||||
|
was created by
|
||||||
|
.An stag019
|
||||||
|
to be included in RGBDS.
|
||||||
|
It is now maintained by a number of contributors at
|
||||||
|
.Lk https://github.com/gbdev/rgbds .
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -447,27 +446,9 @@ void assign_AssignSections(void)
|
|||||||
|
|
||||||
/* Overlaying requires only fully-constrained sections */
|
/* Overlaying requires only fully-constrained sections */
|
||||||
verbosePrint("Assigning other sections...\n");
|
verbosePrint("Assigning other sections...\n");
|
||||||
if (overlayFileName) {
|
if (overlayFileName)
|
||||||
fprintf(stderr, "FATAL: All sections must be fixed when using an overlay file");
|
errx("All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
|
||||||
uint8_t nbSections = 0;
|
nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
|
||||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0; constraints--) {
|
|
||||||
for (sectionPtr = unassignedSections[constraints];
|
|
||||||
sectionPtr;
|
|
||||||
sectionPtr = sectionPtr->next) {
|
|
||||||
fprintf(stderr, "%c \"%s\"",
|
|
||||||
nbSections == 0 ? ';': ',', sectionPtr->section->name);
|
|
||||||
nbSections++;
|
|
||||||
if (nbSections == 10)
|
|
||||||
goto max_out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
max_out:
|
|
||||||
if (nbSectionsToAssign != nbSections)
|
|
||||||
fprintf(stderr, " and %" PRIu64 " more", nbSectionsToAssign - nbSections);
|
|
||||||
fprintf(stderr, " %sn't\n", nbSectionsToAssign == 1 ? "is" : "are");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Assign all remaining sections by decreasing constraint order */
|
/* Assign all remaining sections by decreasing constraint order */
|
||||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
|
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
|
||||||
|
|||||||
@@ -151,10 +151,8 @@ static char *readstr(FILE *file)
|
|||||||
/* Read char */
|
/* Read char */
|
||||||
int byte = getc(file);
|
int byte = getc(file);
|
||||||
|
|
||||||
if (byte == EOF) {
|
if (byte == EOF)
|
||||||
free(str);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
str[index] = byte;
|
str[index] = byte;
|
||||||
} while (str[index]);
|
} while (str[index]);
|
||||||
return str;
|
return str;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ struct SortedSymbol {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
uint32_t nbBanks; // Size of the array below (which may be NULL if this is 0)
|
uint32_t nbBanks;
|
||||||
struct SortedSections {
|
struct SortedSections {
|
||||||
struct SortedSection *sections;
|
struct SortedSection *sections;
|
||||||
struct SortedSection *zeroLenSections;
|
struct SortedSection *zeroLenSections;
|
||||||
@@ -243,8 +243,8 @@ static void writeROM(void)
|
|||||||
coverOverlayBanks(nbOverlayBanks);
|
coverOverlayBanks(nbOverlayBanks);
|
||||||
|
|
||||||
if (outputFile) {
|
if (outputFile) {
|
||||||
writeBank(sections[SECTTYPE_ROM0].banks ? sections[SECTTYPE_ROM0].banks[0].sections
|
if (sections[SECTTYPE_ROM0].nbBanks > 0)
|
||||||
: NULL,
|
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
|
||||||
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
|
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
|
||||||
|
|
||||||
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
add_executable(randtilegen gfx/randtilegen.c)
|
|
||||||
|
|
||||||
add_executable(rgbgfx_test gfx/rgbgfx_test.cpp)
|
|
||||||
|
|
||||||
install(TARGETS randtilegen rgbgfx_test DESTINATION ${rgbds_SOURCE_DIR}/test/gfx)
|
|
||||||
|
|
||||||
foreach(TARGET randtilegen rgbgfx_test)
|
|
||||||
if(LIBPNG_FOUND) # pkg-config
|
|
||||||
target_include_directories(${TARGET} PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
|
||||||
target_link_directories(${TARGET} PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
|
||||||
target_link_libraries(${TARGET} PRIVATE ${LIBPNG_LIBRARIES})
|
|
||||||
else()
|
|
||||||
target_compile_definitions(${TARGET} PRIVATE ${PNG_DEFINITIONS})
|
|
||||||
target_include_directories(${TARGET} PRIVATE ${PNG_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(${TARGET} PRIVATE ${PNG_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
3
test/asm/deprecated-pi.asm
Normal file
3
test/asm/deprecated-pi.asm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
; Remove this test case when _PI is removed.
|
||||||
|
PRINTLN "{f:_PI}"
|
||||||
|
PURGE _PI
|
||||||
5
test/asm/deprecated-pi.err
Normal file
5
test/asm/deprecated-pi.err
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
warning: deprecated-pi.asm(2): [-Wobsolete]
|
||||||
|
`_PI` is deprecated; use 3.14159
|
||||||
|
error: deprecated-pi.asm(3):
|
||||||
|
Built-in symbol '_PI' cannot be purged
|
||||||
|
error: Assembly aborted (1 error)!
|
||||||
1
test/asm/deprecated-pi.out
Normal file
1
test/asm/deprecated-pi.out
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.14159
|
||||||
@@ -10,8 +10,6 @@ pusho
|
|||||||
println $8000_0000 / -1
|
println $8000_0000 / -1
|
||||||
popo
|
popo
|
||||||
|
|
||||||
opt H, l
|
|
||||||
|
|
||||||
ds 1
|
ds 1
|
||||||
ld [$ff88], a
|
ld [$ff88], a
|
||||||
halt
|
halt
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
warning: opt.asm(18): [-Wdiv]
|
warning: opt.asm(16): [-Wdiv]
|
||||||
Division of -2147483648 by -1 yields -2147483648
|
Division of -2147483648 by -1 yields -2147483648
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
export LC_ALL=C
|
export LC_ALL=C
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
MACRO print_all
|
|
||||||
REPT _NARG
|
|
||||||
PRINTLN "{d:_NARG}: \"\1\""
|
|
||||||
SHIFT
|
|
||||||
ENDR
|
|
||||||
ENDM
|
|
||||||
|
|
||||||
print_all a, \
|
|
||||||
b \
|
|
||||||
, c
|
|
||||||
|
|
||||||
DEF EMPTY equs ""
|
|
||||||
print_all a, \
|
|
||||||
{EMPTY} b \
|
|
||||||
{EMPTY}, c
|
|
||||||
|
|
||||||
print_all a, \
|
|
||||||
/* . */ b \
|
|
||||||
/* . */, c
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
3: "a"
|
|
||||||
2: "b"
|
|
||||||
1: "c"
|
|
||||||
3: "a"
|
|
||||||
2: "b"
|
|
||||||
1: "c"
|
|
||||||
3: "a"
|
|
||||||
2: " b"
|
|
||||||
1: "c"
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
fname=$(mktemp)
|
fname=$(mktemp)
|
||||||
|
|
||||||
for i in *.asm; do
|
for i in *.asm; do
|
||||||
|
|||||||
@@ -80,23 +80,22 @@ echo "${bold}Checking padding...${resbold}"
|
|||||||
cp "$src"/padding{,-large,-larger}.bin .
|
cp "$src"/padding{,-large,-larger}.bin .
|
||||||
touch padding{,-large,-larger}.err
|
touch padding{,-large,-larger}.err
|
||||||
progress=0
|
progress=0
|
||||||
for (( i=0; i < 10; ++i )); do
|
for b in {0..254}; do
|
||||||
(( padding = RANDOM % 256 ))
|
printf "\r$b..."
|
||||||
echo "$padding..."
|
|
||||||
for suffix in '' -large -larger; do
|
for suffix in '' -large -larger; do
|
||||||
cat <<<"-p $padding" >padding$suffix.flags
|
cat <<<'-p $b' >padding$suffix.flags
|
||||||
tr '\377' \\$((padding / 64))$(((padding / 8) % 8))$((padding % 8)) <"$src/padding$suffix.gb" >padding$suffix.gb # OK because $FF bytes are only used for padding
|
tr '\377' \\$(($b / 64))$((($b / 8) % 8))$(($b % 8)) <"$src/padding$suffix.gb" >padding$suffix.gb # OK because $FF bytes are only used for padding
|
||||||
runTest padding${suffix} .
|
runTest padding${suffix} .
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
echo "Done!"
|
printf "\rDone! \n"
|
||||||
|
|
||||||
# TODO: check MBC names
|
# TODO: check MBC names
|
||||||
|
|
||||||
# Check that RGBFIX errors out when inputting a non-existent file...
|
# Check that RGBFIX errors out when inputting a non-existent file...
|
||||||
$RGBFIX noexist 2>out.err
|
$RGBFIX noexist 2>out.err
|
||||||
rc=$((rc || $? != 1))
|
rc=$(($rc || $? != 1))
|
||||||
tryDiff "$src/noexist.err" out.err noexist.err
|
tryDiff "$src/noexist.err" out.err noexist.err
|
||||||
rc=$((rc || $?))
|
rc=$(($rc || $?))
|
||||||
|
|
||||||
exit $rc
|
exit $rc
|
||||||
|
|||||||
11
test/gfx/.gitignore
vendored
11
test/gfx/.gitignore
vendored
@@ -1,11 +0,0 @@
|
|||||||
# Test binaries
|
|
||||||
/randtilegen
|
|
||||||
/rgbgfx_test
|
|
||||||
# Generated by randtilegen
|
|
||||||
/out*.png
|
|
||||||
/*.rng
|
|
||||||
# Generated by the test program
|
|
||||||
/result.2bpp
|
|
||||||
/result.pal
|
|
||||||
/result.attrmap
|
|
||||||
/result.png
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
error: Could not fit tile colors [$6c8a, $7f55, $7fff] in specified palettes
|
|
||||||
error: Could not fit tile colors [$6c8a, $7f55] in specified palettes
|
|
||||||
note: The following palettes were specified:
|
|
||||||
[$7fff, $7f55]
|
|
||||||
[$7fff, $6c8a]
|
|
||||||
Conversion aborted after 2 errors
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
-c #fff,#a9d4fe:#fff,#5721d9
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 712 B |
@@ -1 +0,0 @@
|
|||||||
-L 2,1:1,1
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 671 B |
@@ -1,2 +0,0 @@
|
|||||||
FATAL: Error reading input image ("damaged1.png"): IDAT: invalid code -- missing end-of-block
|
|
||||||
Conversion aborted after 1 error
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 433 B |
@@ -1,2 +0,0 @@
|
|||||||
FATAL: Error reading input image ("damaged2.png"): IDAT: invalid code -- missing end-of-block
|
|
||||||
Conversion aborted after 1 error
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 433 B |
@@ -1,2 +0,0 @@
|
|||||||
FATAL: Error reading input image ("damaged9.png"): IDAT: invalid code -- missing end-of-block
|
|
||||||
Conversion aborted after 1 error
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 433 B |
@@ -1,320 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Originally:
|
|
||||||
* // This program is hereby released to the public domain.
|
|
||||||
* // ~aaaaaa123456789, released 2022-03-15
|
|
||||||
* https://gist.github.com/aaaaaa123456789/3feccf085ab4f82d144d9a47fb1b4bdf
|
|
||||||
*
|
|
||||||
* This was modified to use libpng instead of libplum, as well as comments and style changes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <png.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "platform.h"
|
|
||||||
|
|
||||||
#define STR(x) #x
|
|
||||||
#define XSTR(x) STR(x)
|
|
||||||
|
|
||||||
struct Attributes {
|
|
||||||
unsigned char palette;
|
|
||||||
unsigned char nbColors;
|
|
||||||
};
|
|
||||||
|
|
||||||
static unsigned long long randbits = 0;
|
|
||||||
static unsigned char randcount = 0;
|
|
||||||
|
|
||||||
static _Noreturn void fatal(char const *error) {
|
|
||||||
fprintf(stderr, "FATAL: %s\n", error);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static FILE *seed;
|
|
||||||
|
|
||||||
static unsigned long long getRandomBits(unsigned count) {
|
|
||||||
while (count > randcount) {
|
|
||||||
// Get new random bytes from stdin (assumed to be a stream of random data) to fulfill the
|
|
||||||
// random bits request
|
|
||||||
int data = getc(seed);
|
|
||||||
if (data == EOF) {
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
randbits |= (unsigned long long)data << randcount;
|
|
||||||
randcount += 8;
|
|
||||||
}
|
|
||||||
unsigned long long result = randbits & ((1ull << count) - 1);
|
|
||||||
randbits >>= count;
|
|
||||||
randcount -= count;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generate_tile_attributes(struct Attributes * restrict attributes) {
|
|
||||||
/*
|
|
||||||
* Images have ten colors, grouped into two groups of 5 colors. The palette index indicates two
|
|
||||||
* things: which one of those groups will be used, and which colors out of those 5 will be used
|
|
||||||
* by the tile. The low bit indicates the group, and the rest of the value indicates the subset
|
|
||||||
* of colors. The remainder of the number is treated as a bitfield, where each bit represents a
|
|
||||||
* color: for instance, a value of 13 in the upper bits (binary 01101) indicates that colors 0,
|
|
||||||
* 2 and 3 from that group will be used. Values of 0 and 31 are naturally invalid because they
|
|
||||||
* indicate zero and five colors respectively, and 30 is also excluded to ensure that the
|
|
||||||
* particular subset of colors 1, 2, 3 and 4 never shows up. This guarantees that every tile
|
|
||||||
* will be representable using a palette containing color 0 (since those that don't contain
|
|
||||||
* color 0 will have three colors at most), which in turn ensures that only 4 palettes per group
|
|
||||||
* (and thus 8 total) are needed to cover the image: 0, 1, 2, 3; 0, 1, 2, 4; 0, 1, 3, 4; and 0,
|
|
||||||
* 2, 3, 4. This also implies that making color 0 transparent (in both groups) adds a
|
|
||||||
* transparent color to every palette.
|
|
||||||
*/
|
|
||||||
unsigned char pal;
|
|
||||||
do {
|
|
||||||
pal = getRandomBits(5);
|
|
||||||
} while (pal == 0 || (pal > 29));
|
|
||||||
attributes->palette = 2 * pal + getRandomBits(1);
|
|
||||||
|
|
||||||
// Use an array to look up the number of colors in the palette; this is faster (and simpler)
|
|
||||||
// than doing a population count over the bits
|
|
||||||
static char const popcount[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3,
|
|
||||||
4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4};
|
|
||||||
attributes->nbColors = popcount[pal];
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generate_tile_data(unsigned char tiledata[ARR_QUALS(restrict) MIN_NB_ELMS(8)][8],
|
|
||||||
unsigned colorcount) {
|
|
||||||
switch (colorcount) {
|
|
||||||
case 2: // 1bpp
|
|
||||||
for (uint8_t y = 0; y < 8; y++) {
|
|
||||||
for (uint8_t x = 0; x < 8; x++) {
|
|
||||||
tiledata[y][x] = getRandomBits(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4: // 2bpp
|
|
||||||
for (uint8_t y = 0; y < 8; y++) {
|
|
||||||
for (uint8_t x = 0; x < 8; x++) {
|
|
||||||
tiledata[y][x] = getRandomBits(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3: // 2bpp with resampling
|
|
||||||
for (uint8_t y = 0; y < 8; y++) {
|
|
||||||
for (uint8_t x = 0; x < 8; x++) {
|
|
||||||
do {
|
|
||||||
tiledata[y][x] = getRandomBits(2);
|
|
||||||
} while (tiledata[y][x] == 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't mark as `const`, as the array type is otherwise not compatible (augh)
|
|
||||||
static void
|
|
||||||
copy_tile_data(unsigned char destination[ARR_QUALS(restrict) MIN_NB_ELMS(8)][8],
|
|
||||||
unsigned char /* const */ source[ARR_QUALS(restrict) MIN_NB_ELMS(8)][8]) {
|
|
||||||
// Apply a random rotation to the copy
|
|
||||||
// coord ^ 7 = inverted coordinate; coord ^ 0 = regular coordinate
|
|
||||||
unsigned xmask = getRandomBits(1) * 7;
|
|
||||||
unsigned ymask = getRandomBits(1) * 7;
|
|
||||||
for (unsigned y = 0; y < 8; y++) {
|
|
||||||
for (unsigned x = 0; x < 8; x++) {
|
|
||||||
destination[y][x] = source[y ^ ymask][x ^ xmask];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generate_palettes(uint16_t palettes[ARR_QUALS(restrict) MIN_NB_ELMS(60)][4]) {
|
|
||||||
uint16_t colors[10];
|
|
||||||
// Generate 10 random colors (two groups of 5 colors)
|
|
||||||
for (unsigned p = 0; p < 10; p++) {
|
|
||||||
colors[p] = getRandomBits(15);
|
|
||||||
}
|
|
||||||
// Potentially make the first color of each group transparent
|
|
||||||
if (!getRandomBits(2)) {
|
|
||||||
colors[0] |= 0x8000;
|
|
||||||
colors[5] |= 0x8000;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned p = 0; p < 60; p++) {
|
|
||||||
uint16_t const *group = colors + 5 * (p & 1);
|
|
||||||
uint16_t *palette = palettes[p];
|
|
||||||
for (unsigned index = 0; index < 5; index++) {
|
|
||||||
if (p & (2 << index)) {
|
|
||||||
*(palette++) = group[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expand a 5-bit color component to 8 bits with minimal bias
|
|
||||||
*/
|
|
||||||
static uint8_t _5to8(uint8_t five) {
|
|
||||||
return five << 3 | five >> 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't mark as `const`, as the array type is otherwise not compatible (augh)
|
|
||||||
static void write_image(char const *filename, uint16_t /* const */ palettes[MIN_NB_ELMS(60)][4],
|
|
||||||
unsigned char /* const */ (*tileData)[8][8],
|
|
||||||
struct Attributes const *attributes, uint8_t width, uint8_t height) {
|
|
||||||
uint8_t const nbTiles = width * height;
|
|
||||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
|
||||||
png_infop pngInfo = png_create_info_struct(png);
|
|
||||||
|
|
||||||
if (setjmp(png_jmpbuf(png))) {
|
|
||||||
fprintf(stderr, "FATAL: An error occurred while writing image \"%s\"", filename);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *file = fopen(filename, "wb");
|
|
||||||
if (file == NULL) {
|
|
||||||
fprintf(stderr, "FATAL: Failed to open \"%s\": %s\n", filename, strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
png_init_io(png, file);
|
|
||||||
|
|
||||||
png_set_IHDR(png, pngInfo, width * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
|
||||||
getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
|
|
||||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
|
|
||||||
// While it would be nice to write the image little by little, I really don't want to handle
|
|
||||||
// interlacing myself. (We're doing interlacing to test that RGBGFX correctly handles it.)
|
|
||||||
uint8_t const SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component)
|
|
||||||
assert(width != 0);
|
|
||||||
assert(height != 0);
|
|
||||||
uint8_t *data = malloc(height * 8 * width * 8 * SIZEOF_PIXEL);
|
|
||||||
uint8_t **rowPtrs = malloc(height * 8 * sizeof(*rowPtrs));
|
|
||||||
if (data == NULL || rowPtrs == NULL) {
|
|
||||||
fatal("Out of memory");
|
|
||||||
}
|
|
||||||
for (uint8_t y = 0; y < height * 8; ++y) {
|
|
||||||
rowPtrs[y] = &data[y * width * 8 * SIZEOF_PIXEL];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t p = 0; p < nbTiles; p++) {
|
|
||||||
uint8_t const tx = 8 * (p % width), ty = 8 * (p / width);
|
|
||||||
for (uint8_t y = 0; y < 8; y++) {
|
|
||||||
uint8_t * const row = rowPtrs[ty + y];
|
|
||||||
for (uint8_t x = 0; x < 8; x++) {
|
|
||||||
uint8_t * const pixel = &row[(tx + x) * SIZEOF_PIXEL];
|
|
||||||
uint16_t const color = palettes[attributes[p].palette][tileData[p][y][x]];
|
|
||||||
pixel[0] = _5to8(color & 0x1F);
|
|
||||||
pixel[1] = _5to8(color >> 5 & 0x1F);
|
|
||||||
pixel[2] = _5to8(color >> 10 & 0x1F);
|
|
||||||
pixel[3] = color & 0x8000 ? 0x00 : 0xFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
png_set_rows(png, pngInfo, rowPtrs);
|
|
||||||
png_write_png(png, pngInfo, PNG_TRANSFORM_IDENTITY, NULL);
|
|
||||||
fclose(file);
|
|
||||||
free(rowPtrs);
|
|
||||||
free(data);
|
|
||||||
png_destroy_write_struct(&png, &pngInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generate_random_image(char const *filename) {
|
|
||||||
#define MIN_TILES_PER_SIDE 3
|
|
||||||
#define MAX_TILES ((MIN_TILES_PER_SIDE + 7) * (MIN_TILES_PER_SIDE + 7))
|
|
||||||
struct Attributes attributes[MAX_TILES];
|
|
||||||
unsigned char tileData[MAX_TILES][8][8];
|
|
||||||
uint8_t width = getRandomBits(3) + MIN_TILES_PER_SIDE,
|
|
||||||
height = getRandomBits(3) + MIN_TILES_PER_SIDE;
|
|
||||||
for (uint8_t tile = 0; tile < (width * height); tile++) {
|
|
||||||
generate_tile_attributes(attributes + tile);
|
|
||||||
// If a tile contains only one color, then there's no tile data to generate: all pixels will
|
|
||||||
// use color 0
|
|
||||||
if (attributes[tile].nbColors < 2) {
|
|
||||||
memset(tileData[tile], 0, sizeof(tileData[tile]));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find tiles with the same number of colors
|
|
||||||
unsigned index = 0, total = 0;
|
|
||||||
for (; index < tile; index++) {
|
|
||||||
if (attributes[index].nbColors == attributes[tile].nbColors) {
|
|
||||||
total++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(index == tile); // This is used as a sentinel later on to indicate no tile was found
|
|
||||||
|
|
||||||
if (total) {
|
|
||||||
// If there are such tiles, there's a random chance that this tile will replicate one of
|
|
||||||
// those tiles (potentially rotated)
|
|
||||||
index = getRandomBits(8);
|
|
||||||
if (index < total) {
|
|
||||||
total = index + 1;
|
|
||||||
for (index = 0; total; index++) {
|
|
||||||
if (attributes[index].nbColors == attributes[tile].nbColors) {
|
|
||||||
total--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (total == 0) {
|
|
||||||
index--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
index = tile; // Restore the sentinel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index == tile) {
|
|
||||||
generate_tile_data(tileData[tile], attributes[index].nbColors);
|
|
||||||
} else {
|
|
||||||
copy_tile_data(tileData[tile], tileData[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t palettes[60][4];
|
|
||||||
generate_palettes(palettes);
|
|
||||||
write_image(filename, palettes, tileData, attributes, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
if (argc < 3 || argc > 4) {
|
|
||||||
fprintf(stderr, "usage: %s <input file> <basename> [<maxcount>]\n", argv[0]);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
seed = fopen(argv[1], "rb");
|
|
||||||
if (!seed) {
|
|
||||||
fprintf(stderr, "FATAL: Cannot open seed file (%s)\n", strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t const nameLen = strlen(argv[2]);
|
|
||||||
unsigned long long maxcount = ULLONG_MAX;
|
|
||||||
if (argc > 3) {
|
|
||||||
char *error;
|
|
||||||
maxcount = strtoull(argv[3], &error, 0);
|
|
||||||
if (*error != '\0') {
|
|
||||||
fatal("invalid count");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *filename = malloc(nameLen + sizeof(XSTR(ULLONG_MAX) ".png"));
|
|
||||||
if (!filename) {
|
|
||||||
fatal("out of memory");
|
|
||||||
}
|
|
||||||
memcpy(filename, argv[2], nameLen);
|
|
||||||
|
|
||||||
for (unsigned long long count = 0; count < maxcount; count++) {
|
|
||||||
sprintf(&filename[nameLen], "%llu.png", count);
|
|
||||||
generate_random_image(filename);
|
|
||||||
// Reset the global random state so that subsequent images don't share a random byte
|
|
||||||
randbits = 0;
|
|
||||||
randcount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
|
|
||||||
// For `execProg` (Windows is its special little snowflake again)
|
|
||||||
#if !defined(_MSC_VER) && !defined(__MINGW32__)
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
#include <spawn.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#else
|
|
||||||
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h` to avoid conflicts
|
|
||||||
#include <windows.h>
|
|
||||||
#include <errhandlingapi.h>
|
|
||||||
#include <processthreadsapi.h>
|
|
||||||
#undef max // This macro conflicts with `std::numeric_limits<...>::max()`
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
|
||||||
#include <png.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
|
|
||||||
#include "gfx/rgba.hpp" // Reused from RGBGFX
|
|
||||||
|
|
||||||
static uintmax_t nbErrors;
|
|
||||||
|
|
||||||
static void warning(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("warning: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void error(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("error: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
|
||||||
nbErrors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] static void fatal(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("FATAL: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
|
||||||
nbErrors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "Test aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy-pasted from RGBGFX
|
|
||||||
class Png {
|
|
||||||
std::string const &path;
|
|
||||||
std::filebuf file{};
|
|
||||||
png_structp png = nullptr;
|
|
||||||
png_infop info = nullptr;
|
|
||||||
|
|
||||||
// These are cached for speed
|
|
||||||
uint32_t width, height;
|
|
||||||
DefaultInitVec<Rgba> pixels;
|
|
||||||
int colorType;
|
|
||||||
int nbColors;
|
|
||||||
png_colorp embeddedPal = nullptr;
|
|
||||||
png_bytep transparencyPal = nullptr;
|
|
||||||
|
|
||||||
[[noreturn]] static void handleError(png_structp png, char const *msg) {
|
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
|
||||||
|
|
||||||
fatal("Error reading input image (\"%s\"): %s", self->path.c_str(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleWarning(png_structp png, char const *msg) {
|
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
|
||||||
|
|
||||||
warning("In input image (\"%s\"): %s", self->path.c_str(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void readData(png_structp png, png_bytep data, size_t length) {
|
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_io_ptr(png));
|
|
||||||
std::streamsize expectedLen = length;
|
|
||||||
std::streamsize nbBytesRead = self->file.sgetn(reinterpret_cast<char *>(data), expectedLen);
|
|
||||||
|
|
||||||
if (nbBytesRead != expectedLen) {
|
|
||||||
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
|
||||||
"bytes after reading %lld)",
|
|
||||||
self->path.c_str(), length - nbBytesRead,
|
|
||||||
self->file.pubseekoff(0, std::ios_base::cur));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
uint32_t getWidth() const { return width; }
|
|
||||||
|
|
||||||
uint32_t getHeight() const { return height; }
|
|
||||||
|
|
||||||
Rgba &pixel(uint32_t x, uint32_t y) { return pixels[y * width + x]; }
|
|
||||||
|
|
||||||
Rgba const &pixel(uint32_t x, uint32_t y) const { return pixels[y * width + x]; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a PNG and notes all of its colors
|
|
||||||
*
|
|
||||||
* This code is more complicated than strictly necessary, but that's because of the API
|
|
||||||
* being used: the "high-level" interface doesn't provide all the transformations we need,
|
|
||||||
* so we use the "lower-level" one instead.
|
|
||||||
* We also use that occasion to only read the PNG one line at a time, since we store all of
|
|
||||||
* the pixel data in `pixels`, which saves on memory allocations.
|
|
||||||
*/
|
|
||||||
explicit Png(std::string const &filePath) : path(filePath) {
|
|
||||||
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
|
||||||
fatal("Failed to open input image (\"%s\"): %s", path.c_str(), strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<unsigned char, 8> pngHeader;
|
|
||||||
|
|
||||||
if (file.sgetn(reinterpret_cast<char *>(pngHeader.data()), pngHeader.size())
|
|
||||||
!= static_cast<std::streamsize>(pngHeader.size()) // Not enough bytes?
|
|
||||||
|| png_sig_cmp(pngHeader.data(), 0, pngHeader.size()) != 0) {
|
|
||||||
fatal("Input file (\"%s\") is not a PNG image!", path.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError,
|
|
||||||
handleWarning);
|
|
||||||
if (!png) {
|
|
||||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
info = png_create_info_struct(png);
|
|
||||||
if (!info) {
|
|
||||||
png_destroy_read_struct(&png, nullptr, nullptr);
|
|
||||||
fatal("Failed to allocate PNG info structure: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
png_set_read_fn(png, this, readData);
|
|
||||||
png_set_sig_bytes(png, pngHeader.size());
|
|
||||||
|
|
||||||
// TODO: png_set_crc_action(png, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
|
|
||||||
|
|
||||||
// Skipping chunks we don't use should improve performance
|
|
||||||
// TODO: png_set_keep_unknown_chunks(png, ...);
|
|
||||||
|
|
||||||
// Process all chunks up to but not including the image data
|
|
||||||
png_read_info(png, info);
|
|
||||||
|
|
||||||
int bitDepth, interlaceType; //, compressionType, filterMethod;
|
|
||||||
|
|
||||||
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
if (width % 8 != 0) {
|
|
||||||
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
|
|
||||||
}
|
|
||||||
if (height % 8 != 0) {
|
|
||||||
fatal("Image height (%" PRIu32 " pixels) is not a multiple of 8!", height);
|
|
||||||
}
|
|
||||||
|
|
||||||
pixels.resize(static_cast<size_t>(width) * static_cast<size_t>(height));
|
|
||||||
|
|
||||||
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
|
|
||||||
int nbTransparentEntries;
|
|
||||||
if (png_get_tRNS(png, info, &transparencyPal, &nbTransparentEntries, nullptr)) {
|
|
||||||
assert(nbTransparentEntries == nbColors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up transformations; to turn everything into RGBA888
|
|
||||||
// TODO: it's not necessary to uniformize the pixel data (in theory), and not doing
|
|
||||||
// so *might* improve performance, and should reduce memory usage.
|
|
||||||
|
|
||||||
// Convert grayscale to RGB
|
|
||||||
switch (colorType & ~PNG_COLOR_MASK_ALPHA) {
|
|
||||||
case PNG_COLOR_TYPE_GRAY:
|
|
||||||
png_set_gray_to_rgb(png); // This also converts tRNS to alpha
|
|
||||||
break;
|
|
||||||
case PNG_COLOR_TYPE_PALETTE:
|
|
||||||
png_set_palette_to_rgb(png);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (png_get_valid(png, info, PNG_INFO_tRNS)) {
|
|
||||||
// If we read a tRNS chunk, convert it to alpha
|
|
||||||
png_set_tRNS_to_alpha(png);
|
|
||||||
} else if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
|
|
||||||
// Otherwise, if we lack an alpha channel, default to full opacity
|
|
||||||
png_set_add_alpha(png, 0xFFFF, PNG_FILLER_AFTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale 16bpp back to 8 (we don't need all of that precision anyway)
|
|
||||||
if (bitDepth == 16) {
|
|
||||||
png_set_scale_16(png);
|
|
||||||
} else if (bitDepth < 8) {
|
|
||||||
png_set_packing(png);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do NOT call `png_set_interlace_handling`. We want to expand the rows ourselves.
|
|
||||||
|
|
||||||
// Update `info` with the transformations
|
|
||||||
png_read_update_info(png, info);
|
|
||||||
// These shouldn't have changed
|
|
||||||
assert(png_get_image_width(png, info) == width);
|
|
||||||
assert(png_get_image_height(png, info) == height);
|
|
||||||
// These should have changed, however
|
|
||||||
assert(png_get_color_type(png, info) == PNG_COLOR_TYPE_RGBA);
|
|
||||||
assert(png_get_bit_depth(png, info) == 8);
|
|
||||||
|
|
||||||
// Now that metadata has been read, we can process the image data
|
|
||||||
|
|
||||||
size_t nbRowBytes = png_get_rowbytes(png, info);
|
|
||||||
assert(nbRowBytes != 0);
|
|
||||||
DefaultInitVec<png_byte> row(nbRowBytes);
|
|
||||||
|
|
||||||
if (interlaceType == PNG_INTERLACE_NONE) {
|
|
||||||
for (png_uint_32 y = 0; y < height; ++y) {
|
|
||||||
png_read_row(png, row.data(), nullptr);
|
|
||||||
|
|
||||||
for (png_uint_32 x = 0; x < width; ++x) {
|
|
||||||
Rgba rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]);
|
|
||||||
pixel(x, y) = rgba;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assert(interlaceType == PNG_INTERLACE_ADAM7);
|
|
||||||
|
|
||||||
// For interlace to work properly, we must read the image `nbPasses` times
|
|
||||||
for (int pass = 0; pass < PNG_INTERLACE_ADAM7_PASSES; ++pass) {
|
|
||||||
// The interlacing pass must be skipped if its width or height is reported as zero
|
|
||||||
if (PNG_PASS_COLS(width, pass) == 0 || PNG_PASS_ROWS(height, pass) == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
png_uint_32 xStep = 1u << PNG_PASS_COL_SHIFT(pass);
|
|
||||||
png_uint_32 yStep = 1u << PNG_PASS_ROW_SHIFT(pass);
|
|
||||||
|
|
||||||
for (png_uint_32 y = PNG_PASS_START_ROW(pass); y < height; y += yStep) {
|
|
||||||
png_bytep ptr = row.data();
|
|
||||||
png_read_row(png, ptr, nullptr);
|
|
||||||
|
|
||||||
for (png_uint_32 x = PNG_PASS_START_COL(pass); x < width; x += xStep) {
|
|
||||||
Rgba rgba(ptr[0], ptr[1], ptr[2], ptr[3]);
|
|
||||||
pixel(x, y) = rgba;
|
|
||||||
ptr += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't care about chunks after the image data (comments, etc.)
|
|
||||||
png_read_end(png, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
~Png() { png_destroy_read_struct(&png, &info, nullptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
static char *execProg(char const *name, char * const *argv) {
|
|
||||||
#if !defined(_MSC_VER) && !defined(__MINGW32__)
|
|
||||||
pid_t pid;
|
|
||||||
int err = posix_spawn(&pid, argv[0], nullptr, nullptr, argv, nullptr);
|
|
||||||
if (err != 0) {
|
|
||||||
return strerror(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
siginfo_t info;
|
|
||||||
if (waitid(P_PID, pid, &info, WEXITED) != 0) {
|
|
||||||
fatal("Error waiting for %s: %s", name, strerror(errno));
|
|
||||||
} else if (info.si_code != CLD_EXITED) {
|
|
||||||
assert(info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED);
|
|
||||||
fatal("%s was terminated by signal %s%s", name, strsignal(info.si_status),
|
|
||||||
info.si_code == CLD_DUMPED ? " (core dumped)" : "");
|
|
||||||
} else if (info.si_status != 0) {
|
|
||||||
fatal("%s returned with status %d", name, info.si_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else /* defined(_MSC_VER) || defined(__MINGW32__) */
|
|
||||||
|
|
||||||
auto winStrerror = [](DWORD errnum) {
|
|
||||||
LPTSTR buf;
|
|
||||||
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
|
|
||||||
| FORMAT_MESSAGE_MAX_WIDTH_MASK,
|
|
||||||
nullptr, errnum, 0, (LPTSTR)&buf, 0, nullptr)
|
|
||||||
== 0) {
|
|
||||||
fatal("Failed to get error message for error 0x%x", errnum);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
char cmdLine[32768]; // Max command line size on Windows
|
|
||||||
char *ptr = cmdLine;
|
|
||||||
for (size_t i = 0; argv[i]; ++i) {
|
|
||||||
char const *src = argv[i];
|
|
||||||
// I miss you, `stpcpy`
|
|
||||||
while (*src) {
|
|
||||||
*ptr++ = *src++;
|
|
||||||
}
|
|
||||||
*ptr++ = ' ';
|
|
||||||
}
|
|
||||||
*ptr = '\0';
|
|
||||||
|
|
||||||
STARTUPINFOA startupInfo;
|
|
||||||
GetStartupInfoA(&startupInfo);
|
|
||||||
STARTUPINFOA childStartupInfo{sizeof(startupInfo),
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0};
|
|
||||||
|
|
||||||
PROCESS_INFORMATION child;
|
|
||||||
if (CreateProcessA(nullptr, cmdLine, nullptr, nullptr, true, 0, nullptr, nullptr,
|
|
||||||
&childStartupInfo, &child)
|
|
||||||
== 0) {
|
|
||||||
return winStrerror(GetLastError());
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD status;
|
|
||||||
do {
|
|
||||||
if (GetExitCodeProcess(child.hProcess, &status) == 0) {
|
|
||||||
fatal("Error waiting for %s: %ls", name, winStrerror(GetLastError()));
|
|
||||||
}
|
|
||||||
} while (status == STILL_ACTIVE);
|
|
||||||
CloseHandle(child.hProcess);
|
|
||||||
CloseHandle(child.hThread);
|
|
||||||
|
|
||||||
if (status != 0) {
|
|
||||||
fatal("%s returned with status %ld", name, status);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
if (argc < 2) {
|
|
||||||
fprintf(stderr, "usage: %s <rng file> [rgbgfx flags]\n", argv[0]);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char path[] = "./randtilegen", file[] = "out";
|
|
||||||
char *args[] = {path, argv[1], file, nullptr};
|
|
||||||
|
|
||||||
if (auto ret = execProg("randtilegen", args); ret != nullptr) {
|
|
||||||
fatal("Failed to excute ./randtilegen (%s). Is it in the current working directory?",
|
|
||||||
ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char path[] = "../../rgbgfx", out_opt[] = "-o", out_file[] = "result.2bpp",
|
|
||||||
pal_opt[] = "-p", pal_file[] = "result.pal", attr_opt[] = "-a",
|
|
||||||
attr_file[] = "result.attrmap", in_file[] = "out0.png";
|
|
||||||
std::vector<char *> args(
|
|
||||||
{path, out_opt, out_file, pal_opt, pal_file, attr_opt, attr_file, in_file});
|
|
||||||
// Also copy the trailing `nullptr`
|
|
||||||
std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
|
|
||||||
|
|
||||||
if (auto ret = execProg("rgbgfx conversion", args.data()); ret != nullptr) {
|
|
||||||
fatal("Failed to execute ../../rgbgfx (%s). Is it in the parent directory?", ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Png image0{"out0.png"};
|
|
||||||
|
|
||||||
{
|
|
||||||
char path[] = "../../rgbgfx", reverse_opt[] = "-r", out_opt[] = "-o",
|
|
||||||
out_file[] = "result.2bpp", pal_opt[] = "-p", pal_file[] = "result.pal",
|
|
||||||
attr_opt[] = "-a", attr_file[] = "result.attrmap", in_file[] = "result.png";
|
|
||||||
auto width_string = std::to_string(image0.getWidth() / 8);
|
|
||||||
std::vector<char *> args = {
|
|
||||||
path, reverse_opt, width_string.data(), out_opt, out_file, pal_opt,
|
|
||||||
pal_file, attr_opt, attr_file, in_file};
|
|
||||||
// Also copy the trailing `nullptr`
|
|
||||||
std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
|
|
||||||
|
|
||||||
if (auto ret = execProg("rgbgfx reversal", args.data()); ret != nullptr) {
|
|
||||||
fatal("Failed to execute ../../rgbgfx -r (%s)", ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Png image1{"result.png"};
|
|
||||||
|
|
||||||
if (image0.getWidth() != image1.getWidth()) {
|
|
||||||
fatal("Image widths do not match!");
|
|
||||||
}
|
|
||||||
if (image0.getHeight() != image1.getHeight()) {
|
|
||||||
fatal("Image heights do not match!");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t y = 0; y < image0.getHeight(); y++) {
|
|
||||||
for (uint32_t x = 0; x < image0.getWidth(); x++) {
|
|
||||||
Rgba px0 = image0.pixel(x, y);
|
|
||||||
Rgba px1 = image1.pixel(x, y);
|
|
||||||
|
|
||||||
auto cgbColor = [](Rgba const &rgba) {
|
|
||||||
auto field = [](uint16_t component, uint8_t shift) {
|
|
||||||
return (component & 0x1F) << shift;
|
|
||||||
};
|
|
||||||
return rgba.isTransparent()
|
|
||||||
? Rgba::transparent
|
|
||||||
: field(rgba.red, 0) | field(rgba.green, 5) | field(rgba.blue, 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cgbColor(px0) != cgbColor(px1)) {
|
|
||||||
error("Color mismatch at (%" PRIu32 ", %" PRIu32
|
|
||||||
"): (%u,%u,%u,%u) became (%u,%u,%u,%u) after round-tripping",
|
|
||||||
x, y, px0.red, px0.green, px0.blue, px0.alpha, px1.red, px1.green, px1.blue,
|
|
||||||
px1.alpha);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbErrors > 0) {
|
|
||||||
fprintf(stderr, "Test failed with %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
2<EFBFBD>.<2E><>C<><43><EFBFBD>pA22<32>VD<56><44>u2IEU<45><55><EFBFBD>_<EFBFBD>/!<21><05>p<EFBFBD><EFBFBD><C281>!<21>o-/^<5E><><EFBFBD>uNV0<56><30>g<12><>T <20><>?<3F>%<25><16>IA#1<>|<7C>$<24>tt<74><05>τ$<24><>W<10>^`ďف;W<><57>JP!<21><pm<18><17><><EFBFBD><EFBFBD>;HZw<5A><77>MvUx<55><78>+<2B><><16><>e<EFBFBD><65><EFBFBD>'[$<24><>؋g<D88B><08>HcL<15><><EFBFBD>\u<>E,ԥ_<><5F><EFBFBD>!<21><><EFBFBD>;lw<6C>?<3F><><15><><EFBFBD>?܃<>k<>TЎ{Z<><5A><EFBFBD><EFBFBD>AR<41><52>W˱<57><CBB1><EFBFBD><EFBFBD>h*<05>b<10><>C$<24><><EFBFBD>[2x(<28>E`<60>8,<2C>=<3D>,ᾡ<>i<EFBFBD>;<3B><><EFBFBD><EFBFBD>7<EFBFBD>!<21><>0<EFBFBD><30>m<EFBFBD><6D><EFBFBD>fy+<2B><>wk<77>PYS<59><53>&<26><>Wb<><62>A<>x<EFBFBD><02>O<EFBFBD><4F>F\y<05><><EFBFBD><EFBFBD>r>f<>q.ժ<>#<23>]<5D>i<03><>D<>.m<><13>v<EFBFBD><<3C><11><>\5c<35>JW<4A>N<><4E>=nq<6E><71><EFBFBD>gY9<59>Aa<41><61>a<EFBFBD><61>8?<3F><><EFBFBD><11>~uc<>[<5B><><EFBFBD>
|
|
||||||
<17><>(<28><>[<5B>q<EFBFBD><71><EFBFBD>B>QM<51><05>z<EFBFBD>_.<2E><>p<EFBFBD>r<EFBFBD><72><EFBFBD>y<EFBFBD><79><EFBFBD>w<EFBFBD><77><EFBFBD><EFBFBD>~<7E>s<EFBFBD>;<0F><>Mߏ<4D>]/<2F>hX<68>+6fq<13><><EFBFBD>0Go<47><6F>BE<42><45><EFBFBD>R<EFBFBD>D<><44>b<EFBFBD><62>Am5İ<35><C4B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>g<EFBFBD><67>l<EFBFBD><1F><>:H<18><>
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
?x<><78>)&<<3C>av<61><01>(˷z<><0F>ߕx_;<3B>yc&o<><6F>$<24><>w<EFBFBD><77><EFBFBD>x<>TST+<2B><>ǑX<><58>g<EFBFBD><67>t<07><><EFBFBD><EFBFBD><EFBFBD>|c<0B>UJ=<3D><>u<lP<6C><50>>І<><D086><EFBFBD><EFBFBD>[g$Ca<43><07>I<49>ÃsI5;<3B><>D2<44><32>jD[ <20>S+<2B><><EFBFBD>c<EFBFBD><63><0F>8[j<12>?<3F><>D<EFBFBD><44><EFBFBD><19><><01><><EFBFBD>D,<2C><><EFBFBD>r<EFBFBD><72>Iٽ<49>N<EFBFBD>
|
|
||||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user