Translated using Weblate (Chinese (Simplified))
[oweals/minetest.git] / src / client / imagefilters.cpp
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 <cmath>
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 void imageCleanTransparent(video::IImage *src, u32 threshold)
35 {
36         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         for (u32 ctry = 0; ctry < dim.Height; ctry++)
41         for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
42
43                 // Ignore opaque pixels.
44                 irr::video::SColor c = src->getPixel(ctrx, ctry);
45                 if (c.getAlpha() > threshold)
46                         continue;
47
48                 // Sample size and total weighted r, g, b values.
49                 u32 ss = 0, sr = 0, sg = 0, sb = 0;
50
51                 // Walk each neighbor pixel (clipped to image bounds).
52                 for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
53                                 sy <= (ctry + 1) && sy < dim.Height; sy++)
54                 for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
55                                 sx <= (ctrx + 1) && sx < dim.Width; sx++) {
56
57                         // Ignore transparent pixels.
58                         irr::video::SColor d = src->getPixel(sx, sy);
59                         if (d.getAlpha() <= threshold)
60                                 continue;
61
62                         // Add RGB values weighted by alpha.
63                         u32 a = d.getAlpha();
64                         ss += a;
65                         sr += a * d.getRed();
66                         sg += a * d.getGreen();
67                         sb += a * d.getBlue();
68                 }
69
70                 // If we found any neighbor RGB data, set pixel to average
71                 // weighted by alpha.
72                 if (ss > 0) {
73                         c.setRed(sr / ss);
74                         c.setGreen(sg / ss);
75                         c.setBlue(sb / ss);
76                         src->setPixel(ctrx, ctry, c);
77                 }
78         }
79 }
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 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         video::SColor pxl;
93
94         // Cache rectangle boundaries.
95         double sox = srcrect.UpperLeftCorner.X * 1.0;
96         double soy = srcrect.UpperLeftCorner.Y * 1.0;
97         double sw = srcrect.getWidth() * 1.0;
98         double sh = srcrect.getHeight() * 1.0;
99
100         // Walk each destination image pixel.
101         // Note: loop y around x for better cache locality.
102         core::dimension2d<u32> dim = dest->getDimension();
103         for (dy = 0; dy < dim.Height; dy++)
104         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                 minsx = sox + (dx * sw / dim.Width);
110                 minsx = rangelim(minsx, 0, sox + sw);
111                 maxsx = minsx + sw / dim.Width;
112                 maxsx = rangelim(maxsx, 0, sox + sw);
113                 if (minsx > maxsx)
114                         SWAP(double, minsx, maxsx);
115                 minsy = soy + (dy * sh / dim.Height);
116                 minsy = rangelim(minsy, 0, soy + sh);
117                 maxsy = minsy + sh / dim.Height;
118                 maxsy = rangelim(maxsy, 0, soy + sh);
119                 if (minsy > maxsy)
120                         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                 area = 0;
125                 ra = 0;
126                 ga = 0;
127                 ba = 0;
128                 aa = 0;
129
130                 // Loop over the integral pixel positions described by those bounds.
131                 for (sy = floor(minsy); sy < maxsy; sy++)
132                 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                         pw = 1;
137                         if (minsx > sx)
138                                 pw += sx - minsx;
139                         if (maxsx < (sx + 1))
140                                 pw += maxsx - sx - 1;
141                         ph = 1;
142                         if (minsy > sy)
143                                 ph += sy - minsy;
144                         if (maxsy < (sy + 1))
145                                 ph += maxsy - sy - 1;
146                         pa = pw * ph;
147
148                         // Get source pixel and add it to totals, weighted
149                         // by covered area and alpha.
150                         pxl = src->getPixel((u32)sx, (u32)sy);
151                         area += pa;
152                         ra += pa * pxl.getRed();
153                         ga += pa * pxl.getGreen();
154                         ba += pa * pxl.getBlue();
155                         aa += pa * pxl.getAlpha();
156                 }
157
158                 // Set the destination image pixel to the average color.
159                 if (area > 0) {
160                         pxl.setRed(ra / area + 0.5);
161                         pxl.setGreen(ga / area + 0.5);
162                         pxl.setBlue(ba / area + 0.5);
163                         pxl.setAlpha(aa / area + 0.5);
164                 } else {
165                         pxl.setRed(0);
166                         pxl.setGreen(0);
167                         pxl.setBlue(0);
168                         pxl.setAlpha(0);
169                 }
170                 dest->setPixel(dx, dy, pxl);
171         }
172 }