README.md edited online with Bitbucket
[gdash.git] / src / gfx / pixbufmanip.cpp
blob7ef9c3aa49c22340205ce2d33a507ac3d264fb2d
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "config.h"
26 #include <cstring>
27 #include <cmath>
29 #include "settings.hpp"
30 #include "gfx/pixbuf.hpp"
31 #include "cave/colors.hpp"
33 /* somewhat optimized implementation of the Scale2x algorithm. */
34 /* http://scale2x.sourceforge.net */
35 void scale2x(const Pixbuf &src, Pixbuf &dest) {
36 int sh = src.get_height(), sw = src.get_width();
38 for (int y = 0; y < sh; ++y) {
39 // wraparound
40 int ym = (y + sh - 1) % sh;
41 int yp = (y + sh + 1) % sh;
43 for (int x = 0; x < sw; ++x) {
44 int xm = (x + sw - 1) % sw;
45 int xp = (x + sw + 1) % sw;
47 guint32 B = src(x, ym);
48 guint32 D = src(xm, y);
49 guint32 E = src(x, y);
50 guint32 F = src(xp, y);
51 guint32 H = src(x, yp);
53 guint32 E0, E1, E2, E3;
54 if (B != H && D != F) {
55 E0 = D == B ? D : E;
56 E1 = B == F ? F : E;
57 E2 = D == H ? D : E;
58 E3 = H == F ? F : E;
59 } else {
60 E0 = E;
61 E1 = E;
62 E2 = E;
63 E3 = E;
66 dest(x * 2, y * 2) = E0;
67 dest(x * 2 + 1, y * 2) = E1;
68 dest(x * 2, y * 2 + 1) = E2;
69 dest(x * 2 + 1, y * 2 + 1) = E3;
75 void scale2xnearest(const Pixbuf &src, Pixbuf &dest) {
76 int const width = src.get_width(), height = src.get_height();
77 for (int y = 0; y < height; ++y) {
78 // first double a line horizontally
79 guint32 const *srcpix = src.get_row(y);
80 guint32 *dstpix = dest.get_row(2 * y);
81 for (int x = 0; x < width; ++x) {
82 *dstpix++ = *srcpix;
83 *dstpix++ = *srcpix;
84 srcpix++;
86 // then make a fast copy of the doubled line
87 memcpy(dest.get_row(2 * y + 1), dest.get_row(2 * y), 4 * dest.get_width());
92 void scale3xnearest(const Pixbuf &src, Pixbuf &dest) {
93 int const width = src.get_width(), height = src.get_height();
94 for (int y = 0; y < height; ++y) {
95 // first double a line horizontally
96 guint32 const *srcpix = src.get_row(y);
97 guint32 *dstpix = dest.get_row(3 * y);
98 for (int x = 0; x < width; ++x) {
99 *dstpix++ = *srcpix;
100 *dstpix++ = *srcpix;
101 *dstpix++ = *srcpix;
102 srcpix++;
104 // then make a fast copy of the tripled line
105 // 4* is because of 32-bit pixbufs (4 bytes/pixel)
106 memcpy(dest.get_row(3 * y + 1), dest.get_row(3 * y), 4 * dest.get_width());
107 memcpy(dest.get_row(3 * y + 2), dest.get_row(3 * y), 4 * dest.get_width());
112 void scale3x(const Pixbuf &src, Pixbuf &dest) {
113 int sh = src.get_height(), sw = src.get_width();
115 for (int y = 0; y < sh; ++y) {
116 int ny = y * 3; /* new coordinate */
117 // wraparound
118 int ym = (y + sh - 1) % sh;
119 int yp = (y + sh + 1) % sh;
121 for (int x = 0; x < sw; ++x) {
122 int nx = x * 3; /* new coordinate */
123 int xm = (x + sw - 1) % sw;
124 int xp = (x + sw + 1) % sw;
126 guint32 A = src(xm, ym);
127 guint32 B = src(x, ym);
128 guint32 C = src(xp, ym);
129 guint32 D = src(xm, y);
130 guint32 E = src(x, y);
131 guint32 F = src(xp, y);
132 guint32 G = src(xm, yp);
133 guint32 H = src(x, yp);
134 guint32 I = src(xp, yp);
136 guint32 E0, E1, E2, E3, E4, E5, E6, E7, E8;
137 if (B != H && D != F) {
138 E0 = D == B ? D : E;
139 E1 = (D == B && E != C) || (B == F && E != A) ? B : E;
140 E2 = B == F ? F : E;
141 E3 = (D == B && E != G) || (D == H && E != A) ? D : E;
142 E4 = E;
143 E5 = (B == F && E != I) || (H == F && E != C) ? F : E;
144 E6 = D == H ? D : E;
145 E7 = (D == H && E != I) || (H == F && E != G) ? H : E;
146 E8 = H == F ? F : E;
147 } else {
148 E0 = E;
149 E1 = E;
150 E2 = E;
151 E3 = E;
152 E4 = E;
153 E5 = E;
154 E6 = E;
155 E7 = E;
156 E8 = E;
159 dest(nx, ny) = E0;
160 dest(nx + 1, ny) = E1;
161 dest(nx + 2, ny) = E2;
162 dest(nx, ny + 1) = E3;
163 dest(nx + 1, ny + 1) = E4;
164 dest(nx + 2, ny + 1) = E5;
165 dest(nx, ny + 2) = E6;
166 dest(nx + 1, ny + 2) = E7;
167 dest(nx + 2, ny + 2) = E8;
173 /* pal emulation for 32-bit rgba images. */
175 /* used:
176 y=0.299r+0.587g+0.114b
177 u=b-y=-0.299r-0.587g+0.886b
178 v=r-y=0.701r-0.587g-0.114b
180 r=(r-y)+y=v+y
181 b=(b-y)+y=u+y
182 g=(y-0.299r-0.114b)/0.587=...=y-0.509v-0.194u
184 we multiply every floating point value with 256:
186 y=77r+150g+29b
187 u=g-y=-77r-150g+227b
188 v=r-y=179r-150g-29b
190 256*r=v+y
191 65536*g=256y-130v-50u
192 256*b=u+y
195 struct YUV {
196 /* we use values *256 here for fixed point math, so 8bits is not enough */
197 gint32 y, u, v;
200 static void luma_blur(YUV **yuv, YUV **work, int width, int height) {
201 /* convolution "matrices" could be 5 numbers, ie. x-2, x-1, x, x+1, x+2... */
202 /* but the output already has problems for x-1 and x+1. as the game only
203 pal_emus cells, not complete screens - so they are only 3 pixels wide */
204 /* convolution "matrix" for luminance */
205 static const int lconv[] = { 1, 3, 1, }, ldiv = lconv[0] + lconv[1] + lconv[2];
206 /* for left edge of image. */
207 static const int lconv_left[] = { 1, 10, 6, }, ldiv_left = lconv_left[0] + lconv_left[1] + lconv_left[2];
208 /* for right edge of image. */
209 static const int lconv_right[] = { 6, 10, 1, }, ldiv_right = lconv_right[0] + lconv_right[1] + lconv_right[2];
211 /* apply convolution matrix */
212 /* luma blur */
213 for (int y = 0; y < height; y++) {
214 YUV n; /* new value of this pixel in yuv */
215 int x, xm, xp;
217 /* for x = 0 (left edge) */
218 xm = width - 1;
219 x = 0;
220 xp = 1;
221 n.y = (yuv[y][xm].y * lconv_left[0] + yuv[y][x].y * lconv_left[1] + yuv[y][xp].y * lconv_left[2]) / ldiv_left;
222 work[y][x].y = n.y;
224 /* for x = 1..width-2 */
225 for (int x = 1; x < width - 1; x++) {
226 int xm = x - 1;
227 int xp = x + 1;
228 n.y = (yuv[y][xm].y * lconv[0] + yuv[y][x].y * lconv[1] + yuv[y][xp].y * lconv[2]) / ldiv;
229 work[y][x].y = n.y;
232 /* for x = width-1 (right edge) */
233 xm = width - 2;
234 x = width - 1;
235 xp = 0;
236 n.y = (yuv[y][xm].y * lconv_right[0] + yuv[y][x].y * lconv_right[1] + yuv[y][xp].y * lconv_right[2]) / ldiv_right;
237 work[y][x].y = n.y;
240 for (int y = 0; y < height; y++)
241 for (int x = 0; x < width; x++)
242 yuv[y][x].y = work[y][x].y;
246 static void chroma_blur(YUV **yuv, YUV **work, int width, int height) {
247 /* convolution "matrix" for chrominance */
248 /* x-2, x-1, x, x+1, x+2 */
249 static const int cconv[] = { 1, 1, 1, 1, 1, }, cdiv = cconv[0] + cconv[1] + cconv[2] + cconv[3] + cconv[4];
251 /* apply convolution matrix */
252 for (int y = 0; y < height; y++) {
253 for (int x = 0; x < width; x++) {
254 int xm, xm2, xp, xp2;
256 /* turnaround coordinates */
257 xm2 = (x - 2 + width) % width;
258 xm = (x - 1 + width) % width;
259 xp = (x + 1) % width;
260 xp2 = (x + 2) % width;
262 /* chroma blur */
263 work[y][x].u = (yuv[y][xm2].u * cconv[0] + yuv[y][xm].u * cconv[1] + yuv[y][x].u * cconv[2] + yuv[y][xp].u * cconv[3] + yuv[y][xp2].u * cconv[4]) / cdiv;
264 work[y][x].v = (yuv[y][xm2].v * cconv[0] + yuv[y][xm].v * cconv[1] + yuv[y][x].v * cconv[2] + yuv[y][xp].v * cconv[3] + yuv[y][xp2].v * cconv[4]) / cdiv;
268 for (int y = 0; y < height; y++)
269 for (int x = 0; x < width; x++) {
270 yuv[y][x].u = work[y][x].u;
271 yuv[y][x].v = work[y][x].v;
275 #define CROSSTALK_SIZE 16
277 static void chroma_crosstalk_to_luma(YUV **yuv, YUV **work, int width, int height) {
278 /* arrays to store things */
279 static int crosstalk_sin[CROSSTALK_SIZE];
280 static int crosstalk_cos[CROSSTALK_SIZE];
281 /* crosstalk will be amplitude/div; we use these two to have integer arithmetics */
282 const int crosstalk_amplitude = 384;
283 const int crosstalk_div = 256;
284 static bool crosstalk_calculated = false;
286 if (!crosstalk_calculated) {
287 crosstalk_calculated = true;
288 for (int i = 0; i < CROSSTALK_SIZE; i++) {
289 double f = (double)i / CROSSTALK_SIZE * 2.0 * G_PI * 2;
290 crosstalk_sin[i] = crosstalk_amplitude * sin(f);
291 crosstalk_cos[i] = crosstalk_amplitude * cos(f);
295 /* apply edge detection matrix */
296 for (int y = 0; y < height; y++) {
297 for (int x = 0; x < width; x++) {
298 const int conv[] = { -1, 1, 0, 0, 0,};
300 /* turnaround coordinates */
301 int xm2 = (x - 2 + width) % width;
302 int xm = (x - 1 + width) % width;
303 int xp = (x + 1) % width;
304 int xp2 = (x + 2) % width;
306 /* edge detect */
307 work[y][x].u = yuv[y][xm2].u * conv[0] + yuv[y][xm].u * conv[1] + yuv[y][x].u * conv[2] + yuv[y][xp].u * conv[3] + yuv[y][xp2].u * conv[4];
308 work[y][x].v = yuv[y][xm2].v * conv[0] + yuv[y][xm].v * conv[1] + yuv[y][x].v * conv[2] + yuv[y][xp].v * conv[3] + yuv[y][xp2].v * conv[4];
312 for (int y = 0; y < height; y++)
313 if (y / 2 % 2 == 1) /* rows 3&4 */
314 for (int x = 0; x < width; x++)
315 yuv[y][x].y += (crosstalk_sin[x % CROSSTALK_SIZE] * work[y][x].u - crosstalk_cos[x % CROSSTALK_SIZE] * work[y][x].v) / crosstalk_div; /* odd lines (/2) */
316 else /* rows 1&2 */
317 for (int x = 0; x < width; x++)
318 yuv[y][x].y += (crosstalk_sin[x % CROSSTALK_SIZE] * work[y][x].u + crosstalk_cos[x % CROSSTALK_SIZE] * work[y][x].v) / crosstalk_div; /* even lines (/2) */
321 #undef CROSSTALK_SIZE
324 static void scanline_shade(YUV **yuv, YUV **work, int width, int height) {
325 if (gd_pal_emu_scanline_shade < 0)
326 gd_pal_emu_scanline_shade = 0;
327 if (gd_pal_emu_scanline_shade > 100)
328 gd_pal_emu_scanline_shade = 100;
329 int shade = gd_pal_emu_scanline_shade * 256 / 100;
331 /* apply shade for every second row */
332 for (int y = 1; y < height; y += 2)
333 for (int x = 0; x < width; x++)
334 yuv[y][x].y = yuv[y][x].y * shade / 256;
338 static inline int clamp(int value, int min, int max) {
339 if (value < min)
340 return min;
341 if (value > max)
342 return max;
343 return value;
346 void pal_emulate(Pixbuf &pb) {
347 int width = pb.get_width();
348 int height = pb.get_height();
350 /* memory for yuv images */
351 YUV **yuv = new YUV*[height];
352 for (int y = 0; y < height; y++)
353 yuv[y] = new YUV[width];
354 YUV **work = new YUV*[height];
355 for (int y = 0; y < height; y++)
356 work[y] = new YUV[width];
357 YUV **alpha = new YUV*[height]; /* alpha is not really yuv, only y will be used. luma blur touches only y. */
358 for (int y = 0; y < height; y++)
359 alpha[y] = new YUV[width];
361 /* convert to yuv */
362 for (int y = 0; y < height; y++) {
363 guint32 *row = pb.get_row(y);
365 for (int x = 0; x < width; x++) {
366 int r = (row[x] >> pb.rshift) & 0xff;
367 int g = (row[x] >> pb.gshift) & 0xff;
368 int b = (row[x] >> pb.bshift) & 0xff;
370 /* now y, u, v will contain values * 256 */
371 yuv[y][x].y = 77 * r + 150 * g + 29 * b; /* always pos */
372 yuv[y][x].u = -37 * r - 74 * g + 111 * b; /* pos or neg */
373 yuv[y][x].v = 157 * r - 131 * g - 26 * b; /* pos or neg */
375 /* alpha is copied as is, and is not *256 */
376 alpha[y][x].y = (row[x] >> pb.ashift) & 0xff;
380 /* we give them an array to "work" in, so that is not free()d and malloc() four times */
381 luma_blur(yuv, work, width, height);
382 chroma_blur(yuv, work, width, height);
383 chroma_crosstalk_to_luma(yuv, work, width, height);
384 scanline_shade(yuv, work, width, height);
386 luma_blur(alpha, work, width, height);
388 /* convert back to rgb */
389 for (int y = 0; y < height; y++) {
390 guint32 *row = pb.get_row(y);
392 for (int x = 0; x < width; x++) {
393 /* back to rgb */
394 int r = clamp((256 * yuv[y][x].y + 292 * yuv[y][x].v + 32768) / 65536, 0, 255);
395 int g = clamp((256 * yuv[y][x].y - 101 * yuv[y][x].u - 149 * yuv[y][x].v + 32768) / 65536, 0, 255);
396 int b = clamp((256 * yuv[y][x].y + 519 * yuv[y][x].u + 32768) / 65536, 0, 255);
398 /* alpha channel is preserved, others are converted back from yuv */
399 row[x] = (alpha[y][x].y << pb.ashift) | (r << pb.rshift) | (g << pb.gshift) | (b << pb.bshift);
404 /* free arrays */
405 for (int y = 0; y < height; y++)
406 delete[] yuv[y];
407 delete[] yuv;
408 for (int y = 0; y < height; y++)
409 delete[] alpha[y];
410 delete[] alpha;
411 for (int y = 0; y < height; y++)
412 delete[] work[y];
413 delete[] work;
417 GdColor average_nonblack_colors_in_pixbuf(Pixbuf const &pb) {
418 guint32 red = 0, green = 0, blue = 0, count = 0;
419 int w = pb.get_width(), h = pb.get_height();
420 for (int y = 0; y < h; ++y) {
421 guint32 const *row = pb.get_row(y);
422 for (int x = 0; x < w; ++x) {
423 guint32 pixel = row[x];
424 unsigned char tred = (pixel >> pb.rshift) & 0xFF;
425 unsigned char tgreen = (pixel >> pb.gshift) & 0xFF;
426 unsigned char tblue = (pixel >> pb.bshift) & 0xFF;
427 // if not almost black (otherwise skip)
428 if ((tred + tgreen + tblue) / 3 >= 16) {
429 red += tred;
430 green += tgreen;
431 blue += tblue;
432 count++;
436 if (count > 0)
437 return GdColor::from_rgb(red / count, green / count, blue / count);
438 else
439 return GdColor::from_rgb(0, 0, 0);
440 /* if no colors counted, all pixels were almost black - so simply return black. */
444 GdColor lightest_color_in_pixbuf(Pixbuf const &pb) {
445 int red = 0, green = 0, blue = 0;
446 int w = pb.get_width(), h = pb.get_height();
447 for (int y = 0; y < h; ++y) {
448 guint32 const *row = pb.get_row(y);
449 for (int x = 0; x < w; ++x) {
450 guint32 pixel = row[x];
451 int tred = (pixel >> pb.rshift) & 0xFF;
452 int tgreen = (pixel >> pb.gshift) & 0xFF;
453 int tblue = (pixel >> pb.bshift) & 0xFF;
454 // if lighter than previous
455 if (tred + tgreen + tblue > red + green + blue) {
456 red = tred;
457 green = tgreen;
458 blue = tblue;
462 return GdColor::from_rgb(red, green, blue);