shithub: rgbds

ref: fa13611bbf6a1db1a439d9ba4ec9e6e8bb4f9f7c
dir: /src/gfx/pal_sorting.cpp/

View raw version
/*
 * This file is part of RGBDS.
 *
 * Copyright (c) 2022, Eldred Habert and RGBDS contributors.
 *
 * SPDX-License-Identifier: MIT
 */

#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