123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- /*
- 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 <cmath>
- #include <cassert>
- #include <vector>
- #include <algorithm>
- // Simple 2D bitmap class with just the functionality needed here
- class Bitmap {
- u32 linesize, lines;
- std::vector<u8> data;
- static inline u32 bytepos(u32 index) { return index >> 3; }
- static inline u8 bitpos(u32 index) { return index & 7; }
- public:
- Bitmap(u32 width, u32 height) : linesize(width), lines(height),
- data(bytepos(width * height) + 1) {}
- inline bool get(u32 x, u32 y) const {
- u32 index = y * linesize + x;
- return data[bytepos(index)] & (1 << bitpos(index));
- }
- inline void set(u32 x, u32 y) {
- u32 index = y * linesize + x;
- data[bytepos(index)] |= 1 << bitpos(index);
- }
- inline bool all() const {
- for (u32 i = 0; i < data.size() - 1; i++) {
- if (data[i] != 0xff)
- return false;
- }
- // last byte not entirely filled
- for (u8 i = 0; i < bitpos(linesize * lines); i++) {
- bool value_of_bit = data.back() & (1 << i);
- if (!value_of_bit)
- return false;
- }
- return true;
- }
- inline void copy(Bitmap &to) const {
- assert(to.linesize == linesize && to.lines == lines);
- to.data = data;
- }
- };
- /* 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 show 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 when the texture is used with ALPHA_CHANNEL_REF,
- * 0 when alpha blending is used.
- */
- void imageCleanTransparent(video::IImage *src, u32 threshold)
- {
- core::dimension2d<u32> dim = src->getDimension();
- Bitmap bitmap(dim.Width, dim.Height);
- // First pass: Mark all opaque pixels
- // 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++) {
- if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
- bitmap.set(ctrx, ctry);
- }
- // Exit early if all pixels opaque
- if (bitmap.all())
- return;
- Bitmap newmap = bitmap;
- // Cap iterations to keep runtime reasonable, for higher-res textures we can
- // get away with filling less pixels.
- int iter_max = 11 - std::max(dim.Width, dim.Height) / 16;
- iter_max = std::max(iter_max, 2);
- // Then repeatedly look for transparent pixels, filling them in until
- // we're finished.
- for (int iter = 0; iter < iter_max; iter++) {
- for (u32 ctry = 0; ctry < dim.Height; ctry++)
- for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
- // Skip pixels we have already processed
- if (bitmap.get(ctrx, ctry))
- continue;
- video::SColor c = src->getPixel(ctrx, ctry);
- // 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 pixels we haven't processed
- if (!bitmap.get(sx, sy))
- continue;
- // Add RGB values weighted by alpha IF the pixel is opaque, otherwise
- // use full weight since we want to propagate colors.
- video::SColor d = src->getPixel(sx, sy);
- u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
- ss += a;
- sr += a * d.getRed();
- sg += a * d.getGreen();
- sb += a * d.getBlue();
- }
- // 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);
- newmap.set(ctrx, ctry);
- }
- }
- if (newmap.all())
- return;
- // Apply changes to bitmap for next run. This is done so we don't introduce
- // a bias in color propagation in the direction pixels are processed.
- newmap.copy(bitmap);
- }
- }
- /* 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 rectangle 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, sox + sw);
- maxsx = minsx + sw / dim.Width;
- maxsx = rangelim(maxsx, 0, sox + sw);
- if (minsx > maxsx)
- SWAP(double, minsx, maxsx);
- minsy = soy + (dy * sh / dim.Height);
- minsy = rangelim(minsy, 0, soy + sh);
- maxsy = minsy + sh / dim.Height;
- maxsy = rangelim(maxsy, 0, soy + 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);
- }
- }
|