Skip to content

Commit a99b405

Browse files
committed
Factor out image processing code
1 parent 951159c commit a99b405

File tree

3 files changed

+280
-151
lines changed

3 files changed

+280
-151
lines changed

Dither.c

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/**************************************/
2+
#include <math.h>
3+
#include <stdlib.h>
4+
/**************************************/
5+
#include "Colourspace.h"
6+
#include "Dither.h"
7+
#include "Qualetize.h"
8+
/**************************************/
9+
10+
//! Palette entry matching
11+
static int FindPaletteEntry(const struct BGRAf_t *Px, const struct BGRAf_t *Pal, int MaxPalSize, int PalUnused) {
12+
int i;
13+
int MinIdx = 0;
14+
float MinDst = INFINITY;
15+
struct BGRAf_t PxYUV = BGRAf_AsYUV(Px), PalYUV;
16+
for(i=PalUnused-1;i<MaxPalSize;i++) {
17+
PalYUV = BGRAf_AsYUV(&Pal[i]);
18+
float Dst = BGRAf_ColDistance(&PxYUV, &PalYUV);
19+
if(Dst < MinDst) MinIdx = i, MinDst = Dst;
20+
}
21+
return MinIdx;
22+
}
23+
24+
/**************************************/
25+
26+
//! Handle conversion of image with given palette, return RMS error
27+
struct BGRAf_t DitherImage(
28+
const struct BmpCtx_t *Image,
29+
const struct BGRA8_t *BitRange,
30+
struct BGRAf_t *RawPxOutput,
31+
32+
int TileW,
33+
int TileH,
34+
int MaxTilePals,
35+
int MaxPalSize,
36+
int PalUnused,
37+
const int32_t *TilePalIndices,
38+
const struct BGRAf_t *TilePalettes,
39+
uint8_t *TilePxOutput,
40+
41+
int DitherType,
42+
float DitherLevel,
43+
struct BGRAf_t *DiffusionBuffer
44+
) {
45+
int i;
46+
47+
//! Get parameters, pointers, etc.
48+
int x, y;
49+
int ImgW = Image->Width;
50+
int ImgH = Image->Height;
51+
#if MEASURE_PSNR
52+
struct BGRAf_t RMSE = (struct BGRAf_t){0,0,0,0};
53+
#endif
54+
const uint8_t *PxSrcIdx = Image->ColPal ? Image->PxIdx : NULL;
55+
const struct BGRA8_t *PxSrcBGR = Image->ColPal ? Image->ColPal : Image->PxBGR;
56+
57+
//! Initialize dither patterns
58+
//! For Floyd-Steinberg dithering, we only keep track of two scanlines
59+
//! of diffusion error (the current line and the next), and just swap
60+
//! back-and-forth between them to avoid a memcpy(). We also append an
61+
//! extra 2 pixels at the end of each line to avoid extra comparisons.
62+
union {
63+
struct BGRAf_t *DiffuseError; //! DITHER_FLOYDSTEINBERG only
64+
struct BGRAf_t *PaletteSpread; //! DITHER_ORDERED only
65+
void *DataPtr;
66+
} Dither;
67+
Dither.DataPtr = DiffusionBuffer;
68+
if(DitherType != DITHER_NONE) {
69+
if(DitherType == DITHER_FLOYDSTEINBERG) {
70+
//! Error diffusion dithering
71+
for(i=0;i<(ImgW+2)*2;i++) Dither.DiffuseError[i] = (struct BGRAf_t){0,0,0,0};
72+
} else if(TilePxOutput) {
73+
//! Ordered dithering (with tile palettes)
74+
for(i=0;i<MaxTilePals;i++) {
75+
//! Find the mean values of this palette
76+
int n;
77+
struct BGRAf_t Mean = (struct BGRAf_t){0,0,0,0};
78+
for(n=PalUnused;n<MaxPalSize;n++) Mean = BGRAf_Add(&Mean, &TilePalettes[i*MaxPalSize+n]);
79+
Mean = BGRAf_Divi(&Mean, MaxPalSize-PalUnused);
80+
81+
//! Compute slopes and store to the palette spread
82+
//! NOTE: For some reason, it works better to use the square root as a weight.
83+
//! This probably gives a value somewhere between the arithmetic mean and
84+
//! the smooth-max, which should result in better quality.
85+
//! NOTE: Pre-multiply by DitherLevel to remove a multiply from the main loop.
86+
struct BGRAf_t Spread = {0,0,0,0}, SpreadW = {0,0,0,0};
87+
for(n=PalUnused;n<MaxPalSize;n++) {
88+
struct BGRAf_t d = BGRAf_Sub(&TilePalettes[i*MaxPalSize+n], &Mean);
89+
d = BGRAf_Abs(&d);
90+
struct BGRAf_t w = BGRAf_Sqrt(&d);
91+
d = BGRAf_Mul(&d, &w);
92+
Spread = BGRAf_Add(&Spread, &d);
93+
SpreadW = BGRAf_Add(&SpreadW, &w);
94+
}
95+
Spread = BGRAf_DivSafe(&Spread, &SpreadW, NULL);
96+
#ifdef DITHER_NO_ALPHA
97+
Spread.a = 0.0f;
98+
#endif
99+
Dither.PaletteSpread[i] = BGRAf_Muli(&Spread, DitherLevel);
100+
}
101+
} else {
102+
//! "Real" ordered dithering (without tile palettes)
103+
static const struct BGRA8_t MinValue = {1,1,1,1};
104+
struct BGRAf_t Spread = BGRAf_FromBGRA(&MinValue, BitRange);
105+
Dither.PaletteSpread[0] = BGRAf_Muli(&Spread, DitherLevel);
106+
}
107+
}
108+
109+
//! Begin processing of pixels
110+
int TileHeightCounter = TileH;
111+
struct BGRAf_t *DiffuseThisLine = Dither.DiffuseError + 1; //! <- 1px padding on left
112+
struct BGRAf_t *DiffuseNextLine = DiffuseThisLine + (ImgW+1); //! <- 1px padding on right
113+
struct BGRAf_t RMSE = (struct BGRAf_t){0,0,0,0};
114+
for(y=0;y<ImgH;y++) {
115+
int TilePalIdx = 0;
116+
int TileWidthCounter = 0;
117+
for(x=0;x<ImgW;x++) {
118+
//! Advance tile palette index
119+
if(TilePxOutput && --TileWidthCounter <= 0) {
120+
TilePalIdx = *TilePalIndices++;
121+
TileWidthCounter = TileW;
122+
}
123+
124+
//! Get pixel and apply dithering
125+
struct BGRAf_t Px, Px_Original; {
126+
//! Read original pixel data
127+
struct BGRA8_t p;
128+
if(PxSrcIdx) p = PxSrcBGR[*PxSrcIdx++];
129+
else p = *PxSrcBGR++;
130+
Px = Px_Original = BGRAf_FromBGRA8(&p);
131+
}
132+
if(DitherType != DITHER_NONE) {
133+
if(DitherType == DITHER_FLOYDSTEINBERG) {
134+
//! Adjust for diffusion error
135+
struct BGRAf_t t = DiffuseThisLine[x];
136+
#ifdef DITHER_NO_ALPHA
137+
t.a = 0.0f;
138+
#endif
139+
t = BGRAf_Muli(&t, DitherLevel);
140+
Px = BGRAf_Add (&Px, &t);
141+
} else {
142+
//! Adjust for dither matrix
143+
int Threshold = 0, xKey = x, yKey = x^y;
144+
int Bit = DitherType-1; do {
145+
Threshold = Threshold*2 + (yKey & 1), yKey >>= 1; //! <- Hopefully turned into "SHR, ADC"
146+
Threshold = Threshold*2 + (xKey & 1), xKey >>= 1;
147+
} while(--Bit >= 0);
148+
float fThres = Threshold * (1.0f / (1 << (2*DitherType))) - 0.5f;
149+
struct BGRAf_t DitherVal = BGRAf_Muli(&Dither.PaletteSpread[TilePalIdx], fThres);
150+
Px = BGRAf_Add(&Px, &DitherVal);
151+
}
152+
}
153+
154+
//! Find matching palette entry, store to output, and get error
155+
if(TilePxOutput) {
156+
int PalIdx = FindPaletteEntry(&Px, TilePalettes + TilePalIdx*MaxPalSize, MaxPalSize, PalUnused);
157+
PalIdx += TilePalIdx*MaxPalSize;
158+
*TilePxOutput++ = PalIdx;
159+
Px = TilePalettes[PalIdx];
160+
} else {
161+
//! Reduce range when not using tile output
162+
struct BGRA8_t t = BGRA_FromBGRAf(&Px, BitRange);
163+
Px = BGRAf_FromBGRA(&t, BitRange);
164+
}
165+
if(RawPxOutput) {
166+
*RawPxOutput++ = Px;
167+
}
168+
struct BGRAf_t Error = BGRAf_Sub(&Px_Original, &Px);
169+
170+
//! Add to error diffusion
171+
if(DitherType == DITHER_FLOYDSTEINBERG) {
172+
struct BGRAf_t t;
173+
174+
//! {x+1,y} @ 7/16
175+
t = BGRAf_Muli(&Error, 7.0f/16);
176+
DiffuseThisLine[x+1] = BGRAf_Add(&DiffuseThisLine[x+1], &t);
177+
178+
//! {x-1,y+1} @ 3/16
179+
t = BGRAf_Muli(&Error, 3.0f/16);
180+
DiffuseNextLine[x-1] = BGRAf_Add(&DiffuseNextLine[x-1], &t);
181+
182+
//! {x+0,y+1} @ 5/16
183+
t = BGRAf_Muli(&Error, 5.0f/16);
184+
DiffuseNextLine[x+0] = BGRAf_Add(&DiffuseNextLine[x+0], &t);
185+
186+
//! {x+1,y+1} @ 1/16
187+
t = BGRAf_Muli(&Error, 1.0f/16);
188+
DiffuseNextLine[x+1] = BGRAf_Add(&DiffuseNextLine[x+1], &t);
189+
}
190+
191+
//! Accumulate error for RMS calculation
192+
Error = BGRAf_Mul(&Error, &Error);
193+
RMSE = BGRAf_Add(&RMSE, &Error);
194+
}
195+
196+
//! Advance tile palette index pointer
197+
//! At this point, we're already pointing to the next row of tiles,
198+
//! so we only need to either update the counter, or rewind the pointer.
199+
if(TilePxOutput) {
200+
if(--TileHeightCounter <= 0) {
201+
TileHeightCounter = TileH;
202+
} else TilePalIndices -= (ImgW/TileW);
203+
}
204+
205+
//! Swap diffusion dithering pointers and clear buffer for next line
206+
if(DitherType == DITHER_FLOYDSTEINBERG) {
207+
struct BGRAf_t *t = DiffuseThisLine;
208+
DiffuseThisLine = DiffuseNextLine;
209+
DiffuseNextLine = t;
210+
for(x=0;x<ImgW;x++) DiffuseNextLine[x] = (struct BGRAf_t){0,0,0,0};
211+
}
212+
}
213+
214+
//! Return error
215+
RMSE = BGRAf_Divi(&RMSE, ImgW*ImgH);
216+
RMSE = BGRAf_Sqrt(&RMSE);
217+
return RMSE;
218+
}
219+
220+
/**************************************/
221+
//! EOF
222+
/**************************************/

