123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "Sprite.h"
- #include <CryPath.h>
- #include <ISerialize.h>
- #include <AzCore/Serialization/Locale.h>
- #include <AzFramework/API/ApplicationAPI.h>
- #include <AzFramework/Asset/AssetSystemBus.h>
- #include <LyShine/Bus/Sprite/UiSpriteBus.h>
- #include <Atom/RPI.Public/Image/StreamingImage.h>
- #include <Atom/RPI.Public/Image/AttachmentImage.h>
- #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
- #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
- namespace
- {
- const char* const spriteExtension = "sprite";
- const char* const streamingImageExtension = "streamingimage";
- // Increment this when the Sprite Serialize(TSerialize) function
- // changes to be incompatible with previous data
- uint32 spriteFileVersionNumber = 2;
- const char* spriteVersionNumberTag = "versionNumber";
- const char* allowedSpriteTextureExtensions[] = {
- "tif", "jpg", "jpeg", "tga", "bmp", "png", "gif", "dds"
- };
- const int numAllowedSpriteTextureExtensions = AZ_ARRAY_SIZE(allowedSpriteTextureExtensions);
- bool IsValidImageExtension(const AZStd::string& extension)
- {
- for (int i = 0; i < numAllowedSpriteTextureExtensions; ++i)
- {
- if (extension.compare(allowedSpriteTextureExtensions[i]) == 0)
- {
- return true;
- }
- }
- return false;
- }
- bool IsImageProductPath(const AZStd::string& pathname)
- {
- AZStd::string extension;
- AzFramework::StringFunc::Path::GetExtension(pathname.c_str(), extension, false);
- return (extension.compare(streamingImageExtension) == 0);
- }
- // 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,
- // and so that it will work before the AssetCatalog has loaded
- bool CheckIfFileExists(const AZStd::string& sourceRelativePath, const AZStd::string& cacheRelativePath)
- {
- // If the file exists, it has already been processed and does not need to be modified
- bool fileExists = AZ::IO::FileIOBase::GetInstance()->Exists(cacheRelativePath.c_str());
- if (!fileExists)
- {
- // If the texture doesn't exist check if it's queued or being compiled.
- AzFramework::AssetSystem::AssetStatus status;
- AzFramework::AssetSystemRequestBus::BroadcastResult(status, &AzFramework::AssetSystemRequestBus::Events::GetAssetStatus, sourceRelativePath);
- switch (status)
- {
- case AzFramework::AssetSystem::AssetStatus_Queued:
- case AzFramework::AssetSystem::AssetStatus_Compiling:
- case AzFramework::AssetSystem::AssetStatus_Compiled:
- case AzFramework::AssetSystem::AssetStatus_Failed:
- {
- // The file is queued, in progress, or finished processing after the initial FileIO check
- fileExists = true;
- break;
- }
- case AzFramework::AssetSystem::AssetStatus_Unknown:
- case AzFramework::AssetSystem::AssetStatus_Missing:
- default:
- {
- // The file does not exist
- fileExists = false;
- break;
- }
- }
- }
- return fileExists;
- }
- bool GetSourceAssetPaths(const AZStd::string& pathname, AZStd::string& spritePath, AZStd::string& texturePath)
- {
- // Remove product extension from the texture path if it exists
- AZStd::string sourcePathname(pathname);
- if (IsImageProductPath(pathname))
- {
- sourcePathname = CSprite::GetImageSourcePathFromProductPath(pathname);
- }
- // the input string could be in any form. So make it normalized (forward slashes and lower case)
- // NOTE: it should not be a full path at this point. If called from the UI editor it will
- // have been transformed to a game path. If being called with a hard coded path it should be a
- // game path already - it is not good for code to be using full paths.
- AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::NormalizePath, sourcePathname);
- // check the extension and work out the pathname of the sprite file and the texture file
- // currently it works if the input path is either a sprite file or a texture file
- AZStd::string extension;
- AzFramework::StringFunc::Path::GetExtension(sourcePathname.c_str(), extension, false);
- if (extension.compare(spriteExtension) == 0)
- {
- // The .sprite file has been specified
- spritePath = sourcePathname;
- // look for a texture file with the same name
- if (!CSprite::FixUpSourceImagePathFromUserDefinedPath(spritePath, texturePath))
- {
- gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
- spritePath.c_str(), "No texture file found for sprite: %s, no sprite will be used", spritePath.c_str());
- return false;
- }
- }
- else if (IsValidImageExtension(extension))
- {
- texturePath = sourcePathname;
- spritePath = sourcePathname;
- AzFramework::StringFunc::Path::ReplaceExtension(spritePath, spriteExtension);
- }
- else
- {
- gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
- pathname.c_str(), "Invalid file extension for sprite: %s, no sprite will be used", pathname.c_str());
- return false;
- }
- return true;
- }
- //! \brief Reads a Vec2 tuple (as a string) into an AZ::Vector2
- //!
- //! Example XML string data: "1.0 2.0"
- void SerializeAzVector2(TSerialize ser, const char* attributeName, AZ::Vector2& azVec2)
- {
- AZ::Locale::ScopedSerializationLocale scopedLocale;
- if (ser.IsReading())
- {
- AZStd::string stringVal;
- ser.Value(attributeName, stringVal);
- AZ::StringFunc::Replace(stringVal, ',', ' ');
- char* pEnd = nullptr;
- float uVal = strtof(stringVal.c_str(), &pEnd);
- float vVal = strtof(pEnd, nullptr);
- azVec2.Set(uVal, vVal);
- }
- else
- {
- Vec2 legacyVec2(azVec2.GetX(), azVec2.GetY());
- ser.Value(attributeName, legacyVec2);
- }
- }
- //! \return The number of child <Cell> tags off the <SpriteSheet> parent tag.
- int GetNumSpriteSheetCellTags(const XmlNodeRef& root)
- {
- int numCellTags = 0;
- XmlNodeRef spriteSheetTag = root->findChild("SpriteSheet");
- if (spriteSheetTag)
- {
- numCellTags = spriteSheetTag->getChildCount();
- }
- return numCellTags;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- // STATIC MEMBER DATA
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- CSprite::CSpriteHashMap* CSprite::s_loadedSprites;
- AZStd::string CSprite::s_emptyString;
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- // PUBLIC MEMBER FUNCTIONS
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- CSprite::CSprite()
- : m_numSpriteSheetCellTags(0)
- , m_atlas(nullptr)
- {
- AddRef();
- TextureAtlasNamespace::TextureAtlasNotificationBus::Handler::BusConnect();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- CSprite::~CSprite()
- {
- if (s_loadedSprites)
- {
- s_loadedSprites->erase(m_pathname);
- }
- TextureAtlasNamespace::TextureAtlasNotificationBus::Handler::BusDisconnect();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- const AZStd::string& CSprite::GetPathname() const
- {
- return m_pathname;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- const AZStd::string& CSprite::GetTexturePathname() const
- {
- return m_texturePathname;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- ISprite::Borders CSprite::GetBorders() const
- {
- return m_borders;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::SetBorders(Borders borders)
- {
- m_borders = borders;
- NotifyChanged();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::SetCellBorders(int cellIndex, Borders borders)
- {
- if (CellIndexWithinRange(cellIndex))
- {
- m_spriteSheetCells[cellIndex].borders = borders;
- NotifyChanged();
- }
- else
- {
- SetBorders(borders);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- AZ::Data::Instance<AZ::RPI::Image> CSprite::GetImage()
- {
- // Prioritize usage of an atlas
- if (m_atlas)
- {
- return m_atlas->GetTexture();
- }
- return m_image;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::Serialize(TSerialize ser)
- {
- // When reading, get sprite-sheet info from XML tag data, otherwise get
- // it from this sprite object directly.
- const bool hasSpriteSheetCells = ser.IsReading() ? m_numSpriteSheetCellTags > 0 : !GetSpriteSheetCells().empty();
- if (!hasSpriteSheetCells && ser.BeginOptionalGroup("Sprite", true))
- {
- ser.Value("m_left", m_borders.m_left);
- ser.Value("m_right", m_borders.m_right);
- ser.Value("m_top", m_borders.m_top);
- ser.Value("m_bottom", m_borders.m_bottom);
- ser.EndGroup();
- }
- if (hasSpriteSheetCells && ser.BeginOptionalGroup("SpriteSheet", true))
- {
- const int numSpriteSheetCells = static_cast<int>(ser.IsReading() ? m_numSpriteSheetCellTags : GetSpriteSheetCells().size());
- for (int i = 0; i < numSpriteSheetCells; ++i)
- {
- ser.BeginOptionalGroup("Cell", true);
- if (ser.IsReading())
- {
- m_spriteSheetCells.push_back(SpriteSheetCell());
- }
- AZStd::string aliasTemp;
- if (ser.IsReading())
- {
- ser.Value("alias", aliasTemp);
- m_spriteSheetCells[i].alias = aliasTemp.c_str();
- }
- else
- {
- aliasTemp = m_spriteSheetCells[i].alias.c_str();
- ser.Value("alias", aliasTemp);
- }
- SerializeAzVector2(ser, "topLeft", m_spriteSheetCells[i].uvCellCoords.TopLeft());
- SerializeAzVector2(ser, "topRight", m_spriteSheetCells[i].uvCellCoords.TopRight());
- SerializeAzVector2(ser, "bottomRight", m_spriteSheetCells[i].uvCellCoords.BottomRight());
- SerializeAzVector2(ser, "bottomLeft", m_spriteSheetCells[i].uvCellCoords.BottomLeft());
- if (ser.BeginOptionalGroup("Sprite", true))
- {
- ser.Value("m_left", m_spriteSheetCells[i].borders.m_left);
- ser.Value("m_right", m_spriteSheetCells[i].borders.m_right);
- ser.Value("m_top", m_spriteSheetCells[i].borders.m_top);
- ser.Value("m_bottom", m_spriteSheetCells[i].borders.m_bottom);
- ser.EndGroup();
- }
- ser.EndGroup();
- }
- ser.EndGroup();
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::SaveToXml(const AZStd::string& pathname)
- {
- // NOTE: The input pathname has to be a path that can used to save - so not an Asset ID
- // because of this we do not store the pathname
- XmlNodeRef root = GetISystem()->CreateXmlNode("Sprite");
- std::unique_ptr<IXmlSerializer> pSerializer(GetISystem()->GetXmlUtils()->CreateXmlSerializer());
- ISerialize* pWriter = pSerializer->GetWriter(root);
- TSerialize ser = TSerialize(pWriter);
- ser.Value(spriteVersionNumberTag, spriteFileVersionNumber);
- Serialize(ser);
- return root->saveToFile(pathname.c_str());
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::AreBordersZeroWidth() const
- {
- return (m_borders.m_left == 0 && m_borders.m_right == 1 && m_borders.m_top == 0 && m_borders.m_bottom == 1);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::AreCellBordersZeroWidth(int cellIndex) const
- {
- if (CellIndexWithinRange(cellIndex))
- {
- return m_spriteSheetCells[cellIndex].borders.m_left == 0
- && m_spriteSheetCells[cellIndex].borders.m_right == 1
- && m_spriteSheetCells[cellIndex].borders.m_top == 0
- && m_spriteSheetCells[cellIndex].borders.m_bottom == 1;
- }
- else
- {
- return AreBordersZeroWidth();
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- AZ::Vector2 CSprite::GetSize()
- {
- AZ::Data::Instance<AZ::RPI::Image> image = GetImage();
- if (image)
- {
- if (m_atlas)
- {
- return AZ::Vector2(static_cast<float>(m_atlasCoordinates.GetWidth()), static_cast<float>(m_atlasCoordinates.GetHeight()));
- }
- AZ::RHI::Size size = image->GetRHIImage()->GetDescriptor().m_size;
- return AZ::Vector2(static_cast<float>(size.m_width), static_cast<float>(size.m_height));
- }
- else
- {
- return AZ::Vector2(0.0f, 0.0f);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- AZ::Vector2 CSprite::GetCellSize(int cellIndex)
- {
- AZ::Vector2 textureSize(GetSize());
- if (CellIndexWithinRange(cellIndex))
- {
- // Assume top width is same as bottom width
- const float normalizedCellWidth =
- m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() -
- m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX();
- // Similar, assume height of cell is same for left and right sides
- const float normalizedCellHeight =
- m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() -
- m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY();
- textureSize.SetX(textureSize.GetX() * normalizedCellWidth);
- textureSize.SetY(textureSize.GetY() * normalizedCellHeight);
- }
- return textureSize;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- const ISprite::SpriteSheetCellContainer& CSprite::GetSpriteSheetCells() const
- {
- return m_spriteSheetCells;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::SetSpriteSheetCells(const SpriteSheetCellContainer& cells)
- {
- m_spriteSheetCells = cells;
- NotifyChanged();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::ClearSpriteSheetCells()
- {
- m_spriteSheetCells.clear();
- NotifyChanged();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::AddSpriteSheetCell(const SpriteSheetCell& spriteSheetCell)
- {
- m_spriteSheetCells.push_back(spriteSheetCell);
- NotifyChanged();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- AZ::Vector2 CSprite::GetCellUvSize(int cellIndex) const
- {
- AZ::Vector2 result(1.0f, 1.0f);
- if (CellIndexWithinRange(cellIndex))
- {
- result.SetX(m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() - m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX());
- result.SetY(m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() - m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY());
- }
- if (m_atlas)
- {
- result.SetX(result.GetX() * m_atlasCoordinates.GetWidth() / m_atlas->GetWidth());
- result.SetY(result.GetY() * m_atlasCoordinates.GetHeight() / m_atlas->GetHeight());
- }
- return result;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- UiTransformInterface::RectPoints CSprite::GetCellUvCoords(int cellIndex) const
- {
- if (CellIndexWithinRange(cellIndex))
- {
- if (m_atlas)
- {
- return UiTransformInterface::RectPoints(
- static_cast<float>(m_atlasCoordinates.GetLeft() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX() * m_atlasCoordinates.GetWidth()))
- / m_atlas->GetWidth(),
- static_cast<float>(m_atlasCoordinates.GetLeft() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() * m_atlasCoordinates.GetWidth()))
- / m_atlas->GetWidth(),
- static_cast<float>(m_atlasCoordinates.GetTop() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY() * m_atlasCoordinates.GetHeight()))
- / m_atlas->GetHeight(),
- static_cast<float>(m_atlasCoordinates.GetTop() + (m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() * m_atlasCoordinates.GetHeight()))
- / m_atlas->GetHeight());
- }
- return m_spriteSheetCells[cellIndex].uvCellCoords;
- }
- else if (m_atlas)
- {
- return UiTransformInterface::RectPoints(
- static_cast<float>(m_atlasCoordinates.GetLeft()) / m_atlas->GetWidth(),
- static_cast<float>(m_atlasCoordinates.GetRight()) / m_atlas->GetWidth(),
- static_cast<float>(m_atlasCoordinates.GetTop()) / m_atlas->GetHeight(),
- static_cast<float>(m_atlasCoordinates.GetBottom()) / m_atlas->GetHeight());
- }
- static UiTransformInterface::RectPoints rectPoints(0.0f, 1.0f, 0.0f, 1.0f);
- return rectPoints;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- UiTransformInterface::RectPoints CSprite::GetSourceCellUvCoords(int cellIndex) const
- {
- if (CellIndexWithinRange(cellIndex))
- {
- return m_spriteSheetCells[cellIndex].uvCellCoords;
- }
- static UiTransformInterface::RectPoints rectPoints(0.0f, 1.0f, 0.0f, 1.0f);
- return rectPoints;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- ISprite::Borders CSprite::GetCellUvBorders(int cellIndex) const
- {
- if (CellIndexWithinRange(cellIndex))
- {
- return m_spriteSheetCells[cellIndex].borders;
- }
- return m_borders;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- ISprite::Borders CSprite::GetTextureSpaceCellUvBorders(int cellIndex) const
- {
- Borders textureSpaceBorders(m_borders);
- if (CellIndexWithinRange(cellIndex))
- {
- const float cellWidth = GetCellUvSize(cellIndex).GetX();
- const float cellNormalizedLeftBorder = GetCellUvBorders(cellIndex).m_left * cellWidth;
- textureSpaceBorders.m_left = cellNormalizedLeftBorder;
- const float cellNormalizedRightBorder = GetCellUvBorders(cellIndex).m_right * cellWidth;
- textureSpaceBorders.m_right = cellNormalizedRightBorder;
- const float cellHeight = GetCellUvSize(cellIndex).GetY();
- const float cellNormalizedTopBorder = GetCellUvBorders(cellIndex).m_top * cellHeight;
- textureSpaceBorders.m_top = cellNormalizedTopBorder;
- const float cellNormalizedBottomBorder = GetCellUvBorders(cellIndex).m_bottom * cellHeight;
- textureSpaceBorders.m_bottom = cellNormalizedBottomBorder;
- }
- return textureSpaceBorders;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- const AZStd::string& CSprite::GetCellAlias(int cellIndex) const
- {
- if (CellIndexWithinRange(cellIndex))
- {
- return m_spriteSheetCells[cellIndex].alias;
- }
- return s_emptyString;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::SetCellAlias(int cellIndex, const AZStd::string& cellAlias)
- {
- if (CellIndexWithinRange(cellIndex))
- {
- m_spriteSheetCells[cellIndex].alias = cellAlias;
- NotifyChanged();
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::IsSpriteSheet() const
- {
- return m_spriteSheetCells.size() > 1;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::OnAtlasLoaded(const TextureAtlasNamespace::TextureAtlas* atlas)
- {
- if (!m_atlas)
- {
- m_atlasCoordinates = atlas->GetAtlasCoordinates(m_pathname.c_str());
- if (m_atlasCoordinates.GetWidth() > 0)
- {
- m_atlas = atlas;
- // Release the non-atlas version of the texture
- ReleaseImage(m_image);
- NotifyChanged();
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::OnAtlasUnloaded(const TextureAtlasNamespace::TextureAtlas* atlas)
- {
- if (atlas == m_atlas)
- {
- m_atlas = nullptr;
- TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(m_atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, m_pathname.c_str());
- if (m_atlas)
- {
- m_atlasCoordinates = m_atlas->GetAtlasCoordinates(m_pathname.c_str());
- }
- else
- {
- // No replacement atlas found
- // load the texture file
- LoadImage(m_texturePathname.c_str(), m_image);
- }
- }
- NotifyChanged();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- int CSprite::GetCellIndexFromAlias(const AZStd::string& cellAlias) const
- {
- int result = 0;
- int indexIter = 0;
- for (auto spriteCell : m_spriteSheetCells)
- {
- if (cellAlias == spriteCell.alias)
- {
- result = indexIter;
- break;
- }
- ++indexIter;
- }
- return result;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- // PUBLIC STATIC MEMBER FUNCTIONS
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::Initialize()
- {
- s_loadedSprites = new CSpriteHashMap;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::Shutdown()
- {
- delete s_loadedSprites;
- s_loadedSprites = nullptr;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- CSprite* CSprite::LoadSprite(const AZStd::string& pathname)
- {
- AZStd::string spritePath;
- AZStd::string texturePath;
- bool validAssetPaths = GetSourceAssetPaths(pathname.c_str(), spritePath, texturePath);
-
- if (!validAssetPaths)
- {
- return nullptr;
- }
- // test if the sprite is already loaded, if so return loaded sprite
- auto result = s_loadedSprites->find(spritePath.c_str());
- CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
- if (loadedSprite)
- {
- loadedSprite->AddRef();
- return loadedSprite;
- }
- // Try to use a texture atlas instead
- TextureAtlasNamespace::TextureAtlas* atlas = nullptr;
- TextureAtlasNamespace::AtlasCoordinates atlasCoordinates;
- TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, texturePath.c_str());
- AZ::Data::Instance<AZ::RPI::Image> image;
- if (atlas)
- {
- atlasCoordinates = atlas->GetAtlasCoordinates(texturePath.c_str());
- }
- else
- {
- // load the texture file
- if (!LoadImage(texturePath, image))
- {
- return nullptr;
- }
- }
- // create Sprite object
- CSprite* sprite = new CSprite;
- sprite->m_image = image;
- sprite->m_pathname = spritePath.c_str();
- sprite->m_texturePathname = texturePath.c_str();
- sprite->m_atlas = atlas;
- sprite->m_atlasCoordinates = atlasCoordinates;
- // try to load the sprite side-car file if it exists, it is optional and if it does not
- // exist we just stay with default values
- AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
- if (fileIO && fileIO->Exists(sprite->m_pathname.c_str()))
- {
- sprite->LoadFromXmlFile();
- }
- // add sprite to list of loaded sprites
- (*s_loadedSprites)[sprite->m_pathname] = sprite;
- return sprite;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- CSprite* CSprite::CreateSprite(const AZ::Data::Asset<AZ::RPI::AttachmentImageAsset>& attachmentImageAsset)
- {
- auto attachmentImage = AZ::RPI::AttachmentImage::FindOrCreate(attachmentImageAsset);
- if (!attachmentImage)
- {
- AZ_Warning("UI", false, "Failed to find or create render target");
- return nullptr;
- }
- // test if the sprite is already loaded, if so return loaded sprite
- auto result = s_loadedSprites->find(attachmentImage->GetAttachmentId().GetCStr());
- CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
- if (loadedSprite)
- {
- loadedSprite->AddRef();
- return loadedSprite;
- }
- // create Sprite object
- CSprite* sprite = new CSprite;
- sprite->m_image = attachmentImage;
- sprite->m_pathname = attachmentImage->GetAttachmentId().GetCStr();
- sprite->m_texturePathname.clear();
- // add sprite to list of loaded sprites
- (*s_loadedSprites)[sprite->m_pathname] = sprite;
- return sprite;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::DoesSpriteTextureAssetExist(const AZStd::string& pathname)
- {
- AZStd::string spritePath;
- AZStd::string texturePath;
- bool validAssetPaths = GetSourceAssetPaths(pathname.c_str(), spritePath, texturePath);
- if (!validAssetPaths)
- {
- return false;
- }
- // Check if the sprite is already loaded
- auto result = s_loadedSprites->find(spritePath.c_str());
- CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
- if (loadedSprite)
- {
- return true;
- }
- // Try to use a texture atlas instead
- TextureAtlasNamespace::TextureAtlas* atlas = nullptr;
- TextureAtlasNamespace::AtlasCoordinates atlasCoordinates;
- TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, texturePath.c_str());
- if (atlas)
- {
- return true;
- }
- // Check if the texture asset exists
- const AZStd::string cacheRelativePath = AZStd::string::format("%s.%s", texturePath.c_str(), streamingImageExtension);
- bool textureExists = CheckIfFileExists(texturePath, cacheRelativePath);
- return textureExists;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::ReplaceSprite(ISprite** baseSprite, ISprite* newSprite)
- {
- if (baseSprite)
- {
- if (newSprite)
- {
- newSprite->AddRef();
- }
- SAFE_RELEASE(*baseSprite);
- *baseSprite = newSprite;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::FixUpSourceImagePathFromUserDefinedPath(const AZStd::string& userDefinedPath, AZStd::string& sourceImagePath)
- {
- static const char* textureExtensions[] = { "png", "tif", "tiff", "tga", "jpg", "jpeg", "bmp", "gif" };
- AZStd::string sourceRelativePath(userDefinedPath);
- AZStd::string cacheRelativePath = AZStd::string::format("%s.%s", sourceRelativePath.c_str(), streamingImageExtension);
- bool textureExists = CheckIfFileExists(sourceRelativePath, cacheRelativePath);
- if (textureExists)
- {
- sourceImagePath = userDefinedPath;
- return true;
- }
- AZStd::string curSourceImagePath(userDefinedPath);
- for (const char* extensionReplacement : textureExtensions)
- {
- AzFramework::StringFunc::Path::ReplaceExtension(curSourceImagePath, extensionReplacement);
- cacheRelativePath = AZStd::string::format("%s.%s", curSourceImagePath.c_str(), streamingImageExtension);
- textureExists = CheckIfFileExists(curSourceImagePath, cacheRelativePath);
- if (textureExists)
- {
- sourceImagePath = curSourceImagePath;
- return true;
- }
- }
- return false;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- AZStd::string CSprite::GetImageSourcePathFromProductPath(const AZStd::string& productPathname)
- {
- AZStd::string sourcePathname(productPathname);
- if (IsImageProductPath(sourcePathname))
- {
- AzFramework::StringFunc::Path::StripExtension(sourcePathname);
- }
- return sourcePathname;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::LoadImage(const AZStd::string& nameTex, AZ::Data::Instance<AZ::RPI::Image>& image)
- {
- AZStd::string sourceRelativePath(nameTex);
- AZStd::string cacheRelativePath = AZStd::string::format("%s.%s", sourceRelativePath.c_str(), streamingImageExtension);
- bool textureExists = CheckIfFileExists(sourceRelativePath, cacheRelativePath);
- if (!textureExists)
- {
- // LyShine allows passing in a .dds extension even when the actual source file
- // is differnt like a .tif. For the product file, we need the correct source extension
- // prepended to the .streamingimage extension. So if the file doesn't exist and the
- // extension passed in is .dds then try replacing it with .tif
- // LYSHINE_ATOM_TODO - to remove this conversion we will have to update the existing
- // .dds references in Lua scripts, prefabs etc.
- AZStd::string extension;
- AzFramework::StringFunc::Path::GetExtension(sourceRelativePath.c_str(), extension, false);
- if (extension == "dds")
- {
- textureExists = FixUpSourceImagePathFromUserDefinedPath(nameTex, sourceRelativePath);
- }
- }
- if (!textureExists)
- {
- AZ_Error("CSprite", false, "Attempted to load '%s', but it does not exist.", nameTex.c_str());
- return false;
- }
- // The file may not be in the AssetCatalog at this point if it is still processing or doesn't exist on disk.
- // Use GenerateAssetIdTEMP instead of GetAssetIdByPath so that it will return a valid AssetId anyways
- AZ::Data::AssetId streamingImageAssetId;
- AZ::Data::AssetCatalogRequestBus::BroadcastResult(
- streamingImageAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GenerateAssetIdTEMP,
- sourceRelativePath.c_str());
- streamingImageAssetId.m_subId = AZ::RPI::StreamingImageAsset::GetImageAssetSubId();
- auto streamingImageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::RPI::StreamingImageAsset>(streamingImageAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
- image = AZ::RPI::StreamingImage::FindOrCreate(streamingImageAsset);
- if (!image)
- {
- AZ_Error("CSprite", false, "Failed to find or create an image instance from image asset '%s', ID %s",
- streamingImageAsset.GetHint().c_str(), streamingImageAsset.GetId().ToString<AZStd::string>().c_str());
- return false;
- }
- return true;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::ReleaseImage(AZ::Data::Instance<AZ::RPI::Image>& image)
- {
- image.reset();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::CellIndexWithinRange(int cellIndex) const
- {
- return cellIndex >= 0 && cellIndex < m_spriteSheetCells.size();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- // PRIVATE MEMBER FUNCTIONS
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- bool CSprite::LoadFromXmlFile()
- {
- XmlNodeRef root = GetISystem()->LoadXmlFromFile(m_pathname.c_str());
- if (!root)
- {
- gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
- m_pathname.c_str(),
- "No sprite file found for sprite: %s, default sprite values will be used", m_pathname.c_str());
- return false;
- }
- std::unique_ptr<IXmlSerializer> pSerializer(GetISystem()->GetXmlUtils()->CreateXmlSerializer());
- ISerialize* pReader = pSerializer->GetReader(root);
- TSerialize ser = TSerialize(pReader);
- uint32 versionNumber = spriteFileVersionNumber;
- ser.Value(spriteVersionNumberTag, versionNumber);
- const bool validVersionNumber = versionNumber >= 1 && versionNumber <= spriteFileVersionNumber;
- if (!validVersionNumber)
- {
- gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
- m_pathname.c_str(),
- "Unsupported version number found for sprite file: %s, default sprite values will be used",
- m_pathname.c_str());
- return false;
- }
- // The serializer doesn't seem to have good support for parsing a variable
- // number of tags of the same type, so we count up the children ourselves
- // before starting serialization.
- m_numSpriteSheetCellTags = GetNumSpriteSheetCellTags(root);
- Serialize(ser);
- NotifyChanged();
- return true;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- void CSprite::NotifyChanged()
- {
- UiSpriteSettingsChangeNotificationBus::Event(this, &UiSpriteSettingsChangeNotificationBus::Events::OnSpriteSettingsChanged);
- }
|