Sprite.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  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 "Sprite.h"
  9. #include <CryPath.h>
  10. #include <ISerialize.h>
  11. #include <AzFramework/API/ApplicationAPI.h>
  12. #include <AzFramework/Asset/AssetSystemBus.h>
  13. #include <LyShine/Bus/Sprite/UiSpriteBus.h>
  14. #include <Atom/RPI.Public/Image/StreamingImage.h>
  15. #include <Atom/RPI.Public/Image/AttachmentImage.h>
  16. #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
  17. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  18. namespace
  19. {
  20. const char* const spriteExtension = "sprite";
  21. const char* const streamingImageExtension = "streamingimage";
  22. // Increment this when the Sprite Serialize(TSerialize) function
  23. // changes to be incompatible with previous data
  24. uint32 spriteFileVersionNumber = 2;
  25. const char* spriteVersionNumberTag = "versionNumber";
  26. const char* allowedSpriteTextureExtensions[] = {
  27. "tif", "jpg", "jpeg", "tga", "bmp", "png", "gif", "dds"
  28. };
  29. const int numAllowedSpriteTextureExtensions = AZ_ARRAY_SIZE(allowedSpriteTextureExtensions);
  30. bool IsValidImageExtension(const AZStd::string& extension)
  31. {
  32. for (int i = 0; i < numAllowedSpriteTextureExtensions; ++i)
  33. {
  34. if (extension.compare(allowedSpriteTextureExtensions[i]) == 0)
  35. {
  36. return true;
  37. }
  38. }
  39. return false;
  40. }
  41. bool IsImageProductPath(const AZStd::string& pathname)
  42. {
  43. AZStd::string extension;
  44. AzFramework::StringFunc::Path::GetExtension(pathname.c_str(), extension, false);
  45. return (extension.compare(streamingImageExtension) == 0);
  46. }
  47. // Check if a file exists. This does not go through the AssetCatalog so that it can identify files that exist but aren't processed yet,
  48. // and so that it will work before the AssetCatalog has loaded
  49. bool CheckIfFileExists(const AZStd::string& sourceRelativePath, const AZStd::string& cacheRelativePath)
  50. {
  51. // If the file exists, it has already been processed and does not need to be modified
  52. bool fileExists = AZ::IO::FileIOBase::GetInstance()->Exists(cacheRelativePath.c_str());
  53. if (!fileExists)
  54. {
  55. // If the texture doesn't exist check if it's queued or being compiled.
  56. AzFramework::AssetSystem::AssetStatus status;
  57. AzFramework::AssetSystemRequestBus::BroadcastResult(status, &AzFramework::AssetSystemRequestBus::Events::GetAssetStatus, sourceRelativePath);
  58. switch (status)
  59. {
  60. case AzFramework::AssetSystem::AssetStatus_Queued:
  61. case AzFramework::AssetSystem::AssetStatus_Compiling:
  62. case AzFramework::AssetSystem::AssetStatus_Compiled:
  63. case AzFramework::AssetSystem::AssetStatus_Failed:
  64. {
  65. // The file is queued, in progress, or finished processing after the initial FileIO check
  66. fileExists = true;
  67. break;
  68. }
  69. case AzFramework::AssetSystem::AssetStatus_Unknown:
  70. case AzFramework::AssetSystem::AssetStatus_Missing:
  71. default:
  72. {
  73. // The file does not exist
  74. fileExists = false;
  75. break;
  76. }
  77. }
  78. }
  79. return fileExists;
  80. }
  81. bool GetSourceAssetPaths(const AZStd::string& pathname, AZStd::string& spritePath, AZStd::string& texturePath)
  82. {
  83. // Remove product extension from the texture path if it exists
  84. AZStd::string sourcePathname(pathname);
  85. if (IsImageProductPath(pathname))
  86. {
  87. sourcePathname = CSprite::GetImageSourcePathFromProductPath(pathname);
  88. }
  89. // the input string could be in any form. So make it normalized (forward slashes and lower case)
  90. // NOTE: it should not be a full path at this point. If called from the UI editor it will
  91. // have been transformed to a game path. If being called with a hard coded path it should be a
  92. // game path already - it is not good for code to be using full paths.
  93. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::NormalizePath, sourcePathname);
  94. // check the extension and work out the pathname of the sprite file and the texture file
  95. // currently it works if the input path is either a sprite file or a texture file
  96. AZStd::string extension;
  97. AzFramework::StringFunc::Path::GetExtension(sourcePathname.c_str(), extension, false);
  98. if (extension.compare(spriteExtension) == 0)
  99. {
  100. // The .sprite file has been specified
  101. spritePath = sourcePathname;
  102. // look for a texture file with the same name
  103. if (!CSprite::FixUpSourceImagePathFromUserDefinedPath(spritePath, texturePath))
  104. {
  105. gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
  106. spritePath.c_str(), "No texture file found for sprite: %s, no sprite will be used", spritePath.c_str());
  107. return false;
  108. }
  109. }
  110. else if (IsValidImageExtension(extension))
  111. {
  112. texturePath = sourcePathname;
  113. spritePath = sourcePathname;
  114. AzFramework::StringFunc::Path::ReplaceExtension(spritePath, spriteExtension);
  115. }
  116. else
  117. {
  118. gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
  119. pathname.c_str(), "Invalid file extension for sprite: %s, no sprite will be used", pathname.c_str());
  120. return false;
  121. }
  122. return true;
  123. }
  124. //! \brief Reads a Vec2 tuple (as a string) into an AZ::Vector2
  125. //!
  126. //! Example XML string data: "1.0 2.0"
  127. void SerializeAzVector2(TSerialize ser, const char* attributeName, AZ::Vector2& azVec2)
  128. {
  129. if (ser.IsReading())
  130. {
  131. AZStd::string stringVal;
  132. ser.Value(attributeName, stringVal);
  133. AZ::StringFunc::Replace(stringVal, ',', ' ');
  134. char* pEnd = nullptr;
  135. float uVal = strtof(stringVal.c_str(), &pEnd);
  136. float vVal = strtof(pEnd, nullptr);
  137. azVec2.Set(uVal, vVal);
  138. }
  139. else
  140. {
  141. Vec2 legacyVec2(azVec2.GetX(), azVec2.GetY());
  142. ser.Value(attributeName, legacyVec2);
  143. }
  144. }
  145. //! \return The number of child <Cell> tags off the <SpriteSheet> parent tag.
  146. int GetNumSpriteSheetCellTags(const XmlNodeRef& root)
  147. {
  148. int numCellTags = 0;
  149. XmlNodeRef spriteSheetTag = root->findChild("SpriteSheet");
  150. if (spriteSheetTag)
  151. {
  152. numCellTags = spriteSheetTag->getChildCount();
  153. }
  154. return numCellTags;
  155. }
  156. }
  157. ////////////////////////////////////////////////////////////////////////////////////////////////////
  158. // STATIC MEMBER DATA
  159. ////////////////////////////////////////////////////////////////////////////////////////////////////
  160. CSprite::CSpriteHashMap* CSprite::s_loadedSprites;
  161. AZStd::string CSprite::s_emptyString;
  162. ////////////////////////////////////////////////////////////////////////////////////////////////////
  163. // PUBLIC MEMBER FUNCTIONS
  164. ////////////////////////////////////////////////////////////////////////////////////////////////////
  165. ////////////////////////////////////////////////////////////////////////////////////////////////////
  166. CSprite::CSprite()
  167. : m_numSpriteSheetCellTags(0)
  168. , m_atlas(nullptr)
  169. {
  170. AddRef();
  171. TextureAtlasNamespace::TextureAtlasNotificationBus::Handler::BusConnect();
  172. }
  173. ////////////////////////////////////////////////////////////////////////////////////////////////////
  174. CSprite::~CSprite()
  175. {
  176. if (s_loadedSprites)
  177. {
  178. s_loadedSprites->erase(m_pathname);
  179. }
  180. TextureAtlasNamespace::TextureAtlasNotificationBus::Handler::BusDisconnect();
  181. }
  182. ////////////////////////////////////////////////////////////////////////////////////////////////////
  183. const AZStd::string& CSprite::GetPathname() const
  184. {
  185. return m_pathname;
  186. }
  187. ////////////////////////////////////////////////////////////////////////////////////////////////////
  188. const AZStd::string& CSprite::GetTexturePathname() const
  189. {
  190. return m_texturePathname;
  191. }
  192. ////////////////////////////////////////////////////////////////////////////////////////////////////
  193. ISprite::Borders CSprite::GetBorders() const
  194. {
  195. return m_borders;
  196. }
  197. ////////////////////////////////////////////////////////////////////////////////////////////////////
  198. void CSprite::SetBorders(Borders borders)
  199. {
  200. m_borders = borders;
  201. NotifyChanged();
  202. }
  203. ////////////////////////////////////////////////////////////////////////////////////////////////////
  204. void CSprite::SetCellBorders(int cellIndex, Borders borders)
  205. {
  206. if (CellIndexWithinRange(cellIndex))
  207. {
  208. m_spriteSheetCells[cellIndex].borders = borders;
  209. NotifyChanged();
  210. }
  211. else
  212. {
  213. SetBorders(borders);
  214. }
  215. }
  216. ////////////////////////////////////////////////////////////////////////////////////////////////////
  217. AZ::Data::Instance<AZ::RPI::Image> CSprite::GetImage()
  218. {
  219. // Prioritize usage of an atlas
  220. if (m_atlas)
  221. {
  222. return m_atlas->GetTexture();
  223. }
  224. return m_image;
  225. }
  226. ////////////////////////////////////////////////////////////////////////////////////////////////////
  227. void CSprite::Serialize(TSerialize ser)
  228. {
  229. // When reading, get sprite-sheet info from XML tag data, otherwise get
  230. // it from this sprite object directly.
  231. const bool hasSpriteSheetCells = ser.IsReading() ? m_numSpriteSheetCellTags > 0 : !GetSpriteSheetCells().empty();
  232. if (!hasSpriteSheetCells && ser.BeginOptionalGroup("Sprite", true))
  233. {
  234. ser.Value("m_left", m_borders.m_left);
  235. ser.Value("m_right", m_borders.m_right);
  236. ser.Value("m_top", m_borders.m_top);
  237. ser.Value("m_bottom", m_borders.m_bottom);
  238. ser.EndGroup();
  239. }
  240. if (hasSpriteSheetCells && ser.BeginOptionalGroup("SpriteSheet", true))
  241. {
  242. const int numSpriteSheetCells = static_cast<int>(ser.IsReading() ? m_numSpriteSheetCellTags : GetSpriteSheetCells().size());
  243. for (int i = 0; i < numSpriteSheetCells; ++i)
  244. {
  245. ser.BeginOptionalGroup("Cell", true);
  246. if (ser.IsReading())
  247. {
  248. m_spriteSheetCells.push_back(SpriteSheetCell());
  249. }
  250. AZStd::string aliasTemp;
  251. if (ser.IsReading())
  252. {
  253. ser.Value("alias", aliasTemp);
  254. m_spriteSheetCells[i].alias = aliasTemp.c_str();
  255. }
  256. else
  257. {
  258. aliasTemp = m_spriteSheetCells[i].alias.c_str();
  259. ser.Value("alias", aliasTemp);
  260. }
  261. SerializeAzVector2(ser, "topLeft", m_spriteSheetCells[i].uvCellCoords.TopLeft());
  262. SerializeAzVector2(ser, "topRight", m_spriteSheetCells[i].uvCellCoords.TopRight());
  263. SerializeAzVector2(ser, "bottomRight", m_spriteSheetCells[i].uvCellCoords.BottomRight());
  264. SerializeAzVector2(ser, "bottomLeft", m_spriteSheetCells[i].uvCellCoords.BottomLeft());
  265. if (ser.BeginOptionalGroup("Sprite", true))
  266. {
  267. ser.Value("m_left", m_spriteSheetCells[i].borders.m_left);
  268. ser.Value("m_right", m_spriteSheetCells[i].borders.m_right);
  269. ser.Value("m_top", m_spriteSheetCells[i].borders.m_top);
  270. ser.Value("m_bottom", m_spriteSheetCells[i].borders.m_bottom);
  271. ser.EndGroup();
  272. }
  273. ser.EndGroup();
  274. }
  275. ser.EndGroup();
  276. }
  277. }
  278. ////////////////////////////////////////////////////////////////////////////////////////////////////
  279. bool CSprite::SaveToXml(const AZStd::string& pathname)
  280. {
  281. // NOTE: The input pathname has to be a path that can used to save - so not an Asset ID
  282. // because of this we do not store the pathname
  283. XmlNodeRef root = GetISystem()->CreateXmlNode("Sprite");
  284. std::unique_ptr<IXmlSerializer> pSerializer(GetISystem()->GetXmlUtils()->CreateXmlSerializer());
  285. ISerialize* pWriter = pSerializer->GetWriter(root);
  286. TSerialize ser = TSerialize(pWriter);
  287. ser.Value(spriteVersionNumberTag, spriteFileVersionNumber);
  288. Serialize(ser);
  289. return root->saveToFile(pathname.c_str());
  290. }
  291. ////////////////////////////////////////////////////////////////////////////////////////////////////
  292. bool CSprite::AreBordersZeroWidth() const
  293. {
  294. return (m_borders.m_left == 0 && m_borders.m_right == 1 && m_borders.m_top == 0 && m_borders.m_bottom == 1);
  295. }
  296. ////////////////////////////////////////////////////////////////////////////////////////////////////
  297. bool CSprite::AreCellBordersZeroWidth(int cellIndex) const
  298. {
  299. if (CellIndexWithinRange(cellIndex))
  300. {
  301. return m_spriteSheetCells[cellIndex].borders.m_left == 0
  302. && m_spriteSheetCells[cellIndex].borders.m_right == 1
  303. && m_spriteSheetCells[cellIndex].borders.m_top == 0
  304. && m_spriteSheetCells[cellIndex].borders.m_bottom == 1;
  305. }
  306. else
  307. {
  308. return AreBordersZeroWidth();
  309. }
  310. }
  311. ////////////////////////////////////////////////////////////////////////////////////////////////////
  312. AZ::Vector2 CSprite::GetSize()
  313. {
  314. AZ::Data::Instance<AZ::RPI::Image> image = GetImage();
  315. if (image)
  316. {
  317. if (m_atlas)
  318. {
  319. return AZ::Vector2(static_cast<float>(m_atlasCoordinates.GetWidth()), static_cast<float>(m_atlasCoordinates.GetHeight()));
  320. }
  321. AZ::RHI::Size size = image->GetRHIImage()->GetDescriptor().m_size;
  322. return AZ::Vector2(static_cast<float>(size.m_width), static_cast<float>(size.m_height));
  323. }
  324. else
  325. {
  326. return AZ::Vector2(0.0f, 0.0f);
  327. }
  328. }
  329. ////////////////////////////////////////////////////////////////////////////////////////////////////
  330. AZ::Vector2 CSprite::GetCellSize(int cellIndex)
  331. {
  332. AZ::Vector2 textureSize(GetSize());
  333. if (CellIndexWithinRange(cellIndex))
  334. {
  335. // Assume top width is same as bottom width
  336. const float normalizedCellWidth =
  337. m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() -
  338. m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX();
  339. // Similar, assume height of cell is same for left and right sides
  340. const float normalizedCellHeight =
  341. m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() -
  342. m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY();
  343. textureSize.SetX(textureSize.GetX() * normalizedCellWidth);
  344. textureSize.SetY(textureSize.GetY() * normalizedCellHeight);
  345. }
  346. return textureSize;
  347. }
  348. ////////////////////////////////////////////////////////////////////////////////////////////////////
  349. const ISprite::SpriteSheetCellContainer& CSprite::GetSpriteSheetCells() const
  350. {
  351. return m_spriteSheetCells;
  352. }
  353. ////////////////////////////////////////////////////////////////////////////////////////////////////
  354. void CSprite::SetSpriteSheetCells(const SpriteSheetCellContainer& cells)
  355. {
  356. m_spriteSheetCells = cells;
  357. NotifyChanged();
  358. }
  359. ////////////////////////////////////////////////////////////////////////////////////////////////////
  360. void CSprite::ClearSpriteSheetCells()
  361. {
  362. m_spriteSheetCells.clear();
  363. NotifyChanged();
  364. }
  365. ////////////////////////////////////////////////////////////////////////////////////////////////////
  366. void CSprite::AddSpriteSheetCell(const SpriteSheetCell& spriteSheetCell)
  367. {
  368. m_spriteSheetCells.push_back(spriteSheetCell);
  369. NotifyChanged();
  370. }
  371. ////////////////////////////////////////////////////////////////////////////////////////////////////
  372. AZ::Vector2 CSprite::GetCellUvSize(int cellIndex) const
  373. {
  374. AZ::Vector2 result(1.0f, 1.0f);
  375. if (CellIndexWithinRange(cellIndex))
  376. {
  377. result.SetX(m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() - m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX());
  378. result.SetY(m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() - m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY());
  379. }
  380. if (m_atlas)
  381. {
  382. result.SetX(result.GetX() * m_atlasCoordinates.GetWidth() / m_atlas->GetWidth());
  383. result.SetY(result.GetY() * m_atlasCoordinates.GetHeight() / m_atlas->GetHeight());
  384. }
  385. return result;
  386. }
  387. ////////////////////////////////////////////////////////////////////////////////////////////////////
  388. UiTransformInterface::RectPoints CSprite::GetCellUvCoords(int cellIndex) const
  389. {
  390. if (CellIndexWithinRange(cellIndex))
  391. {
  392. if (m_atlas)
  393. {
  394. return UiTransformInterface::RectPoints(
  395. static_cast<float>(m_atlasCoordinates.GetLeft() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX() * m_atlasCoordinates.GetWidth()))
  396. / m_atlas->GetWidth(),
  397. static_cast<float>(m_atlasCoordinates.GetLeft() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() * m_atlasCoordinates.GetWidth()))
  398. / m_atlas->GetWidth(),
  399. static_cast<float>(m_atlasCoordinates.GetTop() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY() * m_atlasCoordinates.GetHeight()))
  400. / m_atlas->GetHeight(),
  401. static_cast<float>(m_atlasCoordinates.GetTop() + (m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() * m_atlasCoordinates.GetHeight()))
  402. / m_atlas->GetHeight());
  403. }
  404. return m_spriteSheetCells[cellIndex].uvCellCoords;
  405. }
  406. else if (m_atlas)
  407. {
  408. return UiTransformInterface::RectPoints(
  409. static_cast<float>(m_atlasCoordinates.GetLeft()) / m_atlas->GetWidth(),
  410. static_cast<float>(m_atlasCoordinates.GetRight()) / m_atlas->GetWidth(),
  411. static_cast<float>(m_atlasCoordinates.GetTop()) / m_atlas->GetHeight(),
  412. static_cast<float>(m_atlasCoordinates.GetBottom()) / m_atlas->GetHeight());
  413. }
  414. static UiTransformInterface::RectPoints rectPoints(0.0f, 1.0f, 0.0f, 1.0f);
  415. return rectPoints;
  416. }
  417. ////////////////////////////////////////////////////////////////////////////////////////////////////
  418. UiTransformInterface::RectPoints CSprite::GetSourceCellUvCoords(int cellIndex) const
  419. {
  420. if (CellIndexWithinRange(cellIndex))
  421. {
  422. return m_spriteSheetCells[cellIndex].uvCellCoords;
  423. }
  424. static UiTransformInterface::RectPoints rectPoints(0.0f, 1.0f, 0.0f, 1.0f);
  425. return rectPoints;
  426. }
  427. ////////////////////////////////////////////////////////////////////////////////////////////////////
  428. ISprite::Borders CSprite::GetCellUvBorders(int cellIndex) const
  429. {
  430. if (CellIndexWithinRange(cellIndex))
  431. {
  432. return m_spriteSheetCells[cellIndex].borders;
  433. }
  434. return m_borders;
  435. }
  436. ////////////////////////////////////////////////////////////////////////////////////////////////////
  437. ISprite::Borders CSprite::GetTextureSpaceCellUvBorders(int cellIndex) const
  438. {
  439. Borders textureSpaceBorders(m_borders);
  440. if (CellIndexWithinRange(cellIndex))
  441. {
  442. const float cellWidth = GetCellUvSize(cellIndex).GetX();
  443. const float cellNormalizedLeftBorder = GetCellUvBorders(cellIndex).m_left * cellWidth;
  444. textureSpaceBorders.m_left = cellNormalizedLeftBorder;
  445. const float cellNormalizedRightBorder = GetCellUvBorders(cellIndex).m_right * cellWidth;
  446. textureSpaceBorders.m_right = cellNormalizedRightBorder;
  447. const float cellHeight = GetCellUvSize(cellIndex).GetY();
  448. const float cellNormalizedTopBorder = GetCellUvBorders(cellIndex).m_top * cellHeight;
  449. textureSpaceBorders.m_top = cellNormalizedTopBorder;
  450. const float cellNormalizedBottomBorder = GetCellUvBorders(cellIndex).m_bottom * cellHeight;
  451. textureSpaceBorders.m_bottom = cellNormalizedBottomBorder;
  452. }
  453. return textureSpaceBorders;
  454. }
  455. ////////////////////////////////////////////////////////////////////////////////////////////////////
  456. const AZStd::string& CSprite::GetCellAlias(int cellIndex) const
  457. {
  458. if (CellIndexWithinRange(cellIndex))
  459. {
  460. return m_spriteSheetCells[cellIndex].alias;
  461. }
  462. return s_emptyString;
  463. }
  464. ////////////////////////////////////////////////////////////////////////////////////////////////////
  465. void CSprite::SetCellAlias(int cellIndex, const AZStd::string& cellAlias)
  466. {
  467. if (CellIndexWithinRange(cellIndex))
  468. {
  469. m_spriteSheetCells[cellIndex].alias = cellAlias;
  470. NotifyChanged();
  471. }
  472. }
  473. ////////////////////////////////////////////////////////////////////////////////////////////////////
  474. bool CSprite::IsSpriteSheet() const
  475. {
  476. return m_spriteSheetCells.size() > 1;
  477. }
  478. ////////////////////////////////////////////////////////////////////////////////////////////////////
  479. void CSprite::OnAtlasLoaded(const TextureAtlasNamespace::TextureAtlas* atlas)
  480. {
  481. if (!m_atlas)
  482. {
  483. m_atlasCoordinates = atlas->GetAtlasCoordinates(m_pathname.c_str());
  484. if (m_atlasCoordinates.GetWidth() > 0)
  485. {
  486. m_atlas = atlas;
  487. // Release the non-atlas version of the texture
  488. ReleaseImage(m_image);
  489. NotifyChanged();
  490. }
  491. }
  492. }
  493. ////////////////////////////////////////////////////////////////////////////////////////////////////
  494. void CSprite::OnAtlasUnloaded(const TextureAtlasNamespace::TextureAtlas* atlas)
  495. {
  496. if (atlas == m_atlas)
  497. {
  498. m_atlas = nullptr;
  499. TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(m_atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, m_pathname.c_str());
  500. if (m_atlas)
  501. {
  502. m_atlasCoordinates = m_atlas->GetAtlasCoordinates(m_pathname.c_str());
  503. }
  504. else
  505. {
  506. // No replacement atlas found
  507. // load the texture file
  508. LoadImage(m_texturePathname.c_str(), m_image);
  509. }
  510. }
  511. NotifyChanged();
  512. }
  513. ////////////////////////////////////////////////////////////////////////////////////////////////////
  514. int CSprite::GetCellIndexFromAlias(const AZStd::string& cellAlias) const
  515. {
  516. int result = 0;
  517. int indexIter = 0;
  518. for (auto spriteCell : m_spriteSheetCells)
  519. {
  520. if (cellAlias == spriteCell.alias)
  521. {
  522. result = indexIter;
  523. break;
  524. }
  525. ++indexIter;
  526. }
  527. return result;
  528. }
  529. ////////////////////////////////////////////////////////////////////////////////////////////////////
  530. // PUBLIC STATIC MEMBER FUNCTIONS
  531. ////////////////////////////////////////////////////////////////////////////////////////////////////
  532. ////////////////////////////////////////////////////////////////////////////////////////////////////
  533. void CSprite::Initialize()
  534. {
  535. s_loadedSprites = new CSpriteHashMap;
  536. }
  537. ////////////////////////////////////////////////////////////////////////////////////////////////////
  538. void CSprite::Shutdown()
  539. {
  540. delete s_loadedSprites;
  541. s_loadedSprites = nullptr;
  542. }
  543. ////////////////////////////////////////////////////////////////////////////////////////////////////
  544. CSprite* CSprite::LoadSprite(const AZStd::string& pathname)
  545. {
  546. AZStd::string spritePath;
  547. AZStd::string texturePath;
  548. bool validAssetPaths = GetSourceAssetPaths(pathname.c_str(), spritePath, texturePath);
  549. if (!validAssetPaths)
  550. {
  551. return nullptr;
  552. }
  553. // test if the sprite is already loaded, if so return loaded sprite
  554. auto result = s_loadedSprites->find(spritePath.c_str());
  555. CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
  556. if (loadedSprite)
  557. {
  558. loadedSprite->AddRef();
  559. return loadedSprite;
  560. }
  561. // Try to use a texture atlas instead
  562. TextureAtlasNamespace::TextureAtlas* atlas = nullptr;
  563. TextureAtlasNamespace::AtlasCoordinates atlasCoordinates;
  564. TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, texturePath.c_str());
  565. AZ::Data::Instance<AZ::RPI::Image> image;
  566. if (atlas)
  567. {
  568. atlasCoordinates = atlas->GetAtlasCoordinates(texturePath.c_str());
  569. }
  570. else
  571. {
  572. // load the texture file
  573. if (!LoadImage(texturePath, image))
  574. {
  575. return nullptr;
  576. }
  577. }
  578. // create Sprite object
  579. CSprite* sprite = new CSprite;
  580. sprite->m_image = image;
  581. sprite->m_pathname = spritePath.c_str();
  582. sprite->m_texturePathname = texturePath.c_str();
  583. sprite->m_atlas = atlas;
  584. sprite->m_atlasCoordinates = atlasCoordinates;
  585. // try to load the sprite side-car file if it exists, it is optional and if it does not
  586. // exist we just stay with default values
  587. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  588. if (fileIO && fileIO->Exists(sprite->m_pathname.c_str()))
  589. {
  590. sprite->LoadFromXmlFile();
  591. }
  592. // add sprite to list of loaded sprites
  593. (*s_loadedSprites)[sprite->m_pathname] = sprite;
  594. return sprite;
  595. }
  596. ////////////////////////////////////////////////////////////////////////////////////////////////////
  597. CSprite* CSprite::CreateSprite(const AZ::Data::Asset<AZ::RPI::AttachmentImageAsset>& attachmentImageAsset)
  598. {
  599. auto attachmentImage = AZ::RPI::AttachmentImage::FindOrCreate(attachmentImageAsset);
  600. if (!attachmentImage)
  601. {
  602. AZ_Warning("UI", false, "Failed to find or create render target");
  603. return nullptr;
  604. }
  605. // test if the sprite is already loaded, if so return loaded sprite
  606. auto result = s_loadedSprites->find(attachmentImage->GetAttachmentId().GetCStr());
  607. CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
  608. if (loadedSprite)
  609. {
  610. loadedSprite->AddRef();
  611. return loadedSprite;
  612. }
  613. // create Sprite object
  614. CSprite* sprite = new CSprite;
  615. sprite->m_image = attachmentImage;
  616. sprite->m_pathname = attachmentImage->GetAttachmentId().GetCStr();
  617. sprite->m_texturePathname.clear();
  618. // add sprite to list of loaded sprites
  619. (*s_loadedSprites)[sprite->m_pathname] = sprite;
  620. return sprite;
  621. }
  622. ////////////////////////////////////////////////////////////////////////////////////////////////////
  623. bool CSprite::DoesSpriteTextureAssetExist(const AZStd::string& pathname)
  624. {
  625. AZStd::string spritePath;
  626. AZStd::string texturePath;
  627. bool validAssetPaths = GetSourceAssetPaths(pathname.c_str(), spritePath, texturePath);
  628. if (!validAssetPaths)
  629. {
  630. return false;
  631. }
  632. // Check if the sprite is already loaded
  633. auto result = s_loadedSprites->find(spritePath.c_str());
  634. CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
  635. if (loadedSprite)
  636. {
  637. return true;
  638. }
  639. // Try to use a texture atlas instead
  640. TextureAtlasNamespace::TextureAtlas* atlas = nullptr;
  641. TextureAtlasNamespace::AtlasCoordinates atlasCoordinates;
  642. TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, texturePath.c_str());
  643. if (atlas)
  644. {
  645. return true;
  646. }
  647. // Check if the texture asset exists
  648. const AZStd::string cacheRelativePath = AZStd::string::format("%s.%s", texturePath.c_str(), streamingImageExtension);
  649. bool textureExists = CheckIfFileExists(texturePath, cacheRelativePath);
  650. return textureExists;
  651. }
  652. ////////////////////////////////////////////////////////////////////////////////////////////////////
  653. void CSprite::ReplaceSprite(ISprite** baseSprite, ISprite* newSprite)
  654. {
  655. if (baseSprite)
  656. {
  657. if (newSprite)
  658. {
  659. newSprite->AddRef();
  660. }
  661. SAFE_RELEASE(*baseSprite);
  662. *baseSprite = newSprite;
  663. }
  664. }
  665. ////////////////////////////////////////////////////////////////////////////////////////////////////
  666. bool CSprite::FixUpSourceImagePathFromUserDefinedPath(const AZStd::string& userDefinedPath, AZStd::string& sourceImagePath)
  667. {
  668. static const char* textureExtensions[] = { "png", "tif", "tiff", "tga", "jpg", "jpeg", "bmp", "gif" };
  669. AZStd::string sourceRelativePath(userDefinedPath);
  670. AZStd::string cacheRelativePath = AZStd::string::format("%s.%s", sourceRelativePath.c_str(), streamingImageExtension);
  671. bool textureExists = CheckIfFileExists(sourceRelativePath, cacheRelativePath);
  672. if (textureExists)
  673. {
  674. sourceImagePath = userDefinedPath;
  675. return true;
  676. }
  677. AZStd::string curSourceImagePath(userDefinedPath);
  678. for (const char* extensionReplacement : textureExtensions)
  679. {
  680. AzFramework::StringFunc::Path::ReplaceExtension(curSourceImagePath, extensionReplacement);
  681. cacheRelativePath = AZStd::string::format("%s.%s", curSourceImagePath.c_str(), streamingImageExtension);
  682. textureExists = CheckIfFileExists(curSourceImagePath, cacheRelativePath);
  683. if (textureExists)
  684. {
  685. sourceImagePath = curSourceImagePath;
  686. return true;
  687. }
  688. }
  689. return false;
  690. }
  691. ////////////////////////////////////////////////////////////////////////////////////////////////////
  692. AZStd::string CSprite::GetImageSourcePathFromProductPath(const AZStd::string& productPathname)
  693. {
  694. AZStd::string sourcePathname(productPathname);
  695. if (IsImageProductPath(sourcePathname))
  696. {
  697. AzFramework::StringFunc::Path::StripExtension(sourcePathname);
  698. }
  699. return sourcePathname;
  700. }
  701. ////////////////////////////////////////////////////////////////////////////////////////////////////
  702. bool CSprite::LoadImage(const AZStd::string& nameTex, AZ::Data::Instance<AZ::RPI::Image>& image)
  703. {
  704. AZStd::string sourceRelativePath(nameTex);
  705. AZStd::string cacheRelativePath = AZStd::string::format("%s.%s", sourceRelativePath.c_str(), streamingImageExtension);
  706. bool textureExists = CheckIfFileExists(sourceRelativePath, cacheRelativePath);
  707. if (!textureExists)
  708. {
  709. // LyShine allows passing in a .dds extension even when the actual source file
  710. // is differnt like a .tif. For the product file, we need the correct source extension
  711. // prepended to the .streamingimage extension. So if the file doesn't exist and the
  712. // extension passed in is .dds then try replacing it with .tif
  713. // LYSHINE_ATOM_TODO - to remove this conversion we will have to update the existing
  714. // .dds references in Lua scripts, prefabs etc.
  715. AZStd::string extension;
  716. AzFramework::StringFunc::Path::GetExtension(sourceRelativePath.c_str(), extension, false);
  717. if (extension == "dds")
  718. {
  719. textureExists = FixUpSourceImagePathFromUserDefinedPath(nameTex, sourceRelativePath);
  720. }
  721. }
  722. if (!textureExists)
  723. {
  724. AZ_Error("CSprite", false, "Attempted to load '%s', but it does not exist.", nameTex.c_str());
  725. return false;
  726. }
  727. // The file may not be in the AssetCatalog at this point if it is still processing or doesn't exist on disk.
  728. // Use GenerateAssetIdTEMP instead of GetAssetIdByPath so that it will return a valid AssetId anyways
  729. AZ::Data::AssetId streamingImageAssetId;
  730. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  731. streamingImageAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GenerateAssetIdTEMP,
  732. sourceRelativePath.c_str());
  733. streamingImageAssetId.m_subId = AZ::RPI::StreamingImageAsset::GetImageAssetSubId();
  734. auto streamingImageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::RPI::StreamingImageAsset>(streamingImageAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
  735. image = AZ::RPI::StreamingImage::FindOrCreate(streamingImageAsset);
  736. if (!image)
  737. {
  738. AZ_Error("CSprite", false, "Failed to find or create an image instance from image asset '%s', ID %s",
  739. streamingImageAsset.GetHint().c_str(), streamingImageAsset.GetId().ToString<AZStd::string>().c_str());
  740. return false;
  741. }
  742. return true;
  743. }
  744. ////////////////////////////////////////////////////////////////////////////////////////////////////
  745. void CSprite::ReleaseImage(AZ::Data::Instance<AZ::RPI::Image>& image)
  746. {
  747. image.reset();
  748. }
  749. ////////////////////////////////////////////////////////////////////////////////////////////////////
  750. bool CSprite::CellIndexWithinRange(int cellIndex) const
  751. {
  752. return cellIndex >= 0 && cellIndex < m_spriteSheetCells.size();
  753. }
  754. ////////////////////////////////////////////////////////////////////////////////////////////////////
  755. // PRIVATE MEMBER FUNCTIONS
  756. ////////////////////////////////////////////////////////////////////////////////////////////////////
  757. ////////////////////////////////////////////////////////////////////////////////////////////////////
  758. bool CSprite::LoadFromXmlFile()
  759. {
  760. XmlNodeRef root = GetISystem()->LoadXmlFromFile(m_pathname.c_str());
  761. if (!root)
  762. {
  763. gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
  764. m_pathname.c_str(),
  765. "No sprite file found for sprite: %s, default sprite values will be used", m_pathname.c_str());
  766. return false;
  767. }
  768. std::unique_ptr<IXmlSerializer> pSerializer(GetISystem()->GetXmlUtils()->CreateXmlSerializer());
  769. ISerialize* pReader = pSerializer->GetReader(root);
  770. TSerialize ser = TSerialize(pReader);
  771. uint32 versionNumber = spriteFileVersionNumber;
  772. ser.Value(spriteVersionNumberTag, versionNumber);
  773. const bool validVersionNumber = versionNumber >= 1 && versionNumber <= spriteFileVersionNumber;
  774. if (!validVersionNumber)
  775. {
  776. gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
  777. m_pathname.c_str(),
  778. "Unsupported version number found for sprite file: %s, default sprite values will be used",
  779. m_pathname.c_str());
  780. return false;
  781. }
  782. // The serializer doesn't seem to have good support for parsing a variable
  783. // number of tags of the same type, so we count up the children ourselves
  784. // before starting serialization.
  785. m_numSpriteSheetCellTags = GetNumSpriteSheetCellTags(root);
  786. Serialize(ser);
  787. NotifyChanged();
  788. return true;
  789. }
  790. ////////////////////////////////////////////////////////////////////////////////////////////////////
  791. void CSprite::NotifyChanged()
  792. {
  793. UiSpriteSettingsChangeNotificationBus::Event(this, &UiSpriteSettingsChangeNotificationBus::Events::OnSpriteSettingsChanged);
  794. }