ImageRgbaU8.cpp 12 KB

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