Skip to content

Instantly share code, notes, and snippets.

@profi200
Last active December 4, 2024 13:29
Show Gist options
  • Select an option

  • Save profi200/bfa7be60b3eecb8c43f59000f626c743 to your computer and use it in GitHub Desktop.

Select an option

Save profi200/bfa7be60b3eecb8c43f59000f626c743 to your computer and use it in GitHub Desktop.
// License: Do what you want. I don't care.
#include <algorithm>
#include <math.h>
#include <cstdio>
#include <cinttypes>
#include "lodepng.h"
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef struct
{
float targetGamma;
float lum;
float r, gr, br;
float rg, g, bg;
float rb, gb, b;
float displayGamma;
} ColorProfile;
// libretro shader values. Credits: hunterk and Pokefan531.
// Last updated 2014-12-03.
static const ColorProfile g_colorProfiles[7] =
{
{ // libretro GBA color (sRGB).
2.2f + (0.f * 1.6f), // Darken screen. Default 0.
0.91f,
0.905f, 0.195f, -0.1f,
0.1f, 0.65f, 0.25f,
0.1575f, 0.1425f, 0.7f,
1.f / 2.2f
},
{ // libretro GB micro color (sRGB).
2.2f,
0.9f,
0.8025f, 0.31f, -0.1125f,
0.1f, 0.6875f, 0.2125f,
0.1225f, 0.1125f, 0.765f,
1.f / 2.2f
},
{ // libretro GBA SP (AGS-101) color (sRGB).
2.2f,
0.935f,
0.96f, 0.11f, -0.07f,
0.0325f, 0.89f, 0.0775f,
0.001f, -0.03f, 1.029f,
1.f / 2.2f
},
{ // libretro NDS color (sRGB).
2.2f,
0.905f,
0.835f, 0.27f, -0.105f,
0.1f, 0.6375f, 0.2625f,
0.105f, 0.175f, 0.72f,
1.f / 2.2f
},
{ // libretro NDS lite color (sRGB).
2.2f,
0.935f,
0.93f, 0.14f, -0.07f,
0.025f, 0.9f, 0.075f,
0.008f, -0.03f, 1.022f,
1.f / 2.2f
},
{ // libretro Nintendo Switch Online color (sRGB).
2.2f + 0.8f, // Darken screen. Default 0.8.
1.f,
0.865f, 0.1225f, 0.0125f,
0.0575f, 0.925f, 0.0125f,
0.0575f, 0.1225f, 0.82f,
1.f / 2.2f
},
{ // libretro Visual Boy Advance/No$GBA full color.
1.45f + 1.f, // Darken screen. Default 1.
1.f,
0.73f, 0.27f, 0.f,
0.0825f, 0.6775f, 0.24f,
0.0825f, 0.24f, 0.6775f,
1.f / 1.45f
}
};
static void printHelp(void)
{
puts("Usage: color_convert profile_index input_png output_png\n\n"
"Available profiles (all sRGB):\n"
"Index Description\n"
"0 GBA\n"
"1 GB micro\n"
"2 GBA SP (AGS-101)\n"
"3 DS phat\n"
"4 DS lite\n"
"5 Nintendo Switch Online\n"
"6 Visual Boy Advance/No$GBA full");
}
// Compile with "g++ -std=c++17 -s -flto -O2 -fstrict-aliasing -ffunction-sections -Wall -Wextra -I./lodepng -Wl,--gc-sections ./lodepng/lodepng.cpp ./color_convert.cpp -lm -o ./color_convert"
int main(int argc, char const *argv[])
{
if(argc != 4)
{
printHelp();
return 1;
}
// TODO: Arbitrary strings not representing a number should be treated as error.
const u32 profileIdx = strtoul(argv[1], NULL, 10);
if(profileIdx > 6)
{
printHelp();
return 1;
}
unsigned char *buf;
u32 width, height;
u32 lpngErr = lodepng_decode32_file(&buf, &width, &height, argv[2]);
if(lpngErr)
{
fprintf(stderr, "lodepng error: %s", lodepng_error_text(lpngErr));
return 2;
}
if(width == 0 || width > 65535 || height == 0 || height > 65535)
{
fputs("Error: Input image too big.", stderr);
free(buf);
return 3;
}
const ColorProfile *const p = &g_colorProfiles[profileIdx];
u32 i = 0;
do
{
// Normalize.
float r = (float)buf[4 * i + 0] / 255;
float g = (float)buf[4 * i + 1] / 255;
float b = (float)buf[4 * i + 2] / 255;
// Convert to linear gamma.
const float targetGamma = p->targetGamma;
r = powf(r, targetGamma);
g = powf(g, targetGamma);
b = powf(b, targetGamma);
// Luminance.
const float lum = p->lum;
r = std::clamp(r * lum, 0.f, 1.f);
g = std::clamp(g * lum, 0.f, 1.f);
b = std::clamp(b * lum, 0.f, 1.f);
/*
* Input
* [r]
* [g]
* [b]
*
* Profile Output
* [ r][gr][br] [r]
* [rg][ g][bg] [g]
* [rb][gb][ b] [b]
*/
// Assuming alpha channel unused in original calculation.
float newR = p->r * r + p->gr * g + p->br * b;
float newG = p->rg * r + p->g * g + p->bg * b;
float newB = p->rb * r + p->gb * g + p->b * b;
newR = (newR < 0.f ? 0.f : newR);
newG = (newG < 0.f ? 0.f : newG);
newB = (newB < 0.f ? 0.f : newB);
// Convert to display gamma.
const float displayGamma = p->displayGamma;
newR = powf(newR, displayGamma);
newG = powf(newG, displayGamma);
newB = powf(newB, displayGamma);
// Denormalize, clamp and convert to RGB8.
const u32 a = buf[4 * i + 3];
buf[3 * i + 0] = std::clamp<s32>(lroundf(newR * a), 0, 255);
buf[3 * i + 1] = std::clamp<s32>(lroundf(newG * a), 0, 255);
buf[3 * i + 2] = std::clamp<s32>(lroundf(newB * a), 0, 255);
} while(++i < width * height);
lpngErr = lodepng_encode24_file(argv[3], buf, width, height);
if(lpngErr)
{
fprintf(stderr, "lodepng error: %s", lodepng_error_text(lpngErr));
free(buf);
return 4;
}
free(buf);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment