Skip to content

Commit 9b545b5

Browse files
committed
fully customizable options
1 parent ab472dd commit 9b545b5

File tree

4 files changed

+139
-42
lines changed

4 files changed

+139
-42
lines changed

Qualetize.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ struct BGRAf_t Qualetize(
3333
struct TilesData_t *TilesData,
3434
uint8_t *PxData,
3535
struct BGRAf_t *Palette,
36-
int MaxTilePals,
37-
int MaxPalSize,
38-
int PalUnused,
36+
int MaxTilePals,
37+
int MaxPalSize,
38+
int PalUnused,
3939
const struct BGRA8_t *BitRange,
40-
int DitherType,
41-
int ReplaceImage
40+
int DitherType,
41+
float DitherLevel,
42+
int ReplaceImage
4243
) {
4344
int i;
4445

@@ -91,6 +92,7 @@ struct BGRAf_t Qualetize(
9192
//! NOTE: For some reason, it works better to use the square root as a weight.
9293
//! This probably gives a value somewhere between the arithmetic mean and
9394
//! the smooth-max, which should result in better quality.
95+
//! NOTE: Pre-multiply by DitherLevel to remove a multiply from the main loop.
9496
struct BGRAf_t Spread = {0,0,0,0}, SpreadW = {0,0,0,0};
9597
for(n=PalUnused;n<MaxPalSize;n++) {
9698
struct BGRAf_t d = BGRAf_Sub(&Palette[i*MaxPalSize+n], &Mean);
@@ -100,10 +102,11 @@ struct BGRAf_t Qualetize(
100102
Spread = BGRAf_Add(&Spread, &d);
101103
SpreadW = BGRAf_Add(&SpreadW, &w);
102104
}
103-
PaletteSpread[i] = BGRAf_DivSafe(&Spread, &SpreadW, NULL);
105+
Spread = BGRAf_DivSafe(&Spread, &SpreadW, NULL);
104106
#ifdef DITHER_NO_ALPHA
105-
PaletteSpread[i].a = 0.0f;
107+
Spread.a = 0.0f;
106108
#endif
109+
PaletteSpread[i] = BGRAf_Muli(&Spread, DitherLevel);
107110
}
108111
}
109112

@@ -129,7 +132,7 @@ struct BGRAf_t Qualetize(
129132
#ifdef DITHER_NO_ALPHA
130133
Dif.a = 0.0f;
131134
#endif
132-
Dif = BGRAf_Muli(&Dif, DITHER_LEVEL);
135+
Dif = BGRAf_Muli(&Dif, DitherLevel);
133136
Px = BGRAf_Add (&Px, &Dif);
134137
} else {
135138
//! Adjust for dither matrix
@@ -139,7 +142,7 @@ struct BGRAf_t Qualetize(
139142
Threshold = Threshold*2 + (xKey & 1), xKey >>= 1;
140143
} while(--Bit >= 0);
141144
float fThres = Threshold * (1.0f / (1 << (2*DitherType))) - 0.5f;
142-
struct BGRAf_t DitherVal = BGRAf_Muli(&PaletteSpread[PalIdx], fThres*DITHER_LEVEL);
145+
struct BGRAf_t DitherVal = BGRAf_Muli(&PaletteSpread[PalIdx], fThres);
143146
Px = BGRAf_Add(&Px, &DitherVal);
144147
}
145148
}
@@ -172,6 +175,8 @@ struct BGRAf_t Qualetize(
172175
}
173176
#if MEASURE_PSNR
174177
//! Accumulate squared error
178+
//! NOTE: Accumulate in BGRA, as this is more meaningful.
179+
Error = BGRAf_FromYCoCg(&Error);
175180
Error = BGRAf_Mul(&Error, &Error);
176181
RMSE = BGRAf_Add(&RMSE, &Error);
177182
#endif

Qualetize.h

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
/**************************************/
88

99
//! Dither modes available
10-
#define DITHER_NONE ( 0) //! No dither
11-
#define DITHER_ORDERED(n) ( n) //! Ordered dithering (Kernel size: (2^n) x (2^n))
12-
#define DITHER_FLOYDSTEINBERG (-1) //! Floyd-Steinberg (diffusion)
13-
14-
//! Dither settings
1510
//! NOTE: Ordered dithering gives consistent tiled results, but Floyd-Steinberg can look nicer.
1611
//! Recommend dither level of 0.5 for ordered, and 1.0 for Floyd-Steinberg.
1712
//! NOTE: DITHER_NO_ALPHA disables dithering on the alpha channel.
18-
#define DITHER_LEVEL 0.5f
13+
#define DITHER_NONE ( 0) //! No dither
14+
#define DITHER_ORDERED(n) ( n) //! Ordered dithering (Kernel size: (2^n) x (2^n))
15+
#define DITHER_FLOYDSTEINBERG (-1) //! Floyd-Steinberg (diffusion)
1916
#define DITHER_NO_ALPHA
2017

2118
/**************************************/
@@ -29,12 +26,13 @@ struct BGRAf_t Qualetize(
2926
struct TilesData_t *TilesData,
3027
uint8_t *PxData,
3128
struct BGRAf_t *Palette,
32-
int MaxTilePals,
33-
int MaxPalSize,
34-
int PalUnused,
29+
int MaxTilePals,
30+
int MaxPalSize,
31+
int PalUnused,
3532
const struct BGRA8_t *BitRange,
36-
int DitherType,
37-
int ReplaceImage
33+
int DitherType,
34+
float DitherLevel,
35+
int ReplaceImage
3836
);
3937

4038
/**************************************/

tilequant.c

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,139 @@
22
#include <stddef.h>
33
#include <stdio.h>
44
#include <stdlib.h>
5+
#include <string.h>
56
/**************************************/
67
#include "Bitmap.h"
78
#include "Colourspace.h"
89
#include "Qualetize.h"
910
#include "Tiles.h"
1011
/**************************************/
11-
#define TILE_W 8
12-
#define TILE_H 8
13-
#define PALUNUSED 1 //! These will be filled with {0,0,0,0} but still be used for matching
14-
#define BITRANGE (const struct BGRA8_t){0x1F,0x1F,0x1F,0x01} //! None of these may be 0
1512

16-
//! Default dither mode
17-
#define DITHER_TYPE DITHER_ORDERED(3)
13+
//! When not zero, the PSNR for each channel will be displayed
14+
#define MEASURE_PSNR 1
1815

1916
/**************************************/
2017

21-
//! When not zero, the PSNR for each channel will be displayed
22-
#define MEASURE_PSNR 1
18+
//! strcmp() implementation that ACTUALLY returns the difference between
19+
//! characters instead of just the signs. Blame the C standard -_-
20+
static int mystrcmp(const char *s1, const char *s2) {
21+
while(*s1 && *s1 == *s2) s1++, s2++;
22+
return *s1 - *s2;
23+
}
2324

2425
/**************************************/
2526

2627
int main(int argc, const char *argv[]) {
2728
//! Check arguments
28-
if(argc != 5) {
29+
if(argc < 3) {
2930
printf(
3031
"tilequant - Tiled colour-quantization tool\n"
31-
"Usage: tilequant Input.bmp Output.bmp (no. of palettes) (entries/palette)\n"
32+
"Usage:\n"
33+
" tilequant Input.bmp Output.bmp [options]\n"
34+
"Options:\n"
35+
" -np:16 - Set number of palettes available\n"
36+
" -ps:16 - Set number of colours per palette\n"
37+
" -tw:8 - Set tile width\n"
38+
" -th:8 - Set tile height\n"
39+
" -bgra:5551 - Set BGRA bit depth\n"
40+
" -dither:floyd,1.0 - Set dither mode, level\n"
41+
"Dither modes available (and default level):\n"
42+
" -dither:none - No dithering\n"
43+
" -dither:floyd,1.0 - Floyd-Steinberg\n"
44+
" -dither:ord2,0.5 - 2x2 ordered dithering\n"
45+
" -dither:ord4,0.5 - 4x4 ordered dithering\n"
46+
" -dither:ord8,0.5 - 8x8 ordered dithering\n"
47+
" -dither:ord16,0.5 - 16x16 ordered dithering\n"
48+
" -dither:ord32,0.5 - 32x32 ordered dithering\n"
49+
" -dither:ord64,0.5 - 64x64 ordered dithering\n"
3250
);
3351
return 1;
3452
}
3553

3654
//! Parse arguments
37-
int MaxTilePals = atoi(argv[3]);
38-
int MaxPalSize = atoi(argv[4]);
55+
int nPalettes = 16;
56+
int nColoursPerPalette = 16;
57+
int nUnusedColoursPerPalette = 1;
58+
int TileW = 8;
59+
int TileH = 8;
60+
uint8_t BitRange[4] = {0x1F,0x1F,0x1F,0x01};
61+
int DitherMode = DITHER_FLOYDSTEINBERG;
62+
float DitherLevel = 1.0f; {
63+
int argi;
64+
for(argi=3;argi<argc;argi++) {
65+
int ArgOk = 0;
66+
67+
const char *ArgStr;
68+
#define ARGMATCH(Input, Target) \
69+
ArgStr = Input + strlen(Target); \
70+
if(!memcmp(Input, Target, strlen(Target)))
71+
//! nPalettes
72+
ARGMATCH(argv[argi], "-np:") ArgOk = 1, nPalettes = atoi(ArgStr);
73+
74+
//! nColoursPerPalette
75+
ARGMATCH(argv[argi], "-ps:") ArgOk = 1, nColoursPerPalette = atoi(ArgStr);
76+
77+
//! nUnusedColoursPerPalette
78+
79+
//! TileW
80+
ARGMATCH(argv[argi], "-tw:") ArgOk = 1, TileW = atoi(ArgStr);
81+
82+
//! TileH
83+
ARGMATCH(argv[argi], "-th:") ArgOk = 1, TileH = atoi(ArgStr);
84+
85+
//! BitRange
86+
ARGMATCH(argv[argi], "-bgra:") {
87+
ArgOk = 1;
88+
BitRange[0] = (1 << (*ArgStr++ - '0')) - 1;
89+
BitRange[1] = (1 << (*ArgStr++ - '0')) - 1;
90+
BitRange[2] = (1 << (*ArgStr++ - '0')) - 1;
91+
BitRange[3] = (1 << (*ArgStr++ - '0')) - 1;
92+
}
93+
94+
//! DitherMode,DitherLevel
95+
ARGMATCH(argv[argi], "-dither:") {
96+
int d;
97+
#define DITHERMODE_MATCH(Input, Target, ModeValue, DefaultLevel) \
98+
d = mystrcmp(Input, Target); \
99+
if(!d || d == ',') { \
100+
ArgOk = 1; \
101+
DitherMode = ModeValue; \
102+
DitherLevel = !d ? DefaultLevel : atof(strchr(Input, ',')+1); \
103+
}
104+
DITHERMODE_MATCH(ArgStr, "none", DITHER_NONE, 0.0f);
105+
DITHERMODE_MATCH(ArgStr, "floyd", DITHER_FLOYDSTEINBERG, 1.0f);
106+
DITHERMODE_MATCH(ArgStr, "ord2", DITHER_ORDERED(1), 0.5f);
107+
DITHERMODE_MATCH(ArgStr, "ord4", DITHER_ORDERED(2), 0.5f);
108+
DITHERMODE_MATCH(ArgStr, "ord8", DITHER_ORDERED(3), 0.5f);
109+
DITHERMODE_MATCH(ArgStr, "ord16", DITHER_ORDERED(4), 0.5f);
110+
DITHERMODE_MATCH(ArgStr, "ord32", DITHER_ORDERED(5), 0.5f);
111+
DITHERMODE_MATCH(ArgStr, "ord64", DITHER_ORDERED(6), 0.5f);
112+
#undef DITHERMODE_MATCH
113+
if(!ArgOk) printf("Unrecognized dither mode: %s\n", ArgStr);
114+
ArgOk = 1;
115+
}
116+
#undef ARGMATCH
117+
118+
//! Unrecognized?
119+
if(!ArgOk) printf("Unrecognized argument: %s\n", ArgStr);
120+
}
121+
}
39122

40123
//! Get input image
41124
struct BmpCtx_t Image;
42125
if(!BmpCtx_FromFile(&Image, argv[1])) {
43126
printf("Unable to read input file\n");
44127
return -1;
45128
}
46-
if(Image.Width%TILE_W || Image.Height%TILE_H) {
47-
printf("Image not a multiple of tile size (%dx%d)\n", TILE_W, TILE_H);
129+
if(Image.Width%TileW || Image.Height%TileH) {
130+
printf("Image not a multiple of tile size (%dx%d)\n", TileW, TileH);
48131
BmpCtx_Destroy(&Image);
49132
return -1;
50133
}
51134

52135
//! Perform processing
53136
//! NOTE: PxData and Palette will be assigned to image; do NOT destroy
54-
struct TilesData_t *TilesData = TilesData_FromBitmap(&Image, TILE_W, TILE_H);
137+
struct TilesData_t *TilesData = TilesData_FromBitmap(&Image, TileW, TileH);
55138
uint8_t *PxData = malloc(Image.Width * Image.Height * sizeof(uint8_t));
56139
struct BGRAf_t *Palette = calloc(BMP_PALETTE_COLOURS, sizeof(struct BGRAf_t));
57140
if(!TilesData || !PxData || !Palette) {
@@ -63,7 +146,7 @@ int main(int argc, const char *argv[]) {
63146
return -1;
64147
}
65148
struct BGRAf_t RMSE = Qualetize(
66-
&Image, TilesData, PxData, Palette, MaxTilePals, MaxPalSize, PALUNUSED, &BITRANGE, DITHER_TYPE, 1
149+
&Image, TilesData, PxData, Palette, nPalettes, nColoursPerPalette, nUnusedColoursPerPalette, BitRange, DitherMode, DitherLevel, 1
67150
);
68151
free(TilesData);
69152

tilequantDLL.c

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
//! SrcPxData = uint8_t[Width*Height]
1717
//! SrcPxPal = (struct BGRA8_t)[]
1818
//! General:
19-
//! DstPxIdx = uint8_t[Width*Height]
20-
//! DstPal = (struct BGRA8_t)[nPalettes * nColoursPerPalette]
19+
//! DstPxIdx = uint8_t[Width*Height]
20+
//! DstPal = (struct BGRA8_t)[nPalettes * nColoursPerPalette]
2121
//! NOTE: DstPal must have enough space to accomodate:
2222
//! (struct BGRAf_t)[nPalettes * nColoursPerPalette]
23-
//! TilePalIdx = NULL or int32_t[(Width*Height) / (TileW*TileH)]
24-
//! DitherMode = Dither mode to use: 0 = DITHER_NONE, -1 = DITHER_FLOYDSTEINBERG, n = DITHER_ORDERED(n)
23+
//! TilePalIdx = NULL or int32_t[(Width*Height) / (TileW*TileH)]
24+
//! DitherMode = Dither mode to use: 0 = DITHER_NONE, -1 = DITHER_FLOYDSTEINBERG, n = DITHER_ORDERED(n)
25+
//! DitherLevel = Scale of the dither (0.0 = No dither, 1.0 = Full dither)
2526
//! OutputPaletteIs24bitRGB outputs RGB (byte order: {RR, GG, BB})
2627
//! colours without an alpha channel; the default is to output to
2728
//! BGRA (byte order: {BB, GG, RR, AA}).
@@ -43,7 +44,8 @@ DECLSPEC int QualetizeFromRawImage(
4344
int TileH,
4445
int32_t *TilePalIdx,
4546
const uint8_t BitRange[4],
46-
int DitherMode
47+
int DitherMode,
48+
float DitherLevel
4749
) {
4850
//! Create image context
4951
//! NOTE: 'const' violations in image data, but not modified so this is safe
@@ -60,7 +62,16 @@ DECLSPEC int QualetizeFromRawImage(
6062
struct TilesData_t *TilesData = TilesData_FromBitmap(&Ctx, TileW, TileH);
6163
if(!TilesData) return 0;
6264
(void)Qualetize(
63-
&Ctx, TilesData, DstPxIdx, (struct BGRAf_t*)DstPal, nPalettes, nColoursPerPalette, nUnusedColoursPerPalette, (const struct BGRA8_t*)BitRange, DitherMode, 0
65+
&Ctx, TilesData,
66+
DstPxIdx,
67+
(struct BGRAf_t*)DstPal,
68+
nPalettes,
69+
nColoursPerPalette,
70+
nUnusedColoursPerPalette,
71+
(const struct BGRA8_t*)BitRange,
72+
DitherMode,
73+
DitherLevel
74+
0
6475
);
6576

6677
//! Store tile palette indices

0 commit comments

Comments
 (0)