ImageRgbaU8.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2017 to 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 "ImageRgbaU8.h"
  24. #include "internal/imageInternal.h"
  25. #include "internal/imageTemplate.h"
  26. #include <algorithm>
  27. #include "../base/simd.h"
  28. using namespace dsr;
  29. IMAGE_DEFINITION(ImageRgbaU8Impl, 4, Color4xU8, uint8_t);
  30. ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset, const PackOrder &packOrder) :
  31. ImageImpl(newWidth, newHeight, newStride, sizeof(Color4xU8), buffer, startOffset), packOrder(packOrder) {
  32. assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
  33. this->initializeRgbaImage();
  34. }
  35. ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t alignment) :
  36. ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(Color4xU8), alignment), sizeof(Color4xU8)) {
  37. this->initializeRgbaImage();
  38. }
  39. // Native canvas constructor
  40. ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, PackOrderIndex packOrderIndex, int32_t alignment) :
  41. ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(Color4xU8), 16), sizeof(Color4xU8)) {
  42. this->packOrder = PackOrder::getPackOrder(packOrderIndex);
  43. this->initializeRgbaImage();
  44. }
  45. bool ImageRgbaU8Impl::isTexture() const {
  46. return this->texture.exists();
  47. }
  48. bool ImageRgbaU8Impl::isTexture(const ImageRgbaU8Impl* image) {
  49. return image ? image->texture.exists() : false;
  50. }
  51. ImageRgbaU8Impl ImageRgbaU8Impl::getWithoutPadding() const {
  52. if (this->stride == this->width * this->pixelSize) {
  53. // No padding
  54. return *this;
  55. } else {
  56. // Copy each row without padding
  57. ImageRgbaU8Impl result = ImageRgbaU8Impl(this->width, this->height, this->packOrder.packOrderIndex, DSR_DEFAULT_ALIGNMENT);
  58. const SafePointer<uint8_t> sourceRow = imageInternal::getSafeData<uint8_t>(*this);
  59. int32_t sourceStride = this->stride;
  60. SafePointer<uint8_t> targetRow = imageInternal::getSafeData<uint8_t>(result);
  61. int32_t targetStride = result.stride;
  62. for (int32_t y = 0; y < this->height; y++) {
  63. safeMemoryCopy(targetRow, sourceRow, targetStride);
  64. sourceRow += sourceStride;
  65. targetRow += targetStride;
  66. }
  67. return result;
  68. }
  69. }
  70. static void extractChannel(SafePointer<uint8_t> targetData, int targetStride, const SafePointer<uint8_t> sourceData, int sourceStride, int sourceChannels, int channelIndex, int width, int height) {
  71. const SafePointer<uint8_t> sourceRow = sourceData + channelIndex;
  72. SafePointer<uint8_t> targetRow = targetData;
  73. for (int y = 0; y < height; y++) {
  74. const SafePointer<uint8_t> sourceElement = sourceRow;
  75. SafePointer<uint8_t> targetElement = targetRow;
  76. for (int x = 0; x < width; x++) {
  77. *targetElement = *sourceElement; // Copy one channel from the soruce
  78. sourceElement += sourceChannels; // Jump to the same channel in the next source pixel
  79. targetElement += 1; // Jump to the next monochrome target pixel
  80. }
  81. sourceRow.increaseBytes(sourceStride);
  82. targetRow.increaseBytes(targetStride);
  83. }
  84. }
  85. ImageU8Impl ImageRgbaU8Impl::getChannel(int32_t channelIndex) const {
  86. // Warning for debug mode
  87. assert(channelIndex >= 0 && channelIndex < channelCount);
  88. // Safety for release mode
  89. if (channelIndex < 0) { channelIndex = 0; }
  90. if (channelIndex > channelCount) { channelIndex = channelCount; }
  91. ImageU8Impl result(this->width, this->height, DSR_DEFAULT_ALIGNMENT);
  92. extractChannel(imageInternal::getSafeData<uint8_t>(result), result.stride, imageInternal::getSafeData<uint8_t>(*this), this->stride, channelCount, channelIndex, this->width, this->height);
  93. return result;
  94. }
  95. static int32_t getSizeGroup(int32_t size) {
  96. int32_t group = -1;
  97. if (size == 1) {
  98. group = 0; // Too small for 16-byte alignment!
  99. } else if (size == 2) {
  100. group = 1; // Too small for 16-byte alignment!
  101. } else if (size == 4) {
  102. group = 2; // Smallest allowed texture dimension
  103. } else if (size == 8) {
  104. group = 3;
  105. } else if (size == 16) {
  106. group = 4;
  107. } else if (size == 32) {
  108. group = 5;
  109. } else if (size == 64) {
  110. group = 6;
  111. } else if (size == 128) {
  112. group = 7;
  113. } else if (size == 256) {
  114. group = 8;
  115. } else if (size == 512) {
  116. group = 9;
  117. } else if (size == 1024) {
  118. group = 10;
  119. } else if (size == 2048) {
  120. group = 11;
  121. } else if (size == 4096) {
  122. group = 12;
  123. } else if (size == 8192) {
  124. group = 13;
  125. } else if (size == 16384) {
  126. group = 14; // Largest allowed texture dimension
  127. } // Higher dimensions should return -1, so that initializeRgbaImage avoids initializing the image as a texture and isTexture returns false
  128. return group;
  129. }
  130. static int32_t getPyramidSize(int32_t width, int32_t height, int32_t pixelSize, int32_t levels) {
  131. uint32_t result = 0;
  132. uint32_t byteCount = width * height * pixelSize;
  133. for (int32_t l = 0; l < levels; l++) {
  134. result += byteCount; // Add image size to pyramid size
  135. byteCount = byteCount >> 2; // Divide size by 4
  136. }
  137. return (int32_t)result;
  138. }
  139. static void downScaleByTwo(SafePointer<uint8_t> targetData, const SafePointer<uint8_t> sourceData, int32_t targetWidth, int32_t targetHeight, int32_t pixelSize, int32_t targetStride) {
  140. int32_t sourceStride = targetStride * 2;
  141. int32_t doubleSourceStride = sourceStride * 2;
  142. SafePointer<uint8_t> targetRow = targetData;
  143. const SafePointer<uint8_t> sourceRow = sourceData;
  144. for (int32_t y = 0; y < targetHeight; y++) {
  145. const SafePointer<uint8_t> sourcePixel = sourceRow;
  146. SafePointer<uint8_t> targetPixel = targetRow;
  147. for (int32_t x = 0; x < targetWidth; x++) {
  148. // TODO: Use pariwise and vector average functions for fixed channel counts (SSE has _mm_avg_epu8 for vector average)
  149. for (int32_t c = 0; c < pixelSize; c++) {
  150. uint8_t value = (uint8_t)((
  151. (uint16_t)(*sourcePixel)
  152. + (uint16_t)(*(sourcePixel + pixelSize))
  153. + (uint16_t)(*(sourcePixel + sourceStride))
  154. + (uint16_t)(*(sourcePixel + sourceStride + pixelSize))) / 4);
  155. *targetPixel = value;
  156. targetPixel += 1;
  157. sourcePixel += 1;
  158. }
  159. sourcePixel += pixelSize;
  160. }
  161. targetRow += targetStride;
  162. sourceRow += doubleSourceStride;
  163. }
  164. }
  165. TextureRgbaLayer::TextureRgbaLayer() {}
  166. TextureRgbaLayer::TextureRgbaLayer(const uint8_t *data, int32_t width, int32_t height) :
  167. data(data),
  168. strideShift(getSizeGroup(width) + 2),
  169. widthMask(width - 1),
  170. heightMask(height - 1),
  171. width(width),
  172. height(height),
  173. subWidth(width * 256),
  174. subHeight(height * 256),
  175. halfPixelOffsetU(1.0f - (0.5f / width)),
  176. halfPixelOffsetV(1.0f - (0.5f / height)) {}
  177. void ImageRgbaU8Impl::generatePyramid() {
  178. if (!this->isTexture()) {
  179. if (this->width < 4 || this->height < 4) {
  180. printText("Cannot generate a pyramid from an image smaller than 4x4 pixels.\n");
  181. } else if (this->width > 16384 || this->height > 16384) {
  182. printText("Cannot generate a pyramid from an image larger than 16384x16384 pixels.\n");
  183. } else if (getSizeGroup(this->width) == -1 || getSizeGroup(this->height) == -1) {
  184. printText("Cannot generate a pyramid from image dimensions that are not powers of two.\n");
  185. } else if (this->stride > this->width * pixelSize) {
  186. printText("Cannot generate a pyramid from an image that contains padding.\n");
  187. } else if (this->stride < this->width * pixelSize) {
  188. printText("Cannot generate a pyramid from an image with corrupted stride.\n");
  189. } else {
  190. printText("Cannot generate a pyramid from an image that has not been initialized correctly.\n");
  191. }
  192. } else {
  193. int32_t pixelSize = this->pixelSize;
  194. int32_t mipmaps = std::min(std::max(getSizeGroup(std::min(this->width, this->height)) - 1, 1), MIP_BIN_COUNT);
  195. if (!this->texture.hasMipBuffer()) {
  196. this->texture.pyramidBuffer = buffer_create(getPyramidSize(this->width / 2, this->height / 2, pixelSize, mipmaps - 1));
  197. }
  198. // Point to the image's original buffer in mip level 0
  199. SafePointer<uint8_t> currentStart = imageInternal::getSafeData<uint8_t>(*this);
  200. int32_t currentWidth = this->width;
  201. int32_t currentHeight = this->height;
  202. this->texture.mips[0] = TextureRgbaLayer(currentStart.getUnsafe(), currentWidth, currentHeight);
  203. // Create smaller pyramid images in the extra buffer
  204. SafePointer<uint8_t> previousStart = currentStart;
  205. currentStart = buffer_getSafeData<uint8_t>(this->texture.pyramidBuffer, "Pyramid generation target");
  206. for (int32_t m = 1; m < mipmaps; m++) {
  207. currentWidth /= 2;
  208. currentHeight /= 2;
  209. this->texture.mips[m] = TextureRgbaLayer(currentStart.getUnsafe(), currentWidth, currentHeight);
  210. int32_t size = currentWidth * currentHeight * pixelSize;
  211. // In-place downscaling by two.
  212. downScaleByTwo(currentStart, previousStart, currentWidth, currentHeight, pixelSize, currentWidth * pixelSize);
  213. previousStart = currentStart;
  214. currentStart.increaseBytes(size);
  215. }
  216. // Fill unused mip levels with duplicates of the last mip level
  217. for (int32_t m = mipmaps; m < MIP_BIN_COUNT; m++) {
  218. // m - 1 is never negative, because mipmaps is clamped to at least 1 and nobody would choose zero for MIP_BIN_COUNT.
  219. this->texture.mips[m] = this->texture.mips[m - 1];
  220. }
  221. }
  222. }
  223. void ImageRgbaU8Impl::removePyramid() {
  224. // Only try to remove if it has a pyramid
  225. if (buffer_exists(this->texture.pyramidBuffer)) {
  226. // Remove the pyramid's buffer
  227. this->texture.pyramidBuffer = Buffer();
  228. // Re-initialize
  229. for (int32_t m = 0; m < MIP_BIN_COUNT; m++) {
  230. this->texture.mips[m] = TextureRgbaLayer(imageInternal::getSafeData<uint8_t>(*this).getUnsafe(), this->width, this->height);
  231. }
  232. }
  233. }
  234. void ImageRgbaU8Impl::initializeRgbaImage() {
  235. // If the image fills the criterias of a texture
  236. if (getSizeGroup(this->width) >= 2
  237. && getSizeGroup(this->height) >= 2
  238. && this->stride == this->width * this->pixelSize) {
  239. // Initialize each mip bin to show the original image
  240. for (int32_t m = 0; m < MIP_BIN_COUNT; m++) {
  241. this->texture.mips[m] = TextureRgbaLayer(imageInternal::getSafeData<uint8_t>(*this).getUnsafe(), this->width, this->height);
  242. }
  243. }
  244. };
  245. Color4xU8 ImageRgbaU8Impl::packRgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) const {
  246. return Color4xU8(this->packOrder.packRgba(red, green, blue, alpha));
  247. }
  248. Color4xU8 ImageRgbaU8Impl::packRgba(ColorRgbaI32 color) const {
  249. return Color4xU8(this->packOrder.packRgba(color.red, color.green, color.blue, color.alpha));
  250. }
  251. ColorRgbaI32 ImageRgbaU8Impl::unpackRgba(Color4xU8 rgba, const PackOrder& order) {
  252. return ColorRgbaI32(
  253. getRed(rgba.packed, order),
  254. getGreen(rgba.packed, order),
  255. getBlue(rgba.packed, order),
  256. getAlpha(rgba.packed, order)
  257. );
  258. }
  259. ColorRgbaI32 ImageRgbaU8Impl::unpackRgba(Color4xU8 rgba) const {
  260. return unpackRgba(rgba, this->packOrder);
  261. }
  262. Color4xU8 ImageRgbaU8Impl::packRgb(uint8_t red, uint8_t green, uint8_t blue) const {
  263. return Color4xU8(this->packOrder.packRgba(red, green, blue, 255));
  264. }
  265. Color4xU8 ImageRgbaU8Impl::packRgb(ColorRgbI32 color) const {
  266. return Color4xU8(this->packOrder.packRgba(color.red, color.green, color.blue, 255));
  267. }
  268. ColorRgbI32 ImageRgbaU8Impl::unpackRgb(Color4xU8 rgb, const PackOrder& order) {
  269. return ColorRgbI32(
  270. getRed(rgb.packed, order),
  271. getGreen(rgb.packed, order),
  272. getBlue(rgb.packed, order)
  273. );
  274. }
  275. ColorRgbI32 ImageRgbaU8Impl::unpackRgb(Color4xU8 rgb) const {
  276. return unpackRgb(rgb, this->packOrder);
  277. }