summaryrefslogtreecommitdiff
path: root/include/palette.h
blob: a4b9f77b6908e4e93b723b40d18768734b76792a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
 * Copyright 2012 Michael Drake <tlsa@netsurf-browser.org>
 *
 * This file is part of libnsfb, http://www.netsurf-browser.org/
 * Licenced under the MIT License,
 *                http://www.opensource.org/licenses/mit-license.php
 *
 * This is the *internal* interface for the cursor. 
 */

#ifndef PALETTE_H
#define PALETTE_H 1

#include <stdint.h>
#include <limits.h>

#include "libnsfb.h"
#include "libnsfb_plot.h"

enum nsfb_palette_type_e {
	NSFB_PALETTE_EMPTY,     /**< empty palette object */
	NSFB_PALETTE_NSFB_8BPP, /**< libnsfb's own 8bpp palette */
	NSFB_PALETTE_OTHER      /**< any other palette  */
};

struct nsfb_palette_s {
	enum nsfb_palette_type_e type; /**< Palette type */
	uint8_t last; /**< Last used palette index */
	nsfb_colour_t data[256]; /**< Palette for index modes */

	bool dither; /**< Whether to use error diffusion */
	struct {
		int width; /**< Length of error value buffer ring*/
		int current; /**< Current pos in ring buffer*/
		int *data; /**< Ring buffer error values */
		int data_len; /**< Max size of ring */
	} dither_ctx;
};


/** Create an empty palette object. */
bool nsfb_palette_new(struct nsfb_palette_s **palette, int width);

/** Free a palette object. */
void nsfb_palette_free(struct nsfb_palette_s *palette);

/** Init error diffusion for a plot. */
void nsfb_palette_dither_init(struct nsfb_palette_s *palette, int width);

/** Finalise error diffusion after a plot. */
void nsfb_palette_dither_fini(struct nsfb_palette_s *palette);

/** Generate libnsfb 8bpp default palette. */
void nsfb_palette_generate_nsfb_8bpp(struct nsfb_palette_s *palette);

/** Find best palette match for given colour. */
static inline uint8_t nsfb_palette_best_match(struct nsfb_palette_s *palette,
		nsfb_colour_t c, int *r_error, int *g_error, int *b_error)
{
	uint8_t best_col = 0;

	nsfb_colour_t palent;
	int col;
	int dr, dg, db; /* delta red, green blue values */

	int cur_distance;
	int best_distance = INT_MAX;

	int r = ( c        & 0xFF);
	int g = ((c >>  8) & 0xFF);
	int b = ((c >> 16) & 0xFF);

	switch (palette->type) {
	case NSFB_PALETTE_NSFB_8BPP:
		/* Index into colour cube part */
		dr = ((r * 5) + 128) / 256;
		dg = ((g * 7) + 128) / 256;
		db = ((b * 4) + 128) / 256;
		col = 40 * dr + 5 * dg + db;

		palent = palette->data[col];
		dr = r - ( palent        & 0xFF);
		dg = g - ((palent >>  8) & 0xFF);
		db = b - ((palent >> 16) & 0xFF);
		cur_distance = (dr * dr) + (dg * dg) + (db * db);

		best_col = col;
		best_distance = cur_distance;
		*r_error = dr;
		*g_error = dg;
		*b_error = db;

		/* Index into grayscale part */
		col = (r + g + b + (45 / 2)) / (15 * 3) - 1 + 240;
		palent = palette->data[col];

		dr = r - (palent & 0xFF);
		dg = g - (palent & 0xFF);
		db = b - (palent & 0xFF);
		cur_distance = (dr * dr) + (dg * dg) + (db * db);
		if (cur_distance < best_distance) {
			best_col = col;
			*r_error = dr;
			*g_error = dg;
			*b_error = db;
		}
		break;

	case NSFB_PALETTE_OTHER:
		/* Try all colours in palette */
		for (col = 0; col <= palette->last; col++) {
			palent = palette->data[col];

			dr = r - ( palent        & 0xFF);
			dg = g - ((palent >>  8) & 0xFF);
			db = b - ((palent >> 16) & 0xFF);
			cur_distance = (dr * dr) + (dg * dg) + (db * db);
			if (cur_distance < best_distance) {
				best_distance = cur_distance;
				best_col = col;
				*r_error = dr;
				*g_error = dg;
				*b_error = db;
			}
		}
		break;

	default:
		break;
	}

        return best_col;
}

/** Find best palette match for given colour, with error diffusion. */
static inline uint8_t nsfb_palette_best_match_dither(
		struct nsfb_palette_s *palette, nsfb_colour_t c)
{
	int r, g, b;
	int current;
	int error;
	int width = palette->dither_ctx.width;
	uint8_t best_col_index;

	if (palette == NULL)
		return 0;

	if (palette->dither == false)
		return nsfb_palette_best_match(palette, c, &r, &g, &b);

	current = palette->dither_ctx.current;

	/* Get RGB components of colour, and apply error */
	r = ( c        & 0xFF) + palette->dither_ctx.data[current    ];
	g = ((c >>  8) & 0xFF) + palette->dither_ctx.data[current + 1];
	b = ((c >> 16) & 0xFF) + palette->dither_ctx.data[current + 2];

	/* Clamp new RGB components to range */
	if (r <   0) r =   0;
	if (r > 255) r = 255;
	if (g <   0) g =   0;
	if (g > 255) g = 255;
	if (b <   0) b =   0;
	if (b > 255) b = 255;

	/* Reset error diffusion slots to 0 */
	palette->dither_ctx.data[current    ] = 0;
	palette->dither_ctx.data[current + 1] = 0;
	palette->dither_ctx.data[current + 2] = 0;

	/* Rebuild colour from modified components */
	c = r + (g << 8) + (b << 16);

	/* Get best match for pixel, and find errors for each component */
	best_col_index = nsfb_palette_best_match(palette, c, &r, &g, &b);

	/* Advance one set of error diffusion slots */
	current += 3;
	if (current >= width)
		current = 0;
	palette->dither_ctx.current = current;

	/* Save errors
	 *
	 *       [*]-[N]
	 *      / | \
	 *   [l]-[m]-[r]
	 */
	error = current;

	/* Error for [N] (next) */
	if (error != 0) {
		/* The pixel exists */
		palette->dither_ctx.data[error    ] += r * 7 / 16;
		palette->dither_ctx.data[error + 1] += g * 7 / 16;
		palette->dither_ctx.data[error + 2] += b * 7 / 16;
	}

	error += width - 2 * 3;
	if (error >= width)
		error -= width;
	/* Error for [l] (below, left) */
	if (error >= 0 && error != 3) {
		/* The pixel exists */
		palette->dither_ctx.data[error    ] += r * 3 / 16;
		palette->dither_ctx.data[error + 1] += g * 3 / 16;
		palette->dither_ctx.data[error + 2] += b * 3 / 16;
	}

	error += 3;
	if (error >= width)
		error -= width;
	/* Error for [m] (below, middle) */
	palette->dither_ctx.data[error    ] += r * 5 / 16;
	palette->dither_ctx.data[error + 1] += g * 5 / 16;
	palette->dither_ctx.data[error + 2] += b * 5 / 16;

	error += 3;
	if (error >= width)
		error -= width;
	/* Error for [r] (below, right) */
	if (error != 0) {
		/* The pixel exists */
		palette->dither_ctx.data[error    ] += r / 16;
		palette->dither_ctx.data[error + 1] += g / 16;
		palette->dither_ctx.data[error + 2] += b / 16;
	}

	return best_col_index;
}

#endif /* PALETTE_H */