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.
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
) {
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
) {
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
) {
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
) {
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 */
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
) {
139 E1
= (D
== B
&& E
!= C
) || (B
== F
&& E
!= A
) ? B
: E
;
141 E3
= (D
== B
&& E
!= G
) || (D
== H
&& E
!= A
) ? D
: E
;
143 E5
= (B
== F
&& E
!= I
) || (H
== F
&& E
!= C
) ? F
: E
;
145 E7
= (D
== H
&& E
!= I
) || (H
== F
&& E
!= G
) ? H
: E
;
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. */
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
182 g=(y-0.299r-0.114b)/0.587=...=y-0.509v-0.194u
184 we multiply every floating point value with 256:
191 65536*g=256y-130v-50u
196 /* we use values *256 here for fixed point math, so 8bits is not enough */
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 */
213 for (int y
= 0; y
< height
; y
++) {
214 YUV n
; /* new value of this pixel in yuv */
217 /* for x = 0 (left edge) */
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
;
224 /* for x = 1..width-2 */
225 for (int x
= 1; x
< width
- 1; x
++) {
228 n
.y
= (yuv
[y
][xm
].y
* lconv
[0] + yuv
[y
][x
].y
* lconv
[1] + yuv
[y
][xp
].y
* lconv
[2]) / ldiv
;
232 /* for x = width-1 (right edge) */
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
;
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
;
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
;
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) */
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
) {
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
];
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
++) {
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
);
405 for (int y
= 0; y
< height
; y
++)
408 for (int y
= 0; y
< height
; y
++)
411 for (int y
= 0; y
< height
; y
++)
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) {
437 return GdColor::from_rgb(red
/ count
, green
/ count
, blue
/ count
);
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
) {
462 return GdColor::from_rgb(red
, green
, blue
);