Line data Source code
1 : /*
2 : Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
3 :
4 : This program is free software; you can redistribute it and/or modify
5 : it under the terms of the GNU Lesser General Public License as published by
6 : the Free Software Foundation; either version 2.1 of the License, or
7 : (at your option) any later version.
8 :
9 : This program is distributed in the hope that it will be useful,
10 : but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : GNU Lesser General Public License for more details.
13 :
14 : You should have received a copy of the GNU Lesser General Public License along
15 : with this program; if not, write to the Free Software Foundation, Inc.,
16 : 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 : */
18 :
19 : #include "imagefilters.h"
20 : #include "util/numeric.h"
21 : #include <math.h>
22 :
23 : /* Fill in RGB values for transparent pixels, to correct for odd colors
24 : * appearing at borders when blending. This is because many PNG optimizers
25 : * like to discard RGB values of transparent pixels, but when blending then
26 : * with non-transparent neighbors, their RGB values will shpw up nonetheless.
27 : *
28 : * This function modifies the original image in-place.
29 : *
30 : * Parameter "threshold" is the alpha level below which pixels are considered
31 : * transparent. Should be 127 for 3d where alpha is threshold, but 0 for
32 : * 2d where alpha is blended.
33 : */
34 0 : void imageCleanTransparent(video::IImage *src, u32 threshold)
35 : {
36 0 : core::dimension2d<u32> dim = src->getDimension();
37 :
38 : // Walk each pixel looking for fully transparent ones.
39 : // Note: loop y around x for better cache locality.
40 0 : for (u32 ctry = 0; ctry < dim.Height; ctry++)
41 0 : for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
42 :
43 : // Ignore opaque pixels.
44 0 : irr::video::SColor c = src->getPixel(ctrx, ctry);
45 0 : if (c.getAlpha() > threshold)
46 0 : continue;
47 :
48 : // Sample size and total weighted r, g, b values.
49 0 : u32 ss = 0, sr = 0, sg = 0, sb = 0;
50 :
51 : // Walk each neighbor pixel (clipped to image bounds).
52 0 : for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
53 0 : sy <= (ctry + 1) && sy < dim.Height; sy++)
54 0 : for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
55 0 : sx <= (ctrx + 1) && sx < dim.Width; sx++) {
56 :
57 : // Ignore transparent pixels.
58 0 : irr::video::SColor d = src->getPixel(sx, sy);
59 0 : if (d.getAlpha() <= threshold)
60 0 : continue;
61 :
62 : // Add RGB values weighted by alpha.
63 0 : u32 a = d.getAlpha();
64 0 : ss += a;
65 0 : sr += a * d.getRed();
66 0 : sg += a * d.getGreen();
67 0 : sb += a * d.getBlue();
68 : }
69 :
70 : // If we found any neighbor RGB data, set pixel to average
71 : // weighted by alpha.
72 0 : if (ss > 0) {
73 0 : c.setRed(sr / ss);
74 0 : c.setGreen(sg / ss);
75 0 : c.setBlue(sb / ss);
76 0 : src->setPixel(ctrx, ctry, c);
77 : }
78 : }
79 0 : }
80 :
81 : /* Scale a region of an image into another image, using nearest-neighbor with
82 : * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
83 : * to prevent non-integer scaling ratio artifacts. Note that this may cause
84 : * some blending at the edges where pixels don't line up perfectly, but this
85 : * filter is designed to produce the most accurate results for both upscaling
86 : * and downscaling.
87 : */
88 0 : void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
89 : {
90 : double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
91 : u32 dy, dx;
92 0 : video::SColor pxl;
93 :
94 : // Cache rectsngle boundaries.
95 0 : double sox = srcrect.UpperLeftCorner.X * 1.0;
96 0 : double soy = srcrect.UpperLeftCorner.Y * 1.0;
97 0 : double sw = srcrect.getWidth() * 1.0;
98 0 : double sh = srcrect.getHeight() * 1.0;
99 :
100 : // Walk each destination image pixel.
101 : // Note: loop y around x for better cache locality.
102 0 : core::dimension2d<u32> dim = dest->getDimension();
103 0 : for (dy = 0; dy < dim.Height; dy++)
104 0 : for (dx = 0; dx < dim.Width; dx++) {
105 :
106 : // Calculate floating-point source rectangle bounds.
107 : // Do some basic clipping, and for mirrored/flipped rects,
108 : // make sure min/max are in the right order.
109 0 : minsx = sox + (dx * sw / dim.Width);
110 0 : minsx = rangelim(minsx, 0, sw);
111 0 : maxsx = minsx + sw / dim.Width;
112 0 : maxsx = rangelim(maxsx, 0, sw);
113 0 : if (minsx > maxsx)
114 0 : SWAP(double, minsx, maxsx);
115 0 : minsy = soy + (dy * sh / dim.Height);
116 0 : minsy = rangelim(minsy, 0, sh);
117 0 : maxsy = minsy + sh / dim.Height;
118 0 : maxsy = rangelim(maxsy, 0, sh);
119 0 : if (minsy > maxsy)
120 0 : SWAP(double, minsy, maxsy);
121 :
122 : // Total area, and integral of r, g, b values over that area,
123 : // initialized to zero, to be summed up in next loops.
124 0 : area = 0;
125 0 : ra = 0;
126 0 : ga = 0;
127 0 : ba = 0;
128 0 : aa = 0;
129 :
130 : // Loop over the integral pixel positions described by those bounds.
131 0 : for (sy = floor(minsy); sy < maxsy; sy++)
132 0 : for (sx = floor(minsx); sx < maxsx; sx++) {
133 :
134 : // Calculate width, height, then area of dest pixel
135 : // that's covered by this source pixel.
136 0 : pw = 1;
137 0 : if (minsx > sx)
138 0 : pw += sx - minsx;
139 0 : if (maxsx < (sx + 1))
140 0 : pw += maxsx - sx - 1;
141 0 : ph = 1;
142 0 : if (minsy > sy)
143 0 : ph += sy - minsy;
144 0 : if (maxsy < (sy + 1))
145 0 : ph += maxsy - sy - 1;
146 0 : pa = pw * ph;
147 :
148 : // Get source pixel and add it to totals, weighted
149 : // by covered area and alpha.
150 0 : pxl = src->getPixel((u32)sx, (u32)sy);
151 0 : area += pa;
152 0 : ra += pa * pxl.getRed();
153 0 : ga += pa * pxl.getGreen();
154 0 : ba += pa * pxl.getBlue();
155 0 : aa += pa * pxl.getAlpha();
156 : }
157 :
158 : // Set the destination image pixel to the average color.
159 0 : if (area > 0) {
160 0 : pxl.setRed(ra / area + 0.5);
161 0 : pxl.setGreen(ga / area + 0.5);
162 0 : pxl.setBlue(ba / area + 0.5);
163 0 : pxl.setAlpha(aa / area + 0.5);
164 : } else {
165 0 : pxl.setRed(0);
166 0 : pxl.setGreen(0);
167 0 : pxl.setBlue(0);
168 0 : pxl.setAlpha(0);
169 : }
170 0 : dest->setPixel(dx, dy, pxl);
171 : }
172 0 : }
|