ImageGradientModification.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <GradientSignal/Components/ImageGradientModification.h>
  9. #include <GradientSignal/Components/ImageGradientComponent.h>
  10. #include <LmbrCentral/Dependency/DependencyNotificationBus.h>
  11. namespace GradientSignal
  12. {
  13. ImageTileBuffer::ImageTileBuffer(uint32_t imageWidth, uint32_t imageHeight, AZ::EntityId imageGradientEntityId)
  14. : m_imageGradientEntityId(imageGradientEntityId)
  15. // Calculate the number of image tiles in each direction that we'll need, rounding up so that we create an image tile
  16. // for fractional tiles as well.
  17. , m_numTilesX((imageWidth + ImageTileSize - 1) / ImageTileSize)
  18. , m_numTilesY((imageHeight + ImageTileSize - 1) / ImageTileSize)
  19. {
  20. // Create empty entries for every tile. Each entry is just a null pointer at the start, so the memory overhead
  21. // of these empty entries at 32x32 pixels per tile, a 1024x1024 image will have 8 KB of overhead.
  22. m_paintedImageTiles.resize(m_numTilesX * m_numTilesY);
  23. }
  24. bool ImageTileBuffer::Empty() const
  25. {
  26. return !m_modifiedAnyPixels;
  27. }
  28. AZStd::pair<float, float> ImageTileBuffer::GetOriginalPixelValueAndOpacity(const PixelIndex& pixelIndex)
  29. {
  30. uint32_t tileIndex = GetTileIndex(pixelIndex);
  31. uint32_t pixelTileIndex = GetPixelTileIndex(pixelIndex);
  32. // Create the tile if it doesn't already exist.
  33. CreateImageTile(tileIndex);
  34. return { m_paintedImageTiles[tileIndex]->m_unmodifiedData[pixelTileIndex],
  35. m_paintedImageTiles[tileIndex]->m_modifiedDataOpacity[pixelTileIndex] };
  36. }
  37. void ImageTileBuffer::SetModifiedPixelValue(const PixelIndex& pixelIndex, float modifiedValue, float opacity)
  38. {
  39. uint32_t tileIndex = GetTileIndex(pixelIndex);
  40. uint32_t pixelTileIndex = GetPixelTileIndex(pixelIndex);
  41. AZ_Assert(m_paintedImageTiles[tileIndex], "Cached image tile hasn't been created yet!");
  42. m_paintedImageTiles[tileIndex]->m_modifiedData[pixelTileIndex] = modifiedValue;
  43. m_paintedImageTiles[tileIndex]->m_modifiedDataOpacity[pixelTileIndex] = opacity;
  44. }
  45. void ImageTileBuffer::ApplyChangeBuffer(bool undo)
  46. {
  47. AZStd::array<PixelIndex, ImageTileSize * ImageTileSize> pixelIndices;
  48. for (int32_t tileIndex = 0; tileIndex < m_paintedImageTiles.size(); tileIndex++)
  49. {
  50. // If we never created this tile, skip it and move on.
  51. if (m_paintedImageTiles[tileIndex] == nullptr)
  52. {
  53. continue;
  54. }
  55. // Create an array of pixel indices for every pixel in this tile.
  56. PixelIndex startIndex = GetStartPixelIndex(tileIndex);
  57. uint32_t index = 0;
  58. for (int16_t y = 0; y < ImageTileSize; y++)
  59. {
  60. for (int16_t x = 0; x < ImageTileSize; x++)
  61. {
  62. pixelIndices[index++] =
  63. PixelIndex(aznumeric_cast<int16_t>(startIndex.first + x), aznumeric_cast<int16_t>(startIndex.second + y));
  64. }
  65. }
  66. // Set the image gradient values for this tile either to the original or the modified values.
  67. // It's possible that not every pixel in the tile was modified, but it's cheaper just to update per-tile
  68. // than to track each individual pixel in the tile and set them individually.
  69. ImageGradientModificationBus::Event(
  70. m_imageGradientEntityId,
  71. &ImageGradientModificationBus::Events::SetPixelValuesByPixelIndex,
  72. pixelIndices,
  73. undo ? m_paintedImageTiles[tileIndex]->m_unmodifiedData : m_paintedImageTiles[tileIndex]->m_modifiedData);
  74. }
  75. }
  76. uint32_t ImageTileBuffer::GetTileIndex(const PixelIndex& pixelIndex) const
  77. {
  78. return ((pixelIndex.second / ImageTileSize) * m_numTilesX) + (pixelIndex.first / ImageTileSize);
  79. }
  80. PixelIndex ImageTileBuffer::GetStartPixelIndex(uint32_t tileIndex) const
  81. {
  82. return PixelIndex(
  83. aznumeric_cast<int16_t>((tileIndex % m_numTilesX) * ImageTileSize),
  84. aznumeric_cast<int16_t>((tileIndex / m_numTilesX) * ImageTileSize));
  85. }
  86. uint32_t ImageTileBuffer::GetPixelTileIndex(const PixelIndex& pixelIndex) const
  87. {
  88. uint32_t xIndex = pixelIndex.first % ImageTileSize;
  89. uint32_t yIndex = pixelIndex.second % ImageTileSize;
  90. return (yIndex * ImageTileSize) + xIndex;
  91. }
  92. void ImageTileBuffer::CreateImageTile(uint32_t tileIndex)
  93. {
  94. // If it already exists, there's nothing more to do.
  95. if (m_paintedImageTiles[tileIndex])
  96. {
  97. return;
  98. }
  99. auto imageTile = AZStd::make_unique<ImageTile>();
  100. // Initialize the list of pixel indices for this tile.
  101. AZStd::array<PixelIndex, ImageTileSize * ImageTileSize> pixelIndices;
  102. PixelIndex startIndex = GetStartPixelIndex(tileIndex);
  103. for (int16_t index = 0; index < (ImageTileSize * ImageTileSize); index++)
  104. {
  105. pixelIndices[index] = PixelIndex(
  106. aznumeric_cast<int16_t>(startIndex.first + (index % ImageTileSize)),
  107. aznumeric_cast<int16_t>(startIndex.second + (index / ImageTileSize)));
  108. }
  109. AZ_Assert(imageTile->m_unmodifiedData.size() == pixelIndices.size(), "ImageTile and PixelIndices are out of sync.");
  110. // Read all of the original gradient values into the image tile buffer.
  111. ImageGradientModificationBus::Event(
  112. m_imageGradientEntityId,
  113. &ImageGradientModificationBus::Events::GetPixelValuesByPixelIndex,
  114. pixelIndices,
  115. imageTile->m_unmodifiedData);
  116. // Initialize the modified value buffer with the original values. This way we can always undo/redo an entire tile at a time
  117. // without tracking which pixels in the tile have been modified.
  118. imageTile->m_modifiedData = imageTile->m_unmodifiedData;
  119. AZStd::fill(imageTile->m_modifiedDataOpacity.begin(), imageTile->m_modifiedDataOpacity.end(), 0.0f);
  120. m_paintedImageTiles[tileIndex] = AZStd::move(imageTile);
  121. // If we create a tile, we'll use that as shorthand for tracking that changed data exists.
  122. m_modifiedAnyPixels = true;
  123. }
  124. ModifiedImageRegion::ModifiedImageRegion(const ImageGradientSizeData& imageData)
  125. : m_minModifiedPixelIndex(AZStd::numeric_limits<int16_t>::max(), AZStd::numeric_limits<int16_t>::max())
  126. , m_maxModifiedPixelIndex(aznumeric_cast<int16_t>(-1), aznumeric_cast<int16_t>(-1))
  127. , m_isModified(false)
  128. , m_imageData(imageData)
  129. {
  130. }
  131. void ModifiedImageRegion::AddPoint(const PixelIndex& pixelIndex)
  132. {
  133. // Each time we modify a pixel, adjust our min and max pixel ranges to include it.
  134. m_minModifiedPixelIndex = PixelIndex(
  135. AZStd::min(m_minModifiedPixelIndex.first, pixelIndex.first), AZStd::min(m_minModifiedPixelIndex.second, pixelIndex.second));
  136. m_maxModifiedPixelIndex = PixelIndex(
  137. AZStd::max(m_maxModifiedPixelIndex.first, pixelIndex.first), AZStd::max(m_maxModifiedPixelIndex.second, pixelIndex.second));
  138. // Keep track of whether or not we've modified a pixel that occurs on an edge. This is used when the wrapping type
  139. // is 'ClampToEdge' to determine if our modified region needs to stretch out to infinity in that direction.
  140. m_modifiedLeftEdge = m_modifiedLeftEdge || (pixelIndex.first == m_imageData.m_topLeftPixelIndex.first);
  141. m_modifiedRightEdge = m_modifiedRightEdge || (pixelIndex.first == m_imageData.m_bottomRightPixelIndex.first);
  142. m_modifiedTopEdge = m_modifiedTopEdge || (pixelIndex.second == m_imageData.m_topLeftPixelIndex.second);
  143. m_modifiedBottomEdge = m_modifiedBottomEdge || (pixelIndex.second == m_imageData.m_bottomRightPixelIndex.second);
  144. // Track that we've modified at least one pixel.
  145. m_isModified = true;
  146. }
  147. void ModifiedImageRegion::AddLocalSpacePixelAabbFromTopLeft(
  148. const ImageGradientSizeData& imageData, int16_t pixelX, int16_t pixelY, AZ::Aabb& region)
  149. {
  150. // This adds an AABB representing the size of one pixel in local space.
  151. // This method calculates the pixel's location from the top left corner of the local bounds.
  152. // Get the local bounds of the image gradient.
  153. const AZ::Aabb localBounds = imageData.m_gradientTransform.GetBounds();
  154. // ShiftedPixel* contains the number of pixels to offset from the first pixel in the top left corner.
  155. // The double-mod is used to wrap around any negative results that can occur with certain combinations of tiling
  156. // and frequency zoom settings.
  157. int16_t shiftedPixelX = (pixelX - imageData.m_topLeftPixelIndex.first) % imageData.m_imageWidth;
  158. shiftedPixelX = (shiftedPixelX + imageData.m_imageWidth) % imageData.m_imageWidth;
  159. int16_t shiftedPixelY = (pixelY - imageData.m_topLeftPixelIndex.second) % imageData.m_imageHeight;
  160. shiftedPixelY = (shiftedPixelY + imageData.m_imageHeight) % imageData.m_imageHeight;
  161. // X pixels run left to right (min to max), but Y pixels run top to bottom (max to min), so we account for that
  162. // in the math below.
  163. region.AddPoint(AZ::Vector3(
  164. localBounds.GetMin().GetX() + (imageData.m_localMetersPerPixelX * shiftedPixelX),
  165. localBounds.GetMax().GetY() - (imageData.m_localMetersPerPixelY * shiftedPixelY),
  166. 0.0f));
  167. region.AddPoint(AZ::Vector3(
  168. localBounds.GetMin().GetX() + (imageData.m_localMetersPerPixelX * (shiftedPixelX + 1)),
  169. localBounds.GetMax().GetY() - (imageData.m_localMetersPerPixelY * shiftedPixelY),
  170. 0.0f));
  171. region.AddPoint(AZ::Vector3(
  172. localBounds.GetMin().GetX() + (imageData.m_localMetersPerPixelX * shiftedPixelX),
  173. localBounds.GetMax().GetY() - (imageData.m_localMetersPerPixelY * (shiftedPixelY + 1)),
  174. 0.0f));
  175. region.AddPoint(AZ::Vector3(
  176. localBounds.GetMin().GetX() + (imageData.m_localMetersPerPixelX * (shiftedPixelX + 1)),
  177. localBounds.GetMax().GetY() - (imageData.m_localMetersPerPixelY * (shiftedPixelY + 1)),
  178. 0.0f));
  179. };
  180. void ModifiedImageRegion::AddLocalSpacePixelAabbFromBottomRight(
  181. const ImageGradientSizeData& imageData, int16_t pixelX, int16_t pixelY, AZ::Aabb& region)
  182. {
  183. // This adds an AABB representing the size of one pixel in local space.
  184. // This method calculates the pixel's location from the bottom right corner of the local bounds.
  185. // Get the local bounds of the image gradient.
  186. const AZ::Aabb localBounds = imageData.m_gradientTransform.GetBounds();
  187. // ShiftedPixel* contains the number of pixels to offset from the first pixel in the top left corner.
  188. // The double-mod is used to wrap around any negative results that can occur with certain combinations of tiling
  189. // and frequency zoom settings.
  190. int16_t shiftedPixelX = (imageData.m_bottomRightPixelIndex.first - pixelX) % imageData.m_imageWidth;
  191. shiftedPixelX = (shiftedPixelX + imageData.m_imageWidth) % imageData.m_imageWidth;
  192. int16_t shiftedPixelY = (imageData.m_bottomRightPixelIndex.second - pixelY) % imageData.m_imageHeight;
  193. shiftedPixelY = (shiftedPixelY + imageData.m_imageHeight) % imageData.m_imageHeight;
  194. // X pixels run left to right (min to max), but Y pixels run top to bottom (max to min), so we account for that
  195. // in the math below.
  196. region.AddPoint(AZ::Vector3(
  197. localBounds.GetMax().GetX() - (imageData.m_localMetersPerPixelX * shiftedPixelX),
  198. localBounds.GetMin().GetY() + (imageData.m_localMetersPerPixelY * shiftedPixelY),
  199. 0.0f));
  200. region.AddPoint(AZ::Vector3(
  201. localBounds.GetMax().GetX() - (imageData.m_localMetersPerPixelX * (shiftedPixelX + 1)),
  202. localBounds.GetMin().GetY() + (imageData.m_localMetersPerPixelY * shiftedPixelY),
  203. 0.0f));
  204. region.AddPoint(AZ::Vector3(
  205. localBounds.GetMax().GetX() - (imageData.m_localMetersPerPixelX * shiftedPixelX),
  206. localBounds.GetMin().GetY() + (imageData.m_localMetersPerPixelY * (shiftedPixelY + 1)),
  207. 0.0f));
  208. region.AddPoint(AZ::Vector3(
  209. localBounds.GetMax().GetX() - (imageData.m_localMetersPerPixelX * (shiftedPixelX + 1)),
  210. localBounds.GetMin().GetY() + (imageData.m_localMetersPerPixelY * (shiftedPixelY + 1)),
  211. 0.0f));
  212. };
  213. AZ::Aabb ModifiedImageRegion::GetDirtyRegion()
  214. {
  215. // If the image hasn't been modified, return an invalid/unbounded dirty region.
  216. if (!m_isModified)
  217. {
  218. return AZ::Aabb::CreateNull();
  219. }
  220. // If the wrapping type uses infinite image repeats, by definition we need to have an unbounded dirty region.
  221. switch (m_imageData.m_gradientTransform.GetWrappingType())
  222. {
  223. case WrappingType::Mirror:
  224. case WrappingType::None:
  225. case WrappingType::Repeat:
  226. return AZ::Aabb::CreateNull();
  227. }
  228. // Our input dirty region is finite, and our wrapping type clamps to the image gradient's shape boundary,
  229. // which means that we can potentially have a finite output dirty region as well.
  230. // We just need to handle indirect effects caused by the painting:
  231. // - Image repeats within the shape boundary means that we need the dirty region to encompass all of the repeating changed data.
  232. // - Changing an edge pixel for ClampToEdge affects an infinite range stretching out from that pixel.
  233. // - The dirty region needs to expand to add a buffer for bilinear filtering.
  234. const AZ::Matrix3x4 gradientTransformMatrix = m_imageData.m_gradientTransform.GetTransformMatrix();
  235. // Create a local space AABB for our modified region based on the min/max pixels. We add the min/max pixels offset from
  236. // both the top left and the bottom right corners to account for any tiling and frequency zoom settings that make the pixels
  237. // appear multiple times in the image.
  238. AZ::Aabb modifiedRegionLocal = AZ::Aabb::CreateNull();
  239. AddLocalSpacePixelAabbFromTopLeft(
  240. m_imageData, m_minModifiedPixelIndex.first, m_minModifiedPixelIndex.second, modifiedRegionLocal);
  241. AddLocalSpacePixelAabbFromBottomRight(
  242. m_imageData, m_minModifiedPixelIndex.first, m_minModifiedPixelIndex.second, modifiedRegionLocal);
  243. AddLocalSpacePixelAabbFromTopLeft(
  244. m_imageData, m_maxModifiedPixelIndex.first, m_maxModifiedPixelIndex.second, modifiedRegionLocal);
  245. AddLocalSpacePixelAabbFromBottomRight(
  246. m_imageData, m_maxModifiedPixelIndex.first, m_maxModifiedPixelIndex.second, modifiedRegionLocal);
  247. AZ::Aabb expandedDirtyRegionLocal(modifiedRegionLocal);
  248. // If our wrapping type is ClampToEdge, check for intersections between the modified region and the image gradient bounds.
  249. // Any modifications that occur on the edge of the image gradient will affect all positions outward infinitely from that edge,
  250. // so we need to make the dirtyRegion stretch infinitely in that direction.
  251. if (m_imageData.m_gradientTransform.GetWrappingType() == WrappingType::ClampToEdge)
  252. {
  253. // If we modified the leftmost pixel, stretch left to -inf in X.
  254. if (m_modifiedLeftEdge)
  255. {
  256. expandedDirtyRegionLocal.SetMin(AZ::Vector3(
  257. AZStd::numeric_limits<float>::lowest(),
  258. expandedDirtyRegionLocal.GetMin().GetY(),
  259. expandedDirtyRegionLocal.GetMin().GetZ()));
  260. }
  261. // If we modified the rightmost pixel, stretch right to +inf in X.
  262. if (m_modifiedRightEdge)
  263. {
  264. expandedDirtyRegionLocal.SetMax(AZ::Vector3(
  265. AZStd::numeric_limits<float>::max(),
  266. expandedDirtyRegionLocal.GetMax().GetY(),
  267. expandedDirtyRegionLocal.GetMax().GetZ()));
  268. }
  269. // If we modified the bottommost pixel, stretch down to -inf in Y.
  270. if (m_modifiedBottomEdge)
  271. {
  272. expandedDirtyRegionLocal.SetMin(AZ::Vector3(
  273. expandedDirtyRegionLocal.GetMin().GetX(),
  274. AZStd::numeric_limits<float>::lowest(),
  275. expandedDirtyRegionLocal.GetMin().GetZ()));
  276. }
  277. // If we modified the topmost pixel, stretch up to +inf in Y.
  278. if (m_modifiedTopEdge)
  279. {
  280. expandedDirtyRegionLocal.SetMax(AZ::Vector3(
  281. expandedDirtyRegionLocal.GetMax().GetX(),
  282. AZStd::numeric_limits<float>::max(),
  283. expandedDirtyRegionLocal.GetMax().GetZ()));
  284. }
  285. }
  286. // Because Image Gradients support bilinear filtering, we need to expand our dirty area by an extra pixel in each direction
  287. // so that the effects of the painted values on adjacent pixels are taken into account when refreshing.
  288. expandedDirtyRegionLocal.Expand(AZ::Vector3(m_imageData.m_localMetersPerPixelX, m_imageData.m_localMetersPerPixelY, 0.0f));
  289. // Finally, transform the dirty region back into world space and
  290. // set it to encompass the full Z range since image gradients are 2D.
  291. AZ::Aabb expandedDirtyRegion = expandedDirtyRegionLocal.GetTransformedAabb(gradientTransformMatrix);
  292. expandedDirtyRegion.Set(
  293. AZ::Vector3(expandedDirtyRegion.GetMin().GetX(), expandedDirtyRegion.GetMin().GetY(), AZStd::numeric_limits<float>::lowest()),
  294. AZ::Vector3(expandedDirtyRegion.GetMax().GetX(), expandedDirtyRegion.GetMax().GetY(), AZStd::numeric_limits<float>::max()));
  295. return expandedDirtyRegion;
  296. }
  297. ImageGradientModifier::ImageGradientModifier(
  298. const AZ::EntityComponentIdPair& entityComponentIdPair)
  299. : m_ownerEntityComponentId(entityComponentIdPair)
  300. {
  301. AzFramework::PaintBrushNotificationBus::Handler::BusConnect(entityComponentIdPair);
  302. auto entityId = entityComponentIdPair.GetEntityId();
  303. // Get the gradient transform. We'll need this to update the dirty region appropriately.
  304. GradientTransformRequestBus::EventResult(
  305. m_imageData.m_gradientTransform, entityId, &GradientTransformRequests::GetGradientTransform);
  306. // Get the spacing to map individual pixels to world space positions.
  307. AZ::Vector2 imagePixelsPerMeter(0.0f);
  308. ImageGradientRequestBus::EventResult(imagePixelsPerMeter, entityId, &ImageGradientRequestBus::Events::GetImagePixelsPerMeter);
  309. // Meters Per Pixel is in world space, so it takes into account the bounds, tiling, frequency zoom, and scale parameters.
  310. m_imageData.m_metersPerPixelX = (imagePixelsPerMeter.GetX() > 0.0f) ? (1.0f / imagePixelsPerMeter.GetX()) : 0.0f;
  311. m_imageData.m_metersPerPixelY = (imagePixelsPerMeter.GetY() > 0.0f) ? (1.0f / imagePixelsPerMeter.GetY()) : 0.0f;
  312. // Since scaling takes place outside of the image's local space, but tiling & frequency zoom take place inside the image's
  313. // local space, we'll create a version of meters per pixel without scaling applied so that we can calculate pixel sizes
  314. // when working in local space.
  315. m_imageData.m_localMetersPerPixelX = m_imageData.m_metersPerPixelX / m_imageData.m_gradientTransform.GetScale().GetX();
  316. m_imageData.m_localMetersPerPixelY = m_imageData.m_metersPerPixelY / m_imageData.m_gradientTransform.GetScale().GetY();
  317. // Get the image width and height in pixels. We'll use these to calculate the pixel indices for the image borders and also
  318. // to verify that the image is valid to modify.
  319. uint32_t imageWidth = 0;
  320. uint32_t imageHeight = 0;
  321. ImageGradientRequestBus::EventResult(imageWidth, entityId, &ImageGradientRequestBus::Events::GetImageWidth);
  322. ImageGradientRequestBus::EventResult(imageHeight, entityId, &ImageGradientRequestBus::Events::GetImageHeight);
  323. m_imageData.m_imageWidth = aznumeric_cast<int16_t>(imageWidth);
  324. m_imageData.m_imageHeight = aznumeric_cast<int16_t>(imageHeight);
  325. // Get the image tiling values. These are used to calculate the pixel indices for the image borders.
  326. float imageTilingX = 1.0f;
  327. float imageTilingY = 1.0f;
  328. ImageGradientRequestBus::EventResult(imageTilingX, entityId, &ImageGradientRequestBus::Events::GetTilingX);
  329. ImageGradientRequestBus::EventResult(imageTilingY, entityId, &ImageGradientRequestBus::Events::GetTilingY);
  330. // Get the normalized UVW values for the image gradient at the corners. Note that "min UVW" is the bottom left corner of the
  331. // AABB and "max UVW" is the top right corner. Depending on tiling, we can get numbers outside the 0-1 range in both the positive
  332. // and negative directions.
  333. AZ::Vector3 bottomLeftUvw;
  334. AZ::Vector3 topRightUvw;
  335. m_imageData.m_gradientTransform.GetMinMaxUvwValuesNormalized(bottomLeftUvw, topRightUvw);
  336. // Calculate min/max pixel values at the boundaries. Depending on tiling, these can be negative or positive, and they might
  337. // fall outside the number of pixels in the image. They will need to be modded to get back into pixel index range.
  338. const float leftImagePixelX = m_imageData.m_imageWidth * imageTilingX * bottomLeftUvw.GetX();
  339. const float bottomImagePixelY = m_imageData.m_imageHeight * imageTilingY * bottomLeftUvw.GetY();
  340. const float rightImagePixelX = m_imageData.m_imageWidth * imageTilingX * topRightUvw.GetX();
  341. const float topImagePixelY = m_imageData.m_imageHeight * imageTilingY * topRightUvw.GetY();
  342. // Calculate the pixel indices for each boundary pixel by double-modding. The second mod is to wrap negative values back into
  343. // the positive value range.
  344. m_imageData.m_topLeftPixelIndex.first = aznumeric_cast<int64_t>(leftImagePixelX) % m_imageData.m_imageWidth;
  345. m_imageData.m_topLeftPixelIndex.first =
  346. (m_imageData.m_topLeftPixelIndex.first + m_imageData.m_imageWidth) % m_imageData.m_imageWidth;
  347. m_imageData.m_topLeftPixelIndex.second = aznumeric_cast<int64_t>(topImagePixelY) % m_imageData.m_imageHeight;
  348. m_imageData.m_topLeftPixelIndex.second =
  349. (m_imageData.m_topLeftPixelIndex.second + m_imageData.m_imageHeight) % m_imageData.m_imageHeight;
  350. m_imageData.m_bottomRightPixelIndex.first = aznumeric_cast<int64_t>(rightImagePixelX) % m_imageData.m_imageWidth;
  351. m_imageData.m_bottomRightPixelIndex.first =
  352. (m_imageData.m_bottomRightPixelIndex.first + m_imageData.m_imageWidth) % m_imageData.m_imageWidth;
  353. m_imageData.m_bottomRightPixelIndex.second = aznumeric_cast<int64_t>(bottomImagePixelY) % m_imageData.m_imageHeight;
  354. m_imageData.m_bottomRightPixelIndex.second =
  355. (m_imageData.m_bottomRightPixelIndex.second + m_imageData.m_imageHeight) % m_imageData.m_imageHeight;
  356. // X pixels go from min to max (left to right), but Y pixels go from max to min (top to bottom), so flip the index
  357. // values to account for this.
  358. m_imageData.m_topLeftPixelIndex.second = m_imageData.m_imageHeight - m_imageData.m_topLeftPixelIndex.second - 1;
  359. m_imageData.m_bottomRightPixelIndex.second = m_imageData.m_imageHeight - m_imageData.m_bottomRightPixelIndex.second - 1;
  360. }
  361. ImageGradientModifier::~ImageGradientModifier()
  362. {
  363. AzFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
  364. }
  365. void ImageGradientModifier::OnBrushStrokeBegin([[maybe_unused]] const AZ::Color& color)
  366. {
  367. AZ::EntityId entityId = m_ownerEntityComponentId.GetEntityId();
  368. ImageGradientModificationNotificationBus::Event(
  369. entityId, &ImageGradientModificationNotificationBus::Events::OnImageGradientBrushStrokeBegin);
  370. // We can't create a stroke buffer if there isn't any pixel data.
  371. if ((m_imageData.m_imageWidth == 0) || (m_imageData.m_imageHeight == 0))
  372. {
  373. return;
  374. }
  375. // Create the buffer for holding all the changes for a single continuous paint brush stroke.
  376. // This buffer will get used during the stroke to hold our accumulated stroke opacity layer,
  377. // and then after the stroke finishes we'll hand the buffer over to the undo system as an undo/redo buffer.
  378. m_paintStrokeData.m_strokeBuffer =
  379. AZStd::make_shared<ImageTileBuffer>(m_imageData.m_imageWidth, m_imageData.m_imageHeight, entityId);
  380. m_modifiedStrokeRegion = ModifiedImageRegion(m_imageData);
  381. }
  382. void ImageGradientModifier::OnBrushStrokeEnd()
  383. {
  384. const AZ::EntityId entityId = m_ownerEntityComponentId.GetEntityId();
  385. const AZ::Aabb dirtyRegion = m_modifiedStrokeRegion.GetDirtyRegion();
  386. ImageGradientModificationNotificationBus::Event(
  387. entityId,
  388. &ImageGradientModificationNotificationBus::Events::OnImageGradientBrushStrokeEnd,
  389. m_paintStrokeData.m_strokeBuffer,
  390. dirtyRegion);
  391. // Make sure we've cleared out our paint stroke and dirty region data until the next paint stroke begins.
  392. m_paintStrokeData = {};
  393. m_modifiedStrokeRegion = {};
  394. }
  395. AZ::Color ImageGradientModifier::OnGetColor(const AZ::Vector3& brushCenter) const
  396. {
  397. AZ::EntityId entityId = m_ownerEntityComponentId.GetEntityId();
  398. // Get the gradient value at the given point.
  399. // We use "GetPixelValuesByPosition" instead of "GetGradientValue" because we want to select unscaled, unsmoothed values.
  400. float gradientValue = 0.0f;
  401. ImageGradientModificationBus::Event(
  402. entityId,
  403. &ImageGradientModificationBus::Events::GetPixelValuesByPosition,
  404. AZStd::span<const AZ::Vector3>(&brushCenter, 1),
  405. AZStd::span<float>(&gradientValue, 1));
  406. return AZ::Color(gradientValue, gradientValue, gradientValue, 1.0f);
  407. }
  408. void ImageGradientModifier::OnPaintSmoothInternal(
  409. const AZ::Aabb& dirtyArea,
  410. ValueLookupFn& valueLookupFn,
  411. AZStd::function<float(const AZ::Vector3& worldPosition, float gradientValue, float opacity)> combineFn)
  412. {
  413. ModifiedImageRegion modifiedRegion(m_imageData);
  414. // We're either painting or smoothing new values into our image gradient.
  415. // To do this, we need to calculate the set of world space positions that map to individual pixels in the image,
  416. // then ask the paint brush for each position what value we should set that pixel to. Finally, we use those modified
  417. // values to change the image gradient.
  418. const AZ::Matrix3x4 gradientTransformMatrix = m_imageData.m_gradientTransform.GetTransformMatrix();
  419. AZ::Aabb dirtyAreaLocal = dirtyArea.GetTransformedAabb(gradientTransformMatrix.GetInverseFull());
  420. const int32_t xPoints = aznumeric_cast<int32_t>(dirtyAreaLocal.GetXExtent() / m_imageData.m_localMetersPerPixelX);
  421. const int32_t yPoints = aznumeric_cast<int32_t>(dirtyAreaLocal.GetYExtent() / m_imageData.m_localMetersPerPixelY);
  422. // Early out if the dirty area is smaller than our point size.
  423. if ((xPoints <= 0) || (yPoints <= 0))
  424. {
  425. return;
  426. }
  427. // Calculate the minimum set of world space points that map to those pixels.
  428. AZStd::vector<AZ::Vector3> points;
  429. points.reserve(xPoints * yPoints);
  430. for (float y = dirtyAreaLocal.GetMin().GetY() + (m_imageData.m_localMetersPerPixelY / 2.0f);
  431. y <= dirtyAreaLocal.GetMax().GetY();
  432. y += m_imageData.m_localMetersPerPixelY)
  433. {
  434. for (float x = dirtyAreaLocal.GetMin().GetX() + (m_imageData.m_localMetersPerPixelX / 2.0f);
  435. x <= dirtyAreaLocal.GetMax().GetX();
  436. x += m_imageData.m_localMetersPerPixelX)
  437. {
  438. AZ::Vector3 worldPoint(gradientTransformMatrix * AZ::Vector3(x, y, 0.0f));
  439. worldPoint.SetZ(dirtyArea.GetMin().GetZ());
  440. points.push_back(worldPoint);
  441. }
  442. }
  443. // Query the paintbrush with those points to get back the subset of points and brush opacities for each point that's
  444. // affected by the brush.
  445. AZStd::vector<AZ::Vector3> validPoints;
  446. AZStd::vector<float> perPixelOpacities;
  447. valueLookupFn(points, validPoints, perPixelOpacities);
  448. // Early out if none of the points were actually affected by the brush.
  449. if (validPoints.empty())
  450. {
  451. return;
  452. }
  453. AZ::EntityId entityId = m_ownerEntityComponentId.GetEntityId();
  454. // Get the pixel indices for each position.
  455. AZStd::vector<PixelIndex> pixelIndices(validPoints.size());
  456. ImageGradientModificationBus::Event(
  457. entityId, &ImageGradientModificationBus::Events::GetPixelIndicesForPositions, validPoints, pixelIndices);
  458. // Create a buffer for all of the modified, blended gradient values.
  459. AZStd::vector<float> paintedValues;
  460. paintedValues.reserve(pixelIndices.size());
  461. // For each pixel, accumulate the per-pixel opacity in the stroke layer, then (re)blend the stroke layer with
  462. // the original data by using the stroke intensity, stroke opacity, per-pixel opacity, and original pre-stroke gradient value.
  463. // The (re)blended value gets sent immediately to the image gradient, as well as getting cached off into the stroke buffer
  464. // for easier and faster undo/redo operations.
  465. for (size_t index = 0; index < pixelIndices.size(); index++)
  466. {
  467. // If we have an invalid pixel index, fill in a placeholder value into paintedValues and move on to the next pixel.
  468. if ((pixelIndices[index].first < 0) || (pixelIndices[index].second < 0))
  469. {
  470. paintedValues.emplace_back(0.0f);
  471. continue;
  472. }
  473. auto [gradientValue, opacityValue] = m_paintStrokeData.m_strokeBuffer->GetOriginalPixelValueAndOpacity(pixelIndices[index]);
  474. // Add the new per-pixel opacity to the existing opacity in our stroke layer.
  475. opacityValue = AZStd::clamp(opacityValue + (1.0f - opacityValue) * perPixelOpacities[index], 0.0f, 1.0f);
  476. // Combine the pixel (either paint or smooth) and store the blended pixel and new opacity back into our paint stroke buffer.
  477. float blendedValue = combineFn(validPoints[index], gradientValue, opacityValue);
  478. m_paintStrokeData.m_strokeBuffer->SetModifiedPixelValue(pixelIndices[index], blendedValue, opacityValue);
  479. // Also store the blended value into a second buffer that we'll use to immediately modify the image gradient.
  480. paintedValues.emplace_back(blendedValue);
  481. // Track the data needed for calculating the dirty region for this specific operation as well as for the overall brush stroke.
  482. modifiedRegion.AddPoint(pixelIndices[index]);
  483. m_modifiedStrokeRegion.AddPoint(pixelIndices[index]);
  484. }
  485. // Modify the image gradient with all of the changed values
  486. ImageGradientModificationBus::Event(
  487. entityId, &ImageGradientModificationBus::Events::SetPixelValuesByPixelIndex, pixelIndices, paintedValues);
  488. // Get the dirty region that actually encompasses everything that we directly modified,
  489. // along with everything it indirectly affected.
  490. if (modifiedRegion.IsModified())
  491. {
  492. AZ::Aabb expandedDirtyArea = modifiedRegion.GetDirtyRegion();
  493. // Notify anything listening to the image gradient that the modified region has changed.
  494. LmbrCentral::DependencyNotificationBus::Event(
  495. entityId, &LmbrCentral::DependencyNotificationBus::Events::OnCompositionRegionChanged, expandedDirtyArea);
  496. }
  497. }
  498. void ImageGradientModifier::OnPaint(const AZ::Color& color, const AZ::Aabb& dirtyArea, ValueLookupFn& valueLookupFn, BlendFn& blendFn)
  499. {
  500. const float intensity = color.GetR();
  501. const float opacity = color.GetA();
  502. // For paint notifications, we'll use the given blend function to blend the original value and the paint brush intensity
  503. // using the built-up opacity.
  504. auto combineFn = [intensity, opacity, blendFn](
  505. [[maybe_unused]] const AZ::Vector3& worldPosition, float gradientValue, float opacityValue) -> float
  506. {
  507. return blendFn(gradientValue, intensity, opacityValue * opacity);
  508. };
  509. // Perform all the common logic between painting and smoothing to modify our image gradient.
  510. OnPaintSmoothInternal(dirtyArea, valueLookupFn, combineFn);
  511. }
  512. void ImageGradientModifier::OnSmooth(
  513. const AZ::Color& color,
  514. const AZ::Aabb& dirtyArea,
  515. ValueLookupFn& valueLookupFn,
  516. AZStd::span<const AZ::Vector3> valuePointOffsets,
  517. SmoothFn& smoothFn)
  518. {
  519. const float opacity = color.GetA();
  520. AZ::EntityId entityId = m_ownerEntityComponentId.GetEntityId();
  521. // Declare our vectors of kernel point locations and values once outside of the combine function so that we
  522. // don't keep reallocating them on every point.
  523. AZStd::vector<AZ::Vector3> kernelPoints;
  524. AZStd::vector<float> kernelValues;
  525. const AZ::Vector3 valuePointOffsetScale(m_imageData.m_metersPerPixelX, m_imageData.m_metersPerPixelY, 0.0f);
  526. kernelPoints.reserve(valuePointOffsets.size());
  527. kernelValues.reserve(valuePointOffsets.size());
  528. // For smoothing notifications, we'll need to gather all of the neighboring gradient values to feed into the given smoothing
  529. // function for our blend operation.
  530. auto combineFn = [entityId, opacity, smoothFn, &valuePointOffsets, valuePointOffsetScale, &kernelPoints, &kernelValues](
  531. const AZ::Vector3& worldPosition, float gradientValue, float opacityValue) -> float
  532. {
  533. kernelPoints.clear();
  534. // Calculate all of the world positions around our base position that we'll use for fetching our blurring kernel values.
  535. for (auto& valuePointOffset : valuePointOffsets)
  536. {
  537. kernelPoints.emplace_back(worldPosition + (valuePointOffset * valuePointOffsetScale));
  538. }
  539. kernelValues.assign(kernelPoints.size(), 0.0f);
  540. // Read all of the original gradient values for the blurring kernel into the buffer.
  541. ImageGradientModificationBus::Event(
  542. entityId, &ImageGradientModificationBus::Events::GetPixelValuesByPosition, kernelPoints, kernelValues);
  543. // Blend all the blurring kernel values together and store the blended pixel and new opacity back into our paint stroke buffer.
  544. return smoothFn(gradientValue, kernelValues, opacityValue * opacity);
  545. };
  546. // Perform all the common logic between painting and smoothing to modify our image gradient.
  547. OnPaintSmoothInternal(dirtyArea, valueLookupFn, combineFn);
  548. }
  549. } // namespace GradientSignal