ImageConvert.cpp 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140
  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 <Processing/PixelFormatInfo.h>
  9. #include <Processing/ImageToProcess.h>
  10. #include <Processing/ImageConvert.h>
  11. #include <Processing/ImageAssetProducer.h>
  12. #include <Processing/ImageFlags.h>
  13. #include <Converters/FIR-Weights.h>
  14. #include <Converters/Cubemap.h>
  15. #include <Converters/PixelOperation.h>
  16. #include <Converters/Histogram.h>
  17. #include <ImageLoader/ImageLoaders.h>
  18. #include <BuilderSettings/BuilderSettingManager.h>
  19. #include <BuilderSettings/PresetSettings.h>
  20. #include <AzFramework/StringFunc/StringFunc.h>
  21. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  22. // for texture splitting
  23. // minimum number of low level mips will be saved in the base file.
  24. #define MinPersistantMips 3
  25. // minimum texture size to be splitted. A texture will only be split when the size is larger than this number
  26. #define MinSizeToSplit 1 << 5
  27. #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
  28. #if defined(TOOLS_SUPPORT_JASPER)
  29. #include AZ_RESTRICTED_FILE_EXPLICIT(ImageProcess, Jasper)
  30. #endif
  31. #if defined(TOOLS_SUPPORT_PROVO)
  32. #include AZ_RESTRICTED_FILE_EXPLICIT(ImageProcess, Provo)
  33. #endif
  34. #if defined(TOOLS_SUPPORT_SALEM)
  35. #include AZ_RESTRICTED_FILE_EXPLICIT(ImageProcess, Salem)
  36. #endif
  37. #endif
  38. namespace ImageProcessingAtom
  39. {
  40. enum ConvertStep
  41. {
  42. StepValidateInput = 0,
  43. StepGenerateColorChart,
  44. StepConvertToLinear,
  45. StepSwizzle,
  46. StepCubemapLayout,
  47. StepPreNormalize,
  48. StepGenerateIBL,
  49. StepMipmap,
  50. StepGlossFromNormal,
  51. StepPostNormalize,
  52. StepCreateHighPass,
  53. StepConvertOutputColorSpace,
  54. StepAlphaImage,
  55. StepConvertPixelFormat,
  56. StepSaveToFile,
  57. StepAll
  58. };
  59. [[maybe_unused]] const char ProcessStepNames[StepAll][64] =
  60. {
  61. "ValidateInput",
  62. "GenerateColorChart",
  63. "ConvertToLinear",
  64. "Swizzle",
  65. "CubemapLayout",
  66. "PreNormalize",
  67. "GenerateIBL",
  68. "Mipmap",
  69. "GlossFromNormal",
  70. "PostNormalize",
  71. "CreateHighPass",
  72. "ConvertOutputColorSpace",
  73. "AlphaImage",
  74. "ConvertPixelFormat",
  75. "SaveToFile",
  76. };
  77. const char* SpecularCubemapSuffix = "_iblspecular";
  78. const char* DiffuseCubemapSuffix = "_ibldiffuse";
  79. IImageObjectPtr ImageConvertProcess::GetOutputImage()
  80. {
  81. if (m_image)
  82. {
  83. return m_image->Get();
  84. }
  85. return nullptr;
  86. }
  87. IImageObjectPtr ImageConvertProcess::GetOutputAlphaImage()
  88. {
  89. return m_alphaImage;
  90. }
  91. IImageObjectPtr ImageConvertProcess::GetOutputIBLSpecularCubemap()
  92. {
  93. return m_iblSpecularCubemapImage;
  94. }
  95. IImageObjectPtr ImageConvertProcess::GetOutputIBLDiffuseCubemap()
  96. {
  97. return m_iblDiffuseCubemapImage;
  98. }
  99. void ImageConvertProcess::GetAppendOutputProducts(AZStd::vector<AssetBuilderSDK::JobProduct>& outProducts)
  100. {
  101. for (const auto& path : m_jobProducts)
  102. {
  103. outProducts.push_back(path);
  104. }
  105. }
  106. const ImageConvertProcessDescriptor* ImageConvertProcess::GetInputDesc() const
  107. {
  108. return m_input.get();
  109. }
  110. ImageConvertProcess::ImageConvertProcess(AZStd::unique_ptr<ImageConvertProcessDescriptor>&& descriptor)
  111. : m_image(nullptr)
  112. , m_progressStep(0)
  113. , m_isFinished(false)
  114. , m_isSucceed(false)
  115. , m_processTime(0)
  116. {
  117. m_input = AZStd::move(descriptor);
  118. }
  119. ImageConvertProcess::~ImageConvertProcess()
  120. {
  121. delete m_image;
  122. }
  123. bool ImageConvertProcess::IsConvertToCubemap()
  124. {
  125. return m_input->m_presetSetting.m_cubemapSetting != nullptr;
  126. }
  127. bool ImageConvertProcess::IsPreconvolvedCubemap()
  128. {
  129. AZStd::unique_ptr<CubemapSettings>& cubemapSettings = m_input->m_presetSetting.m_cubemapSetting;
  130. return (cubemapSettings != nullptr && cubemapSettings->m_requiresConvolve == false);
  131. }
  132. void ImageConvertProcess::UpdateProcess()
  133. {
  134. if (m_isFinished)
  135. {
  136. return;
  137. }
  138. auto stepStartTime = AZStd::GetTimeUTCMilliSecond();
  139. switch (m_progressStep)
  140. {
  141. case StepValidateInput:
  142. // validate
  143. if (!ValidateInput())
  144. {
  145. m_isSucceed = false;
  146. break;
  147. }
  148. // set start time
  149. m_startTime = AZStd::GetTimeUTCMilliSecond();
  150. // identify the alpha content of input image if gloss from normal wasn't set
  151. m_alphaContent = m_input->m_inputImage->GetAlphaContent();
  152. // Create image for process.
  153. // If this is not a pre-convolved cubemap we only copy the highest mip until we figure out what to do with input's mipmaps.
  154. {
  155. uint32 mipsToClone = IsPreconvolvedCubemap() ? (std::numeric_limits<uint32>::max)() : 1;
  156. m_image = new ImageToProcess(IImageObjectPtr(m_input->m_inputImage->Clone(mipsToClone)));
  157. }
  158. break;
  159. case StepGenerateIBL:
  160. if (IsConvertToCubemap())
  161. {
  162. // check and generate IBL specular and diffuse, if necessary
  163. AZStd::unique_ptr<CubemapSettings>& cubemapSettings = m_input->m_presetSetting.m_cubemapSetting;
  164. if (cubemapSettings->m_generateIBLSpecular && !cubemapSettings->m_iblSpecularPreset.IsEmpty())
  165. {
  166. CreateIBLCubemap(cubemapSettings->m_iblSpecularPreset, SpecularCubemapSuffix, m_iblSpecularCubemapImage);
  167. }
  168. if (cubemapSettings->m_generateIBLDiffuse && !cubemapSettings->m_iblDiffusePreset.IsEmpty())
  169. {
  170. CreateIBLCubemap(cubemapSettings->m_iblDiffusePreset, DiffuseCubemapSuffix, m_iblDiffuseCubemapImage);
  171. }
  172. }
  173. if (m_input->m_presetSetting.m_generateIBLOnly)
  174. {
  175. // this preset doesn't output an image of its own, just the IBL cubemaps
  176. m_isSucceed = true;
  177. m_isFinished = true;
  178. }
  179. break;
  180. case StepGenerateColorChart:
  181. // GenerateColorChart.
  182. if (m_input->m_presetSetting.m_isColorChart)
  183. {
  184. // Convert to uncompressed format if it's compressed format. For example, loaded from DDS file.
  185. if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_image->Get()->GetPixelFormat()))
  186. {
  187. m_image->ConvertFormat(ePixelFormat_R32G32B32A32F);
  188. }
  189. m_image->CreateColorChart();
  190. }
  191. break;
  192. case StepConvertToLinear:
  193. // convert to linear space and the output image pixel format should be rgba32f
  194. ConvertToLinear();
  195. break;
  196. case StepSwizzle:
  197. // convert texture format.
  198. if (m_input->m_presetSetting.m_swizzle.size() >= 4)
  199. {
  200. m_image->Get()->Swizzle(m_input->m_presetSetting.m_swizzle.substr(0, 4).c_str());
  201. m_alphaContent = m_image->Get()->GetAlphaContent();
  202. }
  203. // convert gloss map (alhpa channel) from legacy distribution to new one
  204. if (m_input->m_presetSetting.m_isLegacyGloss)
  205. {
  206. m_image->Get()->ConvertLegacyGloss();
  207. }
  208. break;
  209. case StepCubemapLayout:
  210. // convert cubemap image's layout to vertical strip used in game.
  211. if (IsConvertToCubemap())
  212. {
  213. if (!m_image->ConvertCubemapLayout(CubemapLayoutVertical))
  214. {
  215. m_image->Set(nullptr);
  216. }
  217. }
  218. break;
  219. case StepPreNormalize:
  220. // normalize base image before mipmap generation if glossfromnormals is enabled and require normalize
  221. if (m_input->m_presetSetting.m_isMipRenormalize && m_input->m_presetSetting.m_glossFromNormals)
  222. {
  223. // Normalize the base mip map. This has to be done explicitly because we need to disable mip renormalization to
  224. // preserve the normal length when deriving the normal variance
  225. m_image->Get()->NormalizeVectors(0, 1);
  226. }
  227. break;
  228. case StepMipmap:
  229. // generate mipmaps
  230. if (IsConvertToCubemap())
  231. {
  232. if (m_input->m_presetSetting.m_cubemapSetting->m_requiresConvolve)
  233. {
  234. FillCubemapMipmaps();
  235. }
  236. }
  237. else
  238. {
  239. FillMipmaps();
  240. }
  241. // add image flag
  242. if (m_input->m_presetSetting.m_suppressEngineReduce || m_input->m_textureSetting.m_suppressEngineReduce)
  243. {
  244. m_image->Get()->AddImageFlags(EIF_SupressEngineReduce);
  245. }
  246. break;
  247. case StepGlossFromNormal:
  248. // get gloss from normal for all mipmaps and save to alpha channel
  249. if (m_input->m_presetSetting.m_glossFromNormals)
  250. {
  251. bool hasAlpha = (m_alphaContent == EAlphaContent::eAlphaContent_OnlyBlack
  252. || m_alphaContent == EAlphaContent::eAlphaContent_OnlyBlackAndWhite
  253. || m_alphaContent == EAlphaContent::eAlphaContent_Greyscale);
  254. m_image->Get()->GlossFromNormals(hasAlpha);
  255. // set alpha content so it won't be ignored later.
  256. m_alphaContent = EAlphaContent::eAlphaContent_Greyscale;
  257. }
  258. break;
  259. case StepPostNormalize:
  260. // normalize all the other mipmaps
  261. if (!IsConvertToCubemap() && m_input->m_presetSetting.m_isMipRenormalize)
  262. {
  263. if (m_input->m_presetSetting.m_glossFromNormals)
  264. {
  265. // normalize other mips except first mip
  266. m_image->Get()->NormalizeVectors(1, 100);
  267. }
  268. else
  269. {
  270. // normalize all mips
  271. m_image->Get()->NormalizeVectors(0, 100);
  272. }
  273. m_image->Get()->AddImageFlags(EIF_RenormalizedTexture);
  274. }
  275. break;
  276. case StepCreateHighPass:
  277. if (m_input->m_presetSetting.m_highPassMip > 0)
  278. {
  279. m_image->CreateHighPass(m_input->m_presetSetting.m_highPassMip);
  280. }
  281. break;
  282. case StepConvertOutputColorSpace:
  283. // convert image from linear space to desired output color space
  284. ConvertToOuputColorSpace();
  285. break;
  286. case StepAlphaImage:
  287. // save alpha channel to separate image if it's needed
  288. CreateAlphaImage();
  289. break;
  290. case StepConvertPixelFormat:
  291. // convert pixel format
  292. ConvertPixelformat();
  293. break;
  294. case StepSaveToFile:
  295. // save to file
  296. if (!m_input->m_isPreview)
  297. {
  298. m_isSucceed = SaveOutput();
  299. }
  300. else
  301. {
  302. m_isSucceed = true;
  303. }
  304. break;
  305. }
  306. auto stepEndTime = AZStd::GetTimeUTCMilliSecond();
  307. if (stepEndTime - stepStartTime > 1000)
  308. {
  309. AZ_TracePrintf("Image Processing", "Step [%s] took %f seconds\n", ProcessStepNames[m_progressStep],
  310. (stepEndTime - stepStartTime) / 1000.0);
  311. }
  312. m_progressStep++;
  313. if (m_image == nullptr || m_image->Get() == nullptr || m_progressStep >= StepAll)
  314. {
  315. m_isFinished = true;
  316. AZStd::sys_time_t endTime = AZStd::GetTimeUTCMilliSecond();
  317. m_processTime = static_cast<double>(endTime - m_startTime) / 1000.0;
  318. }
  319. // output conversion log
  320. if (m_isSucceed && m_isFinished)
  321. {
  322. [[maybe_unused]] const uint32 sizeTotal = m_image->Get()->GetTextureMemory();
  323. if (m_input->m_isPreview)
  324. {
  325. AZ_TracePrintf("Image Processing", "Image (%d bytes) converted in %f seconds\n", sizeTotal, m_processTime);
  326. }
  327. else if (m_input->m_presetSetting.m_generateIBLOnly)
  328. {
  329. AZ_TracePrintf("Image Processing", "Image (IBL Only) processed in %f seconds\n", m_processTime);
  330. }
  331. else
  332. {
  333. AZ_TracePrintf("Image Processing", "Image converted with preset [%s] [%s] and saved to [%s] (%d bytes) taking %f seconds\n",
  334. m_input->m_presetSetting.m_name.GetCStr(),
  335. m_input->m_filePath.c_str(),
  336. m_input->m_outputFolder.c_str(), sizeTotal, m_processTime);
  337. }
  338. }
  339. }
  340. void ImageConvertProcess::ProcessAll()
  341. {
  342. while (!m_isFinished)
  343. {
  344. UpdateProcess();
  345. }
  346. }
  347. float ImageConvertProcess::GetProgress()
  348. {
  349. return m_progressStep / (float)StepAll;
  350. }
  351. bool ImageConvertProcess::IsFinished()
  352. {
  353. return m_isFinished;
  354. }
  355. bool ImageConvertProcess::IsSucceed()
  356. {
  357. return m_isSucceed;
  358. }
  359. // function to get desired output image extent
  360. void GetOutputExtent(AZ::u32 inputWidth, AZ::u32 inputHeight, AZ::u32& outWidth, AZ::u32& outHeight, AZ::u32& outReduce,
  361. const TextureSettings* textureSettings, const PresetSettings* presetSettings)
  362. {
  363. AZ_Assert(&outWidth != &outHeight, "outWidth and outHeight shouldn't use same address");
  364. outWidth = inputWidth;
  365. outHeight = inputHeight;
  366. if (textureSettings == nullptr || presetSettings == nullptr)
  367. {
  368. return;
  369. }
  370. // don't do any reduce for color chart
  371. if (presetSettings->m_isColorChart)
  372. {
  373. return;
  374. }
  375. // get suitable size for dest pixel format
  376. CPixelFormats::GetInstance().GetSuitableImageSize(presetSettings->m_pixelFormat, inputWidth, inputHeight,
  377. outWidth, outHeight);
  378. // desired reduce level. 1 means reduce one level
  379. uint sizeReduceLevel = textureSettings->m_sizeReduceLevel;
  380. outReduce = 0;
  381. // reduce to not exceed max texture size
  382. if (presetSettings->m_maxTextureSize > 0)
  383. {
  384. while (outWidth > presetSettings->m_maxTextureSize || outHeight > presetSettings->m_maxTextureSize)
  385. {
  386. outWidth >>= 1;
  387. outHeight >>= 1;
  388. outReduce++;
  389. }
  390. }
  391. // if it requires to reduce more and the result size will still larger than min texture size, then reduce
  392. while (outReduce < sizeReduceLevel &&
  393. (outWidth >= presetSettings->m_minTextureSize * 2 && outHeight >= presetSettings->m_minTextureSize * 2))
  394. {
  395. outWidth >>= 1;
  396. outHeight >>= 1;
  397. outReduce++;
  398. }
  399. }
  400. bool ImageConvertProcess::ConvertToLinear()
  401. {
  402. // de-gamma only if the input is sRGB. this will convert other uncompressed format to RGBA32F
  403. return m_image->GammaToLinearRGBA32F(m_input->m_presetSetting.m_srcColorSpace == ColorSpace::sRGB);
  404. }
  405. // mipmap generation
  406. bool ImageConvertProcess::FillMipmaps()
  407. {
  408. //this function only works with pixel format rgba32f
  409. const EPixelFormat srcPixelFormat = m_image->Get()->GetPixelFormat();
  410. if (srcPixelFormat != ePixelFormat_R32G32B32A32F)
  411. {
  412. AZ_Assert(false, "%s only works with pixel format rgba32f", __FUNCTION__);
  413. return false;
  414. }
  415. // only if the src image has one mip
  416. if (m_image->Get()->GetMipCount() != 1)
  417. {
  418. AZ_Assert(false, "%s called for a mipmapped image. ", __FUNCTION__);
  419. return false;
  420. }
  421. // get output image size
  422. uint32 outWidth;
  423. uint32 outHeight;
  424. uint32 outReduce = 0;
  425. GetOutputExtent(m_image->Get()->GetWidth(0), m_image->Get()->GetHeight(0), outWidth, outHeight, outReduce, &m_input->m_textureSetting,
  426. &m_input->m_presetSetting);
  427. // max mipmap count
  428. uint32 mipCount = UINT32_MAX;
  429. if (m_input->m_presetSetting.m_mipmapSetting == nullptr || !m_input->m_textureSetting.m_enableMipmap)
  430. {
  431. mipCount = 1;
  432. }
  433. // create new new output image with proper side
  434. IImageObjectPtr outImage(IImageObject::CreateImage(outWidth, outHeight, mipCount, ePixelFormat_R32G32B32A32F));
  435. // filter setting for mip map generation
  436. float blurH = 0;
  437. float blurV = 0;
  438. // fill mipmap data for uncompressed output image
  439. for (uint32 mip = 0; mip < outImage->GetMipCount(); mip++)
  440. {
  441. FilterImage(m_input->m_textureSetting.m_mipGenType, m_input->m_textureSetting.m_mipGenEval, blurH, blurV, m_image->Get(), 0, outImage, mip, nullptr, nullptr);
  442. }
  443. // transfer alpha coverage
  444. if (m_input->m_textureSetting.m_maintainAlphaCoverage)
  445. {
  446. outImage->TransferAlphaCoverage(&m_input->m_textureSetting, m_image->Get());
  447. }
  448. // set back to image
  449. m_image->Set(outImage);
  450. return true;
  451. }
  452. void ImageConvertProcess::CreateAlphaImage()
  453. {
  454. // if alpha content doesn't have alpha or we need to discard alpha, skip
  455. // we won't create alpha image for cubemap too
  456. if (m_alphaContent == EAlphaContent::eAlphaContent_Absent
  457. || m_alphaContent == EAlphaContent::eAlphaContent_OnlyWhite
  458. || m_input->m_presetSetting.m_discardAlpha || IsConvertToCubemap())
  459. {
  460. return;
  461. }
  462. // if dest format could save alpha, skip too
  463. if (!CPixelFormats::GetInstance().IsPixelFormatWithoutAlpha(m_input->m_presetSetting.m_pixelFormat))
  464. {
  465. return;
  466. }
  467. // now create alpha image
  468. ImageToProcess alphaImage(m_image->Get());
  469. alphaImage.ConvertFormat(ePixelFormat_A8);
  470. // validate pixelformatalpha
  471. if (CPixelFormats::GetInstance().IsFormatSingleChannel(m_input->m_presetSetting.m_pixelFormatAlpha))
  472. {
  473. alphaImage.ConvertFormat(m_input->m_presetSetting.m_pixelFormatAlpha);
  474. }
  475. else
  476. {
  477. //For ASTC compression we need to clear out the alpha to get accurate rgb compression.
  478. if (IsASTCFormat(m_input->m_presetSetting.m_pixelFormat))
  479. {
  480. alphaImage.ConvertFormat(ePixelFormat_R8G8B8X8);
  481. alphaImage.ConvertFormat(m_input->m_presetSetting.m_pixelFormatAlpha);
  482. }
  483. else
  484. {
  485. AZ_Assert(false, "PixelFormatAlpha only supports single channel pixel formats or ASTC formats");
  486. }
  487. }
  488. // get final result and save it to member variable for later use
  489. m_alphaImage = alphaImage.Get();
  490. m_image->Get()->AddImageFlags(EIF_AttachedAlpha);
  491. }
  492. // pixel format conversion
  493. bool ImageConvertProcess::ConvertPixelformat()
  494. {
  495. //set up compress option
  496. ICompressor::EQuality quality;
  497. if (m_input->m_isPreview)
  498. {
  499. quality = ICompressor::eQuality_Preview;
  500. }
  501. else
  502. {
  503. quality = ICompressor::eQuality_Normal;
  504. }
  505. // set the compression options
  506. m_image->GetCompressOption().compressQuality = quality;
  507. m_image->GetCompressOption().rgbWeight = m_input->m_presetSetting.GetColorWeight();
  508. m_image->GetCompressOption().discardAlpha = m_input->m_presetSetting.m_discardAlpha;
  509. //For ASTC compression we need to clear out the alpha to get accurate rgb compression.
  510. if(m_alphaImage && IsASTCFormat(m_input->m_presetSetting.m_pixelFormat))
  511. {
  512. m_image->GetCompressOption().discardAlpha = true;
  513. }
  514. m_image->ConvertFormat(m_input->m_presetSetting.m_pixelFormat);
  515. return true;
  516. }
  517. // convert color space from linear to sRGB space if it's necessary
  518. bool ImageConvertProcess::ConvertToOuputColorSpace()
  519. {
  520. if (m_input->m_presetSetting.m_destColorSpace == ColorSpace::sRGB)
  521. {
  522. m_image->LinearToGamma();
  523. }
  524. else if (m_input->m_presetSetting.m_destColorSpace == ColorSpace::autoSelect)
  525. {
  526. // check the compressor's colorspace preference
  527. const EPixelFormat sourceFormat = m_image->Get()->GetPixelFormat();
  528. const EPixelFormat destinationFormat = m_input->m_presetSetting.m_pixelFormat;
  529. const bool isSourceFormatUncompressed = CPixelFormats::GetInstance().IsPixelFormatUncompressed(sourceFormat);
  530. const bool isDestinationFormatUncompressed = CPixelFormats::GetInstance().IsPixelFormatUncompressed(destinationFormat);
  531. // compression is only required if either the source or destination is uncompressed
  532. if (isSourceFormatUncompressed != isDestinationFormatUncompressed)
  533. {
  534. // find out if the process is compressing or decompressing
  535. const bool isCompressing = isSourceFormatUncompressed ? true : false;
  536. const EPixelFormat outputFormat = isCompressing ? destinationFormat : sourceFormat;
  537. ICompressorPtr compressor = ICompressor::FindCompressor(outputFormat, m_input->m_presetSetting.m_destColorSpace, isCompressing);
  538. // find out if the compressor has a preference to any specific colorspace
  539. const ColorSpace compressorColorSpace = compressor->GetSupportedColorSpace(outputFormat);
  540. if (compressorColorSpace == ColorSpace::sRGB)
  541. {
  542. m_image->LinearToGamma();
  543. return true;
  544. }
  545. else if (compressorColorSpace == ColorSpace::linear)
  546. {
  547. return true;
  548. }
  549. }
  550. // convert to sRGB color space if it's dark image (converting bright images decreases image quality)
  551. bool bThresholded = false;
  552. {
  553. Histogram<256> histogram;
  554. if (ComputeLuminanceHistogram(m_image->Get(), histogram))
  555. {
  556. const size_t medianBinIndex = 116;
  557. float percentage = histogram.getPercentage(medianBinIndex, 255);
  558. // The image has significant amount of dark pixels, it's good to use sRGB
  559. bThresholded = (percentage < 50.0f);
  560. }
  561. }
  562. if (bThresholded)
  563. {
  564. bool convertToSRGB = true;
  565. // if the image is BC1 compressible, additionally estimate the conversion error
  566. // to only convert if it doesn't introduce error
  567. if (CPixelFormats::GetInstance().IsImageSizeValid(ePixelFormat_BC1, m_image->Get()->GetWidth(0),
  568. m_image->Get()->GetHeight(0), false))
  569. {
  570. //get image in RGB space
  571. ImageToProcess imageProcess(m_image->Get());
  572. imageProcess.LinearToGamma();
  573. ICompressor::CompressOption option;
  574. option.compressQuality = ICompressor::eQuality_Preview;
  575. option.rgbWeight = m_input->m_presetSetting.GetColorWeight();
  576. float errorLinearBC1;
  577. float errorSrgbBC1;
  578. GetBC1CompressionErrors(m_image->Get(), errorLinearBC1, errorSrgbBC1, option);
  579. // Don't convert if it would lower the image quality when saved as sRGB according to GetDXT1GammaCompressionError()
  580. if (errorSrgbBC1 >= errorLinearBC1)
  581. {
  582. convertToSRGB = false;
  583. }
  584. }
  585. // our final conclusion: if the texture had a significant percentage of dark pixels and,
  586. // if applicable, it was BC1 compressible and gamma compression wouldn't introduce error,
  587. // then we convert it to sRGB
  588. if (convertToSRGB)
  589. {
  590. m_image->LinearToGamma();
  591. }
  592. }
  593. }
  594. return true;
  595. }
  596. bool ImageConvertProcess::ValidateInput()
  597. {
  598. // validate the input image and output settings here.
  599. uint32 dwWidth, dwHeight;
  600. dwWidth = m_input->m_inputImage->GetWidth(0);
  601. dwHeight = m_input->m_inputImage->GetHeight(0);
  602. EPixelFormat dstFmt = m_input->m_presetSetting.m_pixelFormat;
  603. // check if whether input image can be a cubemap
  604. if (m_input->m_presetSetting.m_cubemapSetting)
  605. {
  606. // check requirements for pre-convolved cubemaps
  607. // note: only check formatting if there are multiple mip levels in the source cubemap,
  608. // since some of the conversion functions should not be used when mips are present
  609. if (IsPreconvolvedCubemap() && m_input->m_inputImage->GetMipCount() > 1)
  610. {
  611. if (m_input->m_presetSetting.m_srcColorSpace != ColorSpace::linear)
  612. {
  613. AZ_Error("Image Processing", false, "Pre-convolved environment map image must use linear colorspace");
  614. return false;
  615. }
  616. if (m_input->m_inputImage->GetPixelFormat() != ePixelFormat_R32G32B32A32F
  617. && m_input->m_inputImage->GetPixelFormat() != ePixelFormat_R16G16B16A16F)
  618. {
  619. AZ_Error("Image Processing", false, "Pre-convolved environment map image must be R32G32B32A32F or R16G16B16A16F");
  620. return false;
  621. }
  622. CubemapLayoutInfo* layoutInfo = CubemapLayout::GetCubemapLayoutInfo(m_input->m_inputImage);
  623. if (IsValidLatLongMap(m_input->m_inputImage) || layoutInfo->m_type != CubemapLayoutVertical)
  624. {
  625. AZ_Error("Image Processing", false, "Pre-convolved environment map image with multiple mips must be in Vertical layout format");
  626. return false;
  627. }
  628. }
  629. else if (CubemapLayout::GetCubemapLayoutInfo(m_input->m_inputImage) == nullptr && !IsValidLatLongMap(m_input->m_inputImage))
  630. {
  631. AZ_Error("Image Processing", false, "Environment map image size %dx%d is invalid. Requires power of two with 6x1, 1x6, 4x3 or 3x4 layouts"
  632. " or 2x1 latitude-longitude map", dwWidth, dwHeight);
  633. return false;
  634. }
  635. }
  636. else if (!CPixelFormats::GetInstance().IsImageSizeValid(dstFmt, dwWidth, dwHeight, false))
  637. {
  638. AZ_Warning("Image Processing", false, "Image size will be scaled for pixel format %s", CPixelFormats::GetInstance().GetPixelFormatInfo(dstFmt)->szName);
  639. }
  640. #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
  641. #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3) \
  642. if (ImageProcess##PrivateName::DoesSupport(m_input->m_platform)) \
  643. { \
  644. if (!ImageProcess##PrivateName::IsPixelFormatSupported(m_input->m_presetSetting.m_pixelFormat)) \
  645. { \
  646. AZ_Error("Image Processing", false, "Unsupported pixel format %s for %s", \
  647. CPixelFormats::GetInstance().GetPixelFormatInfo(dstFmt)->szName, m_input->m_platform.c_str()); \
  648. return false; \
  649. } \
  650. }
  651. AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
  652. #undef AZ_RESTRICTED_PLATFORM_EXPANSION
  653. #endif //AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
  654. return true;
  655. }
  656. bool ImageConvertProcess::SaveOutput()
  657. {
  658. // if the folder wasn't specified, skip
  659. if (m_input->m_outputFolder.empty())
  660. {
  661. AZ_Error("Image Processing", false, "No output folder provided for saving");
  662. return false;
  663. }
  664. // [GFX TODO] [ATOM-781] Platform related image prepare need to be reworked on.
  665. // Disabled for now since it's not working properly for atom
  666. #if IMAGEBUILDER_ENABLE_PLATFORM_EXPORT_PREPARE
  667. #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
  668. #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3) \
  669. if (ImageProcess##PrivateName::DoesSupport(m_input->m_platform)) \
  670. { \
  671. ImageProcess##PrivateName::PrepareImageForExport(m_image->Get()); \
  672. ImageProcess##PrivateName::PrepareImageForExport(m_alphaImage); \
  673. }
  674. AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
  675. #undef AZ_RESTRICTED_PLATFORM_EXPANSION
  676. #endif //AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
  677. #endif
  678. // cubemaps can have a specific subId, standard images use the subId specified in StreamingImageAsset
  679. uint32_t subId = IsConvertToCubemap() ? m_input->m_presetSetting.m_cubemapSetting->m_subId : RPI::StreamingImageAsset::GetImageAssetSubId();
  680. // Save the image to atom image assets
  681. ImageAssetProducer assetProducer(
  682. m_image->Get(),
  683. m_input->m_outputFolder,
  684. m_input->m_sourceAssetId,
  685. m_input->m_imageName,
  686. m_input->m_presetSetting.m_numResidentMips,
  687. subId);
  688. if (assetProducer.BuildImageAssets())
  689. {
  690. m_jobProducts = assetProducer.GetJobProducts();
  691. return true;
  692. }
  693. AZ_Error("Image Processing", false, "Failed to generate StreamingImageAsset");
  694. return false;
  695. }
  696. ImageConvertProcess* CreateImageConvertProcess(const AZStd::string& imageFilePath, const AZStd::string& exportDir
  697. , const PlatformName& platformName, AZStd::vector<AssetBuilderSDK::JobProduct>& jobProducts, AZ::SerializeContext* context)
  698. {
  699. AZStd::unique_ptr<ImageConvertProcessDescriptor> desc = AZStd::make_unique<ImageConvertProcessDescriptor>();
  700. TextureSettings& textureSettings = desc->m_textureSetting;
  701. MultiplatformTextureSettings multiTextureSetting;
  702. bool canOverridePreset = false;
  703. multiTextureSetting = TextureSettings::GetMultiplatformTextureSetting(imageFilePath, canOverridePreset, context);
  704. if (multiTextureSetting.size() == 0)
  705. {
  706. AZ_Error("Image Processing", false, "Failed to generate texture setting");
  707. return nullptr;
  708. }
  709. if (multiTextureSetting.find(platformName) != multiTextureSetting.end())
  710. {
  711. textureSettings = multiTextureSetting[platformName];
  712. }
  713. else
  714. {
  715. PlatformName defaultPlatform = BuilderSettingManager::s_defaultPlatform;
  716. if (multiTextureSetting.find(defaultPlatform) != multiTextureSetting.end())
  717. {
  718. textureSettings = multiTextureSetting[defaultPlatform];
  719. }
  720. else
  721. {
  722. textureSettings = (*multiTextureSetting.begin()).second;
  723. }
  724. }
  725. // Load image. Do it earlier so GetSuggestedPreset function could use the information of file to choose better preset
  726. IImageObjectPtr srcImage(LoadImageFromFile(imageFilePath));
  727. if (srcImage == nullptr)
  728. {
  729. AZ_Error("Image Processing", false, "Load image file %s failed", imageFilePath.c_str());
  730. return nullptr;
  731. }
  732. // if get textureSetting failed, use the default texture setting, and find suitable preset for this file
  733. // in very rare user case, an old texture setting file may not have a preset. We fix it over here too.
  734. if (textureSettings.m_preset.IsEmpty())
  735. {
  736. textureSettings.m_preset = BuilderSettingManager::Instance()->GetSuggestedPreset(imageFilePath, srcImage);
  737. }
  738. // Get preset
  739. AZStd::string_view filePath;
  740. const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(textureSettings.m_preset, platformName, &filePath);
  741. if (preset == nullptr)
  742. {
  743. AZ_Assert(false, "%s cannot find image preset %s.", imageFilePath.c_str(), textureSettings.m_preset.GetCStr());
  744. return nullptr;
  745. }
  746. desc->m_presetSetting = *preset;
  747. desc->m_platform = platformName;
  748. desc->m_filePath = filePath;
  749. desc->m_inputImage = srcImage;
  750. desc->m_isPreview = false;
  751. desc->m_isStreaming = BuilderSettingManager::Instance()->GetBuilderSetting(platformName)->m_enableStreaming;
  752. desc->m_outputFolder = exportDir;
  753. desc->m_jobProducts = &jobProducts;
  754. AzFramework::StringFunc::Path::GetFullFileName(imageFilePath.c_str(), desc->m_imageName);
  755. // Get source asset id. Create random id if it's not found which is useful if this functions wasn't called under asset builder environment. For example, unit test.
  756. AZStd::string watchFolder;
  757. AZ::Data::AssetInfo catalogAssetInfo;
  758. bool sourceInfoFound = false;
  759. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
  760. imageFilePath.c_str(), catalogAssetInfo, watchFolder);
  761. desc->m_sourceAssetId = sourceInfoFound ? catalogAssetInfo.m_assetId : AZ::Data::AssetId(AZ::Uuid::CreateRandom());
  762. // Create convert process
  763. ImageConvertProcess* process = new ImageConvertProcess(AZStd::move(desc));
  764. return process;
  765. }
  766. void ImageConvertProcess::CreateIBLCubemap(PresetName preset, const char* fileNameSuffix, IImageObjectPtr& cubemapImage)
  767. {
  768. const AZStd::string& platformId = m_input->m_platform;
  769. AZStd::string_view filePath;
  770. const PresetSettings* presetSettings = BuilderSettingManager::Instance()->GetPreset(preset, platformId, &filePath);
  771. if (presetSettings == nullptr)
  772. {
  773. AZ_Error("Image Processing", false, "Couldn't find preset for IBL cubemap generation");
  774. return;
  775. }
  776. // generate export file name
  777. AZStd::string fileName;
  778. AzFramework::StringFunc::Path::GetFileName(m_input->m_imageName.c_str(), fileName);
  779. fileName += fileNameSuffix;
  780. AZStd::string extension;
  781. AzFramework::StringFunc::Path::GetExtension(m_input->m_imageName.c_str(), extension);
  782. fileName += extension;
  783. AZStd::string outProductPath;
  784. AzFramework::StringFunc::Path::Join(m_input->m_outputFolder.c_str(), fileName.c_str(), outProductPath, true, true);
  785. // the diffuse irradiance cubemap is generated with a separate ImageConvertProcess
  786. TextureSettings textureSettings = m_input->m_textureSetting;
  787. textureSettings.m_preset = preset;
  788. AZStd::unique_ptr<ImageConvertProcessDescriptor> desc = AZStd::make_unique<ImageConvertProcessDescriptor>();
  789. desc->m_presetSetting = *presetSettings;
  790. desc->m_textureSetting = textureSettings;
  791. desc->m_platform = platformId;
  792. desc->m_filePath = filePath;
  793. desc->m_inputImage = m_input->m_inputImage;
  794. desc->m_isPreview = false;
  795. desc->m_isStreaming = m_input->m_isStreaming;
  796. desc->m_outputFolder = m_input->m_outputFolder;
  797. desc->m_imageName = fileName;
  798. desc->m_sourceAssetId = m_input->m_sourceAssetId;
  799. AZStd::unique_ptr<ImageConvertProcess> imageConvertProcess = AZStd::make_unique<ImageConvertProcess>(AZStd::move(desc));
  800. if (!imageConvertProcess)
  801. {
  802. AZ_Error("Image Processing", false, "Failed to create image convert process for the IBL cubemap");
  803. return;
  804. }
  805. imageConvertProcess->ProcessAll();
  806. if (!imageConvertProcess->IsSucceed())
  807. {
  808. AZ_Error("Image Processing", false, "Image convert process for the IBL cubemap failed");
  809. return;
  810. }
  811. // append the output products to the job's product list
  812. imageConvertProcess->GetAppendOutputProducts(*m_input->m_jobProducts);
  813. // store the output cubemap so it can be accessed by unit tests
  814. cubemapImage = imageConvertProcess->m_image->Get();
  815. }
  816. bool ConvertImageFile(const AZStd::string& imageFilePath, const AZStd::string& exportDir,
  817. const PlatformName& platformName, AZ::SerializeContext* context, AZStd::vector<AssetBuilderSDK::JobProduct>& outProducts)
  818. {
  819. bool result = false;
  820. ImageConvertProcess* process = CreateImageConvertProcess(imageFilePath, exportDir, platformName, outProducts, context);
  821. if (process)
  822. {
  823. process->ProcessAll();
  824. result = process->IsSucceed();
  825. if (result)
  826. {
  827. process->GetAppendOutputProducts(outProducts);
  828. }
  829. delete process;
  830. }
  831. return result;
  832. }
  833. IImageObjectPtr MergeOutputImageForPreview(IImageObjectPtr image, IImageObjectPtr alphaImage)
  834. {
  835. if (!image)
  836. {
  837. return IImageObjectPtr();
  838. }
  839. ImageToProcess imageToProcess(image);
  840. imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
  841. IImageObjectPtr previewImage = imageToProcess.Get();
  842. // If there is separate Alpha image, combine it with output
  843. if (alphaImage)
  844. {
  845. // Create pixel operation function for rgb and alpha images
  846. IPixelOperationPtr imageOp = CreatePixelOperation(ePixelFormat_R8G8B8A8);
  847. IPixelOperationPtr alphaOp = CreatePixelOperation(ePixelFormat_A8);
  848. // Convert the alpha image to A8 first
  849. ImageToProcess imageToProcess2(alphaImage);
  850. imageToProcess2.ConvertFormat(ePixelFormat_A8);
  851. IImageObjectPtr previewImageAlpha = imageToProcess2.Get();
  852. const uint32 imageMips = previewImage->GetMipCount();
  853. [[maybe_unused]] const uint32 alphaMips = previewImageAlpha->GetMipCount();
  854. // Get count of bytes per pixel for both rgb and alpha images
  855. uint32 imagePixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(ePixelFormat_R8G8B8A8)->bitsPerBlock / 8;
  856. uint32 alphaPixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(ePixelFormat_A8)->bitsPerBlock / 8;
  857. AZ_Assert(imageMips <= alphaMips, "Mip level of alpha image is less than origin image!");
  858. // For each mip level, set the alpha value to the image
  859. for (uint32 mipLevel = 0; mipLevel < imageMips; ++mipLevel)
  860. {
  861. const uint32 pixelCount = previewImage->GetPixelCount(mipLevel);
  862. [[maybe_unused]] const uint32 alphaPixelCount = previewImageAlpha->GetPixelCount(mipLevel);
  863. AZ_Assert(pixelCount == alphaPixelCount, "Pixel count for image and alpha image at mip level %d is not equal!", mipLevel);
  864. uint8* imageBuf;
  865. uint32 pitch;
  866. previewImage->GetImagePointer(mipLevel, imageBuf, pitch);
  867. uint8* alphaBuf;
  868. uint32 alphaPitch;
  869. previewImageAlpha->GetImagePointer(mipLevel, alphaBuf, alphaPitch);
  870. float rAlpha, gAlpha, bAlpha, aAlpha, rImage, gImage, bImage, aImage;
  871. for (uint32 i = 0; i < pixelCount; ++i, imageBuf += imagePixelBytes, alphaBuf += alphaPixelBytes)
  872. {
  873. alphaOp->GetRGBA(alphaBuf, rAlpha, gAlpha, bAlpha, aAlpha);
  874. imageOp->GetRGBA(imageBuf, rImage, gImage, bImage, aImage);
  875. imageOp->SetRGBA(imageBuf, rImage, gImage, bImage, aAlpha);
  876. }
  877. }
  878. }
  879. return previewImage;
  880. }
  881. IImageObjectPtr ConvertImageForPreview(IImageObjectPtr image)
  882. {
  883. if (!image)
  884. {
  885. return IImageObjectPtr();
  886. }
  887. ImageToProcess imageToProcess(image);
  888. imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
  889. IImageObjectPtr previewImage = imageToProcess.Get();
  890. return previewImage;
  891. }
  892. // This function will convert compressed image to RGBA32.
  893. // Also if the image is in sRGB space will convert it to Linear space.
  894. IImageObjectPtr GetUncompressedLinearImage(IImageObjectPtr ddsImage)
  895. {
  896. if (ddsImage)
  897. {
  898. ImageToProcess processImage(ddsImage);
  899. if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(ddsImage->GetPixelFormat()))
  900. {
  901. processImage.ConvertFormat(ePixelFormat_R32G32B32A32F);
  902. }
  903. if (ddsImage->HasImageFlags(EIF_SRGBRead))
  904. {
  905. processImage.GammaToLinearRGBA32F(true);
  906. }
  907. return processImage.Get();
  908. }
  909. return nullptr;
  910. }
  911. float GetErrorBetweenImages(IImageObjectPtr inputImage1, IImageObjectPtr inputImage2)
  912. {
  913. // First make sure images are in uncompressed format and linear space
  914. // Convert them if necessary
  915. IImageObjectPtr image1 = GetUncompressedLinearImage(inputImage1);
  916. IImageObjectPtr image2 = GetUncompressedLinearImage(inputImage2);
  917. const float errorValue = FLT_MAX;
  918. if (!image1 || !image2)
  919. {
  920. AZ_Warning("Image Processing", false, "Invalid images passed into %s function", __FUNCTION__);
  921. return errorValue;
  922. }
  923. // Two images should share same size
  924. if (image1->GetWidth(0) != image2->GetWidth(0) || image1->GetHeight(0) != image2->GetHeight(0))
  925. {
  926. AZ_Warning("Image Processing", false, "%s function only can get error between two images with same size", __FUNCTION__);
  927. return errorValue;
  928. }
  929. //create pixel operation function
  930. IPixelOperationPtr pixelOp1 = CreatePixelOperation(image1->GetPixelFormat());
  931. IPixelOperationPtr pixelOp2 = CreatePixelOperation(image2->GetPixelFormat());
  932. //get count of bytes per pixel
  933. AZ::u32 pixelBytes1 = CPixelFormats::GetInstance().GetPixelFormatInfo(image1->GetPixelFormat())->bitsPerBlock / 8;
  934. AZ::u32 pixelBytes2 = CPixelFormats::GetInstance().GetPixelFormatInfo(image2->GetPixelFormat())->bitsPerBlock / 8;
  935. float color1[4];
  936. float color2[4];
  937. AZ::u8* mem1;
  938. AZ::u8* mem2;
  939. uint32 pitch1, pitch2;
  940. float sumDeltaSqLinear = 0;
  941. //only process the highest mip
  942. image1->GetImagePointer(0, mem1, pitch1);
  943. image2->GetImagePointer(0, mem2, pitch2);
  944. const uint32 pixelCount = image1->GetPixelCount(0);
  945. for (uint32 i = 0; i < pixelCount; ++i)
  946. {
  947. pixelOp1->GetRGBA(mem1, color1[0], color1[1], color1[2], color1[3]);
  948. pixelOp2->GetRGBA(mem2, color2[0], color2[1], color2[2], color2[3]);
  949. sumDeltaSqLinear += (color1[0] - color2[0]) * (color1[0] - color2[0])
  950. + (color1[1] - color2[1]) * (color1[1] - color2[1])
  951. + (color1[2] - color2[2]) * (color1[2] - color2[2]);
  952. mem1 += pixelBytes1;
  953. mem2 += pixelBytes2;
  954. }
  955. return sumDeltaSqLinear / pixelCount;
  956. }
  957. void GetBC1CompressionErrors(IImageObjectPtr originImage, float& errorLinear, float& errorSrgb,
  958. ICompressor::CompressOption option)
  959. {
  960. errorLinear = 0;
  961. errorSrgb = 0;
  962. if (originImage->HasImageFlags(EIF_SRGBRead))
  963. {
  964. AZ_Assert(false, "The input origin image of %s function need be in linear color space", __FUNCTION__);
  965. return;
  966. }
  967. //compress and decompress in linear space
  968. ImageToProcess processLinear(originImage);
  969. processLinear.SetCompressOption(option);
  970. processLinear.ConvertFormat(ePixelFormat_BC1);
  971. processLinear.ConvertFormat(ePixelFormat_R32G32B32A32F);
  972. errorLinear = GetErrorBetweenImages(originImage, processLinear.Get());
  973. //compress and decompress in sRGB space, then convert back to linear space to compare to original image
  974. ImageToProcess processSrgb(originImage);
  975. processSrgb.SetCompressOption(option);
  976. processSrgb.LinearToGamma();
  977. processSrgb.ConvertFormat(ePixelFormat_BC1);
  978. processSrgb.ConvertFormat(ePixelFormat_R32G32B32A32F);
  979. processSrgb.GammaToLinearRGBA32F(true);
  980. errorSrgb = GetErrorBetweenImages(originImage, processSrgb.Get());
  981. }
  982. }// namespace ImageProcessingAtom