imagefilters.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU Lesser General Public License as published by
  5. the Free Software Foundation; either version 2.1 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public License along
  12. with this program; if not, write to the Free Software Foundation, Inc.,
  13. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  14. */
  15. #include "imagefilters.h"
  16. #include "util/numeric.h"
  17. #include <cmath>
  18. #include <cassert>
  19. #include <vector>
  20. #include <algorithm>
  21. // Simple 2D bitmap class with just the functionality needed here
  22. class Bitmap {
  23. u32 linesize, lines;
  24. std::vector<u8> data;
  25. static inline u32 bytepos(u32 index) { return index >> 3; }
  26. static inline u8 bitpos(u32 index) { return index & 7; }
  27. public:
  28. Bitmap(u32 width, u32 height) : linesize(width), lines(height),
  29. data(bytepos(width * height) + 1) {}
  30. inline bool get(u32 x, u32 y) const {
  31. u32 index = y * linesize + x;
  32. return data[bytepos(index)] & (1 << bitpos(index));
  33. }
  34. inline void set(u32 x, u32 y) {
  35. u32 index = y * linesize + x;
  36. data[bytepos(index)] |= 1 << bitpos(index);
  37. }
  38. inline bool all() const {
  39. for (u32 i = 0; i < data.size() - 1; i++) {
  40. if (data[i] != 0xff)
  41. return false;
  42. }
  43. // last byte not entirely filled
  44. for (u8 i = 0; i < bitpos(linesize * lines); i++) {
  45. bool value_of_bit = data.back() & (1 << i);
  46. if (!value_of_bit)
  47. return false;
  48. }
  49. return true;
  50. }
  51. inline void copy(Bitmap &to) const {
  52. assert(to.linesize == linesize && to.lines == lines);
  53. to.data = data;
  54. }
  55. };
  56. template <bool IS_A8R8G8B8>
  57. static void imageCleanTransparentWithInlining(video::IImage *src, u32 threshold)
  58. {
  59. void *const src_data = src->getData();
  60. const core::dimension2d<u32> dim = src->getDimension();
  61. auto get_pixel = [=](u32 x, u32 y) -> video::SColor {
  62. if constexpr (IS_A8R8G8B8) {
  63. return reinterpret_cast<u32 *>(src_data)[y*dim.Width + x];
  64. } else {
  65. return src->getPixel(x, y);
  66. }
  67. };
  68. auto set_pixel = [=](u32 x, u32 y, video::SColor color) {
  69. if constexpr (IS_A8R8G8B8) {
  70. u32 *dest = &reinterpret_cast<u32 *>(src_data)[y*dim.Width + x];
  71. *dest = color.color;
  72. } else {
  73. src->setPixel(x, y, color);
  74. }
  75. };
  76. Bitmap bitmap(dim.Width, dim.Height);
  77. // First pass: Mark all opaque pixels
  78. // Note: loop y around x for better cache locality.
  79. for (u32 ctry = 0; ctry < dim.Height; ctry++)
  80. for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
  81. if (get_pixel(ctrx, ctry).getAlpha() > threshold)
  82. bitmap.set(ctrx, ctry);
  83. }
  84. // Exit early if all pixels opaque
  85. if (bitmap.all())
  86. return;
  87. Bitmap newmap = bitmap;
  88. // Cap iterations to keep runtime reasonable, for higher-res textures we can
  89. // get away with filling less pixels.
  90. int iter_max = 11 - std::max(dim.Width, dim.Height) / 16;
  91. iter_max = std::max(iter_max, 2);
  92. // Then repeatedly look for transparent pixels, filling them in until
  93. // we're finished.
  94. for (int iter = 0; iter < iter_max; iter++) {
  95. for (u32 ctry = 0; ctry < dim.Height; ctry++)
  96. for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
  97. // Skip pixels we have already processed
  98. if (bitmap.get(ctrx, ctry))
  99. continue;
  100. // Sample size and total weighted r, g, b values
  101. u32 ss = 0, sr = 0, sg = 0, sb = 0;
  102. // Walk each neighbor pixel (clipped to image bounds)
  103. for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
  104. sy <= (ctry + 1) && sy < dim.Height; sy++)
  105. for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
  106. sx <= (ctrx + 1) && sx < dim.Width; sx++) {
  107. // Ignore pixels we haven't processed
  108. if (!bitmap.get(sx, sy))
  109. continue;
  110. // Add RGB values weighted by alpha IF the pixel is opaque, otherwise
  111. // use full weight since we want to propagate colors.
  112. video::SColor d = get_pixel(sx, sy);
  113. u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
  114. ss += a;
  115. sr += a * d.getRed();
  116. sg += a * d.getGreen();
  117. sb += a * d.getBlue();
  118. }
  119. // Set pixel to average weighted by alpha
  120. if (ss > 0) {
  121. video::SColor c = get_pixel(ctrx, ctry);
  122. c.setRed(sr / ss);
  123. c.setGreen(sg / ss);
  124. c.setBlue(sb / ss);
  125. set_pixel(ctrx, ctry, c);
  126. newmap.set(ctrx, ctry);
  127. }
  128. }
  129. if (newmap.all())
  130. return;
  131. // Apply changes to bitmap for next run. This is done so we don't introduce
  132. // a bias in color propagation in the direction pixels are processed.
  133. newmap.copy(bitmap);
  134. }
  135. }
  136. /* Fill in RGB values for transparent pixels, to correct for odd colors
  137. * appearing at borders when blending. This is because many PNG optimizers
  138. * like to discard RGB values of transparent pixels, but when blending then
  139. * with non-transparent neighbors, their RGB values will show up nonetheless.
  140. *
  141. * This function modifies the original image in-place.
  142. *
  143. * Parameter "threshold" is the alpha level below which pixels are considered
  144. * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
  145. * 0 when alpha blending is used.
  146. */
  147. void imageCleanTransparent(video::IImage *src, u32 threshold)
  148. {
  149. if (src->getColorFormat() == video::ECF_A8R8G8B8)
  150. imageCleanTransparentWithInlining<true>(src, threshold);
  151. else
  152. imageCleanTransparentWithInlining<false>(src, threshold);
  153. }
  154. /* Scale a region of an image into another image, using nearest-neighbor with
  155. * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
  156. * to prevent non-integer scaling ratio artifacts. Note that this may cause
  157. * some blending at the edges where pixels don't line up perfectly, but this
  158. * filter is designed to produce the most accurate results for both upscaling
  159. * and downscaling.
  160. */
  161. void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
  162. {
  163. double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
  164. u32 dy, dx;
  165. video::SColor pxl;
  166. // Cache rectangle boundaries.
  167. double sox = srcrect.UpperLeftCorner.X * 1.0;
  168. double soy = srcrect.UpperLeftCorner.Y * 1.0;
  169. double sw = srcrect.getWidth() * 1.0;
  170. double sh = srcrect.getHeight() * 1.0;
  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. // Calculate floating-point source rectangle bounds.
  177. // Do some basic clipping, and for mirrored/flipped rects,
  178. // make sure min/max are in the right order.
  179. minsx = sox + (dx * sw / dim.Width);
  180. minsx = rangelim(minsx, 0, sox + sw);
  181. maxsx = minsx + sw / dim.Width;
  182. maxsx = rangelim(maxsx, 0, sox + sw);
  183. if (minsx > maxsx)
  184. SWAP(double, minsx, maxsx);
  185. minsy = soy + (dy * sh / dim.Height);
  186. minsy = rangelim(minsy, 0, soy + sh);
  187. maxsy = minsy + sh / dim.Height;
  188. maxsy = rangelim(maxsy, 0, soy + sh);
  189. if (minsy > maxsy)
  190. SWAP(double, minsy, maxsy);
  191. // Total area, and integral of r, g, b values over that area,
  192. // initialized to zero, to be summed up in next loops.
  193. area = 0;
  194. ra = 0;
  195. ga = 0;
  196. ba = 0;
  197. aa = 0;
  198. // Loop over the integral pixel positions described by those bounds.
  199. for (sy = floor(minsy); sy < maxsy; sy++)
  200. for (sx = floor(minsx); sx < maxsx; sx++) {
  201. // Calculate width, height, then area of dest pixel
  202. // that's covered by this source pixel.
  203. pw = 1;
  204. if (minsx > sx)
  205. pw += sx - minsx;
  206. if (maxsx < (sx + 1))
  207. pw += maxsx - sx - 1;
  208. ph = 1;
  209. if (minsy > sy)
  210. ph += sy - minsy;
  211. if (maxsy < (sy + 1))
  212. ph += maxsy - sy - 1;
  213. pa = pw * ph;
  214. // Get source pixel and add it to totals, weighted
  215. // by covered area and alpha.
  216. pxl = src->getPixel((u32)sx, (u32)sy);
  217. area += pa;
  218. ra += pa * pxl.getRed();
  219. ga += pa * pxl.getGreen();
  220. ba += pa * pxl.getBlue();
  221. aa += pa * pxl.getAlpha();
  222. }
  223. // Set the destination image pixel to the average color.
  224. if (area > 0) {
  225. pxl.setRed(ra / area + 0.5);
  226. pxl.setGreen(ga / area + 0.5);
  227. pxl.setBlue(ba / area + 0.5);
  228. pxl.setAlpha(aa / area + 0.5);
  229. } else {
  230. pxl.setRed(0);
  231. pxl.setGreen(0);
  232. pxl.setBlue(0);
  233. pxl.setAlpha(0);
  234. }
  235. dest->setPixel(dx, dy, pxl);
  236. }
  237. }