Dither.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**************************************/
2+
#pragma once
3+
/**************************************/
4+
#include "Bitmap.h"
5+
#include "Colourspace.h"
6+
/**************************************/
7+
8+
//! Handle conversion of image, return RMS error.
9+
//! Notes:
10+
//! -Passing RawPxOutput != NULL will store the dithered image there.
11+
//! -Passing TilePxOutput != NULL will store the output image there,
12+
//! using TilePalettes as a reference.
13+
//! -DiffusionBuffer[] needs to be (Image->Width+2)*2 elements in size.
14+
struct BGRAf_t DitherImage(
15+
const struct BmpCtx_t *Image,
16+
const struct BGRA8_t *BitRange,
17+
struct BGRAf_t *RawPxOutput,
18+
19+
int TileW,
20+
int TileH,
21+
int MaxTilePals,
22+
int MaxPalSize,
23+
int PalUnused,
24+
const int32_t *TilePalIndices,
25+
const struct BGRAf_t *TilePalettes,
26+
uint8_t *TilePxOutput,
27+
28+
int DitherType,
29+
float DitherLevel,
30+
struct BGRAf_t *DiffusionBuffer
31+
);
32+
33+
/**************************************/
34+
//! EOF
35+
/**************************************/

0 commit comments

Comments
 (0)