Sprite.cpp 34 KB

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