aboutsummaryrefslogtreecommitdiff
path: root/src/imagefilters.cpp
diff options
context:
space:
mode:
authorAaron Suen <warr1024@gmail.com>2015-03-09 09:32:11 -0400
committerkwolekr <kwolekr@minetest.net>2015-04-01 00:01:05 -0400
commit6d61375cc72bad5c569d25c253adca4e3701dd27 (patch)
tree790accab0443ebcff77790da83a306d713045b01 /src/imagefilters.cpp
parentb4247dff2e003dd8c5ea5a1f3ae349d0bfab90bc (diff)
downloadminetest-6d61375cc72bad5c569d25c253adca4e3701dd27.tar.gz
minetest-6d61375cc72bad5c569d25c253adca4e3701dd27.tar.bz2
minetest-6d61375cc72bad5c569d25c253adca4e3701dd27.zip
Clean scaling pre-filter for formspec/HUD.
Diffstat (limited to 'src/imagefilters.cpp')
-rw-r--r--src/imagefilters.cpp172
1 files changed, 172 insertions, 0 deletions
diff --git a/src/imagefilters.cpp b/src/imagefilters.cpp
new file mode 100644
index 000000000..a995e98d6
--- /dev/null
+++ b/src/imagefilters.cpp
@@ -0,0 +1,172 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "imagefilters.h"
+#include "util/numeric.h"
+#include <math.h>
+
+/* Fill in RGB values for transparent pixels, to correct for odd colors
+ * appearing at borders when blending. This is because many PNG optimizers
+ * like to discard RGB values of transparent pixels, but when blending then
+ * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ *
+ * This function modifies the original image in-place.
+ *
+ * Parameter "threshold" is the alpha level below which pixels are considered
+ * transparent. Should be 127 for 3d where alpha is threshold, but 0 for
+ * 2d where alpha is blended.
+ */
+void imageCleanTransparent(video::IImage *src, u32 threshold) {
+
+ core::dimension2d<u32> dim = src->getDimension();
+
+ // Walk each pixel looking for fully transparent ones.
+ // Note: loop y around x for better cache locality.
+ for (u32 ctry = 0; ctry < dim.Height; ctry++)
+ for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
+
+ // Ignore opaque pixels.
+ irr::video::SColor c = src->getPixel(ctrx, ctry);
+ if (c.getAlpha() > threshold)
+ continue;
+
+ // Sample size and total weighted r, g, b values.
+ u32 ss = 0, sr = 0, sg = 0, sb = 0;
+
+ // Walk each neighbor pixel (clipped to image bounds).
+ for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
+ sy <= (ctry + 1) && sy < dim.Height; sy++)
+ for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
+ sx <= (ctrx + 1) && sx < dim.Width; sx++) {
+
+ // Ignore transparent pixels.
+ irr::video::SColor d = src->getPixel(sx, sy);
+ if (d.getAlpha() <= threshold)
+ continue;
+
+ // Add RGB values weighted by alpha.
+ u32 a = d.getAlpha();
+ ss += a;
+ sr += a * d.getRed();
+ sg += a * d.getGreen();
+ sb += a * d.getBlue();
+ }
+
+ // If we found any neighbor RGB data, set pixel to average
+ // weighted by alpha.
+ if (ss > 0) {
+ c.setRed(sr / ss);
+ c.setGreen(sg / ss);
+ c.setBlue(sb / ss);
+ src->setPixel(ctrx, ctry, c);
+ }
+ }
+}
+
+/* Scale a region of an image into another image, using nearest-neighbor with
+ * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
+ * to prevent non-integer scaling ratio artifacts. Note that this may cause
+ * some blending at the edges where pixels don't line up perfectly, but this
+ * filter is designed to produce the most accurate results for both upscaling
+ * and downscaling.
+ */
+void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest) {
+
+ double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
+ u32 dy, dx;
+ video::SColor pxl;
+
+ // Cache rectsngle boundaries.
+ double sox = srcrect.UpperLeftCorner.X * 1.0;
+ double soy = srcrect.UpperLeftCorner.Y * 1.0;
+ double sw = srcrect.getWidth() * 1.0;
+ double sh = srcrect.getHeight() * 1.0;
+
+ // Walk each destination image pixel.
+ // Note: loop y around x for better cache locality.
+ core::dimension2d<u32> dim = dest->getDimension();
+ for (dy = 0; dy < dim.Height; dy++)
+ for (dx = 0; dx < dim.Width; dx++) {
+
+ // Calculate floating-point source rectangle bounds.
+ // Do some basic clipping, and for mirrored/flipped rects,
+ // make sure min/max are in the right order.
+ minsx = sox + (dx * sw / dim.Width);
+ minsx = rangelim(minsx, 0, sw);
+ maxsx = minsx + sw / dim.Width;
+ maxsx = rangelim(maxsx, 0, sw);
+ if (minsx > maxsx)
+ SWAP(double, minsx, maxsx);
+ minsy = soy + (dy * sh / dim.Height);
+ minsy = rangelim(minsy, 0, sh);
+ maxsy = minsy + sh / dim.Height;
+ maxsy = rangelim(maxsy, 0, sh);
+ if (minsy > maxsy)
+ SWAP(double, minsy, maxsy);
+
+ // Total area, and integral of r, g, b values over that area,
+ // initialized to zero, to be summed up in next loops.
+ area = 0;
+ ra = 0;
+ ga = 0;
+ ba = 0;
+ aa = 0;
+
+ // Loop over the integral pixel positions described by those bounds.
+ for (sy = floor(minsy); sy < maxsy; sy++)
+ for (sx = floor(minsx); sx < maxsx; sx++) {
+
+ // Calculate width, height, then area of dest pixel
+ // that's covered by this source pixel.
+ pw = 1;
+ if (minsx > sx)
+ pw += sx - minsx;
+ if (maxsx < (sx + 1))
+ pw += maxsx - sx - 1;
+ ph = 1;
+ if (minsy > sy)
+ ph += sy - minsy;
+ if (maxsy < (sy + 1))
+ ph += maxsy - sy - 1;
+ pa = pw * ph;
+
+ // Get source pixel and add it to totals, weighted
+ // by covered area and alpha.
+ pxl = src->getPixel((u32)sx, (u32)sy);
+ area += pa;
+ ra += pa * pxl.getRed();
+ ga += pa * pxl.getGreen();
+ ba += pa * pxl.getBlue();
+ aa += pa * pxl.getAlpha();
+ }
+
+ // Set the destination image pixel to the average color.
+ if (area > 0) {
+ pxl.setRed(ra / area + 0.5);
+ pxl.setGreen(ga / area + 0.5);
+ pxl.setBlue(ba / area + 0.5);
+ pxl.setAlpha(aa / area + 0.5);
+ } else {
+ pxl.setRed(0);
+ pxl.setGreen(0);
+ pxl.setBlue(0);
+ pxl.setAlpha(0);
+ }
+ dest->setPixel(dx, dy, pxl);
+ }
+}