ImageProcessing_Test.cpp 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  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 <AzTest/AzTest.h>
  9. #include <AzTest/Utils.h>
  10. #include <AzQtComponents/Utilities/QtPluginPaths.h>
  11. #include <AzCore/AzCore_Traits_Platform.h>
  12. #include <AzCore/Asset/AssetManager.h>
  13. #include <AzCore/Asset/AssetManagerComponent.h>
  14. #include <AzCore/Jobs/JobContext.h>
  15. #include <AzCore/Jobs/JobManager.h>
  16. #include <AzCore/Memory/Memory.h>
  17. #include <AzCore/Memory/PoolAllocator.h>
  18. #include <AzCore/Name/NameDictionary.h>
  19. #include <AzCore/RTTI/ReflectionManager.h>
  20. #include <AzCore/Serialization/DataPatch.h>
  21. #include <AzCore/Serialization/Json/JsonSystemComponent.h>
  22. #include <AzCore/Serialization/Json/RegistrationContext.h>
  23. #include <AzCore/Serialization/ObjectStream.h>
  24. #include <AzCore/Serialization/SerializeContext.h>
  25. #include <AzCore/Serialization/Utils.h>
  26. #include <AzCore/Settings/SettingsRegistryImpl.h>
  27. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  28. #include <AzFramework/IO/LocalFileIO.h>
  29. #include <Atom/ImageProcessing/ImageObject.h>
  30. #include <Atom/ImageProcessing/ImageProcessingDefines.h>
  31. #include <Processing/PixelFormatInfo.h>
  32. #include <Processing/ImageConvert.h>
  33. #include <Processing/ImageToProcess.h>
  34. #include <Processing/ImageAssetProducer.h>
  35. #include <Processing/ImageFlags.h>
  36. #include <ImageLoader/ImageLoaders.h>
  37. #include <Compressors/Compressor.h>
  38. #include <Converters/Cubemap.h>
  39. #include <BuilderSettings/BuilderSettingManager.h>
  40. #include <BuilderSettings/CubemapSettings.h>
  41. #include <BuilderSettings/PresetSettings.h>
  42. #include <Editor/EditorCommon.h>
  43. #include <Atom/RPI.Reflect/Asset/AssetHandler.h>
  44. #include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
  45. #include <Atom/RHI.Reflect/ReflectSystemComponent.h>
  46. #include <QFileInfo>
  47. #include <qdir.h>
  48. #include <QDirIterator>
  49. #include <QIODevice>
  50. #include <array>
  51. #include <utility>
  52. //Enable generate image files for result of some tests.
  53. //This is slow and only useful for debugging. This should be disabled for unit test
  54. //#define DEBUG_OUTPUT_IMAGES
  55. //There are some test functions in this test which are DISABLED. They were mainly for programming tests.
  56. //It's only recommended to enable them for programming test purpose.
  57. #include <AzCore/UnitTest/TestTypes.h>
  58. #include <ImageBuilderComponent.h>
  59. using namespace ImageProcessingAtom;
  60. namespace UnitTest
  61. {
  62. // Expose AZ::AssetManagerComponent::Reflect function for testing
  63. class MyAssetManagerComponent
  64. : public AZ::AssetManagerComponent
  65. {
  66. public:
  67. static void Reflect(ReflectContext* reflection)
  68. {
  69. AZ::AssetManagerComponent::Reflect(reflection);
  70. };
  71. };
  72. class ImageProcessingTest
  73. : public LeakDetectionFixture
  74. , public AZ::ComponentApplicationBus::Handler
  75. {
  76. public:
  77. //////////////////////////////////////////////////////////////////////////
  78. // ComponentApplicationMessages.
  79. AZ::ComponentApplication* GetApplication() override { return nullptr; }
  80. void RegisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
  81. void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
  82. void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
  83. void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
  84. void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override { }
  85. void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override { }
  86. void SignalEntityActivated(AZ::Entity*) override { }
  87. void SignalEntityDeactivated(AZ::Entity*) override { }
  88. bool AddEntity(AZ::Entity*) override { return false; }
  89. bool RemoveEntity(AZ::Entity*) override { return false; }
  90. bool DeleteEntity(const AZ::EntityId&) override { return false; }
  91. Entity* FindEntity(const AZ::EntityId&) override { return nullptr; }
  92. AZ::SerializeContext* GetSerializeContext() override { return m_context.get(); }
  93. AZ::BehaviorContext* GetBehaviorContext() override { return nullptr; }
  94. AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return m_jsonRegistrationContext.get(); }
  95. const char* GetEngineRoot() const override { return nullptr; }
  96. const char* GetExecutableFolder() const override { return nullptr; }
  97. void EnumerateEntities(const AZ::ComponentApplicationRequests::EntityCallback& /*callback*/) override {}
  98. void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override {}
  99. //////////////////////////////////////////////////////////////////////////
  100. protected:
  101. AZStd::unique_ptr<AZ::SerializeContext> m_context;
  102. AZStd::unique_ptr<AZ::JsonRegistrationContext> m_jsonRegistrationContext;
  103. AZStd::unique_ptr<AZ::JsonSystemComponent> m_jsonSystemComponent;
  104. AZStd::vector<AZStd::unique_ptr<AZ::Data::AssetHandler>> m_assetHandlers;
  105. AZ::IO::Path m_gemFolder;
  106. AZ::IO::Path m_outputRootFolder;
  107. AZ::IO::Path m_outputFolder;
  108. AZStd::unique_ptr<AZ::JobManager> m_jobManager;
  109. AZStd::unique_ptr<AZ::JobContext> m_jobContext;
  110. void SetUp() override
  111. {
  112. // Adding this handler to allow utility functions access the serialize context
  113. ComponentApplicationBus::Handler::BusConnect();
  114. AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
  115. // AssetManager required to generate image assets
  116. AZ::Data::AssetManager::Descriptor desc;
  117. AZ::Data::AssetManager::Create(desc);
  118. AZ::NameDictionary::Create();
  119. m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::ImageMipChainAssetHandler>());
  120. m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::StreamingImageAssetHandler>());
  121. m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::StreamingImagePoolAssetHandler>());
  122. BuilderSettingManager::CreateInstance();
  123. //prepare reflection
  124. m_context = AZStd::make_unique<AZ::SerializeContext>();
  125. AZ::Name::Reflect(m_context.get());
  126. BuilderPluginComponent::Reflect(m_context.get());
  127. AZ::DataPatch::Reflect(m_context.get());
  128. AZ::RHI::ReflectSystemComponent::Reflect(m_context.get());
  129. AZ::RPI::ImageMipChainAsset::Reflect(m_context.get());
  130. AZ::RPI::ImageAsset::Reflect(m_context.get());
  131. AZ::RPI::StreamingImageAsset::Reflect(m_context.get());
  132. MyAssetManagerComponent::Reflect(m_context.get());
  133. m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
  134. m_jsonSystemComponent = AZStd::make_unique<AZ::JsonSystemComponent>();
  135. m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
  136. AZ::Name::Reflect(m_jsonRegistrationContext.get());
  137. BuilderPluginComponent::Reflect(m_jsonRegistrationContext.get());
  138. // Setup job context for job system
  139. JobManagerDesc jobManagerDesc;
  140. JobManagerThreadDesc threadDesc;
  141. #if AZ_TRAIT_SET_JOB_PROCESSOR_ID
  142. threadDesc.m_cpuId = 0; // Don't set processors IDs on windows
  143. #endif
  144. uint32_t numWorkerThreads = AZStd::thread::hardware_concurrency();
  145. for (unsigned int i = 0; i < numWorkerThreads; ++i)
  146. {
  147. jobManagerDesc.m_workerThreads.push_back(threadDesc);
  148. #if AZ_TRAIT_SET_JOB_PROCESSOR_ID
  149. threadDesc.m_cpuId++;
  150. #endif
  151. }
  152. m_jobManager = AZStd::make_unique<JobManager>(jobManagerDesc);
  153. m_jobContext = AZStd::make_unique<JobContext>(*m_jobManager);
  154. JobContext::SetGlobalContext(m_jobContext.get());
  155. // Startup default local FileIO (hits OSAllocator) if not already setup.
  156. if (AZ::IO::FileIOBase::GetInstance() == nullptr)
  157. {
  158. AZ::IO::FileIOBase::SetInstance(aznew AZ::IO::LocalFileIO());
  159. }
  160. //load qt plug-ins for some image file formats support
  161. AzQtComponents::PrepareQtPaths();
  162. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  163. AZ::SettingsRegistryImpl localRegistry;
  164. localRegistry.Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, AZ::Test::GetEngineRootPath());
  165. // Merge in the o3de manifest files gem root paths to the Settings Registry
  166. // and set the ImageProcessingAtom gem as an active gem which will add it to the active gem
  167. // section of the Settings Registry as well as add a @gemroot@ alias for it
  168. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ManifestGemsPaths(localRegistry);
  169. AZ::Test::AddActiveGem("ImageProcessingAtom", localRegistry, AZ::IO::FileIOBase::GetInstance());
  170. // Validate the path to the ImageProcessingAtom Gem root is registered in the Settings Registry
  171. ASSERT_TRUE(localRegistry.Get(m_gemFolder.Native(), FixedValueString::format("%s/ImageProcessingAtom/Path",
  172. AZ::SettingsRegistryMergeUtils::ManifestGemsRootKey)));
  173. m_outputFolder = m_gemFolder/ "Code/Tests/TestAssets/temp/";
  174. m_defaultSettingFolder = m_gemFolder / "Assets/Config/";
  175. m_testFileFolder = m_gemFolder /"Code/Tests/TestAssets/";
  176. InitialImageFilenames();
  177. ImageProcessingAtomEditor::EditorHelper::InitPixelFormatString();
  178. }
  179. void TearDown() override
  180. {
  181. m_gemFolder = AZ::IO::Path{};
  182. m_outputFolder = AZ::IO::Path{};
  183. m_defaultSettingFolder = AZ::IO::Path{};
  184. m_testFileFolder = AZ::IO::Path{};
  185. m_imagFileNameMap = AZStd::map<ImageFeature, AZStd::string>();
  186. m_assetHandlers = AZStd::vector<AZStd::unique_ptr<AZ::Data::AssetHandler>>();
  187. delete AZ::IO::FileIOBase::GetInstance();
  188. AZ::IO::FileIOBase::SetInstance(nullptr);
  189. JobContext::SetGlobalContext(nullptr);
  190. m_jobContext = nullptr;
  191. m_jobManager = nullptr;
  192. m_jsonRegistrationContext->EnableRemoveReflection();
  193. m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
  194. BuilderPluginComponent::Reflect(m_jsonRegistrationContext.get());
  195. AZ::Name::Reflect(m_jsonRegistrationContext.get());
  196. m_jsonRegistrationContext->DisableRemoveReflection();
  197. m_jsonRegistrationContext.reset();
  198. m_jsonSystemComponent.reset();
  199. m_context.reset();
  200. BuilderSettingManager::DestroyInstance();
  201. CPixelFormats::DestroyInstance();
  202. AZ::NameDictionary::Destroy();
  203. AZ::Data::AssetManager::Destroy();
  204. AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
  205. ComponentApplicationBus::Handler::BusDisconnect();
  206. }
  207. //enum names for Images with specific identification
  208. enum ImageFeature
  209. {
  210. Image_20X16_RGBA8_Png = 0,
  211. Image_32X32_16bit_F_Tif,
  212. Image_32X32_32bit_F_Tif,
  213. Image_32X32_checkerboard_png,
  214. Image_32X32_halfRedHalfTransparentGreen_png,
  215. Image_200X200_RGB8_Jpg,
  216. Image_512X288_RGB8_Tga,
  217. Image_1024X1024_RGB8_Tif,
  218. Image_UpperCase_Tga,
  219. Image_512x512_normal_tiff,
  220. Image_128x128_Transparent_Tga,
  221. Image_237x177_RGB_Jpg,
  222. Image_GreyScale_Png,
  223. Image_Alpha8_64x64_Mip7_Dds,
  224. Image_BGRA_64x64_Mip7_Dds,
  225. Image_Luminance8bpp_66x33_dds,
  226. Image_BGR_64x64_dds,
  227. Image_defaultprobe_cm_1536x256_64bits_tif,
  228. Image_workshop_iblskyboxcm_exr
  229. };
  230. //image file names for testing
  231. AZStd::map<ImageFeature, AZStd::string> m_imagFileNameMap;
  232. AZ::IO::Path m_defaultSettingFolder;
  233. AZ::IO::Path m_testFileFolder;
  234. //initialize image file names for testing
  235. void InitialImageFilenames()
  236. {
  237. m_imagFileNameMap[Image_20X16_RGBA8_Png] = (m_testFileFolder / "20x16_32bit.png").Native();
  238. m_imagFileNameMap[Image_32X32_16bit_F_Tif] = (m_testFileFolder / "32x32_16bit_f.tif").Native();
  239. m_imagFileNameMap[Image_32X32_32bit_F_Tif] = (m_testFileFolder / "32x32_32bit_f.tif").Native();
  240. m_imagFileNameMap[Image_32X32_checkerboard_png] = (m_testFileFolder / "32x32_checkerboard.png").Native();
  241. m_imagFileNameMap[Image_32X32_halfRedHalfTransparentGreen_png] = (m_testFileFolder / "32x32_halfRedHalfTransparentGreen.png").Native();
  242. m_imagFileNameMap[Image_200X200_RGB8_Jpg] = (m_testFileFolder / "200x200_24bit.jpg").Native();
  243. m_imagFileNameMap[Image_512X288_RGB8_Tga] = (m_testFileFolder / "512x288_24bit.tga").Native();
  244. m_imagFileNameMap[Image_1024X1024_RGB8_Tif] = (m_testFileFolder / "1024x1024_24bit.tif").Native();
  245. m_imagFileNameMap[Image_UpperCase_Tga] = (m_testFileFolder / "uppercase.TGA").Native();
  246. m_imagFileNameMap[Image_512x512_normal_tiff] = (m_testFileFolder / "512x512_normal.tiff").Native();
  247. m_imagFileNameMap[Image_128x128_Transparent_Tga] = (m_testFileFolder / "128x128_RGBA8.tga").Native();
  248. m_imagFileNameMap[Image_237x177_RGB_Jpg] = (m_testFileFolder / "237x177_RGB.jpg").Native();
  249. m_imagFileNameMap[Image_GreyScale_Png] = (m_testFileFolder / "greyscale.png").Native();
  250. m_imagFileNameMap[Image_Alpha8_64x64_Mip7_Dds] = (m_testFileFolder / "Alpha8_64x64_Mip7.dds").Native();
  251. m_imagFileNameMap[Image_BGRA_64x64_Mip7_Dds] = (m_testFileFolder / "BGRA_64x64_MIP7.dds").Native();
  252. m_imagFileNameMap[Image_Luminance8bpp_66x33_dds] = (m_testFileFolder / "Luminance8bpp_66x33.dds").Native();
  253. m_imagFileNameMap[Image_BGR_64x64_dds] = (m_testFileFolder / "RGBA_64x64.dds").Native();
  254. m_imagFileNameMap[Image_defaultprobe_cm_1536x256_64bits_tif] = (m_testFileFolder / "defaultProbe_cm.tif").Native();
  255. m_imagFileNameMap[Image_workshop_iblskyboxcm_exr] = (m_testFileFolder / "workshop_iblskyboxcm.exr").Native();
  256. }
  257. public:
  258. void SetOutputSubFolder(const char* subFolderName)
  259. {
  260. if (subFolderName)
  261. {
  262. m_outputFolder = m_outputRootFolder / subFolderName;
  263. }
  264. else
  265. {
  266. m_outputFolder = m_outputRootFolder;
  267. }
  268. }
  269. //helper function to save an image object to a file through QtImage
  270. void SaveImageToFile([[maybe_unused]] const IImageObjectPtr imageObject, [[maybe_unused]] const AZStd::string imageName, [[maybe_unused]] AZ::u32 maxMipCnt = 100)
  271. {
  272. #ifndef DEBUG_OUTPUT_IMAGES
  273. return;
  274. #else
  275. if (imageObject == nullptr)
  276. {
  277. return;
  278. }
  279. // create dir if it doesn't exist
  280. QDir dir;
  281. QDir outputDir(m_outputFolder.c_str());
  282. if (!outputDir.exists())
  283. {
  284. dir.mkpath(m_outputFolder.c_str());
  285. }
  286. //save origin file pixel format so we could use it to generate name later
  287. EPixelFormat originPixelFormat = imageObject->GetPixelFormat();
  288. //convert to RGBA8 before can be exported.
  289. ImageToProcess imageToProcess(imageObject);
  290. imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
  291. IImageObjectPtr finalImage = imageToProcess.Get();
  292. //for each mipmap
  293. for (uint32 mip = 0; mip < finalImage->GetMipCount() && mip < maxMipCnt; mip++)
  294. {
  295. uint8* imageBuf;
  296. uint32 pitch;
  297. finalImage->GetImagePointer(mip, imageBuf, pitch);
  298. uint32 width = finalImage->GetWidth(mip);
  299. uint32 height = finalImage->GetHeight(mip);
  300. uint32 originalSize = imageObject->GetMipBufSize(mip);
  301. //generate file name
  302. char filePath[2048];
  303. azsprintf(filePath, "%s%s_%s_mip%d_%dx%d_%d.png", m_outputFolder.data(), imageName.c_str()
  304. , CPixelFormats::GetInstance().GetPixelFormatInfo(originPixelFormat)->szName
  305. , mip, width, height, originalSize);
  306. QImage qimage(imageBuf, width, height, pitch, QImage::Format_RGBA8888);
  307. qimage.save(filePath);
  308. }
  309. #endif
  310. }
  311. static bool GetComparisonResult(IImageObjectPtr image1, IImageObjectPtr image2, QString& output)
  312. {
  313. bool isImageLoaded = true;
  314. bool isDifferent = false;
  315. if (image1 == nullptr)
  316. {
  317. isImageLoaded = false;
  318. output += ",Image 1 does not exist. ";
  319. }
  320. if (image2 == nullptr)
  321. {
  322. isImageLoaded = false;
  323. output += ",Image 2 does not exist. ";
  324. }
  325. if (!isImageLoaded)
  326. {
  327. return (!image1 && !image2) ? false : true;
  328. }
  329. // Mip
  330. int mip1 = image1->GetMipCount();
  331. int mip2 = image2->GetMipCount();
  332. int mipDiff = abs(mip1 - mip2);
  333. isDifferent |= mipDiff != 0;
  334. // Format
  335. EPixelFormat format1 = image1->GetPixelFormat();
  336. EPixelFormat format2 = image2->GetPixelFormat();
  337. isDifferent |= (format1 != format2);
  338. // Flag
  339. AZ::u32 flag1 = image1->GetImageFlags();
  340. AZ::u32 flag2 = image2->GetImageFlags();
  341. isDifferent |= (flag1 != flag2);
  342. // Size
  343. int memSize1 = image1->GetTextureMemory();
  344. int memSize2 = image2->GetTextureMemory();
  345. int memDiff = abs(memSize1 - memSize2);
  346. isDifferent |= memDiff != 0;
  347. // Error
  348. float error = GetErrorBetweenImages(image1, image2);
  349. static float EPSILON = 0.000001f;
  350. isDifferent |= abs(error) >= EPSILON;
  351. output += QString(",%1/%2,%3,%4/%5,%6/%7,").arg(QString::number(mip1, 'f', 1), QString::number(mip2, 'f', 1), QString::number(mipDiff),
  352. QString(ImageProcessingAtomEditor::EditorHelper::s_PixelFormatString[format1]),
  353. QString(ImageProcessingAtomEditor::EditorHelper::s_PixelFormatString[format2]),
  354. QString::number(flag1, 16), QString::number(flag2, 16));
  355. output += QString("%1/%2,%3,%4").arg(QString(ImageProcessingAtomEditor::EditorHelper::GetFileSizeString(memSize1).c_str()),
  356. QString(ImageProcessingAtomEditor::EditorHelper::GetFileSizeString(memSize2).c_str()),
  357. QString(ImageProcessingAtomEditor::EditorHelper::GetFileSizeString(memDiff).c_str()),
  358. QString::number(error, 'f', 8));
  359. return isDifferent;
  360. }
  361. };
  362. // test CPixelFormats related functions
  363. TEST_F(ImageProcessingTest, TestPixelFormats)
  364. {
  365. CPixelFormats& pixelFormats = CPixelFormats::GetInstance();
  366. //for all the non-compressed textures, if there minimum required texture size is 1x1
  367. for (uint32 i = 0; i < ePixelFormat_Count; i++)
  368. {
  369. EPixelFormat pixelFormat = (EPixelFormat)i;
  370. if (pixelFormats.IsPixelFormatUncompressed(pixelFormat))
  371. {
  372. //square, power of 2 sizes for uncompressed format which minimum required size is 1x1
  373. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 128, 128) == 8);
  374. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 64, 64) == 7);
  375. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 4, 4) == 3);
  376. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 2, 2) == 2);
  377. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 1, 1) == 1);
  378. //non-square, power of 2 sizes for uncompressed format which minimum required size is 1x1
  379. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 128, 64) == 8);
  380. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 128, 32) == 8);
  381. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 32, 2) == 6);
  382. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 2, 1) == 2);
  383. //Non power of 2 sizes for uncompressed format which minimum required size is 1x1
  384. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 128, 64) == 8);
  385. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 128, 32) == 8);
  386. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 32, 2) == 6);
  387. ASSERT_TRUE(pixelFormats.ComputeMaxMipCount(pixelFormat, 2, 1) == 2);
  388. }
  389. }
  390. //check function IsImageSizeValid && EvaluateImageDataSize function
  391. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 2, 1, false) == false);
  392. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 16, 16, false) == true);
  393. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 16, 32, false) == true);
  394. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 34, 34, false) == false);
  395. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 256, 256, false) == true);
  396. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_ASTC_4x4, 2, 1, false) == false);
  397. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_ASTC_4x4, 16, 16, false) == true);
  398. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_ASTC_4x4, 16, 32, false) == true);
  399. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_ASTC_4x4, 34, 34, false) == true);
  400. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_ASTC_4x4, 256, 256, false) == true);
  401. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_A8, 2, 1, false) == true);
  402. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_A8, 16, 16, false) == true);
  403. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_A8, 16, 32, false) == true);
  404. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_A8, 34, 34, false) == true);
  405. ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_A8, 256, 256, false) == true);
  406. }
  407. TEST_F(ImageProcessingTest, TestCubemapLayouts)
  408. {
  409. {
  410. IImageObjectPtr srcImage(LoadImageFromFile(m_imagFileNameMap[Image_defaultprobe_cm_1536x256_64bits_tif]));
  411. ImageToProcess imageToProcess(srcImage);
  412. imageToProcess.ConvertCubemapLayout(CubemapLayoutVertical);
  413. ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) * 6 == imageToProcess.Get()->GetHeight(0));
  414. SaveImageToFile(imageToProcess.Get(), "Vertical", 100);
  415. imageToProcess.ConvertCubemapLayout(CubemapLayoutHorizontalCross);
  416. ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) * 3 == imageToProcess.Get()->GetHeight(0) * 4);
  417. SaveImageToFile(imageToProcess.Get(), "HorizontalCross", 100);
  418. imageToProcess.ConvertCubemapLayout(CubemapLayoutVerticalCross);
  419. ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) * 4 == imageToProcess.Get()->GetHeight(0) * 3);
  420. SaveImageToFile(imageToProcess.Get(), "VerticalCross", 100);
  421. imageToProcess.ConvertCubemapLayout(CubemapLayoutHorizontal);
  422. ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == imageToProcess.Get()->GetHeight(0) * 6);
  423. SaveImageToFile(imageToProcess.Get(), "VerticalHorizontal", 100);
  424. }
  425. }
  426. // test image file loading
  427. TEST_F(ImageProcessingTest, TestImageLoaders)
  428. {
  429. //file extension support for different loader
  430. ASSERT_TRUE(IsExtensionSupported("jpg") == true);
  431. ASSERT_TRUE(IsExtensionSupported("JPG") == true);
  432. ASSERT_TRUE(IsExtensionSupported(".JPG") == false);
  433. ASSERT_TRUE(IsExtensionSupported("tga") == true);
  434. ASSERT_TRUE(IsExtensionSupported("TGA") == true);
  435. ASSERT_TRUE(IsExtensionSupported("tif") == true);
  436. ASSERT_TRUE(IsExtensionSupported("tiff") == true);
  437. IImageObjectPtr img;
  438. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_1024X1024_RGB8_Tif]));
  439. ASSERT_TRUE(img != nullptr);
  440. ASSERT_TRUE(img->GetWidth(0) == 1024);
  441. ASSERT_TRUE(img->GetHeight(0) == 1024);
  442. ASSERT_TRUE(img->GetMipCount() == 1);
  443. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R8G8B8X8);
  444. //load png
  445. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_20X16_RGBA8_Png]));
  446. ASSERT_TRUE(img != nullptr);
  447. ASSERT_TRUE(img->GetWidth(0) == 20);
  448. ASSERT_TRUE(img->GetHeight(0) == 16);
  449. ASSERT_TRUE(img->GetMipCount() == 1);
  450. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R8G8B8A8);
  451. //load jpg
  452. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_200X200_RGB8_Jpg]));
  453. ASSERT_TRUE(img != nullptr);
  454. ASSERT_TRUE(img->GetWidth(0) == 200);
  455. ASSERT_TRUE(img->GetHeight(0) == 200);
  456. ASSERT_TRUE(img->GetMipCount() == 1);
  457. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R8G8B8A8);
  458. //tga
  459. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_512X288_RGB8_Tga]));
  460. ASSERT_TRUE(img != nullptr);
  461. ASSERT_TRUE(img->GetWidth(0) == 512);
  462. ASSERT_TRUE(img->GetHeight(0) == 288);
  463. ASSERT_TRUE(img->GetMipCount() == 1);
  464. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R8G8B8);
  465. // tga with transparency
  466. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_128x128_Transparent_Tga]));
  467. ASSERT_TRUE(img != nullptr);
  468. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R8G8B8A8);
  469. //image with upper case extension
  470. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_UpperCase_Tga]));
  471. ASSERT_TRUE(img != nullptr);
  472. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R8G8B8);
  473. //16bits float tif
  474. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_32X32_16bit_F_Tif]));
  475. ASSERT_TRUE(img != nullptr);
  476. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R16G16B16A16F);
  477. //32bits float tif
  478. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_32X32_32bit_F_Tif]));
  479. ASSERT_TRUE(img != nullptr);
  480. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_R32G32B32A32F);
  481. // DDS files
  482. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_Alpha8_64x64_Mip7_Dds]));
  483. ASSERT_TRUE(img != nullptr);
  484. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_A8);
  485. ASSERT_TRUE(img->GetMipCount() == 7);
  486. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_BGRA_64x64_Mip7_Dds]));
  487. ASSERT_TRUE(img != nullptr);
  488. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_B8G8R8A8);
  489. ASSERT_TRUE(img->GetMipCount() == 7);
  490. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_Luminance8bpp_66x33_dds]));
  491. ASSERT_TRUE(img != nullptr);
  492. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_A8);
  493. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_BGR_64x64_dds]));
  494. ASSERT_TRUE(img != nullptr);
  495. ASSERT_TRUE(img->GetPixelFormat() == ePixelFormat_B8G8R8);
  496. // Exr file
  497. img = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[Image_workshop_iblskyboxcm_exr]));
  498. ASSERT_TRUE(img != nullptr);
  499. }
  500. TEST_F(ImageProcessingTest, PresetSettingCopyAssignmentOperatorOverload_WithDynamicallyAllocatedSettings_ReturnsTwoSeparateAllocations)
  501. {
  502. PresetSettings presetSetting;
  503. presetSetting.m_mipmapSetting = AZStd::unique_ptr<MipmapSettings>(new MipmapSettings());
  504. presetSetting.m_cubemapSetting = AZStd::unique_ptr<CubemapSettings>(new CubemapSettings());
  505. // Explicit invoke assignment operator by splitting the operation into two lines.
  506. PresetSettings otherPresetSetting;
  507. otherPresetSetting = presetSetting;
  508. EXPECT_NE(otherPresetSetting.m_cubemapSetting, presetSetting.m_cubemapSetting);
  509. EXPECT_NE(otherPresetSetting.m_mipmapSetting, presetSetting.m_mipmapSetting);
  510. }
  511. TEST_F(ImageProcessingTest, PresetSettingCopyConstructor_WithDynamicallyAllocatedSettings_ReturnsTwoSeparateAllocations)
  512. {
  513. PresetSettings presetSetting;
  514. presetSetting.m_mipmapSetting = AZStd::unique_ptr<MipmapSettings>(new MipmapSettings());
  515. presetSetting.m_cubemapSetting = AZStd::unique_ptr<CubemapSettings>(new CubemapSettings());
  516. PresetSettings otherPresetSetting(presetSetting);
  517. EXPECT_NE(otherPresetSetting.m_cubemapSetting, presetSetting.m_cubemapSetting);
  518. EXPECT_NE(otherPresetSetting.m_mipmapSetting, presetSetting.m_mipmapSetting);
  519. }
  520. TEST_F(ImageProcessingTest, PresetSettingEqualityOperatorOverload_WithIdenticalSettings_ReturnsEquivalent)
  521. {
  522. PresetSettings presetSetting;
  523. PresetSettings otherPresetSetting(presetSetting);
  524. EXPECT_TRUE(otherPresetSetting == presetSetting);
  525. }
  526. TEST_F(ImageProcessingTest, PresetSettingEqualityOperatorOverload_WithDifferingDynamicallyAllocatedSettings_ReturnsUnequivalent)
  527. {
  528. PresetSettings presetSetting;
  529. presetSetting.m_mipmapSetting = AZStd::unique_ptr<MipmapSettings>(new MipmapSettings());
  530. presetSetting.m_mipmapSetting->m_type = MipGenType::gaussian;
  531. PresetSettings otherPresetSetting(presetSetting);
  532. otherPresetSetting.m_mipmapSetting = AZStd::unique_ptr<MipmapSettings>(new MipmapSettings());
  533. otherPresetSetting.m_mipmapSetting->m_type = MipGenType::blackmanHarris;
  534. EXPECT_FALSE(otherPresetSetting == presetSetting);
  535. }
  536. //this test is to test image data won't be lost between uncompressed formats (for low to high precision or same precision)
  537. TEST_F(ImageProcessingTest, TestConvertFormatUncompressed)
  538. {
  539. //source image
  540. IImageObjectPtr srcImage(LoadImageFromFile(m_imagFileNameMap[Image_200X200_RGB8_Jpg]));
  541. ImageToProcess imageToProcess(srcImage);
  542. //image pointers to hold precessed images for comparison
  543. IImageObjectPtr dstImage1, dstImage2, dstImage3, dstImage4, dstImage5;
  544. //compare four channels pixel formats
  545. //we will convert to target format then convert back to RGBX8 so they can compare to easy other
  546. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8A8);
  547. dstImage1 = imageToProcess.Get();
  548. imageToProcess.Set(srcImage);
  549. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R16G16B16A16);
  550. ASSERT_FALSE(srcImage->CompareImage(imageToProcess.Get())); //this is different than source image
  551. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8A8);
  552. dstImage2 = imageToProcess.Get();
  553. imageToProcess.Set(srcImage);
  554. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R16G16B16A16F);
  555. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8A8);
  556. dstImage3 = imageToProcess.Get();
  557. imageToProcess.Set(srcImage);
  558. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R32G32B32A32F);
  559. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8A8);
  560. dstImage4 = imageToProcess.Get();
  561. ASSERT_TRUE(dstImage2->CompareImage(dstImage1));
  562. ASSERT_TRUE(dstImage3->CompareImage(dstImage1));
  563. ASSERT_TRUE(dstImage4->CompareImage(dstImage1));
  564. // three channels formats
  565. imageToProcess.Set(srcImage);
  566. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  567. dstImage1 = imageToProcess.Get();
  568. imageToProcess.Set(srcImage);
  569. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R9G9B9E5);
  570. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  571. dstImage2 = imageToProcess.Get();
  572. ASSERT_TRUE(dstImage2->CompareImage(dstImage1));
  573. //convert image to all one channel formats then convert them back to RGBX8 for comparison
  574. imageToProcess.Set(srcImage);
  575. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8);
  576. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  577. dstImage1 = imageToProcess.Get();
  578. imageToProcess.Set(srcImage);
  579. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R16);
  580. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  581. dstImage2 = imageToProcess.Get();
  582. imageToProcess.Set(srcImage);
  583. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R16F);
  584. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  585. dstImage3 = imageToProcess.Get();
  586. imageToProcess.Set(srcImage);
  587. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R32F);
  588. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  589. dstImage4 = imageToProcess.Get();
  590. ASSERT_TRUE(dstImage2->CompareImage(dstImage1));
  591. ASSERT_TRUE(dstImage3->CompareImage(dstImage1));
  592. ASSERT_TRUE(dstImage4->CompareImage(dstImage1));
  593. //convert image to all two channels formats then convert them back to RGBX8 for comparison
  594. imageToProcess.Set(srcImage);
  595. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8);
  596. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  597. dstImage1 = imageToProcess.Get();
  598. imageToProcess.Set(srcImage);
  599. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R16G16);
  600. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  601. dstImage2 = imageToProcess.Get();
  602. imageToProcess.Set(srcImage);
  603. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R16G16F);
  604. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  605. dstImage3 = imageToProcess.Get();
  606. ASSERT_TRUE(dstImage2->CompareImage(dstImage1));
  607. ASSERT_TRUE(dstImage3->CompareImage(dstImage1));
  608. }
  609. TEST_F(ImageProcessingTest, TestConvertFormatCompressed)
  610. {
  611. IImageObjectPtr srcImage;
  612. //images to be tested
  613. static const int imageCount = 4;
  614. ImageFeature images[imageCount] = {
  615. Image_20X16_RGBA8_Png,
  616. Image_237x177_RGB_Jpg,
  617. Image_128x128_Transparent_Tga,
  618. Image_defaultprobe_cm_1536x256_64bits_tif};
  619. // collect all compressed pixel formats
  620. AZStd::vector<EPixelFormat> compressedFormats;
  621. for (uint32 i = 0; i < ePixelFormat_Count; i++)
  622. {
  623. EPixelFormat pixelFormat = (EPixelFormat)i;
  624. auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
  625. if (formatInfo->bCompressed)
  626. {
  627. // skip ASTC formats which are tested in TestConvertASTCCompressor
  628. if (!IsASTCFormat(pixelFormat))
  629. {
  630. compressedFormats.push_back(pixelFormat);
  631. }
  632. }
  633. }
  634. for (int imageIdx = 0; imageIdx < imageCount; imageIdx++)
  635. {
  636. //get image's name and it will be used for output file name
  637. QFileInfo fi(m_imagFileNameMap[images[imageIdx]].c_str());
  638. AZStd::string imageName = fi.baseName().toUtf8().constData();
  639. srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[images[imageIdx]]));
  640. ImageToProcess imageToProcess(srcImage);
  641. //test ConvertFormat functions against all the pixel formats
  642. for (EPixelFormat pixelFormat : compressedFormats)
  643. {
  644. //
  645. if (!CPixelFormats::GetInstance().IsImageSizeValid(pixelFormat, srcImage->GetWidth(0), srcImage->GetHeight(0), false))
  646. {
  647. continue;
  648. }
  649. #if defined(AZ_ENABLE_TRACING)
  650. auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
  651. #endif
  652. ColorSpace sourceColorSpace = srcImage->HasImageFlags(EIF_SRGBRead) ? ColorSpace::sRGB : ColorSpace::linear;
  653. ICompressorPtr compressor = ICompressor::FindCompressor(pixelFormat, sourceColorSpace, true);
  654. if (!compressor)
  655. {
  656. AZ_Warning("test", false, "unsupported format: %s", formatInfo->szName);
  657. continue;
  658. }
  659. imageToProcess.Set(srcImage);
  660. imageToProcess.ConvertFormat(pixelFormat);
  661. ASSERT_TRUE(imageToProcess.Get());
  662. ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == pixelFormat);
  663. //convert back to an uncompressed format and expect it will be successful
  664. imageToProcess.ConvertFormat(srcImage->GetPixelFormat());
  665. ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat());
  666. // Save the image to a file so we can check the visual result
  667. AZStd::string outputName = AZStd::string::format("%s_%s", imageName.c_str(), compressor->GetName());
  668. SaveImageToFile(imageToProcess.Get(), outputName, 1);
  669. }
  670. }
  671. }
  672. TEST_F(ImageProcessingTest, Test_ConvertAllAstc_Success)
  673. {
  674. // Compress/Decompress to all astc formats (LDR)
  675. auto imageIdx = Image_237x177_RGB_Jpg;
  676. IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[imageIdx]));
  677. QFileInfo fi(m_imagFileNameMap[imageIdx].c_str());
  678. AZStd::string imageName = fi.baseName().toUtf8().constData();
  679. for (uint32 i = 0; i < ePixelFormat_Count; i++)
  680. {
  681. EPixelFormat pixelFormat = (EPixelFormat)i;
  682. if (IsASTCFormat(pixelFormat))
  683. {
  684. ImageToProcess imageToProcess(srcImage);
  685. imageToProcess.ConvertFormat(pixelFormat);
  686. ASSERT_TRUE(imageToProcess.Get());
  687. ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == pixelFormat);
  688. ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == srcImage->GetWidth(0));
  689. ASSERT_TRUE(imageToProcess.Get()->GetHeight(0) == srcImage->GetHeight(0));
  690. // convert back to an uncompressed format and expect it will be successful
  691. imageToProcess.ConvertFormat(srcImage->GetPixelFormat());
  692. ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat());
  693. // save the image to a file so we can check the visual result
  694. AZStd::string outputName = AZStd::string::format("ASTC_%s", imageName.c_str());
  695. SaveImageToFile(imageToProcess.Get(), outputName, 1);
  696. }
  697. }
  698. }
  699. TEST_F(ImageProcessingTest, Test_ConvertHdrToAstc_Success)
  700. {
  701. // Compress/Decompress HDR
  702. auto imageIdx = Image_defaultprobe_cm_1536x256_64bits_tif;
  703. IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[imageIdx]));
  704. EPixelFormat dstFormat = ePixelFormat_ASTC_4x4;
  705. ImageToProcess imageToProcess(srcImage);
  706. imageToProcess.ConvertFormat(ePixelFormat_ASTC_4x4);
  707. ASSERT_TRUE(imageToProcess.Get());
  708. ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == dstFormat);
  709. ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == srcImage->GetWidth(0));
  710. ASSERT_TRUE(imageToProcess.Get()->GetHeight(0) == srcImage->GetHeight(0));
  711. //convert back to an uncompressed format and expect it will be successful
  712. imageToProcess.ConvertFormat(srcImage->GetPixelFormat());
  713. ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat());
  714. //save the image to a file so we can check the visual result
  715. SaveImageToFile(imageToProcess.Get(), "ASTC_HDR", 1);
  716. }
  717. TEST_F(ImageProcessingTest, Test_AstcNormalPreset_Success)
  718. {
  719. // Normal.preset which uses ASTC as output format
  720. // This test compress a normal texture and its mipmaps
  721. auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder.Native());
  722. ASSERT_TRUE(outcome.IsSuccess());
  723. AZStd::string inputFile;
  724. AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
  725. inputFile = m_imagFileNameMap[Image_512x512_normal_tiff];
  726. IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(inputFile));
  727. ImageConvertProcess* process = CreateImageConvertProcess(inputFile, m_outputFolder.Native(), "ios", outProducts, m_context.get());
  728. const PresetSettings* preset = &process->GetInputDesc()->m_presetSetting;
  729. if (process != nullptr)
  730. {
  731. process->ProcessAll();
  732. //get process result
  733. ASSERT_TRUE(process->IsSucceed());
  734. auto outputImage = process->GetOutputImage();
  735. ASSERT_TRUE(outputImage->GetPixelFormat() == preset->m_pixelFormat);
  736. ASSERT_TRUE(outputImage->GetWidth(0) == srcImage->GetWidth(0));
  737. ASSERT_TRUE(outputImage->GetHeight(0) == srcImage->GetHeight(0));
  738. SaveImageToFile(outputImage, "ASTC_Normal", 10);
  739. delete process;
  740. }
  741. }
  742. TEST_F(ImageProcessingTest, DISABLED_TestImageFilter)
  743. {
  744. AZStd::string testImageFile = m_imagFileNameMap[Image_1024X1024_RGB8_Tif];
  745. IImageObjectPtr srcImage, dstImage;
  746. QFileInfo fi(testImageFile.c_str());
  747. AZStd::string imageName = fi.baseName().toUtf8().constData();
  748. //load source image and convert it to RGBA32F
  749. srcImage = IImageObjectPtr(LoadImageFromFile(testImageFile));
  750. ImageToProcess imageToProcess(srcImage);
  751. imageToProcess.ConvertFormat(ePixelFormat_R32G32B32A32F);
  752. srcImage = imageToProcess.Get();
  753. //create destination image with same size and mipmaps
  754. dstImage = IImageObjectPtr(
  755. IImageObject::CreateImage(srcImage->GetWidth(0), srcImage->GetHeight(0), 3,
  756. ePixelFormat_R32G32B32A32F));
  757. //for each filters
  758. const std::array<std::pair<MipGenType, AZStd::string>, 7> allFilters =
  759. {
  760. {
  761. {MipGenType::point, "point"},
  762. {MipGenType::box, "box" },
  763. { MipGenType::triangle, "triangle" },
  764. { MipGenType::quadratic, "Quadratic" },
  765. { MipGenType::blackmanHarris, "blackmanHarris" },
  766. { MipGenType::kaiserSinc, "kaiserSinc" }
  767. }
  768. };
  769. for (std::pair<MipGenType, AZStd::string> filter : allFilters)
  770. {
  771. for (uint mip = 0; mip < dstImage->GetMipCount(); mip++)
  772. {
  773. FilterImage(filter.first, MipGenEvalType::sum,
  774. 0, 0, imageToProcess.Get(), 0, dstImage, mip, nullptr, nullptr);
  775. }
  776. SaveImageToFile(dstImage, imageName + "_" + filter.second);
  777. }
  778. }
  779. TEST_F(ImageProcessingTest, TestAverageColor)
  780. {
  781. //load builder presets
  782. auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder.Native());
  783. ASSERT_TRUE(outcome.IsSuccess());
  784. auto checkAverageColor = [&](ImageFeature figureKey, AZ::Color expectedAverage)
  785. {
  786. AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
  787. AZStd::string inputFile = m_imagFileNameMap[figureKey];
  788. ImageConvertProcess* process = CreateImageConvertProcess(inputFile, m_outputFolder.Native(), "pc", outProducts, m_context.get());
  789. if (process != nullptr)
  790. {
  791. process->ProcessAll();
  792. ASSERT_TRUE(process->IsSucceed());
  793. ASSERT_TRUE(process->GetOutputImage());
  794. ASSERT_TRUE(process->GetOutputImage()->GetAverageColor().IsClose(expectedAverage));
  795. delete process;
  796. }
  797. };
  798. checkAverageColor(Image_32X32_checkerboard_png, AZ::Color(0.5f, 0.5f, 0.5f, 1.0f));
  799. checkAverageColor(Image_32X32_halfRedHalfTransparentGreen_png, AZ::Color(1.0f, 0.0f, 0.0f, 0.5f));
  800. }
  801. TEST_F(ImageProcessingTest, TestColorSpaceConversion)
  802. {
  803. IImageObjectPtr srcImage(LoadImageFromFile(m_imagFileNameMap[Image_GreyScale_Png]));
  804. ImageToProcess imageToProcess(srcImage);
  805. imageToProcess.GammaToLinearRGBA32F(true);
  806. SaveImageToFile(imageToProcess.Get(), "GammaTolinear_DeGamma", 1);
  807. imageToProcess.LinearToGamma();
  808. SaveImageToFile(imageToProcess.Get(), "LinearToGamma_DeGamma", 1);
  809. }
  810. TEST_F(ImageProcessingTest, VerifyRestrictedPlatform)
  811. {
  812. auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder.Native());
  813. ASSERT_TRUE(outcome.IsSuccess());
  814. PlatformNameList platforms = BuilderSettingManager::Instance()->GetPlatformList();
  815. #ifndef AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
  816. EXPECT_THAT(platforms, testing::UnorderedPointwise(testing::Eq(), {"pc", "linux", "mac", "ios", "android"}));
  817. #endif //AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
  818. }
  819. //test image conversion for builder
  820. TEST_F(ImageProcessingTest, TestBuilderImageConvertor)
  821. {
  822. //load builder presets
  823. auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder.Native());
  824. ASSERT_TRUE(outcome.IsSuccess());
  825. AZStd::string inputFile;
  826. AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
  827. inputFile = m_imagFileNameMap[Image_128x128_Transparent_Tga];
  828. ImageConvertProcess* process = CreateImageConvertProcess(inputFile, m_outputFolder.Native(), "pc", outProducts, m_context.get());
  829. if (process != nullptr)
  830. {
  831. //the process can be stopped if the job is canceled or the worker is shutting down
  832. while (!process->IsFinished())
  833. {
  834. process->UpdateProcess();
  835. }
  836. //get process result
  837. ASSERT_TRUE(process->IsSucceed());
  838. SaveImageToFile(process->GetOutputImage(), "rgb", 10);
  839. process->GetAppendOutputProducts(outProducts);
  840. delete process;
  841. }
  842. }
  843. TEST_F(ImageProcessingTest, TestIblSkyboxPreset)
  844. {
  845. //load builder presets
  846. auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder.Native());
  847. ASSERT_TRUE(outcome.IsSuccess());
  848. AZStd::string inputFile;
  849. AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
  850. inputFile = m_imagFileNameMap[Image_workshop_iblskyboxcm_exr];
  851. ImageConvertProcess* process = CreateImageConvertProcess(inputFile, m_outputFolder.Native(), "pc", outProducts, m_context.get());
  852. if (process != nullptr)
  853. {
  854. process->ProcessAll();
  855. //get process result
  856. ASSERT_TRUE(process->IsSucceed());
  857. auto specularImage = process->GetOutputIBLSpecularCubemap();
  858. auto diffuseImage = process->GetOutputIBLDiffuseCubemap();
  859. ASSERT_TRUE(process->GetOutputImage());
  860. ASSERT_TRUE(specularImage);
  861. ASSERT_TRUE(diffuseImage);
  862. // output converted result if save image is enabled
  863. SaveImageToFile(process->GetOutputImage(), "ibl_skybox", 10);
  864. SaveImageToFile(specularImage, "ibl_specular", 10);
  865. SaveImageToFile(diffuseImage, "ibl_diffuse", 10);
  866. delete process;
  867. }
  868. }
  869. TEST_F(ImageProcessingTest, TextureSettingReflect_SerializingModernDataInAndOut_WritesAndParsesFileAccurately)
  870. {
  871. AZStd::string filepath = "test.xml";
  872. // Fill-in structure with test data
  873. TextureSettings fakeTextureSettings;
  874. fakeTextureSettings.m_preset = "testPreset";
  875. fakeTextureSettings.m_sizeReduceLevel = 0;
  876. fakeTextureSettings.m_suppressEngineReduce = true;
  877. fakeTextureSettings.m_enableMipmap = false;
  878. fakeTextureSettings.m_maintainAlphaCoverage = true;
  879. fakeTextureSettings.m_mipAlphaAdjust = { 0xDEAD, 0xBADBEEF, 0xBADC0DE, 0xFEEFEE, 0xBADF00D, 0xC0FFEE };
  880. fakeTextureSettings.m_mipGenEval = MipGenEvalType::max;
  881. fakeTextureSettings.m_mipGenType = MipGenType::quadratic;
  882. // Write test data to file
  883. auto writeOutcome = TextureSettings::WriteTextureSetting(filepath, fakeTextureSettings, m_context.get());
  884. EXPECT_TRUE(writeOutcome.IsSuccess());
  885. // Parse test data to file
  886. TextureSettings parsedFakeTextureSettings;
  887. auto readOutcome = TextureSettings::LoadTextureSetting(filepath, parsedFakeTextureSettings, m_context.get());
  888. EXPECT_TRUE(readOutcome.IsSuccess());
  889. EXPECT_TRUE(parsedFakeTextureSettings.Equals(fakeTextureSettings, m_context.get()));
  890. // Delete temp data
  891. AZ::IO::FileIOBase::GetInstance()->Remove(filepath.c_str());
  892. }
  893. } // UnitTest
  894. AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);