mediaFilters.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2019 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #include "mediaFilters.h"
  24. #include "../base/simd.h"
  25. using namespace dsr;
  26. template <typename T, typename U>
  27. static void assertSameSize(const T& imageL, const U& imageR) {
  28. if (!image_exists(imageL) || !image_exists(imageR)) {
  29. if (image_exists(imageL)) {
  30. // Left side exists, so there's no right side
  31. throwError("Media filter: Non-existing right side input image.\n");
  32. } else if (image_exists(imageR)) {
  33. // Right side exists, so there's no left side
  34. throwError("Media filter: Non-existing left side input image.\n");
  35. } else {
  36. // Neither input exists
  37. throwError("Media filter: Non-existing input images.\n");
  38. }
  39. } else if (image_getWidth(imageL) != image_getWidth(imageR)
  40. || image_getHeight(imageL) != image_getHeight(imageR)) {
  41. throwError("Media filter: Taking input images of different dimensions, ", image_getWidth(imageL), "x", image_getHeight(imageL), " and ", image_getWidth(imageR), "x", image_getHeight(imageR), ".\n");
  42. }
  43. }
  44. template <typename T>
  45. static void assertExisting(const T& image) {
  46. if (!image_exists(image)) {
  47. throwError("Media filter: Non-existing input image.\n");
  48. }
  49. }
  50. template <typename T>
  51. static void removeIfShared(T& targetImage) {
  52. if (image_useCount(targetImage) > 1) {
  53. targetImage = AlignedImageU8();
  54. }
  55. }
  56. template <typename T, typename U>
  57. static void allocateToSameSize(T& targetImage, const U& inputImage) {
  58. if (!image_exists(targetImage) || image_getWidth(targetImage) != image_getWidth(inputImage) || image_getHeight(targetImage) != image_getHeight(inputImage)) {
  59. if (!image_exists(inputImage)) {
  60. throwError("Media filter: Cannot allocate to size of non-existing input image.\n");
  61. }
  62. targetImage = image_create_U8(image_getWidth(inputImage), image_getHeight(inputImage));
  63. }
  64. }
  65. void dsr::media_filter_add(AlignedImageU8& targetImage, AlignedImageU8 imageL, AlignedImageU8 imageR) {
  66. assertSameSize(imageL, imageR);
  67. removeIfShared(targetImage);
  68. allocateToSameSize(targetImage, imageL);
  69. // TODO: Implement U8x16 in simd.h
  70. // readAligned, writeAligned, addSaturated, subtractSaturated...
  71. /*for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  72. const SafePointer<uint8_t> targetRow = imageInternal::getSafeData<uint8_t>(targetImage, y);
  73. const SafePointer<uint8_t> sourceRowL = imageInternal::getSafeData<uint8_t>(imageL, y);
  74. const SafePointer<uint8_t> sourceRowR = imageInternal::getSafeData<uint8_t>(imageR, y);
  75. for (int32_t x = 0; x < image_getWidth(targetImage); x += 4) {
  76. ALIGN16 U8x16 colorL = U8x16::readAligned(sourceRowL, "media_filter_add (sourceRowL)");
  77. ALIGN16 U8x16 colorR = U8x16::readAligned(sourceRowR, "media_filter_add (sourceRowR)");
  78. ALIGN16 U8x16 result = U8x16::addSaturated(colorL, colorR);
  79. result.writeAligned(targetRow, "media_filter_add (targetRow)");
  80. targetRow += 16;
  81. sourceRowL += 16;
  82. sourceRowR += 16;
  83. }
  84. }*/
  85. // Reference implementation
  86. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  87. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  88. image_writePixel(targetImage, x, y, image_readPixel_clamp(imageL, x, y) + image_readPixel_clamp(imageR, x, y));
  89. }
  90. }
  91. }
  92. void dsr::media_filter_add(AlignedImageU8& targetImage, AlignedImageU8 image, FixedPoint scalar) {
  93. assertExisting(image);
  94. removeIfShared(targetImage);
  95. allocateToSameSize(targetImage, image);
  96. // Reference implementation
  97. int whole = fixedPoint_round(scalar);
  98. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  99. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  100. image_writePixel(targetImage, x, y, image_readPixel_clamp(image, x, y) + whole);
  101. }
  102. }
  103. }
  104. void dsr::media_filter_sub(AlignedImageU8& targetImage, AlignedImageU8 imageL, AlignedImageU8 imageR) {
  105. assertSameSize(imageL, imageR);
  106. removeIfShared(targetImage);
  107. allocateToSameSize(targetImage, imageL);
  108. // Reference implementation
  109. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  110. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  111. image_writePixel(targetImage, x, y, image_readPixel_clamp(imageL, x, y) - image_readPixel_clamp(imageR, x, y));
  112. }
  113. }
  114. }
  115. void dsr::media_filter_sub(AlignedImageU8& targetImage, AlignedImageU8 image, FixedPoint scalar) {
  116. assertExisting(image);
  117. removeIfShared(targetImage);
  118. allocateToSameSize(targetImage, image);
  119. // Reference implementation
  120. int whole = fixedPoint_round(scalar);
  121. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  122. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  123. image_writePixel(targetImage, x, y, image_readPixel_clamp(image, x, y) - whole);
  124. }
  125. }
  126. }
  127. void dsr::media_filter_sub(AlignedImageU8& targetImage, FixedPoint scalar, AlignedImageU8 image) {
  128. assertExisting(image);
  129. removeIfShared(targetImage);
  130. allocateToSameSize(targetImage, image);
  131. // Reference implementation
  132. int whole = fixedPoint_round(scalar);
  133. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  134. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  135. image_writePixel(targetImage, x, y, whole - image_readPixel_clamp(image, x, y));
  136. }
  137. }
  138. }
  139. void dsr::media_filter_mul(AlignedImageU8& targetImage, AlignedImageU8 image, FixedPoint scalar) {
  140. assertExisting(image);
  141. removeIfShared(targetImage);
  142. allocateToSameSize(targetImage, image);
  143. // Reference implementation
  144. int64_t mantissa = scalar.getMantissa();
  145. if (mantissa < 0) { mantissa = 0; } // At least zero, because negative clamps to zero
  146. if (mantissa > 16711680) { mantissa = 16711680; } // At most 255 whole integers, became more makes no difference
  147. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  148. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  149. image_writePixel(targetImage, x, y, ((int64_t)image_readPixel_clamp(image, x, y) * mantissa) / 65536);
  150. }
  151. }
  152. }
  153. void dsr::media_filter_mul(AlignedImageU8& targetImage, AlignedImageU8 imageL, AlignedImageU8 imageR, FixedPoint scalar) {
  154. assertSameSize(imageL, imageR);
  155. removeIfShared(targetImage);
  156. allocateToSameSize(targetImage, imageL);
  157. // Reference implementation
  158. int64_t mantissa = scalar.getMantissa();
  159. if (mantissa < 0) { mantissa = 0; } // At least zero, because negative clamps to zero
  160. if (mantissa > 16711680) { mantissa = 16711680; } // At most 255 whole integers, became more makes no difference
  161. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  162. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  163. int32_t result = ((uint64_t)image_readPixel_clamp(imageL, x, y) * (uint64_t)image_readPixel_clamp(imageR, x, y) * mantissa) / 65536;
  164. image_writePixel(targetImage, x, y, result);
  165. }
  166. }
  167. }
  168. void dsr::media_fade_region_linear(ImageU8& targetImage, const IRect& viewport, FixedPoint x1, FixedPoint y1, FixedPoint luma1, FixedPoint x2, FixedPoint y2, FixedPoint luma2) {
  169. assertExisting(targetImage);
  170. if (luma1 < 0) { luma1 = FixedPoint::zero(); }
  171. if (luma1 > 255) { luma1 = FixedPoint::fromWhole(255); }
  172. if (luma2 < 0) { luma2 = FixedPoint::zero(); }
  173. if (luma2 > 255) { luma2 = FixedPoint::fromWhole(255); }
  174. // Subtracting half a pixel in the fade line is equivalent to adding half a pixel on X and Y
  175. int64_t startX = x1.getMantissa() - 32768;
  176. int64_t startY = y1.getMantissa() - 32768;
  177. int64_t endX = x2.getMantissa() - 32768;
  178. int64_t endY = y2.getMantissa() - 32768;
  179. int64_t diffX = endX - startX; // x2 - x1 * 65536
  180. int64_t diffY = endY - startY; // y2 - y1 * 65536
  181. // You don't need to get the linear lengths nor distance.
  182. // By both generating a squared length and using a dot product, no square root is required.
  183. // This is because length(v)² = dot(v, v)
  184. int64_t squareLength = ((diffX * diffX) + (diffY * diffY)) / 65536; // length² * 65536
  185. if (squareLength < 65536) { squareLength = 65536; } // Prevent overflow
  186. int64_t reciprocalSquareLength = 4294967296ll / squareLength; // (1 / length²) * 65536
  187. // Calculate ratios for 3 pixels using dot products
  188. int64_t offsetX = -startX; // First pixel relative to x1
  189. int64_t offsetY = -startY; // First pixel relative to y1
  190. int64_t offsetX_right = 65536 - startX; // Right pixel relative to x1
  191. int64_t offsetY_down = 65536 - startY; // Down pixel relative to y1
  192. int64_t dotProduct = ((offsetX * diffX) + (offsetY * diffY)) / 65536; // dot(offset, diff) * 65536
  193. int64_t dotProduct_right = ((offsetX_right * diffX) + (offsetY * diffY)) / 65536; // dot(offsetRight, diff) * 65536
  194. int64_t dotProduct_down = ((offsetX * diffX) + (offsetY_down * diffY)) / 65536; // dot(offsetDown, diff) * 65536
  195. int64_t startRatio = (dotProduct * reciprocalSquareLength) / 65536; // The color mix ratio at the first pixel in a scale from 0 to 65536
  196. int64_t ratioDx = (dotProduct_right * reciprocalSquareLength) / 65536 - startRatio; // The color mix difference when going right
  197. int64_t ratioDy = (dotProduct_down * reciprocalSquareLength) / 65536 - startRatio; // The color mix difference when going down
  198. // TODO: Optimize the cases where ratioDx == 0 (memset per line) or ratioDy == 0 (memcpy from first line)
  199. for (int32_t y = viewport.top(); y < viewport.bottom(); y++) {
  200. int64_t ratio = startRatio;
  201. for (int32_t x = viewport.left(); x < viewport.right(); x++) {
  202. int64_t saturatedRatio = ratio;
  203. // TODO: Reuse this code section
  204. if (saturatedRatio < 0) { saturatedRatio = 0; }
  205. if (saturatedRatio > 65536) { saturatedRatio = 65536; }
  206. int64_t mixedColor = ((luma1.getMantissa() * (65536 - ratio)) + (luma2.getMantissa() * ratio) + 2147483648ll) / 4294967296ll;
  207. if (mixedColor < 0) { mixedColor = 0; }
  208. if (mixedColor > 255) { mixedColor = 255; }
  209. // TODO: Write the already saturated result using safe pointers to the target image
  210. image_writePixel(targetImage, x, y, mixedColor);
  211. ratio += ratioDx;
  212. }
  213. startRatio += ratioDy;
  214. }
  215. }
  216. void dsr::media_fade_linear(ImageU8& targetImage, FixedPoint x1, FixedPoint y1, FixedPoint luma1, FixedPoint x2, FixedPoint y2, FixedPoint luma2) {
  217. media_fade_region_linear(targetImage, image_getBound(targetImage), x1, y1, luma1, x2, y2, luma2);
  218. }
  219. void dsr::media_fade_region_radial(ImageU8& targetImage, const IRect& viewport, FixedPoint centerX, FixedPoint centerY, FixedPoint innerRadius, FixedPoint innerLuma, FixedPoint outerRadius, FixedPoint outerLuma) {
  220. assertExisting(targetImage);
  221. if (innerLuma < 0) { innerLuma = FixedPoint::zero(); }
  222. if (innerLuma > 255) { innerLuma = FixedPoint::fromWhole(255); }
  223. if (outerLuma < 0) { outerLuma = FixedPoint::zero(); }
  224. if (outerLuma > 255) { outerLuma = FixedPoint::fromWhole(255); }
  225. // Subtracting half a pixel in the fade line is equivalent to adding half a pixel on X and Y
  226. FixedPoint originX = centerX + viewport.left() - FixedPoint::half();
  227. FixedPoint originY = centerY + viewport.top() - FixedPoint::half();
  228. // Let outerRadius be slightly outside of innerRadius to prevent division by zero
  229. if (outerRadius <= innerRadius) {
  230. outerRadius = innerRadius + FixedPoint::epsilon();
  231. }
  232. FixedPoint reciprocalFadeLength = FixedPoint::one() / (outerRadius - innerRadius);
  233. for (int32_t y = viewport.top(); y < viewport.bottom(); y++) {
  234. for (int32_t x = viewport.left(); x < viewport.right(); x++) {
  235. FixedPoint diffX = x - originX;
  236. FixedPoint diffY = y - originY;
  237. FixedPoint length = fixedPoint_squareRoot((diffX * diffX) + (diffY * diffY));
  238. FixedPoint ratio = (length - innerRadius) * reciprocalFadeLength;
  239. int64_t saturatedRatio = ratio.getMantissa();
  240. // TODO: Reuse this code section
  241. if (saturatedRatio < 0) { saturatedRatio = 0; }
  242. if (saturatedRatio > 65536) { saturatedRatio = 65536; }
  243. int64_t mixedColor = ((innerLuma.getMantissa() * (65536 - ratio.getMantissa())) + (outerLuma.getMantissa() * ratio.getMantissa()) + 2147483648ll) / 4294967296ll;
  244. if (mixedColor < 0) { mixedColor = 0; }
  245. if (mixedColor > 255) { mixedColor = 255; }
  246. // TODO: Write the already saturated result using safe pointers to the target image
  247. image_writePixel(targetImage, x, y, mixedColor);
  248. }
  249. }
  250. }
  251. void dsr::media_fade_radial(ImageU8& targetImage, FixedPoint centerX, FixedPoint centerY, FixedPoint innerRadius, FixedPoint innerLuma, FixedPoint outerRadius, FixedPoint outerLuma) {
  252. media_fade_region_radial(targetImage, image_getBound(targetImage), centerX, centerY, innerRadius, innerLuma, outerRadius, outerLuma);
  253. }