mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
169 Commits
v0.5.2
...
v0.6.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b2dc37f43 | ||
|
|
2b83a81ceb | ||
|
|
ca8693690a | ||
|
|
87092208bc | ||
|
|
0d32775a1f | ||
|
|
0df07d3688 | ||
|
|
3f70372308 | ||
|
|
9646f15b59 | ||
|
|
973fbb91bc | ||
|
|
903862c451 | ||
|
|
3f5983358c | ||
|
|
7a7126f3b8 | ||
|
|
b4dadd35b6 | ||
|
|
d9b1402ef8 | ||
|
|
832e0ec972 | ||
|
|
caaf7a8444 | ||
|
|
a5ed0292b1 | ||
|
|
05e36767b0 | ||
|
|
531092f5bd | ||
|
|
4c51792f15 | ||
|
|
c4359c1058 | ||
|
|
159efe1257 | ||
|
|
3cfe7800c7 | ||
|
|
01cf0c5f98 | ||
|
|
0dbcebfeb4 | ||
|
|
491b6746ab | ||
|
|
cbf6fadcdb | ||
|
|
a77b0b396a | ||
|
|
568fb5e4c8 | ||
|
|
82012f698e | ||
|
|
57ac07b03e | ||
|
|
c521233499 | ||
|
|
bf869f6961 | ||
|
|
0f8cbb1faf | ||
|
|
fcce42d3d2 | ||
|
|
ed104a9f70 | ||
|
|
f5d4126303 | ||
|
|
78e751f022 | ||
|
|
d569c6392c | ||
|
|
75b9d48990 | ||
|
|
3aabe9c799 | ||
|
|
5be2b96b40 | ||
|
|
7fdfbbbbba | ||
|
|
7fdc6cbced | ||
|
|
56115653ef | ||
|
|
7927dfd2e3 | ||
|
|
b1aec91912 | ||
|
|
7defaad9d2 | ||
|
|
dc9185e50b | ||
|
|
02d957278d | ||
|
|
6feb1fb73a | ||
|
|
dc67f152a9 | ||
|
|
913c3dd711 | ||
|
|
32242e0ff2 | ||
|
|
91071009a8 | ||
|
|
1da884db15 | ||
|
|
ef473de75a | ||
|
|
6b0cab32a6 | ||
|
|
cc27169ecd | ||
|
|
843022772b | ||
|
|
75f8b16f33 | ||
|
|
188027bccc | ||
|
|
79adcdb7ea | ||
|
|
8ed65078da | ||
|
|
7311fc9ef8 | ||
|
|
7d54145e56 | ||
|
|
e753b62d1a | ||
|
|
6ed220b4c1 | ||
|
|
e49fb457ea | ||
|
|
3c9d5b05d6 | ||
|
|
e86eb9337a | ||
|
|
b0f8e04fb7 | ||
|
|
493b94919f | ||
|
|
71e22f3bfe | ||
|
|
ac02382632 | ||
|
|
943d631701 | ||
|
|
d2f9cc7e8c | ||
|
|
76bb950be5 | ||
|
|
f29c5d81ec | ||
|
|
2307981878 | ||
|
|
6bab2ea5c8 | ||
|
|
35e57a55c9 | ||
|
|
21e9a65f0b | ||
|
|
779c8c9368 | ||
|
|
e855b6f622 | ||
|
|
3b1808cc8f | ||
|
|
71cb2854e8 | ||
|
|
2099a25ee0 | ||
|
|
b9de65c9a2 | ||
|
|
c82cce6d95 | ||
|
|
97965c9766 | ||
|
|
5efc49cb12 | ||
|
|
c4361b965c | ||
|
|
8d00a61602 | ||
|
|
20442c8a43 | ||
|
|
b95c26c886 | ||
|
|
a96aa1725f | ||
|
|
3d79f76e41 | ||
|
|
fdfedc45a6 | ||
|
|
c98d92a4c4 | ||
|
|
d438838db4 | ||
|
|
d675523e49 | ||
|
|
ad07c9deb9 | ||
|
|
bf9f99ebf5 | ||
|
|
f0eca86c52 | ||
|
|
e8d8ae4c78 | ||
|
|
0cc62824b9 | ||
|
|
2fb1eb9136 | ||
|
|
bde380f38b | ||
|
|
38e8024ffa | ||
|
|
3bd6078537 | ||
|
|
5409d0d15a | ||
|
|
9262fefd07 | ||
|
|
638d024040 | ||
|
|
3fa1854332 | ||
|
|
6e406b22bb | ||
|
|
d30e507270 | ||
|
|
373a22660b | ||
|
|
8c62e80c18 | ||
|
|
34bc650341 | ||
|
|
43ba7d0efb | ||
|
|
685ea5feed | ||
|
|
f5ac268989 | ||
|
|
d51ab35203 | ||
|
|
3a71910312 | ||
|
|
ec25b4ac0e | ||
|
|
83222a8147 | ||
|
|
97c326942f | ||
|
|
b037d54f64 | ||
|
|
80df6640e3 | ||
|
|
68d3ef8e76 | ||
|
|
01777c96c8 | ||
|
|
28f9183d80 | ||
|
|
74c31f7c0f | ||
|
|
7e94ecbfe6 | ||
|
|
0195196425 | ||
|
|
19c85a7c2e | ||
|
|
59e73e64ca | ||
|
|
972d06bb41 | ||
|
|
e27da737c6 | ||
|
|
e6ae1992fe | ||
|
|
d9b46cdec9 | ||
|
|
570cd62b81 | ||
|
|
a4ead0c25f | ||
|
|
da66eeb40e | ||
|
|
0c4f1f8334 | ||
|
|
af70d555fc | ||
|
|
e07bd92314 | ||
|
|
9169028e57 | ||
|
|
7dd8ba37f1 | ||
|
|
6842c831fd | ||
|
|
6b903059fe | ||
|
|
eb5af70d79 | ||
|
|
cf19879281 | ||
|
|
ac59ecf3c0 | ||
|
|
72b677a8d7 | ||
|
|
bbae9966e9 | ||
|
|
b3304ae1ac | ||
|
|
a48801a675 | ||
|
|
7dc81a64d3 | ||
|
|
3afa6b5a5a | ||
|
|
9b49f788e4 | ||
|
|
acc31feaa1 | ||
|
|
4ed5ba7508 | ||
|
|
20a26599a3 | ||
|
|
7bdfc9da23 | ||
|
|
d073cffa74 | ||
|
|
8435a29c4e | ||
|
|
b7fe78cad8 |
99
.clang-format
Normal file
99
.clang-format
Normal file
@@ -0,0 +1,99 @@
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: Left
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: Consecutive
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- format_
|
||||
- attr_
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: AfterComma
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
EmptyLineBeforeAccessModifier: Leave
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<sys/'
|
||||
Priority: 0
|
||||
- Regex: '^<'
|
||||
Priority: 1
|
||||
- Regex: '^"extern/'
|
||||
Priority: 2
|
||||
- Regex: '^"(asm|link|fix|gfx)/'
|
||||
Priority: 3
|
||||
- Regex: '^"'
|
||||
Priority: 2
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
# Only support for Javascript as of clang-format 13...
|
||||
# InsertTrailingCommas: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PPIndentWidth: -1
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Both
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++17
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: ForIndentation
|
||||
56
.github/actions/doc_postproc.awk
vendored
56
.github/actions/doc_postproc.awk
vendored
@@ -1,56 +0,0 @@
|
||||
#!/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
113
.github/actions/get-pages.sh
vendored
@@ -1,113 +0,0 @@
|
||||
#!/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[@]##*/}"
|
||||
5
.github/actions/install_deps.sh
vendored
5
.github/actions/install_deps.sh
vendored
@@ -1,4 +1,7 @@
|
||||
case `echo $1 | cut -d '-' -f 1` in
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "${1%-*}" in
|
||||
ubuntu)
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -yq bison libpng-dev pkg-config
|
||||
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
- name: Get zlib, libpng and bison
|
||||
run: | # TODO: use an array
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1211.zip', 'zlib.zip')
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||
if ($hash -ne 'd7510a8ee1918b7d0cad197a089c0a2cd4d6df05fee22389f67f115e738b178d') {
|
||||
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
|
||||
Write-Host "zlib SHA256 mismatch! ($hash)"
|
||||
exit 1
|
||||
}
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
Expand-Archive -DestinationPath . "zlib.zip"
|
||||
Expand-Archive -DestinationPath . "libpng.zip"
|
||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||
Move-Item zlib-1.2.11 zlib
|
||||
Move-Item zlib-1.2.12 zlib
|
||||
Move-Item lpng1637 libpng
|
||||
- name: Build 32-bit zlib
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
|
||||
10
.github/workflows/create-release-docs.yml
vendored
10
.github/workflows/create-release-docs.yml
vendored
@@ -23,16 +23,16 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -yq groff zlib1g-dev
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||
tar xf mandoc-1.14.5.tar.gz
|
||||
cd mandoc-1.14.5
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
||||
tar xf mandoc-1.14.6.tar.gz
|
||||
cd mandoc-1.14.6
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
- name: Update pages
|
||||
working-directory: rgbds
|
||||
working-directory: rgbds/man
|
||||
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
||||
./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
|
||||
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} *
|
||||
- name: Push new pages
|
||||
working-directory: rgbds-www
|
||||
run: |
|
||||
|
||||
87
.github/workflows/testing.yml
vendored
87
.github/workflows/testing.yml
vendored
@@ -17,18 +17,10 @@ jobs:
|
||||
- os: macos-11.0
|
||||
cc: gcc
|
||||
include:
|
||||
- os: ubuntu-18.04
|
||||
target: develop
|
||||
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
- os: ubuntu-20.04
|
||||
target: develop
|
||||
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
- os: macos-11.0
|
||||
target: develop
|
||||
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
- os: macos-10.15
|
||||
target: develop
|
||||
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
- cc: gcc
|
||||
cxx: g++
|
||||
- cc: clang
|
||||
cxx: clang++
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -41,25 +33,25 @@ jobs:
|
||||
# Apple's base version is severely outdated, not even supporting -Wall,
|
||||
# but it overrides Homebrew's version nonetheless...
|
||||
- name: Build & install using Make
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
make ${{ matrix.target }} -j Q= CC=${{ matrix.cc }}
|
||||
sudo make install -j Q=
|
||||
if: matrix.buildsys == 'make'
|
||||
- name: Build & install using CMake
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }}
|
||||
cmake --build build -j
|
||||
cp build/src/rgb{asm,link,fix,gfx} .
|
||||
sudo cmake --install build
|
||||
make develop -j Q= CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
sudo make install -j Q=
|
||||
- name: Build & install using CMake
|
||||
if: matrix.buildsys == 'cmake'
|
||||
run: |
|
||||
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 --build build -j --verbose
|
||||
cp build/src/rgb{asm,link,fix,gfx} .
|
||||
sudo cmake --install build --verbose
|
||||
- name: Package binaries
|
||||
run: |
|
||||
mkdir bins
|
||||
cp rgb{asm,link,fix,gfx} bins
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
||||
path: bins
|
||||
@@ -84,11 +76,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get zlib, libpng and bison
|
||||
run: | # TODO: use an array
|
||||
run: | # TODO: use an array; remember to update the versions being downloaded, *and* the paths being extracted! (`Move-Item`)
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1211.zip', 'zlib.zip')
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||
if ($hash -ne 'd7510a8ee1918b7d0cad197a089c0a2cd4d6df05fee22389f67f115e738b178d') {
|
||||
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
|
||||
Write-Host "zlib SHA256 mismatch! ($hash)"
|
||||
exit 1
|
||||
}
|
||||
@@ -106,30 +98,43 @@ jobs:
|
||||
Expand-Archive -DestinationPath . "zlib.zip"
|
||||
Expand-Archive -DestinationPath . "libpng.zip"
|
||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||
Move-Item zlib-1.2.11 zlib
|
||||
Move-Item zlib-1.2.12 zlib
|
||||
Move-Item lpng1637 libpng
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
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 --build zbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install zlib
|
||||
run: |
|
||||
cmake --install zbuild
|
||||
- name: Build libpng
|
||||
run: |
|
||||
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
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install libpng
|
||||
run: |
|
||||
cmake --install pngbuild
|
||||
- name: Build Windows binaries
|
||||
run: |
|
||||
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release -j
|
||||
cmake --install build
|
||||
cmake --build build --config Release -j --verbose
|
||||
cmake --install build --verbose --prefix install_dir
|
||||
- name: Package binaries
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir bins
|
||||
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: rgbds-canary-win${{ matrix.bits }}
|
||||
path: bins
|
||||
@@ -137,6 +142,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cp bins/* .
|
||||
cp bins/*.dll test/gfx
|
||||
test/run-tests.sh
|
||||
|
||||
windows-xbuild:
|
||||
@@ -163,7 +169,7 @@ jobs:
|
||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
||||
- name: Install MinGW
|
||||
run: |
|
||||
sudo apt-get install gcc-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
||||
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
||||
- name: Install libpng dev headers for MinGW
|
||||
run: |
|
||||
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||
@@ -179,12 +185,21 @@ jobs:
|
||||
mv rgbgfx bins/rgbgfx.exe
|
||||
cp /usr/${{ matrix.triplet }}/lib/zlib1.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/libgcc_s_sjlj-1.dll bins; fi
|
||||
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
|
||||
mv test/gfx/randtilegen{,.exe}
|
||||
mv test/gfx/rgbgfx_test{,.exe}
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||
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:
|
||||
needs: windows-xbuild
|
||||
@@ -196,14 +211,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Retrieve binaries
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||
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
|
||||
shell: bash
|
||||
run: |
|
||||
cp bins/* .
|
||||
cp bins/*.dll test/gfx
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
31
.github/workflows/update-master-docs.yml
vendored
31
.github/workflows/update-master-docs.yml
vendored
@@ -4,16 +4,15 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/actions/get-pages.sh
|
||||
- src/gbz80.7
|
||||
- src/rgbds.5
|
||||
- src/rgbds.7
|
||||
- src/asm/rgbasm.1
|
||||
- src/asm/rgbasm.5
|
||||
- src/link/rgblink.1
|
||||
- src/link/rgblink.5
|
||||
- src/fix/rgbfix.1
|
||||
- src/gfx/rgbgfx.1
|
||||
- man/gbz80.7
|
||||
- man/rgbds.5
|
||||
- man/rgbds.7
|
||||
- man/rgbasm.1
|
||||
- man/rgbasm.5
|
||||
- man/rgblink.1
|
||||
- man/rgblink.5
|
||||
- man/rgbfix.1
|
||||
- man/rgbgfx.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -36,16 +35,16 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -yq groff zlib1g-dev
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||
tar xf mandoc-1.14.5.tar.gz
|
||||
cd mandoc-1.14.5
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
||||
tar xf mandoc-1.14.6.tar.gz
|
||||
cd mandoc-1.14.6
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
- name: Update pages
|
||||
working-directory: rgbds
|
||||
working-directory: rgbds/man
|
||||
run: |
|
||||
./.github/actions/get-pages.sh ../rgbds-www master
|
||||
../../rgbds-www/maintainer/man_to_html.sh master *
|
||||
- name: Push new pages
|
||||
working-directory: rgbds-www
|
||||
run: |
|
||||
@@ -56,7 +55,7 @@ jobs:
|
||||
ssh-add ~/.ssh/id_ed25519
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "community@gbdev.io"
|
||||
git add .
|
||||
git add -A
|
||||
git commit -m "Update RGBDS master documentation"
|
||||
if git remote | grep -q origin; then
|
||||
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||
|
||||
project(rgbds
|
||||
LANGUAGES C)
|
||||
LANGUAGES C CXX)
|
||||
|
||||
# get real path of source and binary directories
|
||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||
@@ -29,8 +29,18 @@ option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
|
||||
if(MSVC)
|
||||
# MSVC's standard library triggers warning C5105,
|
||||
# "macro expansion producing 'defined' has undefined behavior"
|
||||
add_compile_options(/std:c11 /W1 /MP /wd5105)
|
||||
add_compile_options(/MP /wd5105)
|
||||
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()
|
||||
add_compile_options(-Wall -pedantic)
|
||||
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
|
||||
@@ -41,38 +51,59 @@ else()
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||
add_compile_options(${SAN_FLAGS})
|
||||
link_libraries(${SAN_FLAGS})
|
||||
add_link_options(${SAN_FLAGS})
|
||||
# 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!
|
||||
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()
|
||||
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra -Wno-type-limits
|
||||
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2
|
||||
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused
|
||||
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=5
|
||||
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond
|
||||
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op
|
||||
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline
|
||||
-Wundef -Wstrict-prototypes -Wold-style-definition
|
||||
-Wno-unknown-warning-option -Wno-tautological-constant-out-of-range-compare)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local ?
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
||||
# We do some range checks that are always false on some platforms (GCC, Clang)
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare
|
||||
-Wvla # MSVC does not support VLAs
|
||||
-Wno-unknown-warning-option) # Clang shouldn't diagnose unknown warnings
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Use versioning consistent with Makefile
|
||||
# the git revision is used but uses the fallback in an archive
|
||||
|
||||
execute_process(COMMAND git describe --tags --dirty --always
|
||||
OUTPUT_VARIABLE GIT_REV
|
||||
ERROR_QUIET)
|
||||
string(STRIP "${GIT_REV}" GIT_REV)
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(COMMAND ${GIT} describe --tags --dirty --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
message(STATUS "RGBDS version: ${GIT_REV}")
|
||||
else(GIT)
|
||||
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
|
||||
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")
|
||||
|
||||
add_definitions(-DBUILD_VERSION_STRING="${GIT_REV}")
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(test)
|
||||
|
||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||
# (You may want to augment it with the options above)
|
||||
@@ -87,3 +118,19 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
message(CHECK_FAIL "no")
|
||||
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()
|
||||
|
||||
81
Makefile
81
Makefile
@@ -7,7 +7,7 @@
|
||||
#
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .h .y .c .o
|
||||
.SUFFIXES: .h .y .c .cpp .o
|
||||
|
||||
# User-defined variables
|
||||
|
||||
@@ -30,14 +30,17 @@ PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
# Note: if this comes up empty, `version.c` will automatically fall back to last release number
|
||||
VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall
|
||||
WARNFLAGS := -Wall -pedantic
|
||||
|
||||
# Overridable CFLAGS
|
||||
CFLAGS ?= -O3 -flto -DNDEBUG
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CFLAGS
|
||||
# _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 \
|
||||
-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
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
@@ -102,9 +105,14 @@ rgbfix_obj := \
|
||||
src/error.o
|
||||
|
||||
rgbgfx_obj := \
|
||||
src/gfx/gb.o \
|
||||
src/gfx/main.o \
|
||||
src/gfx/makepng.o \
|
||||
src/gfx/pal_packing.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/error.o
|
||||
|
||||
@@ -118,7 +126,13 @@ rgbfix: ${rgbfix_obj}
|
||||
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
||||
|
||||
rgbgfx: ${rgbgfx_obj}
|
||||
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCFLAGS} src/version.c ${PNGLDLIBS}
|
||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ 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
|
||||
|
||||
@@ -145,7 +159,10 @@ src/asm/parser.c: src/asm/parser.y
|
||||
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
||||
|
||||
.c.o:
|
||||
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
$Q${CC} ${REALCFLAGS} -c -o $@ $<
|
||||
|
||||
.cpp.o:
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
|
||||
# Target used to remove all files generated by other Makefile targets
|
||||
|
||||
@@ -157,6 +174,7 @@ clean:
|
||||
$Qfind src/ -name "*.o" -exec rm {} \;
|
||||
$Q${RM} rgbshim.sh
|
||||
$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.
|
||||
|
||||
@@ -167,15 +185,15 @@ install: all
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
||||
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
|
||||
$Qinstall -m ${MANMODE} src/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
||||
$Qinstall -m ${MANMODE} src/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
||||
$Qinstall -m ${MANMODE} src/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
||||
$Qinstall -m ${MANMODE} src/asm/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
||||
$Qinstall -m ${MANMODE} src/asm/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
||||
$Qinstall -m ${MANMODE} src/fix/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
||||
$Qinstall -m ${MANMODE} src/link/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
||||
$Qinstall -m ${MANMODE} src/link/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
||||
$Qinstall -m ${MANMODE} src/gfx/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
||||
$Qinstall -m ${MANMODE} man/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
||||
$Qinstall -m ${MANMODE} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
||||
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
||||
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
||||
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
||||
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
||||
|
||||
# Target used to check the coding style of the whole codebase.
|
||||
# `extern/` is excluded, as it contains external code that should not be patched
|
||||
@@ -209,23 +227,26 @@ checkdiff:
|
||||
# This target is used during development in order to prevent adding new issues
|
||||
# to the source code. All warnings are treated as errors in order to block the
|
||||
# compilation and make the continous integration infrastructure return failure.
|
||||
# The rationale for some of the flags is documented in the CMakeLists.
|
||||
|
||||
develop:
|
||||
$Qenv $(MAKE) WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \
|
||||
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \
|
||||
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
|
||||
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \
|
||||
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \
|
||||
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
|
||||
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
|
||||
-Wundef -Wstrict-prototypes -Wold-style-definition \
|
||||
-Wno-unknown-warning-option -Wno-tautological-constant-out-of-range-compare \
|
||||
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused \
|
||||
-Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
||||
-Wvla \
|
||||
-Wno-unknown-warning-option \
|
||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||
CFLAGS="-ggdb3 -O0"
|
||||
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.
|
||||
# This is not for Windows users!
|
||||
@@ -233,12 +254,14 @@ develop:
|
||||
# install instructions instead.
|
||||
|
||||
mingw32:
|
||||
$Qmake CC=i686-w64-mingw32-gcc BISON=bison \
|
||||
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
|
||||
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||
|
||||
mingw64:
|
||||
$Qmake CC=x86_64-w64-mingw32-gcc BISON=bison \
|
||||
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
|
||||
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||
|
||||
wine-shim:
|
||||
$Qecho '#!/bin/bash' > rgbshim.sh
|
||||
|
||||
29
README.rst
29
README.rst
@@ -12,11 +12,10 @@ 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
|
||||
other UNIX tools.
|
||||
|
||||
This toolchain is maintained on `GitHub <https://github.com/rednex/rgbds>`__.
|
||||
This toolchain is maintained `on GitHub <https://github.com/gbdev/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 `here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages found in this repository.
|
||||
The source code of the website itself is on GitHub as well under the repo `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
|
||||
|
||||
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``.
|
||||
|
||||
@@ -24,7 +23,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
|
||||
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/source>`__
|
||||
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/#building-from-source>`__
|
||||
is possible using ``make`` or ``cmake``; follow the link for more detailed instructions.
|
||||
|
||||
.. code:: sh
|
||||
@@ -57,6 +56,8 @@ The RGBDS source code file structure somewhat resembles the following:
|
||||
│ └── ...
|
||||
├── include/
|
||||
│ └── ...
|
||||
├── man/
|
||||
│ └── ...
|
||||
├── src/
|
||||
│ ├── asm/
|
||||
│ │ └── ...
|
||||
@@ -73,10 +74,14 @@ The RGBDS source code file structure somewhat resembles the following:
|
||||
├── test/
|
||||
│ ├── ...
|
||||
│ └── run-tests.sh
|
||||
├── .clang-format
|
||||
├── CMakeLists.txt
|
||||
├── Makefile
|
||||
└── README.rst
|
||||
|
||||
.. |clang-format| replace:: ``clang-format``
|
||||
.. _clang-format: https://clang.llvm.org/docs/ClangFormat.html
|
||||
|
||||
- ``.github/`` - files and scripts related to the integration of the RGBDS codebase with
|
||||
GitHub.
|
||||
|
||||
@@ -92,13 +97,17 @@ The RGBDS source code file structure somewhat resembles the following:
|
||||
|
||||
- ``include/`` - header files for each respective C files in `src`.
|
||||
|
||||
- ``src/`` - source code and manual pages for RGBDS.
|
||||
- ``man/`` - manual pages.
|
||||
|
||||
- ``src/`` - source code of RGBDS.
|
||||
|
||||
* 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.
|
||||
|
||||
- ``test/`` - testing framework used to verify that changes to the code don't break or modify the behavior of RGBDS.
|
||||
|
||||
- ``.clang-format`` - code style for automated formatting with |clang-format|_. The C code does not currently follow this style, but all C++ code should.
|
||||
|
||||
3. History
|
||||
----------
|
||||
|
||||
@@ -120,3 +129,11 @@ The RGBDS source code file structure somewhat resembles the following:
|
||||
- 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.
|
||||
|
||||
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,19 +11,24 @@ _rgbgfx_completions() {
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[C]="color-curve:normal"
|
||||
[D]="debug:normal"
|
||||
[h]="horizontal:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[u]="unique-tiles:normal"
|
||||
[v]="verbose:normal"
|
||||
[f]="fix:normal"
|
||||
[F]="fix-and-save:normal"
|
||||
[Z]="columns:normal"
|
||||
[a]="attr-map:*.attrmap"
|
||||
[A]="output-attr-map:normal"
|
||||
[b]="base-tiles:unk"
|
||||
[d]="depth:unk"
|
||||
[L]="slice:unk"
|
||||
[N]="nb-tiles:unk"
|
||||
[n]="nb-palettes:unk"
|
||||
[o]="output:glob *.2bpp"
|
||||
[p]="palette:glob *.pal"
|
||||
[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]="output-tilemap:normal"
|
||||
[x]="trim-end:unk"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
declare -A FILES
|
||||
while read -r -d '' file; do
|
||||
FILES["$file"]="true"
|
||||
done < <(git diff --name-only -z $1 HEAD)
|
||||
done < <(git diff --name-only -z "$1" HEAD)
|
||||
|
||||
edited () {
|
||||
${FILES["$1"]:-"false"}
|
||||
@@ -40,13 +40,13 @@ dependency () {
|
||||
# Pull requests that edit the first file without the second may be correct,
|
||||
# but are suspicious enough to require review.
|
||||
|
||||
dependency include/linkdefs.h src/rgbds.5 \
|
||||
dependency include/linkdefs.h man/rgbds.5 \
|
||||
"Was the object file format changed?"
|
||||
|
||||
dependency src/asm/parser.y src/asm/rgbasm.5 \
|
||||
dependency src/asm/parser.y man/rgbasm.5 \
|
||||
"Was the rgbasm grammar changed?"
|
||||
|
||||
dependency include/asm/warning.h src/asm/rgbasm.1 \
|
||||
dependency include/asm/warning.h man/rgbasm.1 \
|
||||
"Were the rgbasm warnings changed?"
|
||||
|
||||
dependency src/asm/object.c include/linkdefs.h \
|
||||
@@ -59,27 +59,27 @@ dependency Makefile CMakeLists.txt \
|
||||
dependency Makefile src/CMakeLists.txt \
|
||||
"Did the build process change?"
|
||||
|
||||
dependency src/asm/main.c src/asm/rgbasm.1 \
|
||||
dependency src/asm/main.c man/rgbasm.1 \
|
||||
"Did the rgbasm CLI change?"
|
||||
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
|
||||
"Did the rgbasm CLI change?"
|
||||
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
|
||||
"Did the rgbasm CLI change?"
|
||||
dependency src/link/main.c src/link/rgblink.1 \
|
||||
dependency src/link/main.c man/rgblink.1 \
|
||||
"Did the rgblink CLI change?"
|
||||
dependency src/link/main.c contrib/zsh_compl/_rgblink \
|
||||
"Did the rgblink CLI change?"
|
||||
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
|
||||
"Did the rgblink CLI change?"
|
||||
dependency src/fix/main.c src/fix/rgbfix.1 \
|
||||
dependency src/fix/main.c man/rgbfix.1 \
|
||||
"Did the rgbfix CLI change?"
|
||||
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
|
||||
"Did the rgbfix CLI change?"
|
||||
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
|
||||
"Did the rgbfix CLI change?"
|
||||
dependency src/gfx/main.c src/gfx/rgbgfx.1 \
|
||||
dependency src/gfx/main.cpp man/rgbgfx.1 \
|
||||
"Did the rgbgfx CLI change?"
|
||||
dependency src/gfx/main.c contrib/zsh_compl/_rgbgfx \
|
||||
dependency src/gfx/main.cpp contrib/zsh_compl/_rgbgfx \
|
||||
"Did the rgbgfx CLI change?"
|
||||
dependency src/gfx/main.c contrib/bash_compl/_rgbgfx.bash \
|
||||
dependency src/gfx/main.cpp contrib/bash_compl/_rgbgfx.bash \
|
||||
"Did the rgbgfx CLI change?"
|
||||
|
||||
@@ -23,51 +23,51 @@
|
||||
# SOFTWARE.
|
||||
|
||||
STATE=0
|
||||
diff <(xxd $1) <(xxd $2) | while read -r LINE; do
|
||||
if [ $STATE -eq 0 ]; then
|
||||
diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
||||
if [[ $STATE -eq 0 ]]; then
|
||||
# Discard first line (line info)
|
||||
STATE=1
|
||||
elif [ "$LINE" = '---' ]; then
|
||||
elif [[ "$LINE" = '---' ]]; then
|
||||
# Separator between files switches states
|
||||
echo $LINE
|
||||
echo "$LINE"
|
||||
STATE=3
|
||||
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
||||
# Line info resets the whole thing
|
||||
STATE=1
|
||||
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
|
||||
elif [[ $STATE -eq 1 || $STATE -eq 3 ]]; then
|
||||
# Compute the GB address from the ROM offset
|
||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||
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
|
||||
if [ $STATE -eq 1 ]; then
|
||||
if [[ $STATE -eq 1 ]]; then
|
||||
STATE=2
|
||||
SYMFILE=${1%.*}.sym
|
||||
else
|
||||
STATE=4
|
||||
SYMFILE=${2%.*}.sym
|
||||
fi
|
||||
EXTRA=$(if [ -f "$SYMFILE" ]; then
|
||||
EXTRA=$(if [[ -f "$SYMFILE" ]]; then
|
||||
# Read the sym file for such a symbol
|
||||
# Ignore comment lines, only pick matching bank
|
||||
# (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 |
|
||||
tr -d "\r" |
|
||||
while read -r SYMADDR SYM; do
|
||||
SYMADDR=$((0x${SYMADDR#*:}))
|
||||
if [ $SYMADDR -le $ADDR ]; then
|
||||
printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
|
||||
if [[ $SYMADDR -le $ADDR ]]; then
|
||||
printf " (%s+%#x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||
fi
|
||||
# TODO: assumes sorted sym files
|
||||
done | tail -n 1
|
||||
fi)
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||
fi
|
||||
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
|
||||
if [[ $STATE -eq 2 || $STATE -eq 4 ]]; then
|
||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||
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#*: }"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -15,20 +15,25 @@ local args=(
|
||||
|
||||
'(-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]'
|
||||
'(-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]'
|
||||
'(-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]'
|
||||
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||
{-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'
|
||||
'(-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'
|
||||
'(-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'
|
||||
'(-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'
|
||||
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t fix_Callback_PI(void);
|
||||
void fix_Print(int32_t i);
|
||||
int32_t fix_Sin(int32_t i);
|
||||
int32_t fix_Cos(int32_t i);
|
||||
|
||||
@@ -77,6 +77,7 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
void fstk_StopRept(void);
|
||||
bool fstk_Break(void);
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth);
|
||||
void fstk_Init(char const *mainPath, size_t maxDepth);
|
||||
|
||||
#endif /* RGBDS_ASM_FSTACK_H */
|
||||
|
||||
@@ -83,6 +83,7 @@ struct CaptureBody {
|
||||
size_t size;
|
||||
};
|
||||
|
||||
void lexer_CheckRecursionDepth(void);
|
||||
char const *lexer_GetFileName(void);
|
||||
uint32_t lexer_GetLineNo(void);
|
||||
uint32_t lexer_GetColNo(void);
|
||||
|
||||
@@ -25,7 +25,7 @@ void macro_AppendArg(struct MacroArgs **args, char *s);
|
||||
void macro_UseNewArgs(struct MacroArgs *args);
|
||||
void macro_FreeArgs(struct MacroArgs *args);
|
||||
char const *macro_GetArg(uint32_t i);
|
||||
char *macro_GetAllArgs(void);
|
||||
char const *macro_GetAllArgs(void);
|
||||
|
||||
uint32_t macro_GetUniqueID(void);
|
||||
char const *macro_GetUniqueIDStr(void);
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
#include "helpers.h"
|
||||
|
||||
extern bool haltnop;
|
||||
extern bool warnOnHaltNop;
|
||||
extern bool optimizeLoads;
|
||||
extern bool warnOnLdOpt;
|
||||
extern bool verbose;
|
||||
extern bool warnings; /* True to enable warnings, false to disable them. */
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
#define RGBDS_OPT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void opt_B(char chars[2]);
|
||||
void opt_G(char chars[4]);
|
||||
void opt_B(char const chars[2]);
|
||||
void opt_G(char const chars[4]);
|
||||
void opt_P(uint8_t fill);
|
||||
void opt_L(bool optimize);
|
||||
void opt_W(char const *flag);
|
||||
|
||||
@@ -28,7 +28,7 @@ struct Section {
|
||||
uint32_t size;
|
||||
uint32_t org;
|
||||
uint32_t bank;
|
||||
uint8_t align;
|
||||
uint8_t align; // Exactly as specified in `ALIGN[]`
|
||||
uint16_t alignOfs;
|
||||
struct Section *next;
|
||||
struct Patch *patches;
|
||||
|
||||
39
include/defaultinitalloc.hpp
Normal file
39
include/defaultinitalloc.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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,10 +12,18 @@
|
||||
#include "helpers.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void warn(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 errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RGBDS_ERROR_H */
|
||||
|
||||
8
include/extern/getopt.h
vendored
8
include/extern/getopt.h
vendored
@@ -26,6 +26,10 @@
|
||||
#ifndef RGBDS_EXTERN_GETOPT_H
|
||||
#define RGBDS_EXTERN_GETOPT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
|
||||
@@ -43,4 +47,8 @@ int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
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;
|
||||
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 */
|
||||
123
include/gfx/main.hpp
Normal file
123
include/gfx/main.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 */
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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 */
|
||||
32
include/gfx/pal_packing.hpp
Normal file
32
include/gfx/pal_packing.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 */
|
||||
32
include/gfx/pal_sorting.hpp
Normal file
32
include/gfx/pal_sorting.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 */
|
||||
15
include/gfx/pal_spec.hpp
Normal file
15
include/gfx/pal_spec.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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 */
|
||||
14
include/gfx/process.hpp
Normal file
14
include/gfx/process.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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 */
|
||||
44
include/gfx/proto_palette.hpp
Normal file
44
include/gfx/proto_palette.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 */
|
||||
14
include/gfx/reverse.hpp
Normal file
14
include/gfx/reverse.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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 */
|
||||
67
include/gfx/rgba.hpp
Normal file
67
include/gfx/rgba.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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_(...)
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
|
||||
static inline _Noreturn unreachable_(void) {}
|
||||
static inline _Noreturn void unreachable_(void) {}
|
||||
#endif
|
||||
|
||||
// Use builtins whenever possible, and shim them otherwise
|
||||
|
||||
90
include/itertools.hpp
Normal file
90
include/itertools.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 */
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
||||
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
||||
#define RGBDS_OBJECT_REV 8U
|
||||
#define RGBDS_OBJECT_REV 9U
|
||||
|
||||
enum AssertionType {
|
||||
ASSERT_WARN,
|
||||
@@ -49,6 +49,7 @@ enum RPNCommand {
|
||||
|
||||
RPN_SHL = 0x40,
|
||||
RPN_SHR = 0x41,
|
||||
RPN_USHR = 0x42,
|
||||
|
||||
RPN_BANK_SYM = 0x50,
|
||||
RPN_BANK_SECT = 0x51,
|
||||
|
||||
@@ -16,5 +16,6 @@ int32_t op_modulo(int32_t dividend, int32_t divisor);
|
||||
int32_t op_exponent(int32_t base, uint32_t power);
|
||||
int32_t op_shift_left(int32_t value, int32_t amount);
|
||||
int32_t op_shift_right(int32_t value, int32_t amount);
|
||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
|
||||
|
||||
#endif /* RGBDS_OP_MATH_H */
|
||||
|
||||
@@ -46,12 +46,14 @@
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* MSVC doesn't support `[static N]` for array arguments from C99 */
|
||||
/* MSVC doesn't support `[static N]` for array arguments from C99 or C11 */
|
||||
#ifdef _MSC_VER
|
||||
# define MIN_NB_ELMS(N)
|
||||
# define ARR_QUALS(...)
|
||||
# define NONNULL(ptr) *ptr
|
||||
#else
|
||||
# define MIN_NB_ELMS(N) static (N)
|
||||
# define ARR_QUALS(...) __VA_ARGS__
|
||||
# define NONNULL(ptr) ptr[static 1]
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,10 +9,19 @@
|
||||
#ifndef EXTERN_VERSION_H
|
||||
#define EXTERN_VERSION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 5
|
||||
#define PACKAGE_VERSION_PATCH 2
|
||||
#define PACKAGE_VERSION_MINOR 6
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 1
|
||||
|
||||
char const *get_package_version_string(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* EXTERN_VERSION_H */
|
||||
|
||||
@@ -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
|
||||
Add a string symbol to the compiled source code.
|
||||
This is equivalent to
|
||||
.Ql Ar name Ic EQUS \(dq Ns Ar value Ns \(dq
|
||||
.Ql Ar name Ic EQUS No \(dq Ns Ar value Ns \(dq
|
||||
in code, or
|
||||
.Ql Ar name Ic EQUS \(dq1\(dq
|
||||
.Ql Ar name Ic EQUS No \(dq1\(dq
|
||||
if
|
||||
.Ar value
|
||||
is not specified.
|
||||
@@ -1,3 +1,4 @@
|
||||
'\" e
|
||||
.\"
|
||||
.\" This file is part of RGBDS.
|
||||
.\"
|
||||
@@ -251,7 +252,9 @@ A great number of operators you can use in expressions are available (listed fro
|
||||
.It Li ** Ta Exponent
|
||||
.It Li ~ + - Ta Unary complement/plus/minus
|
||||
.It Li * / % Ta Multiply/divide/modulo
|
||||
.It Li << >> Ta Shift left/right
|
||||
.It Li << Ta Shift left
|
||||
.It Li >> Ta Signed shift right (sign-extension)
|
||||
.It Li >>> Ta Unsigned shift right (zero-extension)
|
||||
.It Li & \&| ^ Ta Binary and/or/xor
|
||||
.It Li + - Ta Add/subtract
|
||||
.It Li != == <= >= < > Ta Comparison
|
||||
@@ -1384,6 +1387,12 @@ Alternatively, you can use
|
||||
to store a list of words (16-bit) or
|
||||
.Ic DL
|
||||
to store a list of double-words/longs (32-bit).
|
||||
Both of these write their data in little-endian byte order; for example,
|
||||
.Ql dw $CAFE
|
||||
is equivalent to
|
||||
.Ql db $FE, $CA
|
||||
and not
|
||||
.Ql db $CA, $FE .
|
||||
.Pp
|
||||
Strings are handled a little specially: they first undergo charmap conversion (see
|
||||
.Sx Character maps ) ,
|
||||
@@ -1985,6 +1994,7 @@ block, all of them but the first one are ignored.
|
||||
.Ss Changing options while assembling
|
||||
.Ic OPT
|
||||
can be used to change some of the options during assembling from within the source, instead of defining them on the command-line.
|
||||
.Pq See Xr rgbasm 1 .
|
||||
.Pp
|
||||
.Ic OPT
|
||||
takes a comma-separated list of options as its argument:
|
||||
@@ -2000,8 +2010,10 @@ POPO
|
||||
LD [$FF88], A ; optimized to use LDH by default
|
||||
.Ed
|
||||
.Pp
|
||||
The options that OPT can modify are currently:
|
||||
.Cm b , g , p , h , L ,
|
||||
The options that
|
||||
.Ic OPT
|
||||
can modify are currently:
|
||||
.Cm b , g , p , r , h , L ,
|
||||
and
|
||||
.Cm W .
|
||||
The Boolean flag options
|
||||
@@ -241,6 +241,7 @@ with some bytes being special prefixes for integers and symbols.
|
||||
.It Li $35 Ta Li <= comparison
|
||||
.It Li $40 Ta Li << operator
|
||||
.It Li $41 Ta Li >> operator
|
||||
.It Li $42 Ta Li >>> operator
|
||||
.It Li $50 Ta Li BANK(symbol) ,
|
||||
a
|
||||
.Ar LONG
|
||||
@@ -192,7 +192,7 @@ SurvivalKids.gbc
|
||||
.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.
|
||||
Its specification, as well as more resources, can be found online at
|
||||
.Lk https://github.com/TwitchPlaysPokemon/tpp1 .
|
||||
.Lk https://github.com/aaaaaa123456789/tpp1 .
|
||||
.Ss MBC name
|
||||
The MBC name for TPP1 is more complex than standard mappers.
|
||||
It must be followed with the revision number, of the form
|
||||
591
man/rgbgfx.1
Normal file
591
man/rgbgfx.1
Normal file
@@ -0,0 +1,591 @@
|
||||
'\" 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 .
|
||||
2
src/.gitignore
vendored
Normal file
2
src/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Generated by CMake
|
||||
/.version.c
|
||||
@@ -6,20 +6,14 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
|
||||
configure_file(version.c _version.c ESCAPE_QUOTES)
|
||||
|
||||
set(common_src
|
||||
"error.c"
|
||||
"extern/getopt.c"
|
||||
"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)
|
||||
set(BISON_FLAGS "-Wall")
|
||||
# Set sompe optimization flags on versions that support them
|
||||
@@ -68,9 +62,16 @@ set(rgbfix_src
|
||||
)
|
||||
|
||||
set(rgbgfx_src
|
||||
"gfx/gb.c"
|
||||
"gfx/main.c"
|
||||
"gfx/makepng.c"
|
||||
"gfx/main.cpp"
|
||||
"gfx/pal_packing.cpp"
|
||||
"gfx/pal_sorting.cpp"
|
||||
"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
|
||||
@@ -95,22 +96,6 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
||||
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
|
||||
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
||||
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
||||
|
||||
@@ -30,16 +30,6 @@
|
||||
#define M_PI 3.14159265358979323846
|
||||
#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
|
||||
*/
|
||||
|
||||
@@ -284,8 +284,9 @@ bool yywrap(void)
|
||||
*/
|
||||
static void newContext(struct FileStackNode *fileInfo)
|
||||
{
|
||||
if (++contextDepth >= maxRecursionDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
++contextDepth;
|
||||
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
|
||||
|
||||
struct Context *context = malloc(sizeof(*context));
|
||||
|
||||
if (!context)
|
||||
@@ -507,6 +508,13 @@ bool fstk_Break(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth)
|
||||
{
|
||||
if (contextDepth >= newDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(char const *mainPath, size_t maxDepth)
|
||||
{
|
||||
struct LexerState *state = lexer_OpenFile(mainPath);
|
||||
|
||||
132
src/asm/lexer.c
132
src/asm/lexer.c
@@ -212,10 +212,6 @@ static struct KeywordMapping {
|
||||
{"INCLUDE", T_POP_INCLUDE},
|
||||
{"PRINT", T_POP_PRINT},
|
||||
{"PRINTLN", T_POP_PRINTLN},
|
||||
{"PRINTT", T_POP_PRINTT},
|
||||
{"PRINTI", T_POP_PRINTI},
|
||||
{"PRINTV", T_POP_PRINTV},
|
||||
{"PRINTF", T_POP_PRINTF},
|
||||
{"EXPORT", T_POP_EXPORT},
|
||||
{"DS", T_POP_DS},
|
||||
{"DB", T_POP_DB},
|
||||
@@ -400,16 +396,16 @@ uint32_t lexer_GetIFDepth(void)
|
||||
|
||||
void lexer_IncIFDepth(void)
|
||||
{
|
||||
struct IfStack *new = malloc(sizeof(*new));
|
||||
struct IfStack *ifStack = malloc(sizeof(*ifStack));
|
||||
|
||||
if (!new)
|
||||
if (!ifStack)
|
||||
fatalerror("Unable to allocate new IF depth: %s\n", strerror(errno));
|
||||
|
||||
new->ranIfBlock = false;
|
||||
new->reachedElseBlock = false;
|
||||
new->next = lexerState->ifStack;
|
||||
ifStack->ranIfBlock = false;
|
||||
ifStack->reachedElseBlock = false;
|
||||
ifStack->next = lexerState->ifStack;
|
||||
|
||||
lexerState->ifStack = new;
|
||||
lexerState->ifStack = ifStack;
|
||||
}
|
||||
|
||||
void lexer_DecIFDepth(void)
|
||||
@@ -668,28 +664,32 @@ static void beginExpansion(char const *str, bool owned, char const *name)
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
if (name) {
|
||||
size_t depth = 0;
|
||||
if (name)
|
||||
lexer_CheckRecursionDepth();
|
||||
|
||||
for (struct Expansion *exp = lexerState->expansions; exp; exp = exp->parent) {
|
||||
if (depth++ >= maxRecursionDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
struct Expansion *exp = malloc(sizeof(*exp));
|
||||
|
||||
struct Expansion *new = malloc(sizeof(*new));
|
||||
|
||||
if (!new)
|
||||
if (!exp)
|
||||
fatalerror("Unable to allocate new expansion: %s\n", strerror(errno));
|
||||
|
||||
new->parent = lexerState->expansions;
|
||||
new->name = name ? strdup(name) : NULL;
|
||||
new->contents.unowned = str;
|
||||
new->size = size;
|
||||
new->offset = 0;
|
||||
new->owned = owned;
|
||||
exp->parent = lexerState->expansions;
|
||||
exp->name = name ? strdup(name) : NULL;
|
||||
exp->contents.unowned = str;
|
||||
exp->size = size;
|
||||
exp->offset = 0;
|
||||
exp->owned = owned;
|
||||
|
||||
lexerState->expansions = new;
|
||||
lexerState->expansions = exp;
|
||||
}
|
||||
|
||||
void lexer_CheckRecursionDepth(void)
|
||||
{
|
||||
size_t depth = 0;
|
||||
|
||||
for (struct Expansion *exp = lexerState->expansions; exp; exp = exp->parent) {
|
||||
if (depth++ >= maxRecursionDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static void freeExpansion(struct Expansion *expansion)
|
||||
@@ -808,6 +808,19 @@ static char const *readMacroArg(char name)
|
||||
return str;
|
||||
}
|
||||
|
||||
static size_t readInternal(size_t bufIndex, size_t nbChars)
|
||||
{
|
||||
// This buffer overflow made me lose WEEKS of my life. Never again.
|
||||
assert(bufIndex + nbChars <= LEXER_BUF_SIZE);
|
||||
ssize_t nbReadChars = read(lexerState->fd, &lexerState->buf[bufIndex], nbChars);
|
||||
|
||||
if (nbReadChars == -1)
|
||||
fatalerror("Error while reading \"%s\": %s\n", lexerState->path, strerror(errno));
|
||||
|
||||
// `nbReadChars` cannot be negative, so it's fine to cast to `size_t`
|
||||
return (size_t)nbReadChars;
|
||||
}
|
||||
|
||||
/* We only need one character of lookahead, for macro arguments */
|
||||
static int peekInternal(uint8_t distance)
|
||||
{
|
||||
@@ -839,42 +852,31 @@ static int peekInternal(uint8_t distance)
|
||||
|
||||
/* Compute the index we'll start writing to */
|
||||
size_t writeIndex = (lexerState->index + lexerState->nbChars) % LEXER_BUF_SIZE;
|
||||
ssize_t nbCharsRead = 0, totalCharsRead = 0;
|
||||
|
||||
#define readChars(size) do { \
|
||||
/* This buffer overflow made me lose WEEKS of my life. Never again. */ \
|
||||
assert(writeIndex + (size) <= LEXER_BUF_SIZE); \
|
||||
nbCharsRead = read(lexerState->fd, &lexerState->buf[writeIndex], (size)); \
|
||||
if (nbCharsRead == -1) \
|
||||
fatalerror("Error while reading \"%s\": %s\n", lexerState->path, strerror(errno)); \
|
||||
totalCharsRead += nbCharsRead; \
|
||||
writeIndex += nbCharsRead; \
|
||||
if (writeIndex == LEXER_BUF_SIZE) \
|
||||
writeIndex = 0; \
|
||||
target -= nbCharsRead; \
|
||||
} while (0)
|
||||
|
||||
/* If the range to fill passes over the buffer wrapping point, we need two reads */
|
||||
if (writeIndex + target > LEXER_BUF_SIZE) {
|
||||
size_t nbExpectedChars = LEXER_BUF_SIZE - writeIndex;
|
||||
size_t nbReadChars = readInternal(writeIndex, nbExpectedChars);
|
||||
|
||||
lexerState->nbChars += nbReadChars;
|
||||
|
||||
writeIndex += nbReadChars;
|
||||
if (writeIndex == LEXER_BUF_SIZE)
|
||||
writeIndex = 0;
|
||||
|
||||
readChars(nbExpectedChars);
|
||||
// If the read was incomplete, don't perform a second read
|
||||
// `nbCharsRead` cannot be negative, so it's fine to cast to `size_t`
|
||||
if ((size_t)nbCharsRead < nbExpectedChars)
|
||||
target -= nbReadChars;
|
||||
if (nbReadChars < nbExpectedChars)
|
||||
target = 0;
|
||||
}
|
||||
if (target != 0)
|
||||
readChars(target);
|
||||
|
||||
#undef readChars
|
||||
|
||||
lexerState->nbChars += totalCharsRead;
|
||||
lexerState->nbChars += readInternal(writeIndex, target);
|
||||
|
||||
/* If there aren't enough chars even after refilling, give up */
|
||||
if (lexerState->nbChars <= distance)
|
||||
return EOF;
|
||||
}
|
||||
|
||||
return (unsigned char)lexerState->buf[(lexerState->index + distance) % LEXER_BUF_SIZE];
|
||||
}
|
||||
|
||||
@@ -1888,18 +1890,23 @@ static int yylex_NORMAL(void)
|
||||
return T_OP_LOGICLT;
|
||||
}
|
||||
|
||||
case '>': /* Either >>=, GT, GTE, or right shift */
|
||||
case '>': /* Either >>=, GT, GTE, or either kind of right shift */
|
||||
switch (peek()) {
|
||||
case '=':
|
||||
shiftChar();
|
||||
return T_OP_LOGICGE;
|
||||
case '>':
|
||||
shiftChar();
|
||||
if (peek() == '=') {
|
||||
switch (peek()) {
|
||||
case '=':
|
||||
shiftChar();
|
||||
return T_POP_SHREQ;
|
||||
case '>':
|
||||
shiftChar();
|
||||
return T_OP_USHR;
|
||||
default:
|
||||
return T_OP_SHR;
|
||||
}
|
||||
return T_OP_SHR;
|
||||
default:
|
||||
return T_OP_LOGICGT;
|
||||
}
|
||||
@@ -2045,9 +2052,23 @@ static int yylex_RAW(void)
|
||||
size_t i = 0;
|
||||
int c;
|
||||
|
||||
/* Trim left whitespace (stops at a block comment or line continuation) */
|
||||
while (isWhitespace(peek()))
|
||||
shiftChar();
|
||||
/* Trim left whitespace (stops at a block comment) */
|
||||
for (;;) {
|
||||
c = peek();
|
||||
if (isWhitespace(c)) {
|
||||
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 (;;) {
|
||||
c = peek();
|
||||
@@ -2096,6 +2117,7 @@ static int yylex_RAW(void)
|
||||
shiftChar();
|
||||
c = peek();
|
||||
|
||||
backslash:
|
||||
switch (c) {
|
||||
case ',': /* Escapes only valid inside a macro arg */
|
||||
case '(':
|
||||
|
||||
@@ -101,7 +101,7 @@ char const *macro_GetArg(uint32_t i)
|
||||
: macroArgs->args[realIndex];
|
||||
}
|
||||
|
||||
char *macro_GetAllArgs(void)
|
||||
char const *macro_GetAllArgs(void)
|
||||
{
|
||||
if (!macroArgs)
|
||||
return NULL;
|
||||
|
||||
@@ -59,7 +59,9 @@ bool generatePhonyDeps;
|
||||
char *targetFileName;
|
||||
|
||||
bool haltnop;
|
||||
bool warnOnHaltNop;
|
||||
bool optimizeLoads;
|
||||
bool warnOnLdOpt;
|
||||
bool verbose;
|
||||
bool warnings; /* True to enable warnings, false to disable them. */
|
||||
|
||||
@@ -84,7 +86,7 @@ static char *make_escape(char const *str)
|
||||
}
|
||||
|
||||
/* Short options */
|
||||
static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
|
||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w";
|
||||
|
||||
/* Variables for the long-only options */
|
||||
static int depType; /* Variants of `-M` */
|
||||
@@ -104,9 +106,11 @@ static struct option const longopts[] = {
|
||||
{ "define", required_argument, NULL, 'D' },
|
||||
{ "export-all", no_argument, NULL, 'E' },
|
||||
{ "gfx-chars", required_argument, NULL, 'g' },
|
||||
{ "nop-after-halt", no_argument, NULL, 'H' },
|
||||
{ "halt-without-nop", no_argument, NULL, 'h' },
|
||||
{ "include", required_argument, NULL, 'i' },
|
||||
{ "preserve-ld", no_argument, NULL, 'L' },
|
||||
{ "auto-ldh", no_argument, NULL, 'l' },
|
||||
{ "dependfile", required_argument, NULL, 'M' },
|
||||
{ "MG", no_argument, &depType, 'G' },
|
||||
{ "MP", no_argument, &depType, 'P' },
|
||||
@@ -170,8 +174,10 @@ int main(int argc, char *argv[])
|
||||
opt_B("01");
|
||||
opt_G("0123");
|
||||
opt_P(0);
|
||||
optimizeLoads = true;
|
||||
haltnop = true;
|
||||
warnOnHaltNop = true;
|
||||
optimizeLoads = true;
|
||||
warnOnLdOpt = true;
|
||||
verbose = false;
|
||||
warnings = true;
|
||||
sym_SetExportAll(false);
|
||||
@@ -209,7 +215,14 @@ int main(int argc, char *argv[])
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
if (!haltnop)
|
||||
errx("`-H` and `-h` don't make sense together");
|
||||
warnOnHaltNop = false;
|
||||
break;
|
||||
case 'h':
|
||||
if (!warnOnHaltNop)
|
||||
errx("`-H` and `-h` don't make sense together");
|
||||
haltnop = false;
|
||||
break;
|
||||
|
||||
@@ -218,8 +231,15 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (!warnOnLdOpt)
|
||||
errx("`-L` and `-l` don't make sense together");
|
||||
optimizeLoads = false;
|
||||
break;
|
||||
case 'l':
|
||||
if (!optimizeLoads)
|
||||
errx("`-L` and `-l` don't make sense together");
|
||||
warnOnLdOpt = false;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (!strcmp("-", musl_optarg))
|
||||
@@ -241,7 +261,7 @@ int main(int argc, char *argv[])
|
||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||
errx("Invalid argument for option 'p'");
|
||||
|
||||
if (fill < 0 || fill > 0xFF)
|
||||
if (fill > 0xFF)
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
|
||||
opt_P(fill);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -5,6 +6,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fstack.h"
|
||||
#include "asm/lexer.h"
|
||||
#include "asm/main.h"
|
||||
#include "asm/section.h"
|
||||
@@ -15,8 +17,11 @@ struct OptStackEntry {
|
||||
char gbgfx[4];
|
||||
int32_t fillByte;
|
||||
bool haltnop;
|
||||
bool warnOnHaltNop;
|
||||
bool optimizeLoads;
|
||||
bool warnOnLdOpt;
|
||||
bool warningsAreErrors;
|
||||
size_t maxRecursionDepth;
|
||||
// Don't be confused: we use the size of the **global variable** `warningStates`!
|
||||
enum WarningState warningStates[sizeof(warningStates)];
|
||||
struct OptStackEntry *next;
|
||||
@@ -24,12 +29,12 @@ struct OptStackEntry {
|
||||
|
||||
static struct OptStackEntry *stack = NULL;
|
||||
|
||||
void opt_B(char chars[2])
|
||||
void opt_B(char const chars[2])
|
||||
{
|
||||
lexer_SetBinDigits(chars);
|
||||
}
|
||||
|
||||
void opt_G(char chars[4])
|
||||
void opt_G(char const chars[4])
|
||||
{
|
||||
lexer_SetGfxDigits(chars);
|
||||
}
|
||||
@@ -39,6 +44,17 @@ void opt_P(uint8_t fill)
|
||||
fillByte = fill;
|
||||
}
|
||||
|
||||
void opt_R(size_t newDepth)
|
||||
{
|
||||
fstk_NewRecursionDepth(newDepth);
|
||||
lexer_CheckRecursionDepth();
|
||||
}
|
||||
|
||||
void opt_H(bool warn)
|
||||
{
|
||||
warnOnHaltNop = warn;
|
||||
}
|
||||
|
||||
void opt_h(bool halt)
|
||||
{
|
||||
haltnop = halt;
|
||||
@@ -49,6 +65,11 @@ void opt_L(bool optimize)
|
||||
optimizeLoads = optimize;
|
||||
}
|
||||
|
||||
void opt_l(bool warn)
|
||||
{
|
||||
warnOnLdOpt = warn;
|
||||
}
|
||||
|
||||
void opt_W(char *flag)
|
||||
{
|
||||
processWarningFlag(flag);
|
||||
@@ -86,6 +107,36 @@ void opt_Parse(char *s)
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
++s; // Skip leading whitespace
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
unsigned long newDepth = strtoul(s, &endptr, 10);
|
||||
|
||||
if (*endptr != '\0') {
|
||||
error("Invalid argument to option 'r' (\"%s\")\n", s);
|
||||
} else if (errno == ERANGE) {
|
||||
error("Argument to 'r' is out of range (\"%s\")\n", s);
|
||||
} else {
|
||||
opt_R(newDepth);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'H':
|
||||
if (s[1] == '\0')
|
||||
opt_H(false);
|
||||
else
|
||||
error("Option 'H' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
if (s[1] == '\0')
|
||||
opt_h(false);
|
||||
@@ -100,6 +151,13 @@ void opt_Parse(char *s)
|
||||
error("Option 'L' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (s[1] == '\0')
|
||||
opt_l(false);
|
||||
else
|
||||
error("Option 'l' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0)
|
||||
opt_W(&s[1]);
|
||||
@@ -154,8 +212,10 @@ void opt_Push(void)
|
||||
entry->fillByte = fillByte; // Pulled from section.h
|
||||
|
||||
entry->haltnop = haltnop; // Pulled from main.h
|
||||
entry->warnOnHaltNop = warnOnHaltNop;
|
||||
|
||||
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
|
||||
entry->warnOnLdOpt = warnOnLdOpt;
|
||||
|
||||
// Both of these pulled from warning.h
|
||||
entry->warningsAreErrors = warningsAreErrors;
|
||||
@@ -177,8 +237,10 @@ void opt_Pop(void)
|
||||
opt_B(entry->binary);
|
||||
opt_G(entry->gbgfx);
|
||||
opt_P(entry->fillByte);
|
||||
opt_H(entry->warnOnHaltNop);
|
||||
opt_h(entry->haltnop);
|
||||
opt_L(entry->optimizeLoads);
|
||||
opt_l(entry->warnOnLdOpt);
|
||||
|
||||
// opt_W does not apply a whole warning state; it processes one flag string
|
||||
warningsAreErrors = entry->warningsAreErrors;
|
||||
|
||||
@@ -192,7 +192,7 @@ static uint32_t getsectid(struct Section const *sect)
|
||||
|
||||
static uint32_t getSectIDIfAny(struct Section const *sect)
|
||||
{
|
||||
return sect ? getsectid(sect) : -1;
|
||||
return sect ? getsectid(sect) : (uint32_t)-1;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -200,7 +200,7 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
|
||||
*/
|
||||
static void writepatch(struct Patch const *patch, FILE *f)
|
||||
{
|
||||
assert(patch->src->ID != -1);
|
||||
assert(patch->src->ID != (uint32_t)-1);
|
||||
putlong(patch->src->ID, f);
|
||||
putlong(patch->lineNo, f);
|
||||
putlong(patch->offset, f);
|
||||
@@ -264,7 +264,7 @@ static void writesymbol(struct Symbol const *sym, FILE *f)
|
||||
if (!sym_IsDefined(sym)) {
|
||||
putc(SYMTYPE_IMPORT, f);
|
||||
} else {
|
||||
assert(sym->src->ID != -1);
|
||||
assert(sym->src->ID != (uint32_t)-1);
|
||||
|
||||
putc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
|
||||
putlong(sym->src->ID, f);
|
||||
@@ -490,7 +490,7 @@ static void freeassert(struct Assertion *assert)
|
||||
|
||||
static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
||||
{
|
||||
putlong(node->parent ? node->parent->ID : -1, f);
|
||||
putlong(node->parent ? node->parent->ID : (uint32_t)-1, f);
|
||||
putlong(node->lineNo, f);
|
||||
putc(node->type, f);
|
||||
if (node->type != NODE_REPT) {
|
||||
|
||||
@@ -218,10 +218,10 @@ static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionN
|
||||
return (uint32_t)pos;
|
||||
}
|
||||
|
||||
static void strrpl(char *dest, size_t destLen, char const *src, char const *old, char const *new)
|
||||
static void strrpl(char *dest, size_t destLen, char const *src, char const *old, char const *rep)
|
||||
{
|
||||
size_t oldLen = strlen(old);
|
||||
size_t newLen = strlen(new);
|
||||
size_t repLen = strlen(rep);
|
||||
size_t i = 0;
|
||||
|
||||
if (!oldLen) {
|
||||
@@ -240,8 +240,8 @@ static void strrpl(char *dest, size_t destLen, char const *src, char const *old,
|
||||
break;
|
||||
|
||||
// Copy the replacement substring
|
||||
memcpy(dest + i, new, newLen < destLen - i ? newLen : destLen - i);
|
||||
i += newLen;
|
||||
memcpy(dest + i, rep, repLen < destLen - i ? repLen : destLen - i);
|
||||
i += repLen;
|
||||
if (i >= destLen)
|
||||
break;
|
||||
|
||||
@@ -385,7 +385,7 @@ static void initDsArgList(struct DsArgList *args)
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
static size_t nextDsArgListIndex(struct DsArgList *args)
|
||||
static void appendDsArgList(struct DsArgList *args, const struct Expression *expr)
|
||||
{
|
||||
if (args->nbArgs == args->capacity) {
|
||||
args->capacity = (args->capacity + 1) * 2;
|
||||
@@ -394,7 +394,7 @@ static size_t nextDsArgListIndex(struct DsArgList *args)
|
||||
fatalerror("realloc error while resizing ds arg list: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
return args->nbArgs++;
|
||||
args->args[args->nbArgs++] = *expr;
|
||||
}
|
||||
|
||||
static void freeDsArgList(struct DsArgList *args)
|
||||
@@ -534,7 +534,7 @@ enum {
|
||||
%token T_OP_LOGICNE "!=" T_OP_LOGICEQU "=="
|
||||
%token T_OP_ADD "+" T_OP_SUB "-"
|
||||
%token T_OP_OR "|" T_OP_XOR "^" T_OP_AND "&"
|
||||
%token T_OP_SHL "<<" T_OP_SHR ">>"
|
||||
%token T_OP_SHL "<<" T_OP_SHR ">>" T_OP_USHR ">>>"
|
||||
%token T_OP_MUL "*" T_OP_DIV "/" T_OP_MOD "%"
|
||||
%token T_OP_NOT "~"
|
||||
%left T_OP_LOGICOR
|
||||
@@ -542,7 +542,7 @@ enum {
|
||||
%left T_OP_LOGICGT T_OP_LOGICLT T_OP_LOGICGE T_OP_LOGICLE T_OP_LOGICNE T_OP_LOGICEQU
|
||||
%left T_OP_ADD T_OP_SUB
|
||||
%left T_OP_OR T_OP_XOR T_OP_AND
|
||||
%left T_OP_SHL T_OP_SHR
|
||||
%left T_OP_SHL T_OP_SHR T_OP_USHR
|
||||
%left T_OP_MUL T_OP_DIV T_OP_MOD
|
||||
|
||||
%precedence NEG /* negation -- unary minus */
|
||||
@@ -599,7 +599,6 @@ enum {
|
||||
|
||||
%token T_POP_INCLUDE "INCLUDE"
|
||||
%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_EXPORT "EXPORT"
|
||||
%token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
|
||||
@@ -856,7 +855,7 @@ macroargs : %empty {
|
||||
|
||||
/* These commands start with a T_LABEL. */
|
||||
assignment_directive : equ
|
||||
| set
|
||||
| assignment
|
||||
| rb
|
||||
| rw
|
||||
| rl
|
||||
@@ -866,10 +865,6 @@ assignment_directive : equ
|
||||
directive : endc
|
||||
| print
|
||||
| println
|
||||
| printf
|
||||
| printt
|
||||
| printv
|
||||
| printi
|
||||
| export
|
||||
| db
|
||||
| dw
|
||||
@@ -927,12 +922,8 @@ compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; }
|
||||
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
|
||||
;
|
||||
|
||||
set : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
|
||||
assignment : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $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); }
|
||||
@@ -1144,14 +1135,10 @@ ds : T_POP_DS uconst { sect_Skip($2, true); }
|
||||
|
||||
ds_args : reloc_8bit {
|
||||
initDsArgList(&$$);
|
||||
size_t i = nextDsArgListIndex(&$$);
|
||||
|
||||
$$.args[i] = $1;
|
||||
appendDsArgList(&$$, &$1);
|
||||
}
|
||||
| ds_args T_COMMA reloc_8bit {
|
||||
size_t i = nextDsArgListIndex(&$1);
|
||||
|
||||
$1.args[i] = $3;
|
||||
appendDsArgList(&$1, &$3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
@@ -1178,14 +1165,6 @@ def_set : def_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); }
|
||||
| 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 {
|
||||
@@ -1299,30 +1278,6 @@ print_expr : const_no_str { printf("$%" PRIX32, $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 {
|
||||
int32_t value = $1;
|
||||
|
||||
@@ -1481,6 +1436,9 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
|
||||
| relocexpr T_OP_SHR relocexpr {
|
||||
rpn_BinaryOp(RPN_SHR, &$$, &$1, &$3);
|
||||
}
|
||||
| relocexpr T_OP_USHR relocexpr {
|
||||
rpn_BinaryOp(RPN_USHR, &$$, &$1, &$3);
|
||||
}
|
||||
| relocexpr T_OP_MUL relocexpr {
|
||||
rpn_BinaryOp(RPN_MUL, &$$, &$1, &$3);
|
||||
}
|
||||
@@ -1843,8 +1801,13 @@ z80_ei : T_Z80_EI { sect_AbsByte(0xFB); }
|
||||
|
||||
z80_halt : T_Z80_HALT {
|
||||
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);
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
@@ -1952,6 +1915,10 @@ 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 {
|
||||
if (optimizeLoads && rpn_isKnown(&$2)
|
||||
&& $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($2.val & 0xFF);
|
||||
rpn_Free(&$2);
|
||||
@@ -2000,6 +1967,10 @@ z80_ld_a : T_Z80_LD reg_r T_COMMA c_ind {
|
||||
if ($2 == REG_A) {
|
||||
if (optimizeLoads && rpn_isKnown(&$4)
|
||||
&& $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($4.val & 0xFF);
|
||||
rpn_Free(&$4);
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
|
||||
if (size >= 128) { /* If this wasn't enough, try again */ \
|
||||
_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__); \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -317,7 +319,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
|
||||
{
|
||||
if (!rpn_isSymbol(expr))
|
||||
return NULL;
|
||||
return sym_FindScopedSymbol((char *)expr->rpn + 1);
|
||||
return sym_FindScopedSymbol((char const *)expr->rpn + 1);
|
||||
}
|
||||
|
||||
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
|
||||
@@ -339,10 +341,51 @@ static bool isDiffConstant(struct Expression const *src1,
|
||||
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to compute a constant binary AND from non-constant operands
|
||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
static int32_t tryConstMask(struct Expression const *lhs, struct Expression const *rhs)
|
||||
{
|
||||
struct Symbol const *sym = rpn_SymbolOf(lhs);
|
||||
struct Expression const *expr = rhs;
|
||||
|
||||
if (!sym || !sym_GetSection(sym)) {
|
||||
// If the lhs isn't a symbol, try again the other way around
|
||||
sym = rpn_SymbolOf(rhs);
|
||||
expr = lhs;
|
||||
|
||||
if (!sym || !sym_GetSection(sym))
|
||||
return -1;
|
||||
}
|
||||
assert(sym_IsNumeric(sym));
|
||||
|
||||
if (!rpn_isKnown(expr))
|
||||
return -1;
|
||||
// We can now safely use `expr->val`
|
||||
struct Section const *sect = sym_GetSection(sym);
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect->align); // The max alignment is 16
|
||||
|
||||
// The mask must ignore all unknown bits
|
||||
if ((expr->val & unknownBits) != 0)
|
||||
return -1;
|
||||
|
||||
// `sym_GetValue()` attempts to add the section's address,
|
||||
// but that's "-1" because the section is floating (otherwise we wouldn't be here)
|
||||
assert(sect->org == (uint32_t)-1);
|
||||
int32_t symbolOfs = sym_GetValue(sym) + 1;
|
||||
|
||||
return (symbolOfs + sect->alignOfs) & ~unknownBits;
|
||||
}
|
||||
|
||||
void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
const struct Expression *src1, const struct Expression *src2)
|
||||
{
|
||||
expr->isSymbol = false;
|
||||
int32_t constMaskVal;
|
||||
|
||||
/* First, check if the expression is known */
|
||||
expr->isKnown = src1->isKnown && src2->isKnown;
|
||||
@@ -421,6 +464,19 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
|
||||
expr->val = op_shift_right(src1->val, src2->val);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (src2->val < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
|
||||
if (src2->val >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by large amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
|
||||
expr->val = op_shift_right_unsigned(src1->val, src2->val);
|
||||
break;
|
||||
case RPN_MUL:
|
||||
expr->val = uleft * uright;
|
||||
break;
|
||||
@@ -477,6 +533,9 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
|
||||
expr->val = sym_GetValue(symbol1) - sym_GetValue(symbol2);
|
||||
expr->isKnown = true;
|
||||
} else if (op == RPN_AND && (constMaskVal = tryConstMask(src1, src2)) != -1) {
|
||||
expr->val = constMaskVal;
|
||||
expr->isKnown = true;
|
||||
} else {
|
||||
/* If it's not known, start computing the RPN expression */
|
||||
|
||||
|
||||
@@ -743,8 +743,13 @@ void sym_Init(time_t now)
|
||||
|
||||
sym_AddVar("_RS", 0)->isBuiltin = true;
|
||||
|
||||
#define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true
|
||||
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
|
||||
#define addSym(fn, name, val) do { \
|
||||
struct Symbol *sym = fn(name, val); \
|
||||
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());
|
||||
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
|
||||
@@ -760,7 +765,6 @@ void sym_Init(time_t now)
|
||||
now = 0;
|
||||
}
|
||||
|
||||
const struct tm *time_utc = gmtime(&now);
|
||||
const struct tm *time_local = localtime(&now);
|
||||
|
||||
strftime(savedTIME, sizeof(savedTIME), "\"%H:%M:%S\"", time_local);
|
||||
@@ -769,6 +773,8 @@ void sym_Init(time_t now)
|
||||
sizeof(savedTIMESTAMP_ISO8601_LOCAL), "\"%Y-%m-%dT%H:%M:%S%z\"",
|
||||
time_local);
|
||||
|
||||
const struct tm *time_utc = gmtime(&now);
|
||||
|
||||
strftime(savedTIMESTAMP_ISO8601_UTC,
|
||||
sizeof(savedTIMESTAMP_ISO8601_UTC), "\"%Y-%m-%dT%H:%M:%SZ\"",
|
||||
time_utc);
|
||||
@@ -787,16 +793,7 @@ void sym_Init(time_t now)
|
||||
|
||||
#undef addNumber
|
||||
#undef addString
|
||||
#undef addSym
|
||||
|
||||
sym_SetCurrentSymbolScope(NULL);
|
||||
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;
|
||||
}
|
||||
anonLabelID = 0;}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
bison -V | awk -v major="$1" -v minor="$2" '
|
||||
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
||||
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
||||
|
||||
@@ -1268,12 +1268,12 @@ do { \
|
||||
#define SPEC_H TRASH_HEADER_SUM
|
||||
#define SPEC_g FIX_GLOBAL_SUM
|
||||
#define SPEC_G TRASH_GLOBAL_SUM
|
||||
#define overrideSpec(new, bad) \
|
||||
#define overrideSpec(cur, bad) \
|
||||
do { \
|
||||
if (fixSpec & SPEC_##bad) \
|
||||
fprintf(stderr, \
|
||||
"warning: '" #new "' overriding '" #bad "' in fix spec\n"); \
|
||||
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##new; \
|
||||
"warning: '" #cur "' overriding '" #bad "' in fix spec\n"); \
|
||||
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##cur; \
|
||||
} while (0)
|
||||
case 'l':
|
||||
overrideSpec(l, L);
|
||||
|
||||
386
src/gfx/gb.c
386
src/gfx/gb.c
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
int x, y, byte;
|
||||
uint8_t index;
|
||||
|
||||
for (y = 0; y < raw_image->height; y++) {
|
||||
for (x = 0; x < raw_image->width; x++) {
|
||||
index = raw_image->data[y][x];
|
||||
index &= (1 << depth) - 1;
|
||||
|
||||
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
358
src/gfx/main.c
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
* 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
Normal file
813
src/gfx/main.cpp
Normal file
@@ -0,0 +1,813 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,807 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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);
|
||||
|
||||
if (opts->debug)
|
||||
free(outfile);
|
||||
}
|
||||
|
||||
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr)
|
||||
{
|
||||
int y;
|
||||
struct RawIndexedImage *raw_image = *raw_image_ptr_ptr;
|
||||
|
||||
for (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);
|
||||
}
|
||||
512
src/gfx/pal_packing.cpp
Normal file
512
src/gfx/pal_packing.cpp
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* 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
|
||||
105
src/gfx/pal_sorting.cpp
Normal file
105
src/gfx/pal_sorting.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
|
||||
#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
|
||||
451
src/gfx/pal_spec.cpp
Normal file
451
src/gfx/pal_spec.cpp
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* 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
Normal file
1114
src/gfx/process.cpp
Normal file
File diff suppressed because it is too large
Load Diff
83
src/gfx/proto_palette.cpp
Normal file
83
src/gfx/proto_palette.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
324
src/gfx/reverse.cpp
Normal file
324
src/gfx/reverse.cpp
Normal file
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
52
src/gfx/rgba.cpp
Normal file
52
src/gfx/rgba.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#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
160
src/gfx/rgbgfx.1
@@ -1,160 +0,0 @@
|
||||
.\"
|
||||
.\" 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,6 +8,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -446,9 +447,27 @@ void assign_AssignSections(void)
|
||||
|
||||
/* Overlaying requires only fully-constrained sections */
|
||||
verbosePrint("Assigning other sections...\n");
|
||||
if (overlayFileName)
|
||||
errx("All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
|
||||
nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
|
||||
if (overlayFileName) {
|
||||
fprintf(stderr, "FATAL: All sections must be fixed when using an overlay file");
|
||||
uint8_t nbSections = 0;
|
||||
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 */
|
||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
|
||||
|
||||
@@ -281,13 +281,12 @@ static void parseScrambleSpec(char const *spec)
|
||||
// Now, determine which region type this is
|
||||
enum ScrambledRegion region = 0;
|
||||
|
||||
while (region < SCRAMBLE_UNK) {
|
||||
for (; region < SCRAMBLE_UNK; region++) {
|
||||
// If the strings match (case-insensitively), we got it!
|
||||
// It's OK not to use `strncasecmp` because `regionName` is still
|
||||
// NUL-terminated, since the encompassing spec is.
|
||||
if (!strcasecmp(scrambleSpecs[region].name, regionName))
|
||||
// `strncasecmp` must be used here since `regionName` points
|
||||
// to the entire remaining argument.
|
||||
if (!strncasecmp(scrambleSpecs[region].name, regionName, regionNameLen))
|
||||
break;
|
||||
region++;
|
||||
}
|
||||
|
||||
if (region == SCRAMBLE_UNK)
|
||||
|
||||
@@ -151,8 +151,10 @@ static char *readstr(FILE *file)
|
||||
/* Read char */
|
||||
int byte = getc(file);
|
||||
|
||||
if (byte == EOF)
|
||||
if (byte == EOF) {
|
||||
free(str);
|
||||
return NULL;
|
||||
}
|
||||
str[index] = byte;
|
||||
} while (str[index]);
|
||||
return str;
|
||||
|
||||
@@ -39,7 +39,7 @@ struct SortedSymbol {
|
||||
};
|
||||
|
||||
static struct {
|
||||
uint32_t nbBanks;
|
||||
uint32_t nbBanks; // Size of the array below (which may be NULL if this is 0)
|
||||
struct SortedSections {
|
||||
struct SortedSection *sections;
|
||||
struct SortedSection *zeroLenSections;
|
||||
@@ -243,9 +243,9 @@ static void writeROM(void)
|
||||
coverOverlayBanks(nbOverlayBanks);
|
||||
|
||||
if (outputFile) {
|
||||
if (sections[SECTTYPE_ROM0].nbBanks > 0)
|
||||
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
|
||||
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
|
||||
writeBank(sections[SECTTYPE_ROM0].banks ? sections[SECTTYPE_ROM0].banks[0].sections
|
||||
: NULL,
|
||||
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
|
||||
|
||||
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
||||
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
|
||||
|
||||
@@ -111,7 +111,7 @@ static uint32_t getRPNByte(uint8_t const **expression, int32_t *size,
|
||||
static struct Symbol const *getSymbol(struct Symbol const * const *symbolList,
|
||||
uint32_t index)
|
||||
{
|
||||
assert(index != -1); /* PC needs to be handled specially, not here */
|
||||
assert(index != (uint32_t)-1); /* PC needs to be handled specially, not here */
|
||||
struct Symbol const *symbol = symbolList[index];
|
||||
|
||||
/* If the symbol is defined elsewhere... */
|
||||
@@ -265,6 +265,10 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
||||
value = popRPN();
|
||||
value = op_shift_right(popRPN(), value);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
value = popRPN();
|
||||
value = op_shift_right_unsigned(popRPN(), value);
|
||||
break;
|
||||
|
||||
case RPN_BANK_SYM:
|
||||
value = 0;
|
||||
|
||||
15
src/opmath.c
15
src/opmath.c
@@ -86,3 +86,18 @@ int32_t op_shift_right(int32_t value, int32_t amount)
|
||||
// undefined, so use a left shift manually sign-extended
|
||||
return ((uint32_t)value >> amount) | amount_high_bits;
|
||||
}
|
||||
|
||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount)
|
||||
{
|
||||
// Repeat the easy cases here to avoid INT_MIN funny business
|
||||
if (amount == 0)
|
||||
return value;
|
||||
if (value == 0 || amount <= -32)
|
||||
return 0;
|
||||
if (amount > 31)
|
||||
return (value < 0) ? -1 : 0;
|
||||
if (amount < 0)
|
||||
return op_shift_left(value, -amount);
|
||||
|
||||
return (uint32_t)value >> amount;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@
|
||||
#include "helpers.h"
|
||||
#include "version.h"
|
||||
|
||||
// This variable is passed via `-D` from the Makefile, but not from CMake
|
||||
// (in which `configure_file()` is used on this file to replace some syntax)
|
||||
#ifndef BUILD_VERSION_STRING
|
||||
// CMake-specific syntax here
|
||||
#define BUILD_VERSION_STRING "@GIT_REV@"
|
||||
#endif
|
||||
|
||||
char const *get_package_version_string(void)
|
||||
{
|
||||
// The following conditional should be simplified by the compiler.
|
||||
|
||||
18
test/CMakeLists.txt
Normal file
18
test/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
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()
|
||||
17
test/asm/const-and.asm
Normal file
17
test/asm/const-and.asm
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
println @ & 0 ; This should produce an error, but not crash
|
||||
|
||||
SECTION "Test", ROM0,ALIGN[4,2]
|
||||
|
||||
ds 40
|
||||
Aligned:
|
||||
|
||||
println Aligned & $0f
|
||||
println ~$fffc & Aligned
|
||||
println Aligned & $1f ; Not constant
|
||||
println @ & $0f
|
||||
|
||||
SECTION "Unaligned", ROM0
|
||||
|
||||
println @ & 0
|
||||
println @ & 1 ; Nope
|
||||
7
test/asm/const-and.err
Normal file
7
test/asm/const-and.err
Normal file
@@ -0,0 +1,7 @@
|
||||
error: const-and.asm(2):
|
||||
PC has no value outside a section
|
||||
error: const-and.asm(11):
|
||||
Expected constant expression: 'Aligned' is not constant at assembly time
|
||||
error: const-and.asm(17):
|
||||
Expected constant expression: PC is not constant at assembly time
|
||||
error: Assembly aborted (3 errors)!
|
||||
7
test/asm/const-and.out
Normal file
7
test/asm/const-and.out
Normal file
@@ -0,0 +1,7 @@
|
||||
$0
|
||||
$A
|
||||
$A
|
||||
$0
|
||||
$A
|
||||
$0
|
||||
$0
|
||||
@@ -1,3 +0,0 @@
|
||||
; Remove this test case when _PI is removed.
|
||||
PRINTLN "{f:_PI}"
|
||||
PURGE _PI
|
||||
@@ -1,5 +0,0 @@
|
||||
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 +0,0 @@
|
||||
3.14159
|
||||
@@ -4,8 +4,8 @@
|
||||
\1: < 1>
|
||||
\2: <2>
|
||||
|
||||
'mac c,d':
|
||||
\1: < c>
|
||||
'mac c,d':
|
||||
\1: <c>
|
||||
\2: <d>
|
||||
|
||||
'mac 1,2 + 2,3':
|
||||
|
||||
13
test/asm/opt-r.asm
Normal file
13
test/asm/opt-r.asm
Normal file
@@ -0,0 +1,13 @@
|
||||
OPT r34 ; :3
|
||||
OPT r 360
|
||||
|
||||
; Invalid
|
||||
OPT r ; Missing arg
|
||||
OPT r 2a ; Bad decimal
|
||||
|
||||
; Check that it has an effect
|
||||
OPT r 1
|
||||
MACRO m
|
||||
m
|
||||
ENDM
|
||||
m
|
||||
6
test/asm/opt-r.err
Normal file
6
test/asm/opt-r.err
Normal file
@@ -0,0 +1,6 @@
|
||||
error: opt-r.asm(5):
|
||||
Missing argument to option 'r'
|
||||
error: opt-r.asm(6):
|
||||
Invalid argument to option 'r' ("2a")
|
||||
FATAL: opt-r.asm(13):
|
||||
Recursion limit (1) exceeded
|
||||
0
test/asm/opt-r.out
Normal file
0
test/asm/opt-r.out
Normal file
@@ -10,6 +10,8 @@ pusho
|
||||
println $8000_0000 / -1
|
||||
popo
|
||||
|
||||
opt H, l
|
||||
|
||||
ds 1
|
||||
ld [$ff88], a
|
||||
halt
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
warning: opt.asm(16): [-Wdiv]
|
||||
warning: opt.asm(18): [-Wdiv]
|
||||
Division of -2147483648 by -1 yields -2147483648
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
test: macro
|
||||
macro test
|
||||
; Test the rpn system, as well as the linker...
|
||||
dl \1 + zero
|
||||
DEF expr EQUS STRRPL(STRRPL("\1 + zero)", "<< ", "<< ("), ">> ", ">> (")
|
||||
dl expr
|
||||
PURGE expr
|
||||
|
||||
; ...as well as the constexpr system
|
||||
result\@ equ \1
|
||||
println "\1 = {result\@}"
|
||||
println "\1 = ", (\1)
|
||||
endm
|
||||
|
||||
section "test", ROM0[0]
|
||||
@@ -23,5 +24,8 @@ section "test", ROM0[0]
|
||||
test -4 >> 2
|
||||
test -1 >> -9001
|
||||
|
||||
test $DEADBEEF >> 1
|
||||
test $DEADBEEF >>> 1
|
||||
|
||||
SECTION "Zero", ROM0[0]
|
||||
zero:
|
||||
|
||||
@@ -1,52 +1,28 @@
|
||||
warning: shift.asm(13) -> shift.asm::test(3): [-Wshift-amount]
|
||||
warning: shift.asm(14) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting left by large amount 32
|
||||
warning: shift.asm(13) -> shift.asm::test(6): [-Wshift-amount]
|
||||
Shifting left by large amount 32
|
||||
warning: shift.asm(14) -> shift.asm::test(3): [-Wshift-amount]
|
||||
warning: shift.asm(15) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting left by large amount 9001
|
||||
warning: shift.asm(14) -> shift.asm::test(6): [-Wshift-amount]
|
||||
Shifting left by large amount 9001
|
||||
warning: shift.asm(16) -> shift.asm::test(3): [-Wshift-amount]
|
||||
warning: shift.asm(17) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting left by large amount 32
|
||||
warning: shift.asm(16) -> shift.asm::test(6): [-Wshift-amount]
|
||||
Shifting left by large amount 32
|
||||
warning: shift.asm(17) -> shift.asm::test(3): [-Wshift-amount]
|
||||
warning: shift.asm(18) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting left by negative amount -9001
|
||||
warning: shift.asm(17) -> shift.asm::test(6): [-Wshift-amount]
|
||||
Shifting left by negative amount -9001
|
||||
warning: shift.asm(19) -> shift.asm::test(3): [-Wshift]
|
||||
warning: shift.asm(20) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(19) -> shift.asm::test(6): [-Wshift]
|
||||
warning: shift.asm(21) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(20) -> shift.asm::test(3): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(20) -> shift.asm::test(3): [-Wshift-amount]
|
||||
warning: shift.asm(21) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting right by large amount 32
|
||||
warning: shift.asm(20) -> shift.asm::test(6): [-Wshift]
|
||||
warning: shift.asm(22) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(20) -> shift.asm::test(6): [-Wshift-amount]
|
||||
Shifting right by large amount 32
|
||||
warning: shift.asm(21) -> shift.asm::test(3): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(21) -> shift.asm::test(3): [-Wshift-amount]
|
||||
warning: shift.asm(22) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting right by large amount 9001
|
||||
warning: shift.asm(21) -> shift.asm::test(6): [-Wshift]
|
||||
warning: shift.asm(23) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -4
|
||||
warning: shift.asm(24) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -4
|
||||
warning: shift.asm(25) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(21) -> shift.asm::test(6): [-Wshift-amount]
|
||||
Shifting right by large amount 9001
|
||||
warning: shift.asm(22) -> shift.asm::test(3): [-Wshift]
|
||||
Shifting right negative value -4
|
||||
warning: shift.asm(22) -> shift.asm::test(6): [-Wshift]
|
||||
Shifting right negative value -4
|
||||
warning: shift.asm(23) -> shift.asm::test(3): [-Wshift]
|
||||
Shifting right negative value -4
|
||||
warning: shift.asm(23) -> shift.asm::test(6): [-Wshift]
|
||||
Shifting right negative value -4
|
||||
warning: shift.asm(24) -> shift.asm::test(3): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(24) -> shift.asm::test(3): [-Wshift-amount]
|
||||
Shifting right by negative amount -9001
|
||||
warning: shift.asm(24) -> shift.asm::test(6): [-Wshift]
|
||||
Shifting right negative value -1
|
||||
warning: shift.asm(24) -> shift.asm::test(6): [-Wshift-amount]
|
||||
warning: shift.asm(25) -> shift.asm::test(8): [-Wshift-amount]
|
||||
Shifting right by negative amount -9001
|
||||
warning: shift.asm(27) -> shift.asm::test(8): [-Wshift]
|
||||
Shifting right negative value -559038737
|
||||
|
||||
@@ -10,3 +10,5 @@
|
||||
-4 >> 1 = $FFFFFFFE
|
||||
-4 >> 2 = $FFFFFFFF
|
||||
-1 >> -9001 = $0
|
||||
$DEADBEEF >> 1 = $EF56DF77
|
||||
$DEADBEEF >>> 1 = $6F56DF77
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user