]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/imagefilters.cpp
Remove unused ITextSceneNode header (#11476)
[dragonfireclient.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 #include <cassert>
23 #include <vector>
24
25 // Simple 2D bitmap class with just the functionality needed here
26 class Bitmap {
27         u32 linesize, lines;
28         std::vector<u8> data;
29
30         static inline u32 bytepos(u32 index) { return index >> 3; }
31         static inline u8 bitpos(u32 index) { return index & 7; }
32
33 public:
34         Bitmap(u32 width, u32 height) :  linesize(width), lines(height),
35                 data(bytepos(width * height) + 1) {}
36
37         inline bool get(u32 x, u32 y) const {
38                 u32 index = y * linesize + x;
39                 return data[bytepos(index)] & (1 << bitpos(index));
40         }
41
42         inline void set(u32 x, u32 y) {
43                 u32 index = y * linesize + x;
44                 data[bytepos(index)] |= 1 << bitpos(index);
45         }
46
47         inline bool all() const {
48                 for (u32 i = 0; i < data.size() - 1; i++) {
49                         if (data[i] != 0xff)
50                                 return false;
51                 }
52                 // last byte not entirely filled
53                 for (u8 i = 0; i < bitpos(linesize * lines); i++) {
54                         bool value_of_bit = data.back() & (1 << i);
55                         if (!value_of_bit)
56                                 return false;
57                 }
58                 return true;
59         }
60
61         inline void copy(Bitmap &to) const {
62                 assert(to.linesize == linesize && to.lines == lines);
63                 to.data = data;
64         }
65 };
66
67 /* Fill in RGB values for transparent pixels, to correct for odd colors
68  * appearing at borders when blending.  This is because many PNG optimizers
69  * like to discard RGB values of transparent pixels, but when blending then
70  * with non-transparent neighbors, their RGB values will show up nonetheless.
71  *
72  * This function modifies the original image in-place.
73  *
74  * Parameter "threshold" is the alpha level below which pixels are considered
75  * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
76  * 0 when alpha blending is used.
77  */
78 void imageCleanTransparent(video::IImage *src, u32 threshold)
79 {
80         core::dimension2d<u32> dim = src->getDimension();
81
82         Bitmap bitmap(dim.Width, dim.Height);
83
84         // First pass: Mark all opaque pixels
85         // Note: loop y around x for better cache locality.
86         for (u32 ctry = 0; ctry < dim.Height; ctry++)
87         for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
88                 if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
89                         bitmap.set(ctrx, ctry);
90         }
91
92         // Exit early if all pixels opaque
93         if (bitmap.all())
94                 return;
95
96         Bitmap newmap = bitmap;
97
98         // Then repeatedly look for transparent pixels, filling them in until
99         // we're finished (capped at 50 iterations).
100         for (u32 iter = 0; iter < 50; iter++) {
101
102         for (u32 ctry = 0; ctry < dim.Height; ctry++)
103         for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
104                 // Skip pixels we have already processed
105                 if (bitmap.get(ctrx, ctry))
106                         continue;
107
108                 video::SColor c = src->getPixel(ctrx, ctry);
109
110                 // Sample size and total weighted r, g, b values
111                 u32 ss = 0, sr = 0, sg = 0, sb = 0;
112
113                 // Walk each neighbor pixel (clipped to image bounds)
114                 for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
115                                 sy <= (ctry + 1) && sy < dim.Height; sy++)
116                 for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
117                                 sx <= (ctrx + 1) && sx < dim.Width; sx++) {
118                         // Ignore pixels we haven't processed
119                         if (!bitmap.get(sx, sy))
120                                 continue;
121         
122                         // Add RGB values weighted by alpha IF the pixel is opaque, otherwise
123                         // use full weight since we want to propagate colors.
124                         video::SColor d = src->getPixel(sx, sy);
125                         u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
126                         ss += a;
127                         sr += a * d.getRed();
128                         sg += a * d.getGreen();
129                         sb += a * d.getBlue();
130                 }
131
132                 // Set pixel to average weighted by alpha
133                 if (ss > 0) {
134                         c.setRed(sr / ss);
135                         c.setGreen(sg / ss);
136                         c.setBlue(sb / ss);
137                         src->setPixel(ctrx, ctry, c);
138                         newmap.set(ctrx, ctry);
139                 }
140         }
141
142         if (newmap.all())
143                 return;
144
145         // Apply changes to bitmap for next run. This is done so we don't introduce
146         // a bias in color propagation in the direction pixels are processed.
147         newmap.copy(bitmap);
148
149         }
150 }
151
152 /* Scale a region of an image into another image, using nearest-neighbor with
153  * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
154  * to prevent non-integer scaling ratio artifacts.  Note that this may cause
155  * some blending at the edges where pixels don't line up perfectly, but this
156  * filter is designed to produce the most accurate results for both upscaling
157  * and downscaling.
158  */
159 void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
160 {
161         double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
162         u32 dy, dx;
163         video::SColor pxl;
164
165         // Cache rectangle boundaries.
166         double sox = srcrect.UpperLeftCorner.X * 1.0;
167         double soy = srcrect.UpperLeftCorner.Y * 1.0;
168         double sw = srcrect.getWidth() * 1.0;
169         double sh = srcrect.getHeight() * 1.0;
170
171         // Walk each destination image pixel.
172         // Note: loop y around x for better cache locality.
173         core::dimension2d<u32> dim = dest->getDimension();
174         for (dy = 0; dy < dim.Height; dy++)
175         for (dx = 0; dx < dim.Width; dx++) {
176
177                 // Calculate floating-point source rectangle bounds.
178                 // Do some basic clipping, and for mirrored/flipped rects,
179                 // make sure min/max are in the right order.
180                 minsx = sox + (dx * sw / dim.Width);
181                 minsx = rangelim(minsx, 0, sox + sw);
182                 maxsx = minsx + sw / dim.Width;
183                 maxsx = rangelim(maxsx, 0, sox + sw);
184                 if (minsx > maxsx)
185                         SWAP(double, minsx, maxsx);
186                 minsy = soy + (dy * sh / dim.Height);
187                 minsy = rangelim(minsy, 0, soy + sh);
188                 maxsy = minsy + sh / dim.Height;
189                 maxsy = rangelim(maxsy, 0, soy + sh);
190                 if (minsy > maxsy)
191                         SWAP(double, minsy, maxsy);
192
193                 // Total area, and integral of r, g, b values over that area,
194                 // initialized to zero, to be summed up in next loops.
195                 area = 0;
196                 ra = 0;
197                 ga = 0;
198                 ba = 0;
199                 aa = 0;
200
201                 // Loop over the integral pixel positions described by those bounds.
202                 for (sy = floor(minsy); sy < maxsy; sy++)
203                 for (sx = floor(minsx); sx < maxsx; sx++) {
204
205                         // Calculate width, height, then area of dest pixel
206                         // that's covered by this source pixel.
207                         pw = 1;
208                         if (minsx > sx)
209                                 pw += sx - minsx;
210                         if (maxsx < (sx + 1))
211                                 pw += maxsx - sx - 1;
212                         ph = 1;
213                         if (minsy > sy)
214                                 ph += sy - minsy;
215                         if (maxsy < (sy + 1))
216                                 ph += maxsy - sy - 1;
217                         pa = pw * ph;
218
219                         // Get source pixel and add it to totals, weighted
220                         // by covered area and alpha.
221                         pxl = src->getPixel((u32)sx, (u32)sy);
222                         area += pa;
223                         ra += pa * pxl.getRed();
224                         ga += pa * pxl.getGreen();
225                         ba += pa * pxl.getBlue();
226                         aa += pa * pxl.getAlpha();
227                 }
228
229                 // Set the destination image pixel to the average color.
230                 if (area > 0) {
231                         pxl.setRed(ra / area + 0.5);
232                         pxl.setGreen(ga / area + 0.5);
233                         pxl.setBlue(ba / area + 0.5);
234                         pxl.setAlpha(aa / area + 0.5);
235                 } else {
236                         pxl.setRed(0);
237                         pxl.setGreen(0);
238                         pxl.setBlue(0);
239                         pxl.setAlpha(0);
240                 }
241                 dest->setPixel(dx, dy, pxl);
242         }
243 }