imageAPI.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. 
  2. // zlib open source license
  3. //
  4. // Copyright (c) 2017 to 2025 David Forsgren Piuva
  5. //
  6. // This software is provided 'as-is', without any express or implied
  7. // warranty. In no event will the authors be held liable for any damages
  8. // arising from the use of this software.
  9. //
  10. // Permission is granted to anyone to use this software for any purpose,
  11. // including commercial applications, and to alter it and redistribute it
  12. // freely, subject to the following restrictions:
  13. //
  14. // 1. The origin of this software must not be misrepresented; you must not
  15. // claim that you wrote the original software. If you use this software
  16. // in a product, an acknowledgment in the product documentation would be
  17. // appreciated but is not required.
  18. //
  19. // 2. Altered source versions must be plainly marked as such, and must not be
  20. // misrepresented as being the original software.
  21. //
  22. // 3. This notice may not be removed or altered from any source
  23. // distribution.
  24. #define DSR_INTERNAL_ACCESS
  25. #include <limits>
  26. #include <cassert>
  27. #include "imageAPI.h"
  28. #include "drawAPI.h"
  29. #include "fileAPI.h"
  30. #include "../image/stbImage/stbImageWrapper.h"
  31. #include "../math/scalar.h"
  32. #include "../settings.h"
  33. namespace dsr {
  34. static const int32_t maximumImageWidth = 65536;
  35. static const int32_t maximumImageHeight = 65536;
  36. template <typename IMAGE_TYPE>
  37. IMAGE_TYPE image_create_template(const char * name, int32_t width, int32_t height, PackOrderIndex packOrderIndex) {
  38. if (width < 1 || width > maximumImageWidth || height < 1 || height > maximumImageHeight) {
  39. sendWarning(U"");
  40. // Return an empty image on failure.
  41. return IMAGE_TYPE();
  42. } else {
  43. static const int32_t pixelSize = image_getPixelSize<IMAGE_TYPE>();
  44. // Calculate the stride.
  45. uintptr_t byteStride = memory_getPaddedSize(width * pixelSize, DSR_MAXIMUM_ALIGNMENT);
  46. uint32_t pixelStride = byteStride / pixelSize;
  47. // Create the image.
  48. return IMAGE_TYPE(buffer_create(byteStride * height).setName(name), 0, width, height, pixelStride, packOrderIndex);
  49. }
  50. }
  51. // Take the dimensions as signed integers to avoid getting extreme dimensions on underflow.
  52. AlignedImageU8 image_create_U8(int32_t width, int32_t height) {
  53. return image_create_template<AlignedImageU8>("U8 pixel buffer", width, height, PackOrderIndex::RGBA);
  54. }
  55. AlignedImageU16 image_create_U16(int32_t width, int32_t height) {
  56. return image_create_template<AlignedImageU16>("U16 pixel buffer", width, height, PackOrderIndex::RGBA);
  57. }
  58. AlignedImageF32 image_create_F32(int32_t width, int32_t height) {
  59. return image_create_template<AlignedImageF32>("F32 pixel buffer", width, height, PackOrderIndex::RGBA);
  60. }
  61. OrderedImageRgbaU8 image_create_RgbaU8(int32_t width, int32_t height) {
  62. return image_create_template<OrderedImageRgbaU8>("RgbaU8 pixel buffer", width, height, PackOrderIndex::RGBA);
  63. }
  64. AlignedImageRgbaU8 image_create_RgbaU8_native(int32_t width, int32_t height, PackOrderIndex packOrderIndex) {
  65. return image_create_template<OrderedImageRgbaU8>("Native pixel buffer", width, height, packOrderIndex);
  66. }
  67. // Pre-condition: image exists.
  68. // Post-condition: Returns true if the stride is larger than the image's width.
  69. template <typename IMAGE_TYPE>
  70. inline bool imageIsPadded(const IMAGE_TYPE &image) {
  71. return image_getWidth(image) * image_getPixelSize(image) < image_getStride(image);
  72. }
  73. // Loading from data pointer
  74. OrderedImageRgbaU8 image_decode_RgbaU8(SafePointer<const uint8_t> data, int size) {
  75. if (data.isNotNull()) {
  76. return image_stb_decode_RgbaU8(data, size);
  77. } else {
  78. return OrderedImageRgbaU8();
  79. }
  80. }
  81. // Loading from buffer
  82. OrderedImageRgbaU8 image_decode_RgbaU8(const Buffer& fileContent) {
  83. return image_decode_RgbaU8(buffer_getSafeData<uint8_t>(fileContent, "image file buffer"), buffer_getSize(fileContent));
  84. }
  85. // Loading from file
  86. OrderedImageRgbaU8 image_load_RgbaU8(const String& filename, bool mustExist) {
  87. OrderedImageRgbaU8 result;
  88. Buffer fileContent = file_loadBuffer(filename, mustExist);
  89. if (buffer_exists(fileContent)) {
  90. result = image_decode_RgbaU8(fileContent);
  91. if (mustExist && !image_exists(result)) {
  92. throwError(U"image_load_RgbaU8: Failed to load the image at ", filename, U".\n");
  93. }
  94. }
  95. return result;
  96. }
  97. Buffer image_encode(const ImageRgbaU8 &image, ImageFileFormat format, int quality) {
  98. if (image_exists(image)) {
  99. ImageRgbaU8 orderedImage;
  100. if (image_getPackOrderIndex(image) != PackOrderIndex::RGBA) {
  101. // Repack into RGBA.
  102. orderedImage = image_clone(image);
  103. } else {
  104. // Take the image handle as is.
  105. orderedImage = image;
  106. }
  107. if (imageIsPadded(orderedImage) && format != ImageFileFormat::PNG) {
  108. // If orderedImage is padded and it's not requested as PNG, the padding has to be removed first.
  109. return image_stb_encode(image_removePadding(orderedImage), format, quality);
  110. } else {
  111. // Send orderedImage directly to encoding.
  112. return image_stb_encode(orderedImage, format, quality);
  113. }
  114. } else {
  115. return Buffer();
  116. }
  117. }
  118. static ImageFileFormat detectImageFileExtension(const String& filename) {
  119. ImageFileFormat result = ImageFileFormat::Unknown;
  120. int lastDotIndex = string_findLast(filename, U'.');
  121. if (lastDotIndex != -1) {
  122. ReadableString extension = string_upperCase(file_getExtension(filename));
  123. if (string_match(extension, U"JPG") || string_match(extension, U"JPEG")) {
  124. result = ImageFileFormat::JPG;
  125. } else if (string_match(extension, U"PNG")) {
  126. result = ImageFileFormat::PNG;
  127. } else if (string_match(extension, U"TARGA") || string_match(extension, U"TGA")) {
  128. result = ImageFileFormat::TGA;
  129. } else if (string_match(extension, U"BMP")) {
  130. result = ImageFileFormat::BMP;
  131. }
  132. }
  133. return result;
  134. }
  135. bool image_save(const ImageRgbaU8 &image, const String& filename, bool mustWork, int quality) {
  136. ImageFileFormat extension = detectImageFileExtension(filename);
  137. Buffer buffer;
  138. if (extension == ImageFileFormat::Unknown) {
  139. if (mustWork) { throwError(U"The extension *.", file_getExtension(filename), " in ", filename, " is not a supported image format.\n"); }
  140. return false;
  141. } else {
  142. buffer = image_encode(image, extension, quality);
  143. }
  144. if (buffer_exists(buffer)) {
  145. return file_saveBuffer(filename, buffer, mustWork);
  146. } else {
  147. if (mustWork) { throwError(U"Failed to encode an image that was going to be saved as ", filename, "\n"); }
  148. return false;
  149. }
  150. }
  151. void image_fill(const ImageU8& image, int32_t color) {
  152. if (image_exists(image)) {
  153. draw_rectangle(image, image_getBound(image), color);
  154. }
  155. }
  156. void image_fill(const ImageU16& image, int32_t color) {
  157. if (image_exists(image)) {
  158. draw_rectangle(image, image_getBound(image), color);
  159. }
  160. }
  161. void image_fill(const ImageF32& image, float color) {
  162. if (image_exists(image)) {
  163. draw_rectangle(image, image_getBound(image), color);
  164. }
  165. }
  166. void image_fill(const ImageRgbaU8& image, const ColorRgbaI32& color) {
  167. if (image_exists(image)) {
  168. draw_rectangle(image, image_getBound(image), color);
  169. }
  170. }
  171. AlignedImageU8 image_clone(const ImageU8& image) {
  172. if (image_exists(image)) {
  173. AlignedImageU8 result = image_create_U8(image_getWidth(image), image_getHeight(image));
  174. draw_copy(result, image);
  175. return result;
  176. } else {
  177. return AlignedImageU8(); // Null gives null
  178. }
  179. }
  180. AlignedImageU16 image_clone(const ImageU16& image) {
  181. if (image_exists(image)) {
  182. AlignedImageU16 result = image_create_U16(image_getWidth(image), image_getHeight(image));
  183. draw_copy(result, image);
  184. return result;
  185. } else {
  186. return AlignedImageU16(); // Null gives null
  187. }
  188. }
  189. AlignedImageF32 image_clone(const ImageF32& image) {
  190. if (image_exists(image)) {
  191. AlignedImageF32 result = image_create_F32(image_getWidth(image), image_getHeight(image));
  192. draw_copy(result, image);
  193. return result;
  194. } else {
  195. return AlignedImageF32(); // Null gives null
  196. }
  197. }
  198. OrderedImageRgbaU8 image_clone(const ImageRgbaU8& image) {
  199. if (image_exists(image)) {
  200. OrderedImageRgbaU8 result = image_create_RgbaU8(image_getWidth(image), image_getHeight(image));
  201. draw_copy(result, image);
  202. return result;
  203. } else {
  204. return OrderedImageRgbaU8(); // Null gives null
  205. }
  206. }
  207. ImageRgbaU8 image_removePadding(const ImageRgbaU8& image) {
  208. if (!image_exists(image)) {
  209. return ImageRgbaU8(); // Null gives null
  210. } else if (imageIsPadded(image)) {
  211. return image;
  212. } else {
  213. uint32_t targetStride = image_getWidth(image) * image_getPixelSize(image);
  214. int32_t sourceStride = image_getStride(image);
  215. Buffer newBuffer = buffer_create(targetStride * image_getHeight(image));
  216. SafePointer<const uint8_t> sourceRow = image_getSafePointer<uint8_t>(image);
  217. SafePointer<uint8_t> targetRow = buffer_getSafeData<uint8_t>(newBuffer, "RgbaU8 padding removal target");
  218. for (int32_t y = 0; y < image_getHeight(image); y++) {
  219. safeMemoryCopy(targetRow, sourceRow, targetStride);
  220. sourceRow.increaseBytes(sourceStride);
  221. targetRow.increaseBytes(targetStride);
  222. }
  223. return ImageRgbaU8(newBuffer, 0, image_getWidth(image), image_getHeight(image), targetStride * image_getPixelSize<ImageRgbaU8>(), image_getPackOrderIndex(image));
  224. }
  225. }
  226. static void extractChannel(SafePointer<uint8_t> targetData, int targetStride, SafePointer<const uint8_t> sourceData, int sourceStride, int sourceChannels, int channelIndex, int width, int height) {
  227. SafePointer<const uint8_t> sourceRow = sourceData + channelIndex;
  228. SafePointer<uint8_t> targetRow = targetData;
  229. for (int y = 0; y < height; y++) {
  230. SafePointer<const uint8_t> sourceElement = sourceRow;
  231. SafePointer<uint8_t> targetElement = targetRow;
  232. for (int x = 0; x < width; x++) {
  233. *targetElement = *sourceElement; // Copy one channel from the soruce
  234. sourceElement += sourceChannels; // Jump to the same channel in the next source pixel
  235. targetElement += 1; // Jump to the next monochrome target pixel
  236. }
  237. sourceRow.increaseBytes(sourceStride);
  238. targetRow.increaseBytes(targetStride);
  239. }
  240. }
  241. static AlignedImageU8 getChannel(const ImageRgbaU8 image, int32_t channelIndex) {
  242. // Warning for debug mode
  243. static int channelCount = 4;
  244. assert(0 <= channelIndex && channelIndex < channelCount);
  245. AlignedImageU8 result = image_create_U8(image_getWidth(image), image_getHeight(image));
  246. extractChannel(image_getSafePointer<uint8_t>(result), image_getStride(result), image_getSafePointer<uint8_t>(image), image_getStride(image), channelCount, channelIndex, image_getWidth(image), image_getHeight(image));
  247. return result;
  248. }
  249. AlignedImageU8 image_get_red(const ImageRgbaU8& image) {
  250. if (image_exists(image)) {
  251. return getChannel(image, image_getPackOrder(image).redIndex);
  252. } else {
  253. return AlignedImageU8();
  254. }
  255. }
  256. AlignedImageU8 image_get_green(const ImageRgbaU8& image) {
  257. if (image_exists(image)) {
  258. return getChannel(image, image_getPackOrder(image).greenIndex);
  259. } else {
  260. return AlignedImageU8(); // Null gives null
  261. }
  262. }
  263. AlignedImageU8 image_get_blue(const ImageRgbaU8& image) {
  264. if (image_exists(image)) {
  265. return getChannel(image, image_getPackOrder(image).blueIndex);
  266. } else {
  267. return AlignedImageU8(); // Null gives null
  268. }
  269. }
  270. AlignedImageU8 image_get_alpha(const ImageRgbaU8& image) {
  271. if (image_exists(image)) {
  272. return getChannel(image, image_getPackOrder(image).alphaIndex);
  273. } else {
  274. return AlignedImageU8(); // Null gives null
  275. }
  276. }
  277. static inline int32_t readColor(const ImageU8& channel, int x, int y) {
  278. return image_accessPixel(channel, x, y);
  279. }
  280. static inline int32_t readColor(int32_t color, int x, int y) {
  281. return color;
  282. }
  283. template <typename R, typename G, typename B, typename A>
  284. static OrderedImageRgbaU8 pack_template(int32_t width, int32_t height, R red, G green, B blue, A alpha) {
  285. OrderedImageRgbaU8 result = image_create_RgbaU8(width, height);
  286. for (int y = 0; y < height; y++) {
  287. for (int x = 0; x < width; x++) {
  288. ColorRgbaI32 color = ColorRgbaI32(readColor(red, x, y), readColor(green, x, y), readColor(blue, x, y), readColor(alpha, x, y));
  289. image_writePixel(result, x, y, color);
  290. }
  291. }
  292. return result;
  293. }
  294. #define PACK1(FIRST) \
  295. if (image_exists(FIRST)) { \
  296. return pack_template(image_getWidth(FIRST), image_getHeight(FIRST), red, green, blue, alpha); \
  297. } else { \
  298. return OrderedImageRgbaU8(); \
  299. }
  300. OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, int32_t blue, int32_t alpha) { PACK1(red); }
  301. OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, int32_t blue, int32_t alpha) { PACK1(green); }
  302. OrderedImageRgbaU8 image_pack(int32_t red, int32_t green, const ImageU8& blue, int32_t alpha) { PACK1(blue); }
  303. OrderedImageRgbaU8 image_pack(int32_t red, int32_t green, int32_t blue, const ImageU8& alpha) { PACK1(alpha); }
  304. #define PACK2(FIRST,SECOND) \
  305. if (image_exists(FIRST) && image_exists(SECOND)) { \
  306. if (image_getWidth(FIRST) != image_getWidth(SECOND) || image_getHeight(FIRST) != image_getHeight(SECOND)) { \
  307. throwError("Cannot pack two channels of different size!\n"); \
  308. } \
  309. return pack_template(image_getWidth(FIRST), image_getHeight(FIRST), red, green, blue, alpha); \
  310. } else { \
  311. return OrderedImageRgbaU8(); \
  312. }
  313. OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, int32_t blue, int32_t alpha) { PACK2(red,green) }
  314. OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, const ImageU8& blue, int32_t alpha) { PACK2(red,blue) }
  315. OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, int32_t blue, const ImageU8& alpha) { PACK2(red,alpha) }
  316. OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, const ImageU8& blue, int32_t alpha) { PACK2(green,blue) }
  317. OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, int32_t blue, const ImageU8& alpha) { PACK2(green,alpha) }
  318. OrderedImageRgbaU8 image_pack(int32_t red, int32_t green, const ImageU8& blue, const ImageU8& alpha) { PACK2(blue,alpha) }
  319. #define PACK3(FIRST,SECOND,THIRD) \
  320. if (image_exists(FIRST) && image_exists(SECOND) && image_exists(THIRD)) { \
  321. if (image_getWidth(FIRST) != image_getWidth(SECOND) || image_getHeight(FIRST) != image_getHeight(SECOND) \
  322. || image_getWidth(FIRST) != image_getWidth(THIRD) || image_getHeight(FIRST) != image_getHeight(THIRD)) { \
  323. throwError("Cannot pack three channels of different size!\n"); \
  324. } \
  325. return pack_template(image_getWidth(FIRST), image_getHeight(FIRST), red, green, blue, alpha); \
  326. } else { \
  327. return OrderedImageRgbaU8(); \
  328. }
  329. OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, const ImageU8& blue, const ImageU8& alpha) { PACK3(green, blue, alpha) }
  330. OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, const ImageU8& blue, const ImageU8& alpha) { PACK3(red, blue, alpha) }
  331. OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, int32_t blue, const ImageU8& alpha) { PACK3(red, green, alpha) }
  332. OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, const ImageU8& blue, int32_t alpha) { PACK3(red, green, blue) }
  333. #define PACK4(FIRST,SECOND,THIRD,FOURTH) \
  334. if (image_exists(FIRST) && image_exists(SECOND) && image_exists(THIRD) && image_exists(FOURTH)) { \
  335. if (image_getWidth(FIRST) != image_getWidth(SECOND) || image_getHeight(FIRST) != image_getHeight(SECOND) \
  336. || image_getWidth(FIRST) != image_getWidth(THIRD) || image_getHeight(FIRST) != image_getHeight(THIRD) \
  337. || image_getWidth(FIRST) != image_getWidth(FOURTH) || image_getHeight(FIRST) != image_getHeight(FOURTH)) { \
  338. throwError("Cannot pack four channels of different size!\n"); \
  339. } \
  340. return pack_template(image_getWidth(FIRST), image_getHeight(FIRST), red, green, blue, alpha); \
  341. } else { \
  342. return OrderedImageRgbaU8(); \
  343. }
  344. OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, const ImageU8& blue, const ImageU8& alpha) { PACK4(red, green, blue, alpha) }
  345. // Convert a grayscale image into an ascii image using the given alphabet.
  346. // Since all 256 characters cannot be in the alphabet, the encoding is lossy.
  347. // Each line is stored within <> to prevent text editors from removing meaningful white space.
  348. // The first line contains the given alphabet as a gradient from black to white.
  349. // Preconditions:
  350. // alphabet may not have extended ascii, non printable, '\', '"', '>' or linebreak
  351. // width <= stride
  352. // size of monochromeImage = height * stride
  353. // Example alphabet: " .,-_':;!+~=^?*abcdefghijklmnopqrstuvwxyz()[]{}|&@#0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  354. String image_toAscii(const ImageU8& image, const String& alphabet) {
  355. if (!image_exists(image)) {
  356. return U"null";
  357. }
  358. String result;
  359. char alphabetMap[256];
  360. int alphabetSize = string_length(alphabet);
  361. int width = image_getWidth(image);
  362. int height = image_getHeight(image);
  363. string_reserve(result, ((width + 4) * height) + alphabetSize + 5);
  364. double scale = (double)(alphabetSize - 1) / 255.0;
  365. double output = 0.49;
  366. for (int rawValue = 0; rawValue < 256; rawValue++) {
  367. int charIndex = (int)output;
  368. if (charIndex < 0) charIndex = 0;
  369. if (charIndex > alphabetSize - 1) charIndex = alphabetSize - 1;
  370. alphabetMap[rawValue] = alphabet[charIndex];
  371. output += scale;
  372. }
  373. string_appendChar(result, U'<');
  374. for (int charIndex = 0; charIndex < alphabetSize; charIndex++) {
  375. string_appendChar(result, alphabet[charIndex]);
  376. }
  377. string_append(result, U">\n");
  378. for (int y = 0; y < height; y++) {
  379. string_appendChar(result, U'<');
  380. for (int x = 0; x < width; x++) {
  381. string_appendChar(result, alphabetMap[image_readPixel_clamp(image, x, y)]);
  382. }
  383. string_append(result, U">\n");
  384. }
  385. return result;
  386. }
  387. String image_toAscii(const ImageU8& image) {
  388. return image_toAscii(image, U" .,-_':;!+~=^?*abcdefghijklmnopqrstuvwxyz()[]{}|&@#0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  389. }
  390. // Create a monochrome image from the ascii image in content.
  391. // String is used instead of ReadableString, so that the content can be decompressed from 8-bit strings in the binary.
  392. AlignedImageU8 image_fromAscii(const String& content) {
  393. char alphabet[128];
  394. uint8_t alphabetMap[128];
  395. char current;
  396. int x = 0;
  397. int y = -1;
  398. int width = 0;
  399. int height = 0;
  400. int alphabetSize = 0;
  401. int contentSize = string_length(content);
  402. bool quoted = false;
  403. int i = 0;
  404. while (i < contentSize && ((current = content[i]) != '\0')) {
  405. if (quoted) {
  406. if (y < 0) {
  407. if (current == '>') {
  408. quoted = false;
  409. y = 0;
  410. } else if (alphabetSize < 128) {
  411. alphabet[alphabetSize] = current;
  412. alphabetSize++;
  413. }
  414. } else {
  415. if (current == '>') {
  416. quoted = false;
  417. if (width < x) width = x;
  418. y++;
  419. x = 0;
  420. } else {
  421. x++;
  422. }
  423. }
  424. } else if (current == '<') {
  425. quoted = true;
  426. }
  427. i++;
  428. }
  429. if (alphabetSize < 2) {
  430. throwError(U"The alphabet needs at least two characters!");
  431. }
  432. height = y;
  433. if (x > 0) {
  434. throwError(U"All ascii images must end with a linebreak!");
  435. }
  436. for (i = 0; i < 128; i++) {
  437. alphabetMap[i] = 0;
  438. }
  439. for (i = 0; i < alphabetSize; i++) {
  440. int code = (int)(alphabet[i]);
  441. if (code < 32 || code > 126) {
  442. throwError(U"Ascii image contained non-printable standard ascii! Use codes 32 to 126.");
  443. }
  444. if (alphabetMap[code] > 0) {
  445. throwError(U"A character in the alphabet was used more than once!");
  446. }
  447. int value = (int)(((double)i) * (255.0f / ((double)(alphabetSize - 1))));
  448. if (value < 0) value = 0;
  449. if (value > 255) value = 255;
  450. alphabetMap[code] = value;
  451. }
  452. if (width <= 0 || height <= 0) {
  453. throwError(U"An ascii image had zero dimensions!");
  454. }
  455. AlignedImageU8 result = image_create_U8(width, height);
  456. x = 0; y = -1;
  457. quoted = false;
  458. i = 0;
  459. while (i < contentSize && ((current = content[i]) != '\0')) {
  460. if (quoted) {
  461. if (current == '>') {
  462. quoted = false;
  463. if (y >= 0 && x != width) {
  464. throwError(U"Lines in the ascii image do not have the same lengths.");
  465. }
  466. y++;
  467. x = 0;
  468. } else if (y >= 0) {
  469. int code = (int)current;
  470. if (code < 0) code = 0;
  471. if (code > 127) code = 127;
  472. image_writePixel(result, x, y, alphabetMap[code]);
  473. x++;
  474. }
  475. } else if (current == '<') {
  476. quoted = true;
  477. }
  478. i++;
  479. }
  480. return result;
  481. }
  482. template <typename IMAGE_TYPE, int CHANNELS, typename ELEMENT_TYPE>
  483. ELEMENT_TYPE maxDifference_template(const IMAGE_TYPE& imageA, const IMAGE_TYPE& imageB) {
  484. if (image_getWidth(imageA) != image_getWidth(imageB) || image_getHeight(imageA) != image_getHeight(imageB)) {
  485. return std::numeric_limits<ELEMENT_TYPE>::max();
  486. } else {
  487. intptr_t strideA = image_getStride(imageA);
  488. intptr_t strideB = image_getStride(imageB);
  489. ELEMENT_TYPE maxDifference = 0;
  490. SafePointer<const ELEMENT_TYPE> rowDataA = image_getSafePointer<ELEMENT_TYPE>(imageA);
  491. SafePointer<const ELEMENT_TYPE> rowDataB = image_getSafePointer<ELEMENT_TYPE>(imageB);
  492. for (int y = 0; y < image_getHeight(imageA); y++) {
  493. SafePointer<const ELEMENT_TYPE> pixelDataA = rowDataA;
  494. SafePointer<const ELEMENT_TYPE> pixelDataB = rowDataB;
  495. for (int x = 0; x < image_getWidth(imageA); x++) {
  496. for (int c = 0; c < CHANNELS; c++) {
  497. ELEMENT_TYPE difference = absDiff(*pixelDataA, *pixelDataB);
  498. if (difference > maxDifference) {
  499. maxDifference = difference;
  500. }
  501. pixelDataA += 1;
  502. pixelDataB += 1;
  503. }
  504. }
  505. rowDataA.increaseBytes(strideA);
  506. rowDataB.increaseBytes(strideB);
  507. }
  508. return maxDifference;
  509. }
  510. }
  511. uint8_t image_maxDifference(const ImageU8& imageA, const ImageU8& imageB) {
  512. if (image_exists(imageA) && image_exists(imageB)) {
  513. return maxDifference_template<ImageU8, 1, uint8_t>(imageA, imageB);
  514. } else {
  515. return std::numeric_limits<uint8_t>::infinity();
  516. }
  517. }
  518. uint16_t image_maxDifference(const ImageU16& imageA, const ImageU16& imageB) {
  519. if (image_exists(imageA) && image_exists(imageB)) {
  520. return maxDifference_template<ImageU16, 1, uint16_t>(imageA, imageB);
  521. } else {
  522. return std::numeric_limits<uint16_t>::infinity();
  523. }
  524. }
  525. float image_maxDifference(const ImageF32& imageA, const ImageF32& imageB) {
  526. if (image_exists(imageA) && image_exists(imageB)) {
  527. return maxDifference_template<ImageF32, 1, float>(imageA, imageB);
  528. } else {
  529. return std::numeric_limits<float>::infinity();
  530. }
  531. }
  532. uint8_t image_maxDifference(const ImageRgbaU8& imageA, const ImageRgbaU8& imageB) {
  533. if (image_exists(imageA) && image_exists(imageB)) {
  534. return maxDifference_template<ImageRgbaU8, 4, uint8_t>(imageA, imageB);
  535. } else {
  536. return std::numeric_limits<uint8_t>::infinity();
  537. }
  538. }
  539. }