imagefilters.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. /* Fill in RGB values for transparent pixels, to correct for odd colors
  57. * appearing at borders when blending. This is because many PNG optimizers
  58. * like to discard RGB values of transparent pixels, but when blending then
  59. * with non-transparent neighbors, their RGB values will show up nonetheless.
  60. *
  61. * This function modifies the original image in-place.
  62. *
  63. * Parameter "threshold" is the alpha level below which pixels are considered
  64. * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
  65. * 0 when alpha blending is used.
  66. */
  67. void imageCleanTransparent(video::IImage *src, u32 threshold)
  68. {
  69. core::dimension2d<u32> dim = src->getDimension();
  70. Bitmap bitmap(dim.Width, dim.Height);
  71. // First pass: Mark all opaque pixels
  72. // Note: loop y around x for better cache locality.
  73. for (u32 ctry = 0; ctry < dim.Height; ctry++)
  74. for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
  75. if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
  76. bitmap.set(ctrx, ctry);
  77. }
  78. // Exit early if all pixels opaque
  79. if (bitmap.all())
  80. return;
  81. Bitmap newmap = bitmap;
  82. // Cap iterations to keep runtime reasonable, for higher-res textures we can
  83. // get away with filling less pixels.
  84. int iter_max = 11 - std::max(dim.Width, dim.Height) / 16;
  85. iter_max = std::max(iter_max, 2);
  86. // Then repeatedly look for transparent pixels, filling them in until
  87. // we're finished.
  88. for (int iter = 0; iter < iter_max; iter++) {
  89. for (u32 ctry = 0; ctry < dim.Height; ctry++)
  90. for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
  91. // Skip pixels we have already processed
  92. if (bitmap.get(ctrx, ctry))
  93. continue;
  94. video::SColor c = src->getPixel(ctrx, ctry);
  95. // Sample size and total weighted r, g, b values
  96. u32 ss = 0, sr = 0, sg = 0, sb = 0;
  97. // Walk each neighbor pixel (clipped to image bounds)
  98. for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
  99. sy <= (ctry + 1) && sy < dim.Height; sy++)
  100. for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
  101. sx <= (ctrx + 1) && sx < dim.Width; sx++) {
  102. // Ignore pixels we haven't processed
  103. if (!bitmap.get(sx, sy))
  104. continue;
  105. // Add RGB values weighted by alpha IF the pixel is opaque, otherwise
  106. // use full weight since we want to propagate colors.
  107. video::SColor d = src->getPixel(sx, sy);
  108. u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
  109. ss += a;
  110. sr += a * d.getRed();
  111. sg += a * d.getGreen();
  112. sb += a * d.getBlue();
  113. }
  114. // Set pixel to average weighted by alpha
  115. if (ss > 0) {
  116. c.setRed(sr / ss);
  117. c.setGreen(sg / ss);
  118. c.setBlue(sb / ss);
  119. src->setPixel(ctrx, ctry, c);
  120. newmap.set(ctrx, ctry);
  121. }
  122. }
  123. if (newmap.all())
  124. return;
  125. // Apply changes to bitmap for next run. This is done so we don't introduce
  126. // a bias in color propagation in the direction pixels are processed.
  127. newmap.copy(bitmap);
  128. }
  129. }
  130. /* Scale a region of an image into another image, using nearest-neighbor with
  131. * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
  132. * to prevent non-integer scaling ratio artifacts. Note that this may cause
  133. * some blending at the edges where pixels don't line up perfectly, but this
  134. * filter is designed to produce the most accurate results for both upscaling
  135. * and downscaling.
  136. */
  137. void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
  138. {
  139. double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
  140. u32 dy, dx;
  141. video::SColor pxl;
  142. // Cache rectangle boundaries.
  143. double sox = srcrect.UpperLeftCorner.X * 1.0;
  144. double soy = srcrect.UpperLeftCorner.Y * 1.0;
  145. double sw = srcrect.getWidth() * 1.0;
  146. double sh = srcrect.getHeight() * 1.0;
  147. // Walk each destination image pixel.
  148. // Note: loop y around x for better cache locality.
  149. core::dimension2d<u32> dim = dest->getDimension();
  150. for (dy = 0; dy < dim.Height; dy++)
  151. for (dx = 0; dx < dim.Width; dx++) {
  152. // Calculate floating-point source rectangle bounds.
  153. // Do some basic clipping, and for mirrored/flipped rects,
  154. // make sure min/max are in the right order.
  155. minsx = sox + (dx * sw / dim.Width);
  156. minsx = rangelim(minsx, 0, sox + sw);
  157. maxsx = minsx + sw / dim.Width;
  158. maxsx = rangelim(maxsx, 0, sox + sw);
  159. if (minsx > maxsx)
  160. SWAP(double, minsx, maxsx);
  161. minsy = soy + (dy * sh / dim.Height);
  162. minsy = rangelim(minsy, 0, soy + sh);
  163. maxsy = minsy + sh / dim.Height;
  164. maxsy = rangelim(maxsy, 0, soy + sh);
  165. if (minsy > maxsy)
  166. SWAP(double, minsy, maxsy);
  167. // Total area, and integral of r, g, b values over that area,
  168. // initialized to zero, to be summed up in next loops.
  169. area = 0;
  170. ra = 0;
  171. ga = 0;
  172. ba = 0;
  173. aa = 0;
  174. // Loop over the integral pixel positions described by those bounds.
  175. for (sy = floor(minsy); sy < maxsy; sy++)
  176. for (sx = floor(minsx); sx < maxsx; sx++) {
  177. // Calculate width, height, then area of dest pixel
  178. // that's covered by this source pixel.
  179. pw = 1;
  180. if (minsx > sx)
  181. pw += sx - minsx;
  182. if (maxsx < (sx + 1))
  183. pw += maxsx - sx - 1;
  184. ph = 1;
  185. if (minsy > sy)
  186. ph += sy - minsy;
  187. if (maxsy < (sy + 1))
  188. ph += maxsy - sy - 1;
  189. pa = pw * ph;
  190. // Get source pixel and add it to totals, weighted
  191. // by covered area and alpha.
  192. pxl = src->getPixel((u32)sx, (u32)sy);
  193. area += pa;
  194. ra += pa * pxl.getRed();
  195. ga += pa * pxl.getGreen();
  196. ba += pa * pxl.getBlue();
  197. aa += pa * pxl.getAlpha();
  198. }
  199. // Set the destination image pixel to the average color.
  200. if (area > 0) {
  201. pxl.setRed(ra / area + 0.5);
  202. pxl.setGreen(ga / area + 0.5);
  203. pxl.setBlue(ba / area + 0.5);
  204. pxl.setAlpha(aa / area + 0.5);
  205. } else {
  206. pxl.setRed(0);
  207. pxl.setGreen(0);
  208. pxl.setBlue(0);
  209. pxl.setAlpha(0);
  210. }
  211. dest->setPixel(dx, dy, pxl);
  212. }
  213. }