mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Implement enough functionality to compile & match pokecrystal
This commit is contained in:
1
Makefile
1
Makefile
@@ -110,6 +110,7 @@ rgbgfx_obj := \
|
|||||||
src/gfx/pal_packing.o \
|
src/gfx/pal_packing.o \
|
||||||
src/gfx/pal_sorting.o \
|
src/gfx/pal_sorting.o \
|
||||||
src/gfx/proto_palette.o \
|
src/gfx/proto_palette.o \
|
||||||
|
src/gfx/rgba.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/error.o
|
src/error.o
|
||||||
|
|
||||||
|
|||||||
@@ -9,59 +9,6 @@
|
|||||||
#ifndef RGBDS_GFX_CONVERT_HPP
|
#ifndef RGBDS_GFX_CONVERT_HPP
|
||||||
#define RGBDS_GFX_CONVERT_HPP
|
#define RGBDS_GFX_CONVERT_HPP
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
struct Rgba {
|
|
||||||
uint8_t red;
|
|
||||||
uint8_t green;
|
|
||||||
uint8_t blue;
|
|
||||||
uint8_t alpha;
|
|
||||||
|
|
||||||
Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : red(r), green(g), blue(b), alpha(a) {}
|
|
||||||
Rgba(uint32_t rgba) : red(rgba), green(rgba >> 8), blue(rgba >> 16), alpha(rgba >> 24) {}
|
|
||||||
|
|
||||||
operator uint32_t() const { return toCSS(); }
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
bool operator!=(Rgba const &other) const {
|
|
||||||
return static_cast<uint32_t>(*this) != static_cast<uint32_t>(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isGray() const { return red == green && green == blue; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All alpha values strictly below this will be considered transparent
|
|
||||||
*/
|
|
||||||
static constexpr uint8_t opacity_threshold = 0xF0; // TODO: adjust this
|
|
||||||
/**
|
|
||||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
|
||||||
*/
|
|
||||||
uint16_t cgbColor() const {
|
|
||||||
if (alpha < opacity_threshold)
|
|
||||||
return transparent;
|
|
||||||
if (options.useColorCurve) {
|
|
||||||
assert(!"TODO");
|
|
||||||
} else {
|
|
||||||
return (red >> 3) | (green >> 3) << 5 | (blue >> 3) << 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_CONVERT_HPP */
|
#endif /* RGBDS_GFX_CONVERT_HPP */
|
||||||
|
|||||||
@@ -13,22 +13,33 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
bool beVerbose = false; // -v
|
bool beVerbose = false; // -v
|
||||||
bool fixInput = false; // -f
|
bool fixInput = false; // -f
|
||||||
bool columnMajor = false; // -h; whether to output the tilemap in columns instead of rows
|
bool columnMajor = false; // -Z, previously -h
|
||||||
bool allowMirroring = false; // -m
|
bool allowMirroring = false; // -m
|
||||||
bool allowDedup = false; // -u
|
bool allowDedup = false; // -u
|
||||||
bool useColorCurve = false; // -C
|
bool useColorCurve = false; // -C
|
||||||
uint8_t bitDepth = 2; // -d
|
uint8_t bitDepth = 2; // -d
|
||||||
uint64_t trim = 0; // -x
|
uint64_t trim = 0; // -x
|
||||||
uint8_t nbPalettes = 8; // TODO
|
uint8_t nbPalettes = 8; // -n
|
||||||
uint8_t nbColorsPerPal = 0; // TODO; 0 means "auto" = 1 << bitDepth;
|
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
||||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // TODO
|
enum {
|
||||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // TODO
|
NO_SPEC,
|
||||||
|
EXPLICIT,
|
||||||
|
EMBEDDED,
|
||||||
|
} palSpecType = NO_SPEC; // -c
|
||||||
|
std::vector<std::array<Rgba, 4>> palSpec{};
|
||||||
|
std::array<uint16_t, 2> unitSize{1, 1}; // -u (in tiles)
|
||||||
|
std::array<uint32_t, 4> inputSlice; // -L
|
||||||
|
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||||
|
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||||
std::filesystem::path tilemap{}; // -t, -T
|
std::filesystem::path tilemap{}; // -t, -T
|
||||||
std::filesystem::path attrmap{}; // -a, -A
|
std::filesystem::path attrmap{}; // -a, -A
|
||||||
std::filesystem::path palettes{}; // -p, -P
|
std::filesystem::path palettes{}; // -p, -P
|
||||||
@@ -37,8 +48,8 @@ struct Options {
|
|||||||
|
|
||||||
format_(printf, 2, 3) void verbosePrint(char const *fmt, ...) const;
|
format_(printf, 2, 3) void verbosePrint(char const *fmt, ...) const;
|
||||||
uint8_t maxPalSize() const {
|
uint8_t maxPalSize() const {
|
||||||
return nbColorsPerPal;
|
return nbColorsPerPal; // TODO: minus 1 when transparency is active
|
||||||
} // TODO: minus 1 when transparency is active
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Options options;
|
extern Options options;
|
||||||
@@ -53,6 +64,8 @@ struct Palette {
|
|||||||
|
|
||||||
void addColor(uint16_t color);
|
void addColor(uint16_t color);
|
||||||
uint8_t indexOf(uint16_t color) const;
|
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 begin();
|
||||||
decltype(colors)::iterator end();
|
decltype(colors)::iterator end();
|
||||||
|
|||||||
@@ -9,16 +9,22 @@
|
|||||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <optional>
|
||||||
#include <png.h>
|
#include <png.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
class Palette;
|
class Palette;
|
||||||
|
|
||||||
namespace sorting {
|
namespace sorting {
|
||||||
|
|
||||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
||||||
png_byte *palAlpha);
|
png_byte *palAlpha);
|
||||||
void grayscale(std::vector<Palette> &palettes);
|
void grayscale(std::vector<Palette> &palettes,
|
||||||
|
std::array<std::optional<Rgba>, 0x8001> const &colors);
|
||||||
void rgb(std::vector<Palette> &palettes);
|
void rgb(std::vector<Palette> &palettes);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
58
include/gfx/rgba.hpp
Normal file
58
include/gfx/rgba.hpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 <stdint.h>
|
||||||
|
|
||||||
|
struct Rgba {
|
||||||
|
uint8_t red;
|
||||||
|
uint8_t green;
|
||||||
|
uint8_t blue;
|
||||||
|
uint8_t alpha;
|
||||||
|
|
||||||
|
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 Rgba(uint32_t rgba = 0)
|
||||||
|
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All alpha values strictly below this will be considered transparent
|
||||||
|
*/
|
||||||
|
static constexpr uint8_t opacity_threshold = 0xF0; // TODO: adjust this
|
||||||
|
// TODO: also a transparency threshold, and error out on "middle" values
|
||||||
|
bool isTransparent() 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 */
|
||||||
23
man/rgbgfx.1
23
man/rgbgfx.1
@@ -129,16 +129,25 @@ PNG), then colors in each output palette will be sorted according to their order
|
|||||||
Any unused entries will be ignored, and only the first entry is considered if there any duplicates.
|
Any unused entries will be ignored, and only the first entry is considered if there any duplicates.
|
||||||
.Pq If you want a given color to appear more than once in a palette, you should specify the palettes explicitly instead using Fl TODO .
|
.Pq If you want a given color to appear more than once in a palette, you should specify the palettes explicitly instead using Fl TODO .
|
||||||
.It
|
.It
|
||||||
Otherwise, if the PNG only contains shades of gray, they will be categorized into 4
|
Otherwise, if the PNG only contains shades of gray, they will be categorized into as many
|
||||||
.Dq bins
|
.Dq bins
|
||||||
.Pq white, light gray, dark gray, and black in this order ,
|
as there are colors per palette, and the palette is set to these bins.
|
||||||
and the palette is set to these four bins.
|
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
||||||
(TODO: how does this interact with 1bpp? With more than 1 palette?)
|
If two distinct grays end up in the same bin, the RGB method is used instead.
|
||||||
If more than one grey ends up in the same bin, the RGB method below is used instead.
|
|
||||||
.It
|
.It
|
||||||
If none of the above apply, colors are sorted from lightest to darkest.
|
If none of the above apply, colors are sorted from lightest to darkest.
|
||||||
(This is what the old documentation claimed, but the definition of luminance that was used wasn't quite right.
|
.EQ
|
||||||
It is kept for compatibility.)
|
delim $$
|
||||||
|
.EN
|
||||||
|
The definition of luminance that
|
||||||
|
.Nm
|
||||||
|
uses is
|
||||||
|
.Do
|
||||||
|
$2126 times red + 7152 times green + 722 times blue$
|
||||||
|
.Dc .
|
||||||
|
.EQ
|
||||||
|
delim off
|
||||||
|
.EN
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Note that the
|
Note that the
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ set(rgbgfx_src
|
|||||||
"gfx/pal_packing.cpp"
|
"gfx/pal_packing.cpp"
|
||||||
"gfx/pal_sorting.cpp"
|
"gfx/pal_sorting.cpp"
|
||||||
"gfx/proto_palette.cpp"
|
"gfx/proto_palette.cpp"
|
||||||
|
"gfx/rgba.cpp"
|
||||||
"extern/getopt.c"
|
"extern/getopt.c"
|
||||||
"error.c"
|
"error.c"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,10 +49,15 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size() const { return _colors.size(); }
|
size_t size() const {
|
||||||
|
return std::count_if(_colors.begin(), _colors.end(),
|
||||||
|
[](decltype(_colors)::value_type const &slot) {
|
||||||
|
return slot.has_value() && !slot->isTransparent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
decltype(_colors) const &raw() const { return _colors; }
|
||||||
|
|
||||||
auto begin() const { return _colors.begin(); }
|
auto begin() const { return _colors.begin(); }
|
||||||
|
|
||||||
auto end() const { return _colors.end(); }
|
auto end() const { return _colors.end(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,13 +69,12 @@ class Png {
|
|||||||
|
|
||||||
// These are cached for speed
|
// These are cached for speed
|
||||||
uint32_t width, height;
|
uint32_t width, height;
|
||||||
DefaultInitVec<uint16_t> pixels;
|
std::vector<Rgba> pixels;
|
||||||
ImagePalette colors;
|
ImagePalette colors;
|
||||||
int colorType;
|
int colorType;
|
||||||
int nbColors;
|
int nbColors;
|
||||||
png_colorp embeddedPal = nullptr;
|
png_colorp embeddedPal = nullptr;
|
||||||
png_bytep transparencyPal = nullptr;
|
png_bytep transparencyPal = nullptr;
|
||||||
bool isGrayOnly = true;
|
|
||||||
|
|
||||||
[[noreturn]] static void handleError(png_structp png, char const *msg) {
|
[[noreturn]] static void handleError(png_structp png, char const *msg) {
|
||||||
struct Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
struct Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
||||||
@@ -110,11 +114,38 @@ public:
|
|||||||
|
|
||||||
uint32_t getHeight() const { return height; }
|
uint32_t getHeight() const { return height; }
|
||||||
|
|
||||||
uint16_t &pixel(uint32_t x, uint32_t y) { return pixels[y * width + x]; }
|
Rgba &pixel(uint32_t x, uint32_t y) { return pixels[y * width + x]; }
|
||||||
|
|
||||||
uint16_t const &pixel(uint32_t x, uint32_t y) const { return pixels[y * width + x]; }
|
Rgba const &pixel(uint32_t x, uint32_t y) const { return pixels[y * width + x]; }
|
||||||
|
|
||||||
bool hasNonGray() const { return !isGrayOnly; }
|
bool isSuitableForGrayscale() const {
|
||||||
|
// Check that all of the grays don't fall into the same "bin"
|
||||||
|
if (colors.size() > options.maxPalSize()) { // Apply the Pigeonhole Principle
|
||||||
|
options.verbosePrint("Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
|
||||||
|
colors.size(), options.maxPalSize());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t bins = 0;
|
||||||
|
for (auto const &color : colors) {
|
||||||
|
if (color->isTransparent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!color->isGray()) {
|
||||||
|
options.verbosePrint("Found non-gray color #%08x, not using grayscale sorting\n",
|
||||||
|
color->toCSS());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t mask = 1 << color->grayIndex();
|
||||||
|
if (bins & mask) { // Two in the same bin!
|
||||||
|
options.verbosePrint(
|
||||||
|
"Color #%08x conflicts with another one, not using grayscale sorting\n",
|
||||||
|
color->toCSS());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bins |= mask;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a PNG and notes all of its colors
|
* Reads a PNG and notes all of its colors
|
||||||
@@ -276,8 +307,7 @@ public:
|
|||||||
Rgba rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]);
|
Rgba rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]);
|
||||||
|
|
||||||
colors.registerColor(rgba);
|
colors.registerColor(rgba);
|
||||||
pixel(x, y) = rgba.cgbColor();
|
pixel(x, y) = rgba;
|
||||||
isGrayOnly &= rgba.isGray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -299,16 +329,15 @@ public:
|
|||||||
Rgba rgba(ptr[0], ptr[1], ptr[2], ptr[3]);
|
Rgba rgba(ptr[0], ptr[1], ptr[2], ptr[3]);
|
||||||
|
|
||||||
colors.registerColor(rgba);
|
colors.registerColor(rgba);
|
||||||
pixel(x, y) = rgba.cgbColor();
|
pixel(x, y) = rgba;
|
||||||
isGrayOnly &= rgba.isGray();
|
|
||||||
ptr += 4;
|
ptr += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
png_read_end(png,
|
// We don't care about chunks after the image data (comments, etc.)
|
||||||
nullptr); // We don't care about chunks after the image data (comments, etc.)
|
png_read_end(png, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Png() { png_destroy_read_struct(&png, &info, nullptr); }
|
~Png() { png_destroy_read_struct(&png, &info, nullptr); }
|
||||||
@@ -330,7 +359,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
Tile(Png const &png, uint32_t x, uint32_t y) : _png(png), _x(x), _y(y) {}
|
Tile(Png const &png, uint32_t x, uint32_t y) : _png(png), _x(x), _y(y) {}
|
||||||
|
|
||||||
uint16_t pixel(uint32_t xOfs, uint32_t yOfs) const {
|
Rgba pixel(uint32_t xOfs, uint32_t yOfs) const {
|
||||||
return _png.pixel(_x + xOfs, _y + yOfs);
|
return _png.pixel(_x + xOfs, _y + yOfs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -362,10 +391,10 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
iterator begin() const { return {*this, _width, 0, 0}; }
|
iterator begin() const { return {*this, _limit, 0, 0}; }
|
||||||
iterator end() const {
|
iterator end() const {
|
||||||
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one
|
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
|
||||||
return ++it; // Now one-past-last
|
return ++it; // ...now one-past-last!
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
@@ -407,6 +436,93 @@ struct AttrmapEntry {
|
|||||||
bool xFlip;
|
bool xFlip;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||||
|
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
|
||||||
|
// Run a "pagination" problem solver
|
||||||
|
// TODO: allow picking one of several solvers?
|
||||||
|
auto [mappings, nbPalettes] = packing::overloadAndRemove(protoPalettes);
|
||||||
|
assert(mappings.size() == protoPalettes.size());
|
||||||
|
|
||||||
|
if (options.beVerbose) {
|
||||||
|
options.verbosePrint("Proto-palette mappings: (%zu palette%s)\n", nbPalettes,
|
||||||
|
nbPalettes != 1 ? "s" : "");
|
||||||
|
for (size_t i = 0; i < mappings.size(); ++i) {
|
||||||
|
options.verbosePrint("%zu -> %zu\n", i, mappings[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Palette> palettes(nbPalettes);
|
||||||
|
// Generate the actual palettes from the mappings
|
||||||
|
for (size_t protoPalID = 0; protoPalID < mappings.size(); ++protoPalID) {
|
||||||
|
auto &pal = palettes[mappings[protoPalID]];
|
||||||
|
for (uint16_t color : protoPalettes[protoPalID]) {
|
||||||
|
pal.addColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Sort" colors in the generated palettes, see the man page for the flowchart
|
||||||
|
auto [embPalSize, embPalRGB, embPalAlpha] = png.getEmbeddedPal();
|
||||||
|
if (embPalRGB != nullptr) {
|
||||||
|
sorting::indexed(palettes, embPalSize, embPalRGB, embPalAlpha);
|
||||||
|
} else if (png.isSuitableForGrayscale()) {
|
||||||
|
sorting::grayscale(palettes, png.getColors().raw());
|
||||||
|
} else {
|
||||||
|
sorting::rgb(palettes);
|
||||||
|
}
|
||||||
|
return {mappings, palettes};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||||
|
makePalsAsSpecified(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
|
||||||
|
if (options.palSpecType == Options::EMBEDDED) {
|
||||||
|
// Generate a palette spec from the first few colors in the embedded palette
|
||||||
|
auto [embPalSize, embPalRGB, embPalAlpha] = png.getEmbeddedPal();
|
||||||
|
if (embPalRGB == nullptr) {
|
||||||
|
fatal("`-c embedded` was given, but the PNG does not have an embedded palette!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the palette spec
|
||||||
|
options.palSpec.emplace_back(); // A single palette, with `#00000000`s (transparent)
|
||||||
|
assert(options.palSpec.size() == 1);
|
||||||
|
// TODO: abort if ignored colors are being used; do it now for a friendlier error
|
||||||
|
// message
|
||||||
|
if (embPalSize > options.maxPalSize()) { // Ignore extraneous colors if they are unused
|
||||||
|
embPalSize = options.maxPalSize();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < embPalSize; ++i) {
|
||||||
|
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
|
||||||
|
embPalAlpha ? embPalAlpha[i] : 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the palette spec to actual palettes
|
||||||
|
std::vector<Palette> palettes(options.palSpec.size());
|
||||||
|
auto palIter = palettes.begin(); // TODO: `zip`
|
||||||
|
for (auto const &spec : options.palSpec) {
|
||||||
|
for (size_t i = 0; i < options.maxPalSize(); ++i) {
|
||||||
|
(*palIter)[i] = spec[i].cgbColor();
|
||||||
|
}
|
||||||
|
++palIter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through proto-palettes, and try mapping them to the specified palettes
|
||||||
|
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
||||||
|
for (size_t i = 0; i < protoPalettes.size(); ++i) {
|
||||||
|
ProtoPalette const &protoPal = protoPalettes[i];
|
||||||
|
// Find the palette...
|
||||||
|
auto iter = std::find_if(palettes.begin(), palettes.end(), [&protoPal](Palette const &pal) {
|
||||||
|
// ...which contains all colors in this proto-pal
|
||||||
|
return std::all_of(protoPal.begin(), protoPal.end(), [&pal](uint16_t color) {
|
||||||
|
return std::find(pal.begin(), pal.end(), color) != pal.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
assert(iter != palettes.end()); // TODO: produce a proper error message
|
||||||
|
mappings[i] = iter - palettes.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {mappings, palettes};
|
||||||
|
}
|
||||||
|
|
||||||
static void outputPalettes(std::vector<Palette> const &palettes) {
|
static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||||
std::filebuf output;
|
std::filebuf output;
|
||||||
output.open(options.palettes, std::ios_base::out | std::ios_base::binary);
|
output.open(options.palettes, std::ios_base::out | std::ios_base::binary);
|
||||||
@@ -421,13 +537,19 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
|||||||
|
|
||||||
namespace unoptimized {
|
namespace unoptimized {
|
||||||
|
|
||||||
// TODO: this is very redundant with `TileData`; try merging both?
|
// TODO: this is very redundant with `TileData::TileData`; try merging both?
|
||||||
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
std::vector<Palette> const &palettes,
|
std::vector<Palette> const &palettes,
|
||||||
DefaultInitVec<size_t> const &mappings) {
|
DefaultInitVec<size_t> const &mappings) {
|
||||||
std::filebuf output;
|
std::filebuf output;
|
||||||
output.open(options.output, std::ios_base::out | std::ios_base::binary);
|
output.open(options.output, std::ios_base::out | std::ios_base::binary);
|
||||||
|
|
||||||
|
uint64_t remainingTiles = (png.getWidth() / 8) * (png.getHeight() / 8);
|
||||||
|
if (remainingTiles <= options.trim) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remainingTiles -= options.trim;
|
||||||
|
|
||||||
auto iter = attrmap.begin();
|
auto iter = attrmap.begin();
|
||||||
for (auto tile : png.visitAsTiles(options.columnMajor)) {
|
for (auto tile : png.visitAsTiles(options.columnMajor)) {
|
||||||
Palette const &palette = palettes[mappings[iter->protoPaletteID]];
|
Palette const &palette = palettes[mappings[iter->protoPaletteID]];
|
||||||
@@ -435,7 +557,7 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
|||||||
uint16_t row = 0;
|
uint16_t row = 0;
|
||||||
for (uint32_t x = 0; x < 8; ++x) {
|
for (uint32_t x = 0; x < 8; ++x) {
|
||||||
row <<= 1;
|
row <<= 1;
|
||||||
uint8_t index = palette.indexOf(tile.pixel(x, y));
|
uint8_t index = palette.indexOf(tile.pixel(x, y).cgbColor());
|
||||||
if (index & 1) {
|
if (index & 1) {
|
||||||
row |= 0x001;
|
row |= 0x001;
|
||||||
}
|
}
|
||||||
@@ -449,8 +571,14 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
++iter;
|
++iter;
|
||||||
|
|
||||||
|
--remainingTiles;
|
||||||
|
if (remainingTiles == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert(iter == attrmap.end());
|
assert(remainingTiles == 0);
|
||||||
|
assert(iter + options.trim == attrmap.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
@@ -485,6 +613,7 @@ static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrm
|
|||||||
}
|
}
|
||||||
++tileID;
|
++tileID;
|
||||||
}
|
}
|
||||||
|
assert(iter == attrmap.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace unoptimized
|
} // namespace unoptimized
|
||||||
@@ -513,7 +642,7 @@ public:
|
|||||||
uint16_t bitplanes = 0;
|
uint16_t bitplanes = 0;
|
||||||
for (uint32_t x = 0; x < 8; ++x) {
|
for (uint32_t x = 0; x < 8; ++x) {
|
||||||
bitplanes <<= 1;
|
bitplanes <<= 1;
|
||||||
uint8_t index = palette.indexOf(tile.pixel(x, y));
|
uint8_t index = palette.indexOf(tile.pixel(x, y).cgbColor());
|
||||||
if (index & 1) {
|
if (index & 1) {
|
||||||
bitplanes |= 1;
|
bitplanes |= 1;
|
||||||
}
|
}
|
||||||
@@ -626,8 +755,8 @@ static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attr
|
|||||||
}
|
}
|
||||||
assert(iter == attrmap.end());
|
assert(iter == attrmap.end());
|
||||||
|
|
||||||
return tiles; // Copy elision should prevent the contained `unordered_set` from being
|
// Copy elision should prevent the contained `unordered_set` from being re-constructed
|
||||||
// re-constructed
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void outputTileData(UniqueTiles const &tiles) {
|
static void outputTileData(UniqueTiles const &tiles) {
|
||||||
@@ -635,7 +764,8 @@ static void outputTileData(UniqueTiles const &tiles) {
|
|||||||
output.open(options.output, std::ios_base::out | std::ios_base::binary);
|
output.open(options.output, std::ios_base::out | std::ios_base::binary);
|
||||||
|
|
||||||
uint16_t tileID = 0;
|
uint16_t tileID = 0;
|
||||||
for (TileData const *tile : tiles) {
|
for (auto iter = tiles.begin(), end = tiles.end() - options.trim; iter != end; ++iter) {
|
||||||
|
TileData const *tile = *iter;
|
||||||
assert(tile->tileID == tileID);
|
assert(tile->tileID == tileID);
|
||||||
++tileID;
|
++tileID;
|
||||||
output.sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8);
|
output.sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8);
|
||||||
@@ -705,7 +835,7 @@ void process() {
|
|||||||
|
|
||||||
for (uint32_t y = 0; y < 8; ++y) {
|
for (uint32_t y = 0; y < 8; ++y) {
|
||||||
for (uint32_t x = 0; x < 8; ++x) {
|
for (uint32_t x = 0; x < 8; ++x) {
|
||||||
tileColors.add(tile.pixel(x, y));
|
tileColors.add(tile.pixel(x, y).cgbColor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,42 +863,14 @@ contained:;
|
|||||||
protoPalettes.size() != 1 ? "s" : "");
|
protoPalettes.size() != 1 ? "s" : "");
|
||||||
|
|
||||||
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
||||||
// TODO: try keeping the palettes stored while inserting them instead, might perform better
|
// We sort after all insertions to avoid moving items: https://stackoverflow.com/a/2710332
|
||||||
std::sort(
|
std::sort(
|
||||||
protoPalettes.begin(), protoPalettes.end(),
|
protoPalettes.begin(), protoPalettes.end(),
|
||||||
[](ProtoPalette const &lhs, ProtoPalette const &rhs) { return lhs.size() < rhs.size(); });
|
[](ProtoPalette const &lhs, ProtoPalette const &rhs) { return lhs.size() < rhs.size(); });
|
||||||
|
|
||||||
// Run a "pagination" problem solver
|
auto [mappings, palettes] = options.palSpecType == Options::NO_SPEC
|
||||||
// TODO: allow picking one of several solvers?
|
? generatePalettes(protoPalettes, png)
|
||||||
auto [mappings, nbPalettes] = packing::overloadAndRemove(protoPalettes);
|
: makePalsAsSpecified(protoPalettes, png);
|
||||||
assert(mappings.size() == protoPalettes.size());
|
|
||||||
if (options.beVerbose) {
|
|
||||||
options.verbosePrint("Proto-palette mappings: (%zu palettes)\n", nbPalettes);
|
|
||||||
for (size_t i = 0; i < mappings.size(); ++i) {
|
|
||||||
options.verbosePrint("%zu -> %zu\n", i, mappings[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: optionally, "decant" the result
|
|
||||||
|
|
||||||
// Generate the actual palettes from the mappings
|
|
||||||
std::vector<Palette> palettes(nbPalettes);
|
|
||||||
for (size_t protoPalID = 0; protoPalID < mappings.size(); ++protoPalID) {
|
|
||||||
auto &pal = palettes[mappings[protoPalID]];
|
|
||||||
for (uint16_t color : protoPalettes[protoPalID]) {
|
|
||||||
pal.addColor(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Sort" colors in the generated palettes, see the man page for the flowchart
|
|
||||||
auto [palSize, palRGB, palAlpha] = png.getEmbeddedPal();
|
|
||||||
if (palRGB) {
|
|
||||||
sorting::indexed(palettes, palSize, palRGB, palAlpha);
|
|
||||||
} else if (png.hasNonGray()) {
|
|
||||||
sorting::rgb(palettes);
|
|
||||||
} else {
|
|
||||||
sorting::grayscale(palettes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.beVerbose) {
|
if (options.beVerbose) {
|
||||||
for (auto &&palette : palettes) {
|
for (auto &&palette : palettes) {
|
||||||
@@ -796,13 +898,11 @@ contained:;
|
|||||||
|
|
||||||
if (!options.output.empty()) {
|
if (!options.output.empty()) {
|
||||||
options.verbosePrint("Generating unoptimized tile data...\n");
|
options.verbosePrint("Generating unoptimized tile data...\n");
|
||||||
|
|
||||||
unoptimized::outputTileData(png, attrmap, palettes, mappings);
|
unoptimized::outputTileData(png, attrmap, palettes, mappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.tilemap.empty() || !options.attrmap.empty()) {
|
if (!options.tilemap.empty() || !options.attrmap.empty()) {
|
||||||
options.verbosePrint("Generating unoptimized tilemap and/or attrmap...\n");
|
options.verbosePrint("Generating unoptimized tilemap and/or attrmap...\n");
|
||||||
|
|
||||||
unoptimized::outputMaps(png, attrmap, mappings);
|
unoptimized::outputMaps(png, attrmap, mappings);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -817,19 +917,16 @@ contained:;
|
|||||||
|
|
||||||
if (!options.output.empty()) {
|
if (!options.output.empty()) {
|
||||||
options.verbosePrint("Generating optimized tile data...\n");
|
options.verbosePrint("Generating optimized tile data...\n");
|
||||||
|
|
||||||
optimized::outputTileData(tiles);
|
optimized::outputTileData(tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.tilemap.empty()) {
|
if (!options.tilemap.empty()) {
|
||||||
options.verbosePrint("Generating optimized tilemap...\n");
|
options.verbosePrint("Generating optimized tilemap...\n");
|
||||||
|
|
||||||
optimized::outputTilemap(attrmap);
|
optimized::outputTilemap(attrmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.attrmap.empty()) {
|
if (!options.attrmap.empty()) {
|
||||||
options.verbosePrint("Generating optimized attrmap...\n");
|
options.verbosePrint("Generating optimized attrmap...\n");
|
||||||
|
|
||||||
optimized::outputAttrmap(attrmap, mappings);
|
optimized::outputAttrmap(attrmap, mappings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ void Options::verbosePrint(char const *fmt, ...) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "Aa:CDd:Ffhmo:Pp:Tt:uVvx:";
|
static char const *optstring = "Aa:Cc:Dd:Ffhmo:Pp:Tt:uVvx:";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Equivalent long options
|
* Equivalent long options
|
||||||
@@ -94,6 +94,7 @@ static struct option const longopts[] = {
|
|||||||
{"output-attr-map", no_argument, NULL, 'A'},
|
{"output-attr-map", no_argument, NULL, 'A'},
|
||||||
{"attr-map", required_argument, NULL, 'a'},
|
{"attr-map", required_argument, NULL, 'a'},
|
||||||
{"color-curve", no_argument, NULL, 'C'},
|
{"color-curve", no_argument, NULL, 'C'},
|
||||||
|
{"colors", required_argument, NULL, 'c'},
|
||||||
{"debug", no_argument, NULL, 'D'},
|
{"debug", no_argument, NULL, 'D'},
|
||||||
{"depth", required_argument, NULL, 'd'},
|
{"depth", required_argument, NULL, 'd'},
|
||||||
{"fix", no_argument, NULL, 'f'},
|
{"fix", no_argument, NULL, 'f'},
|
||||||
@@ -112,8 +113,8 @@ static struct option const longopts[] = {
|
|||||||
{NULL, no_argument, NULL, 0 }
|
{NULL, no_argument, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
static void print_usage(void) {
|
static void printUsage(void) {
|
||||||
fputs("Usage: rgbgfx [-CDhmuVv] [-f | -F] [-a <attr_map> | -A] [-d <depth>]\n"
|
fputs("Usage: rgbgfx [-CcDhmuVv] [-f | -F] [-a <attr_map> | -A] [-d <depth>]\n"
|
||||||
" [-o <out_file>] [-p <pal_file> | -P] [-t <tile_map> | -T]\n"
|
" [-o <out_file>] [-p <pal_file> | -P] [-t <tile_map> | -T]\n"
|
||||||
" [-x <tiles>] <file>\n"
|
" [-x <tiles>] <file>\n"
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
@@ -135,6 +136,22 @@ void fputsv(std::string_view const &view, FILE *f) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void parsePaletteSpec(char *arg) {
|
||||||
|
if (arg[0] == '#') {
|
||||||
|
// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
|
||||||
|
options.palSpecType = Options::EXPLICIT;
|
||||||
|
// TODO
|
||||||
|
} else if (strcasecmp(arg, "embedded") == 0) {
|
||||||
|
// Use PLTE, error out if missing
|
||||||
|
options.palSpecType = Options::EMBEDDED;
|
||||||
|
} else {
|
||||||
|
// `fmt:path`, parse the file according to the given format
|
||||||
|
// TODO: split both parts, error out if malformed or file not found
|
||||||
|
options.palSpecType = Options::EXPLICIT;
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int opt;
|
int opt;
|
||||||
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false;
|
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false;
|
||||||
@@ -166,6 +183,9 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'C':
|
case 'C':
|
||||||
options.useColorCurve = true;
|
options.useColorCurve = true;
|
||||||
break;
|
break;
|
||||||
|
case 'c':
|
||||||
|
parsePaletteSpec(musl_optarg);
|
||||||
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
if (parseDecimalArg(options.bitDepth) && options.bitDepth != 1
|
if (parseDecimalArg(options.bitDepth) && options.bitDepth != 1
|
||||||
&& options.bitDepth != 2) {
|
&& options.bitDepth != 2) {
|
||||||
@@ -177,9 +197,9 @@ int main(int argc, char *argv[]) {
|
|||||||
options.fixInput = true;
|
options.fixInput = true;
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
warning("`-h` is deprecated, use `-???` instead");
|
warning("`-h` is deprecated, use `-Z` instead");
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case '?': // TODO
|
case 'Z':
|
||||||
options.columnMajor = true;
|
options.columnMajor = true;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
@@ -219,7 +239,7 @@ int main(int argc, char *argv[]) {
|
|||||||
warning("Ignoring option '%c'", musl_optopt);
|
warning("Ignoring option '%c'", musl_optopt);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
print_usage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,11 +253,11 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if (musl_optind == argc) {
|
if (musl_optind == argc) {
|
||||||
fputs("FATAL: No input image specified\n", stderr);
|
fputs("FATAL: No input image specified\n", stderr);
|
||||||
print_usage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
} else if (argc - musl_optind != 1) {
|
} else if (argc - musl_optind != 1) {
|
||||||
fprintf(stderr, "FATAL: %d input images were specified instead of 1\n", argc - musl_optind);
|
fprintf(stderr, "FATAL: %d input images were specified instead of 1\n", argc - musl_optind);
|
||||||
print_usage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +279,7 @@ int main(int argc, char *argv[]) {
|
|||||||
if (options.fixInput)
|
if (options.fixInput)
|
||||||
fputs("\tConvert input to indexed\n", stderr);
|
fputs("\tConvert input to indexed\n", stderr);
|
||||||
if (options.columnMajor)
|
if (options.columnMajor)
|
||||||
fputs("\tOutput {tile,attr}map in column-major order\n", stderr);
|
fputs("\tVisit image in column-major order\n", stderr);
|
||||||
if (options.allowMirroring)
|
if (options.allowMirroring)
|
||||||
fputs("\tAllow mirroring tiles\n", stderr);
|
fputs("\tAllow mirroring tiles\n", stderr);
|
||||||
if (options.allowDedup)
|
if (options.allowDedup)
|
||||||
@@ -267,7 +287,8 @@ int main(int argc, char *argv[]) {
|
|||||||
if (options.useColorCurve)
|
if (options.useColorCurve)
|
||||||
fputs("\tUse color curve\n", stderr);
|
fputs("\tUse color curve\n", stderr);
|
||||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
||||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
if (options.trim != 0)
|
||||||
|
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||||
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
||||||
options.baseTileIDs[1]);
|
options.baseTileIDs[1]);
|
||||||
fprintf(stderr, "\tMax number of tiles: [%" PRIu16 ", %" PRIu16 "]\n",
|
fprintf(stderr, "\tMax number of tiles: [%" PRIu16 ", %" PRIu16 "]\n",
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ class AssignedProtos {
|
|||||||
// We leave room for emptied slots to avoid copying the structs around on removal
|
// We leave room for emptied slots to avoid copying the structs around on removal
|
||||||
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
||||||
// For resolving proto-palette indices
|
// For resolving proto-palette indices
|
||||||
std::vector<ProtoPalette> const &_protoPals;
|
std::vector<ProtoPalette> const *_protoPals;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
AssignedProtos(decltype(_protoPals) protoPals, Ts &&...elems)
|
AssignedProtos(std::vector<ProtoPalette> const &protoPals, Ts &&...elems)
|
||||||
: _assigned{std::forward<Ts>(elems)...}, _protoPals{protoPals} {}
|
: _assigned{std::forward<Ts>(elems)...}, _protoPals{&protoPals} {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename Inner, template<typename> typename Constness>
|
template<typename Inner, template<typename> typename Constness>
|
||||||
@@ -93,7 +93,7 @@ private:
|
|||||||
skipEmpty();
|
skipEmpty();
|
||||||
}
|
}
|
||||||
void skipEmpty() {
|
void skipEmpty() {
|
||||||
while (_iter != _array->end() && !(*_iter).has_value()) {
|
while (_iter != _array->end() && !_iter->has_value()) {
|
||||||
++_iter;
|
++_iter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ public:
|
|||||||
* Args are passed to the `ProtoPalAttrs`'s constructor
|
* Args are passed to the `ProtoPalAttrs`'s constructor
|
||||||
*/
|
*/
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
auto assign(Ts &&...args) {
|
void assign(Ts &&...args) {
|
||||||
auto freeSlot = std::find_if_not(
|
auto freeSlot = std::find_if_not(
|
||||||
_assigned.begin(), _assigned.end(),
|
_assigned.begin(), _assigned.end(),
|
||||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
|
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
|
||||||
@@ -147,34 +147,24 @@ public:
|
|||||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
||||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
_assigned.emplace_back(std::forward<Ts>(args)...);
|
||||||
} else { // Reuse a free slot
|
} else { // Reuse a free slot
|
||||||
(*freeSlot).emplace(std::forward<Ts>(args)...);
|
freeSlot->emplace(std::forward<Ts>(args)...);
|
||||||
}
|
}
|
||||||
return freeSlot;
|
|
||||||
}
|
}
|
||||||
void remove(iterator const &iter) {
|
void remove(iterator const &iter) {
|
||||||
(*iter._iter).reset(); // This time, we want to access the `optional` itself
|
iter._iter->reset(); // This time, we want to access the `optional` itself
|
||||||
}
|
}
|
||||||
void clear() { _assigned.clear(); }
|
void clear() { _assigned.clear(); }
|
||||||
|
|
||||||
/**
|
bool empty() const { return std::distance(begin(), end()) == 0; }
|
||||||
* Computes the "relative size" of a proto-palette on this palette
|
|
||||||
*/
|
|
||||||
double relSizeOf(ProtoPalette const &protoPal) const {
|
|
||||||
return std::transform_reduce(
|
|
||||||
protoPal.begin(), protoPal.end(), .0, std::plus<>(), [this](uint16_t color) {
|
|
||||||
// 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.
|
|
||||||
return 1.
|
|
||||||
/ (1
|
|
||||||
+ std::count_if(
|
|
||||||
begin(), end(), [this, &color](ProtoPalAttrs const &attrs) {
|
|
||||||
ProtoPalette const &pal = _protoPals[attrs.palIndex];
|
|
||||||
return std::find(pal.begin(), pal.end(), color) != pal.end();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
|
static void addUniqueColors(std::unordered_set<uint16_t> &colors, AssignedProtos const &pal) {
|
||||||
|
for (ProtoPalAttrs const &attrs : pal) {
|
||||||
|
for (uint16_t color : (*pal._protoPals)[attrs.palIndex]) {
|
||||||
|
colors.insert(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
std::unordered_set<uint16_t> &uniqueColors() const {
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
||||||
// faster than "back-checking" on every element (O(n²))
|
// faster than "back-checking" on every element (O(n²))
|
||||||
@@ -182,18 +172,16 @@ private:
|
|||||||
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
||||||
// performs better:
|
// performs better:
|
||||||
// > So basically you make a priority queue that takes iterators into each of your sets
|
// > 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
|
// > (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,
|
// > values pointed to by each iterator
|
||||||
// optionally add one to your count, increment the iterator and push it back into the queue
|
// > Then each iteration you pop from the queue,
|
||||||
// if it didn't reach the end > and you do this until the priority queue is empty
|
// > 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;
|
static std::unordered_set<uint16_t> colors;
|
||||||
|
|
||||||
colors.clear();
|
colors.clear();
|
||||||
for (ProtoPalAttrs const &attrs : *this) {
|
addUniqueColors(colors, *this);
|
||||||
for (uint16_t color : _protoPals[attrs.palIndex]) {
|
|
||||||
colors.insert(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
@@ -206,8 +194,106 @@ public:
|
|||||||
colors.insert(protoPal.begin(), protoPal.end());
|
colors.insert(protoPal.begin(), protoPal.end());
|
||||||
return colors.size() <= options.maxPalSize();
|
return colors.size() <= options.maxPalSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
return std::transform_reduce(
|
||||||
|
protoPal.begin(), protoPal.end(), 0.0, std::plus<>(), [this](uint16_t color) {
|
||||||
|
// 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.
|
||||||
|
return 1.
|
||||||
|
/ (1
|
||||||
|
+ std::count_if(
|
||||||
|
begin(), end(), [this, &color](ProtoPalAttrs const &attrs) {
|
||||||
|
ProtoPalette const &pal = (*_protoPals)[attrs.palIndex];
|
||||||
|
return std::find(pal.begin(), pal.end(), color) != pal.end();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the "relative size" of a palette on this one
|
||||||
|
*/
|
||||||
|
double combinedVolume(AssignedProtos const &pal) const {
|
||||||
|
auto &colors = uniqueColors();
|
||||||
|
addUniqueColors(colors, pal);
|
||||||
|
return colors.size();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void removeEmptyPals(std::vector<AssignedProtos> &assignments) {
|
||||||
|
// We do this by plucking "replacement" palettes from the end of the vector, so as to minimize
|
||||||
|
// the amount of moves performed. We can afford this because we don't care about their order,
|
||||||
|
// unlike `std::remove_if`, which permits less moves and thus better performance.
|
||||||
|
for (size_t i = 0; i != assignments.size(); ++i) {
|
||||||
|
if (assignments[i].empty()) {
|
||||||
|
// Hinting the compiler that the `return;` can only be reached if entering the loop
|
||||||
|
// produces better assembly
|
||||||
|
if (assignments.back().empty()) {
|
||||||
|
do {
|
||||||
|
assignments.pop_back();
|
||||||
|
assert(assignments.size() != 0);
|
||||||
|
} while (assignments.back().empty());
|
||||||
|
// Worst case, the loop ended on `assignments[i - 1]` (since every slot before `i`
|
||||||
|
// is known to be non-empty).
|
||||||
|
// (This could be a problem if `i` was 0, but we know there must be at least one
|
||||||
|
// color, so we're safe from that. The assertion in the loop checks it to be sure.)
|
||||||
|
// However, if it did stop at `i - 1`, then `i` no longer points to a valid slot,
|
||||||
|
// and we must end.
|
||||||
|
if (i == assignments.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(i < assignments.size());
|
||||||
|
assignments[i] = std::move(assignments.back());
|
||||||
|
assignments.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decant(std::vector<AssignedProtos> &assignments) {
|
||||||
|
// "Decanting" is the process of moving all *things* that can fit in a lower index there
|
||||||
|
auto decantOn = [&assignments](auto const &move) {
|
||||||
|
// 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) {
|
||||||
|
move(assignments[to], assignments[from]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decant on palettes
|
||||||
|
decantOn([](AssignedProtos &to, AssignedProtos &from) {
|
||||||
|
// If the entire palettes can be merged, move all of `from`'s proto-palettes
|
||||||
|
if (to.combinedVolume(from) <= options.maxPalSize()) {
|
||||||
|
for (ProtoPalAttrs &protoPal : from) {
|
||||||
|
to.assign(std::move(protoPal));
|
||||||
|
}
|
||||||
|
from.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decant on "components" (= proto-pals sharing colors)
|
||||||
|
decantOn([](AssignedProtos &to, AssignedProtos &from) {
|
||||||
|
// TODO
|
||||||
|
(void)to;
|
||||||
|
(void)from;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decant on proto-palettes
|
||||||
|
decantOn([](AssignedProtos &to, AssignedProtos &from) {
|
||||||
|
// TODO
|
||||||
|
(void)to;
|
||||||
|
(void)from;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
|
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
|
||||||
options.verbosePrint("Paginating palettes using \"overload-and-remove\" strategy...\n");
|
options.verbosePrint("Paginating palettes using \"overload-and-remove\" strategy...\n");
|
||||||
@@ -256,7 +342,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.verbosePrint("%zu: Rel size: %f (size = %zu)\n", i,
|
options.verbosePrint("%zu/%zu: Rel size: %f (size = %zu)\n", i, assignments.size(),
|
||||||
assignments[i].relSizeOf(protoPal), protoPal.size());
|
assignments[i].relSizeOf(protoPal), protoPal.size());
|
||||||
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
||||||
bestPalIndex = i;
|
bestPalIndex = i;
|
||||||
@@ -330,8 +416,12 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
}
|
}
|
||||||
queue.pop();
|
queue.pop();
|
||||||
}
|
}
|
||||||
// Deal with any empty palettes left over from the "un-overloading" step
|
|
||||||
// TODO (can there be any?)
|
// "Decant" the result
|
||||||
|
decant(assignments);
|
||||||
|
|
||||||
|
// Remove all empty palettes, filling the gaps created.
|
||||||
|
removeEmptyPals(assignments);
|
||||||
|
|
||||||
if (options.beVerbose) {
|
if (options.beVerbose) {
|
||||||
for (auto &&assignment : assignments) {
|
for (auto &&assignment : assignments) {
|
||||||
@@ -341,7 +431,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
options.verbosePrint("%04" PRIx16 ", ", colorIndex);
|
options.verbosePrint("%04" PRIx16 ", ", colorIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.verbosePrint("} (%zu)\n", assignment.volume());
|
options.verbosePrint("} (volume = %zu)\n", assignment.volume());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,21 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void grayscale(std::vector<Palette> &palettes) {
|
void grayscale(std::vector<Palette> &palettes,
|
||||||
options.verbosePrint("Sorting grayscale-only palettes...\n");
|
std::array<std::optional<Rgba>, 0x8001> const &colors) {
|
||||||
|
options.verbosePrint("Sorting grayscale-only palette...\n");
|
||||||
|
|
||||||
for (Palette &pal : palettes) {
|
// This method is only applicable if there are at most as many colors as colors per palette, so
|
||||||
(void)pal; // TODO
|
// we should only have a single palette.
|
||||||
|
assert(palettes.size() == 1);
|
||||||
|
|
||||||
|
Palette &palette = palettes[0];
|
||||||
|
std::fill(palette.begin(), palette.end(), Rgba::transparent);
|
||||||
|
for (auto const &slot : colors) {
|
||||||
|
if (!slot.has_value() || slot->isTransparent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
palette[slot->grayIndex()] = slot->cgbColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
24
src/gfx/rgba.cpp
Normal file
24
src/gfx/rgba.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "gfx/main.hpp" // options
|
||||||
|
|
||||||
|
uint16_t Rgba::cgbColor() const {
|
||||||
|
if (isTransparent()) {
|
||||||
|
return transparent;
|
||||||
|
}
|
||||||
|
if (options.useColorCurve) {
|
||||||
|
assert(!"TODO");
|
||||||
|
} else {
|
||||||
|
return (red >> 3) | (green >> 3) << 5 | (blue >> 3) << 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Rgba::grayIndex() const {
|
||||||
|
assert(isGray());
|
||||||
|
// Convert from [0; 256[ to [0; maxPalSize[
|
||||||
|
return static_cast<uint16_t>(255 - red) * options.maxPalSize() / 256;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user