mediaFilters.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2019 to 2022 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. #include "../api/imageAPI.h"
  26. #include "../api/drawAPI.h"
  27. using namespace dsr;
  28. template <typename T, typename U>
  29. static void assertSameSize(const T& imageA, const U& imageB) {
  30. if (!image_exists(imageA) || !image_exists(imageB)) {
  31. if (image_exists(imageA)) {
  32. // Left side exists, so there's no right side
  33. throwError("Media filter: Non-existing right side input image.\n");
  34. } else if (image_exists(imageB)) {
  35. // Right side exists, so there's no left side
  36. throwError("Media filter: Non-existing left side input image.\n");
  37. } else {
  38. // Neither input exists
  39. throwError("Media filter: Non-existing input images.\n");
  40. }
  41. } else if (image_getWidth(imageA) != image_getWidth(imageB)
  42. || image_getHeight(imageA) != image_getHeight(imageB)) {
  43. throwError("Media filter: Taking input images of different dimensions, ", image_getWidth(imageA), "x", image_getHeight(imageA), " and ", image_getWidth(imageB), "x", image_getHeight(imageB), ".\n");
  44. }
  45. }
  46. template <typename T>
  47. static void assertExisting(const T& image) {
  48. if (!image_exists(image)) {
  49. throwError("Media filter: Non-existing input image.\n");
  50. }
  51. }
  52. template <typename T>
  53. static void removeIfShared(T& targetImage) {
  54. if (image_useCount(targetImage) > 1) {
  55. targetImage = AlignedImageU8();
  56. }
  57. }
  58. template <typename T, typename U>
  59. static void allocateToSameSize(T& targetImage, const U& inputImage) {
  60. if (!image_exists(targetImage) || image_getWidth(targetImage) != image_getWidth(inputImage) || image_getHeight(targetImage) != image_getHeight(inputImage)) {
  61. if (!image_exists(inputImage)) {
  62. throwError("Media filter: Cannot allocate to size of non-existing input image.\n");
  63. }
  64. targetImage = image_create_U8(image_getWidth(inputImage), image_getHeight(inputImage));
  65. }
  66. }
  67. void dsr::media_filter_add(AlignedImageU8& targetImage, AlignedImageU8 imageA, AlignedImageU8 imageB) {
  68. assertSameSize(imageA, imageB);
  69. removeIfShared(targetImage);
  70. allocateToSameSize(targetImage, imageA);
  71. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  72. SafePointer<uint8_t> sourceRowA = image_getSafePointer(imageA);
  73. SafePointer<uint8_t> sourceRowB = image_getSafePointer(imageB);
  74. int32_t targetStride = image_getStride(targetImage);
  75. int32_t sourceStrideA = image_getStride(imageA);
  76. int32_t sourceStrideB = image_getStride(imageB);
  77. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  78. SafePointer<uint8_t> targetPixel = targetRow;
  79. SafePointer<uint8_t> sourcePixelA = sourceRowA;
  80. SafePointer<uint8_t> sourcePixelB = sourceRowB;
  81. for (int32_t x = 0; x < image_getWidth(targetImage); x += 16) {
  82. U8x16 colorA = U8x16::readAligned(sourcePixelA, "media_filter_add (sourcePixelA)");
  83. U8x16 colorB = U8x16::readAligned(sourcePixelB, "media_filter_add (sourcePixelB)");
  84. U8x16 result = saturatedAddition(colorA, colorB);
  85. result.writeAligned(targetPixel, "media_filter_add (targetPixel)");
  86. targetPixel += 16;
  87. sourcePixelA += 16;
  88. sourcePixelB += 16;
  89. }
  90. targetRow.increaseBytes(targetStride);
  91. sourceRowA.increaseBytes(sourceStrideA);
  92. sourceRowB.increaseBytes(sourceStrideB);
  93. }
  94. }
  95. void dsr::media_filter_add(AlignedImageU8& targetImage, AlignedImageU8 image, int32_t luma) {
  96. assertExisting(image);
  97. removeIfShared(targetImage);
  98. allocateToSameSize(targetImage, image);
  99. if (luma < 0) luma = 0;
  100. if (luma > 255) luma = 255;
  101. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  102. SafePointer<uint8_t> sourceRowA = image_getSafePointer(image);
  103. int32_t targetStride = image_getStride(targetImage);
  104. int32_t sourceStride = image_getStride(image);
  105. U8x16 repeatedLuma = U8x16(luma);
  106. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  107. SafePointer<uint8_t> targetPixel = targetRow;
  108. SafePointer<uint8_t> sourcePixel = sourceRowA;
  109. for (int32_t x = 0; x < image_getWidth(targetImage); x += 16) {
  110. U8x16 colorA = U8x16::readAligned(sourcePixel, "media_filter_add (sourcePixel)");
  111. U8x16 result = saturatedAddition(colorA, repeatedLuma);
  112. result.writeAligned(targetPixel, "media_filter_add (targetPixel)");
  113. targetPixel += 16;
  114. sourcePixel += 16;
  115. }
  116. targetRow.increaseBytes(targetStride);
  117. sourceRowA.increaseBytes(sourceStride);
  118. }
  119. }
  120. void dsr::media_filter_add(AlignedImageU8& targetImage, AlignedImageU8 image, FixedPoint luma) {
  121. media_filter_add(targetImage, image, fixedPoint_round(luma));
  122. }
  123. void dsr::media_filter_sub(AlignedImageU8& targetImage, AlignedImageU8 imageA, AlignedImageU8 imageB) {
  124. assertSameSize(imageA, imageB);
  125. removeIfShared(targetImage);
  126. allocateToSameSize(targetImage, imageA);
  127. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  128. SafePointer<uint8_t> sourceRowA = image_getSafePointer(imageA);
  129. SafePointer<uint8_t> sourceRowB = image_getSafePointer(imageB);
  130. int32_t targetStride = image_getStride(targetImage);
  131. int32_t sourceStrideA = image_getStride(imageA);
  132. int32_t sourceStrideB = image_getStride(imageB);
  133. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  134. SafePointer<uint8_t> targetPixel = targetRow;
  135. SafePointer<uint8_t> sourcePixelA = sourceRowA;
  136. SafePointer<uint8_t> sourcePixelB = sourceRowB;
  137. for (int32_t x = 0; x < image_getWidth(targetImage); x += 16) {
  138. U8x16 colorA = U8x16::readAligned(sourcePixelA, "media_filter_add (sourcePixelA)");
  139. U8x16 colorB = U8x16::readAligned(sourcePixelB, "media_filter_add (sourcePixelB)");
  140. U8x16 result = saturatedSubtraction(colorA, colorB);
  141. result.writeAligned(targetPixel, "media_filter_add (targetPixel)");
  142. targetPixel += 16;
  143. sourcePixelA += 16;
  144. sourcePixelB += 16;
  145. }
  146. targetRow.increaseBytes(targetStride);
  147. sourceRowA.increaseBytes(sourceStrideA);
  148. sourceRowB.increaseBytes(sourceStrideB);
  149. }
  150. }
  151. void dsr::media_filter_sub(AlignedImageU8& targetImage, AlignedImageU8 image, int32_t luma) {
  152. assertExisting(image);
  153. removeIfShared(targetImage);
  154. allocateToSameSize(targetImage, image);
  155. if (luma < 0) luma = 0;
  156. if (luma > 255) luma = 255;
  157. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  158. SafePointer<uint8_t> sourceRowA = image_getSafePointer(image);
  159. int32_t targetStride = image_getStride(targetImage);
  160. int32_t sourceStride = image_getStride(image);
  161. U8x16 repeatedLuma = U8x16(luma);
  162. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  163. SafePointer<uint8_t> targetPixel = targetRow;
  164. SafePointer<uint8_t> sourcePixel = sourceRowA;
  165. for (int32_t x = 0; x < image_getWidth(targetImage); x += 16) {
  166. U8x16 colorA = U8x16::readAligned(sourcePixel, "media_filter_add (sourcePixel)");
  167. U8x16 result = saturatedSubtraction(colorA, repeatedLuma);
  168. result.writeAligned(targetPixel, "media_filter_add (targetPixel)");
  169. targetPixel += 16;
  170. sourcePixel += 16;
  171. }
  172. targetRow.increaseBytes(targetStride);
  173. sourceRowA.increaseBytes(sourceStride);
  174. }
  175. }
  176. void dsr::media_filter_sub(AlignedImageU8& targetImage, int32_t luma, AlignedImageU8 image) {
  177. assertExisting(image);
  178. removeIfShared(targetImage);
  179. allocateToSameSize(targetImage, image);
  180. if (luma < 0) luma = 0;
  181. if (luma > 255) luma = 255;
  182. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  183. SafePointer<uint8_t> sourceRowA = image_getSafePointer(image);
  184. int32_t targetStride = image_getStride(targetImage);
  185. int32_t sourceStride = image_getStride(image);
  186. U8x16 repeatedLuma = U8x16(luma);
  187. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  188. SafePointer<uint8_t> targetPixel = targetRow;
  189. SafePointer<uint8_t> sourcePixel = sourceRowA;
  190. for (int32_t x = 0; x < image_getWidth(targetImage); x += 16) {
  191. U8x16 colorA = U8x16::readAligned(sourcePixel, "media_filter_add (sourcePixel)");
  192. U8x16 result = saturatedSubtraction(repeatedLuma, colorA);
  193. result.writeAligned(targetPixel, "media_filter_add (targetPixel)");
  194. targetPixel += 16;
  195. sourcePixel += 16;
  196. }
  197. targetRow.increaseBytes(targetStride);
  198. sourceRowA.increaseBytes(sourceStride);
  199. }
  200. }
  201. void dsr::media_filter_sub(AlignedImageU8& targetImage, AlignedImageU8 image, FixedPoint luma) {
  202. media_filter_sub(targetImage, image, fixedPoint_round(luma));
  203. }
  204. void dsr::media_filter_sub(AlignedImageU8& targetImage, FixedPoint luma, AlignedImageU8 image) {
  205. media_filter_sub(targetImage, fixedPoint_round(luma), image);
  206. }
  207. void dsr::media_filter_mul(AlignedImageU8& targetImage, AlignedImageU8 image, FixedPoint luma) {
  208. assertExisting(image);
  209. removeIfShared(targetImage);
  210. allocateToSameSize(targetImage, image);
  211. // Reference implementation
  212. int64_t mantissa = luma.getMantissa();
  213. if (mantissa < 0) { mantissa = 0; } // At least zero, because negative clamps to zero
  214. if (mantissa > 16711680) { mantissa = 16711680; } // At most 255 whole integers, became more makes no difference
  215. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  216. SafePointer<uint8_t> sourceRow = image_getSafePointer(image);
  217. int32_t targetStride = image_getStride(targetImage);
  218. int32_t sourceStride = image_getStride(image);
  219. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  220. SafePointer<uint8_t> targetPixel = targetRow;
  221. SafePointer<uint8_t> sourcePixel = sourceRow;
  222. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  223. int64_t result = ((int64_t)(*sourcePixel) * mantissa) / 65536;
  224. if (result < 0) { result = 0; }
  225. if (result > 255) { result = 255; }
  226. *targetPixel = result;
  227. targetPixel += 1;
  228. sourcePixel += 1;
  229. }
  230. targetRow.increaseBytes(targetStride);
  231. sourceRow.increaseBytes(sourceStride);
  232. }
  233. }
  234. void dsr::media_filter_mul(AlignedImageU8& targetImage, AlignedImageU8 imageA, AlignedImageU8 imageB, FixedPoint luma) {
  235. assertSameSize(imageA, imageB);
  236. removeIfShared(targetImage);
  237. allocateToSameSize(targetImage, imageA);
  238. // Reference implementation
  239. int64_t mantissa = luma.getMantissa();
  240. if (mantissa < 0) { mantissa = 0; } // At least zero, because negative clamps to zero
  241. if (mantissa > 16711680) { mantissa = 16711680; } // At most 255 whole integers, became more makes no difference
  242. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage);
  243. SafePointer<uint8_t> sourceRowA = image_getSafePointer(imageA);
  244. SafePointer<uint8_t> sourceRowB = image_getSafePointer(imageB);
  245. int32_t targetStride = image_getStride(targetImage);
  246. int32_t sourceStrideA = image_getStride(imageA);
  247. int32_t sourceStrideB = image_getStride(imageB);
  248. for (int32_t y = 0; y < image_getHeight(targetImage); y++) {
  249. SafePointer<uint8_t> targetPixel = targetRow;
  250. SafePointer<uint8_t> sourcePixelA = sourceRowA;
  251. SafePointer<uint8_t> sourcePixelB = sourceRowB;
  252. for (int32_t x = 0; x < image_getWidth(targetImage); x++) {
  253. int64_t result = (((uint64_t)*sourcePixelA) * ((uint64_t)*sourcePixelB) * mantissa) / 65536;
  254. if (result < 0) { result = 0; }
  255. if (result > 255) { result = 255; }
  256. *targetPixel = result;
  257. targetPixel += 1;
  258. sourcePixelA += 1;
  259. sourcePixelB += 1;
  260. }
  261. targetRow.increaseBytes(targetStride);
  262. sourceRowA.increaseBytes(sourceStrideA);
  263. sourceRowB.increaseBytes(sourceStrideB);
  264. }
  265. }
  266. void dsr::media_fade_region_linear(ImageU8& targetImage, const IRect& viewport, FixedPoint x1, FixedPoint y1, FixedPoint luma1, FixedPoint x2, FixedPoint y2, FixedPoint luma2) {
  267. assertExisting(targetImage);
  268. IRect safeBound = IRect::cut(viewport, image_getBound(targetImage));
  269. // Saturate luma in advance
  270. if (luma1 < 0) { luma1 = FixedPoint::zero(); }
  271. if (luma1 > 255) { luma1 = FixedPoint::fromWhole(255); }
  272. if (luma2 < 0) { luma2 = FixedPoint::zero(); }
  273. if (luma2 > 255) { luma2 = FixedPoint::fromWhole(255); }
  274. // Subtracting half a pixel in the fade line is equivalent to adding half a pixel on X and Y during sampling
  275. int64_t startX = x1.getMantissa() - 32768;
  276. int64_t startY = y1.getMantissa() - 32768;
  277. int64_t endX = x2.getMantissa() - 32768;
  278. int64_t endY = y2.getMantissa() - 32768;
  279. int64_t diffX = endX - startX; // x2 - x1 * 65536
  280. int64_t diffY = endY - startY; // y2 - y1 * 65536
  281. // You don't need to get the linear lengths nor distance.
  282. // By both generating a squared length and using a dot product, no square root is required.
  283. // This is because length(v)² = dot(v, v)
  284. int64_t squareLength = ((diffX * diffX) + (diffY * diffY)) / 65536; // length² * 65536
  285. // Limit to at least one pixel's length, both to get anti-aliasing and prevent overflow.
  286. if (squareLength < 65536) { squareLength = 65536; }
  287. // Calculate ratios for 3 pixels using dot products
  288. int64_t offsetX = -startX; // First pixel relative to x1
  289. int64_t offsetY = -startY; // First pixel relative to y1
  290. int64_t offsetX_right = 65536 - startX; // Right pixel relative to x1
  291. int64_t offsetY_down = 65536 - startY; // Down pixel relative to y1
  292. int64_t dotProduct = ((offsetX * diffX) + (offsetY * diffY)) / 65536; // dot(offset, diff) * 65536
  293. int64_t dotProduct_right = ((offsetX_right * diffX) + (offsetY * diffY)) / 65536; // dot(offsetRight, diff) * 65536
  294. int64_t dotProduct_down = ((offsetX * diffX) + (offsetY_down * diffY)) / 65536; // dot(offsetDown, diff) * 65536
  295. int64_t startRatio = (dotProduct * 65536 / squareLength); // The color mix ratio at the first pixel in a scale from 0 to 65536
  296. int64_t ratioDx = (dotProduct_right * 65536 / squareLength) - startRatio; // The color mix difference when going right
  297. int64_t ratioDy = (dotProduct_down * 65536 / squareLength) - startRatio; // The color mix difference when going down
  298. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage, safeBound.top()) + safeBound.left();
  299. int32_t targetStride = image_getStride(targetImage);
  300. if (ratioDx == 0) {
  301. if (ratioDy == 0) {
  302. // No direction at all. Fill the whole rectangle with luma1.
  303. draw_rectangle(targetImage, safeBound, fixedPoint_round(luma1));
  304. } else {
  305. // Up or down using memset per row.
  306. int32_t widthInBytes = safeBound.width();
  307. for (int32_t y = safeBound.top(); y < safeBound.bottom(); y++) {
  308. int64_t saturatedRatio = startRatio;
  309. if (saturatedRatio < 0) { saturatedRatio = 0; }
  310. if (saturatedRatio > 65536) { saturatedRatio = 65536; }
  311. int64_t mixedColor = ((luma1.getMantissa() * (65536 - saturatedRatio)) + (luma2.getMantissa() * saturatedRatio) + 2147483648ll) / 4294967296ll;
  312. if (mixedColor < 0) { mixedColor = 0; }
  313. if (mixedColor > 255) { mixedColor = 255; }
  314. safeMemorySet<uint8_t>(targetRow, mixedColor, widthInBytes);
  315. targetRow.increaseBytes(targetStride);
  316. startRatio += ratioDy;
  317. }
  318. }
  319. } else {
  320. if (ratioDy == 0) {
  321. // Left or right using memcpy per row.
  322. SafePointer<uint8_t> sourceRow = targetRow;
  323. SafePointer<uint8_t> targetPixel = targetRow;
  324. int64_t ratio = startRatio;
  325. int32_t widthInBytes = safeBound.width();
  326. // Evaluate the first line.
  327. for (int32_t x = viewport.left(); x < viewport.right(); x++) {
  328. int64_t saturatedRatio = ratio;
  329. if (saturatedRatio < 0) { saturatedRatio = 0; }
  330. if (saturatedRatio > 65536) { saturatedRatio = 65536; }
  331. int64_t mixedColor = ((luma1.getMantissa() * (65536 - saturatedRatio)) + (luma2.getMantissa() * saturatedRatio) + 2147483648ll) / 4294967296ll;
  332. if (mixedColor < 0) { mixedColor = 0; }
  333. if (mixedColor > 255) { mixedColor = 255; }
  334. *targetPixel = mixedColor;
  335. targetPixel += 1;
  336. ratio += ratioDx;
  337. }
  338. // Copy the rest from the first line.
  339. for (int32_t y = viewport.top() + 1; y < viewport.bottom(); y++) {
  340. safeMemoryCopy<uint8_t>(targetRow, sourceRow, widthInBytes);
  341. targetRow.increaseBytes(targetStride);
  342. }
  343. } else {
  344. // Each pixel needs to be evaluated in this fade.
  345. for (int32_t y = viewport.top(); y < viewport.bottom(); y++) {
  346. SafePointer<uint8_t> targetPixel = targetRow;
  347. int64_t ratio = startRatio;
  348. for (int32_t x = viewport.left(); x < viewport.right(); x++) {
  349. int64_t saturatedRatio = startRatio;
  350. if (saturatedRatio < 0) { saturatedRatio = 0; }
  351. if (saturatedRatio > 65536) { saturatedRatio = 65536; }
  352. int64_t mixedColor = ((luma1.getMantissa() * (65536 - saturatedRatio)) + (luma2.getMantissa() * saturatedRatio) + 2147483648ll) / 4294967296ll;
  353. if (mixedColor < 0) { mixedColor = 0; }
  354. if (mixedColor > 255) { mixedColor = 255; }
  355. *targetPixel = mixedColor;
  356. targetPixel += 1;
  357. ratio += ratioDx;
  358. }
  359. targetRow.increaseBytes(targetStride);
  360. startRatio += ratioDy;
  361. }
  362. }
  363. }
  364. }
  365. void dsr::media_fade_linear(ImageU8& targetImage, FixedPoint x1, FixedPoint y1, FixedPoint luma1, FixedPoint x2, FixedPoint y2, FixedPoint luma2) {
  366. media_fade_region_linear(targetImage, image_getBound(targetImage), x1, y1, luma1, x2, y2, luma2);
  367. }
  368. void dsr::media_fade_region_radial(ImageU8& targetImage, const IRect& viewport, FixedPoint centerX, FixedPoint centerY, FixedPoint innerRadius, FixedPoint innerLuma, FixedPoint outerRadius, FixedPoint outerLuma) {
  369. assertExisting(targetImage);
  370. IRect safeBound = IRect::cut(viewport, image_getBound(targetImage));
  371. if (innerLuma < 0) { innerLuma = FixedPoint::zero(); }
  372. if (innerLuma > 255) { innerLuma = FixedPoint::fromWhole(255); }
  373. if (outerLuma < 0) { outerLuma = FixedPoint::zero(); }
  374. if (outerLuma > 255) { outerLuma = FixedPoint::fromWhole(255); }
  375. // Subtracting half a pixel in the fade line is equivalent to adding half a pixel on X and Y.
  376. int64_t originX = centerX.getMantissa() + safeBound.left() * 65536 - 32768;
  377. int64_t originY = centerY.getMantissa() + safeBound.top() * 65536 - 32768;
  378. // Let outerRadius be slightly outside of innerRadius to prevent division by zero and get anti-aliasing.
  379. if (outerRadius <= innerRadius + FixedPoint::one()) {
  380. outerRadius = innerRadius + FixedPoint::one();
  381. }
  382. int64_t fadeSize = (outerRadius.getMantissa() - innerRadius.getMantissa());
  383. int64_t fadeSlope = 4294967296ll / fadeSize;
  384. SafePointer<uint8_t> targetRow = image_getSafePointer(targetImage, safeBound.top()) + safeBound.left();
  385. int32_t targetStride = image_getStride(targetImage);
  386. for (int64_t y = safeBound.top(); y < safeBound.bottom(); y++) {
  387. SafePointer<uint8_t> targetPixel = targetRow;
  388. for (int64_t x = safeBound.left(); x < safeBound.right(); x++) {
  389. int64_t diffX = (x * 65536) - originX;
  390. int64_t diffY = (y * 65536) - originY;
  391. // Double's square root is guaranteed to be exact for integers fitting inside of its mantissa.
  392. int64_t length = sqrt(((diffX * diffX) + (diffY * diffY)));
  393. // Using a 64-bit integer division per pixel for good quality and high range.
  394. int64_t ratio = ((length - innerRadius.getMantissa()) * fadeSlope) / 65536;
  395. int64_t saturatedRatio = ratio;
  396. if (saturatedRatio < 0) { saturatedRatio = 0; }
  397. if (saturatedRatio > 65536) { saturatedRatio = 65536; }
  398. int64_t mixedColor = ((innerLuma.getMantissa() * (65536 - saturatedRatio)) + (outerLuma.getMantissa() * saturatedRatio) + 2147483648ll) / 4294967296ll;
  399. if (mixedColor < 0) { mixedColor = 0; }
  400. if (mixedColor > 255) { mixedColor = 255; }
  401. *targetPixel = mixedColor;
  402. targetPixel += 1;
  403. }
  404. targetRow.increaseBytes(targetStride);
  405. }
  406. }
  407. void dsr::media_fade_radial(ImageU8& targetImage, FixedPoint centerX, FixedPoint centerY, FixedPoint innerRadius, FixedPoint innerLuma, FixedPoint outerRadius, FixedPoint outerLuma) {
  408. media_fade_region_radial(targetImage, image_getBound(targetImage), centerX, centerY, innerRadius, innerLuma, outerRadius, outerLuma);
  409. }