text: /*
   * Copyright (c) Contributors to the Open 3D Engine Project.
   * For complete copyright and license terms please see the LICENSE at the root of this distribution.
   *
   * SPDX-License-Identifier: Apache-2.0 OR MIT
   *
   */
  
  
  #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
  #include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
  #include <Atom/Utils/DdsFile.h>
  #include <AzFramework/StringFunc/StringFunc.h>
  
  #include <Processing/ImageToProcess.h>
  #include <Processing/PixelFormatInfo.h>
  #include <Processing/Utils.h>
  #include <Processing/ImageFlags.h>
  
  namespace ImageProcessingAtom
  {
      namespace Utils
      {
          using namespace AZ;
  
          EPixelFormat RHIFormatToPixelFormat(AZ::RHI::Format rhiFormat, bool& isSRGB)
          {
              isSRGB = false;
              switch (rhiFormat)
              {
              case AZ::RHI::Format::R8G8B8A8_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::R8G8B8A8_UNORM:
                  return ePixelFormat_R8G8B8A8;
              case AZ::RHI::Format::R8G8_UNORM:
                  return ePixelFormat_R8G8;
              case AZ::RHI::Format::R8_UNORM:
                  return ePixelFormat_R8;
              case AZ::RHI::Format::A8_UNORM:
                  return ePixelFormat_A8;
              case AZ::RHI::Format::R16G16B16A16_UNORM:
                  return ePixelFormat_R16G16B16A16;
              case AZ::RHI::Format::R16G16_UNORM:
                  return ePixelFormat_R16G16;
              case AZ::RHI::Format::R16_UNORM:
                  return ePixelFormat_R16;
  
              case AZ::RHI::Format::ASTC_4x4_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_4x4_UNORM:
                  return ePixelFormat_ASTC_4x4;
              case AZ::RHI::Format::ASTC_5x4_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_5x4_UNORM:
                  return ePixelFormat_ASTC_5x4;
              case AZ::RHI::Format::ASTC_5x5_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_5x5_UNORM:
                  return ePixelFormat_ASTC_5x5;
              case AZ::RHI::Format::ASTC_6x5_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_6x5_UNORM:
                  return ePixelFormat_ASTC_6x5;
              case AZ::RHI::Format::ASTC_6x6_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_6x6_UNORM:
                  return ePixelFormat_ASTC_6x6;
              case AZ::RHI::Format::ASTC_8x5_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_8x5_UNORM:
                  return ePixelFormat_ASTC_8x5;
              case AZ::RHI::Format::ASTC_8x6_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_8x6_UNORM:
                  return ePixelFormat_ASTC_8x6;
              case AZ::RHI::Format::ASTC_8x8_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_8x8_UNORM:
                  return ePixelFormat_ASTC_8x8;
              case AZ::RHI::Format::ASTC_10x5_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_10x5_UNORM:
                  return ePixelFormat_ASTC_10x5;
              case AZ::RHI::Format::ASTC_10x6_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_10x6_UNORM:
                  return ePixelFormat_ASTC_10x6;
              case AZ::RHI::Format::ASTC_10x8_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_10x8_UNORM:
                  return ePixelFormat_ASTC_10x8;
              case AZ::RHI::Format::ASTC_10x10_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_10x10_UNORM:
                  return ePixelFormat_ASTC_10x10;
              case AZ::RHI::Format::ASTC_12x10_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_12x10_UNORM:
                  return ePixelFormat_ASTC_12x10;
              case AZ::RHI::Format::ASTC_12x12_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ASTC_12x12_UNORM:
                  return ePixelFormat_ASTC_12x12;
  
              case AZ::RHI::Format::PVRTC2_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::PVRTC2_UNORM:
                  return ePixelFormat_PVRTC2;
              case AZ::RHI::Format::PVRTC4_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::PVRTC4_UNORM:
                  return ePixelFormat_PVRTC4;
  
              case AZ::RHI::Format::EAC_R11_UNORM:
                  return ePixelFormat_EAC_R11;
              case AZ::RHI::Format::EAC_RG11_UNORM:
                  return ePixelFormat_EAC_RG11;
              case AZ::RHI::Format::ETC2_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ETC2_UNORM:
                  return ePixelFormat_ETC2;
              case AZ::RHI::Format::ETC2A_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ETC2A_UNORM:
                  return ePixelFormat_ETC2a;
              case AZ::RHI::Format::ETC2A1_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::ETC2A1_UNORM:
                  return ePixelFormat_ETC2a1;
  
              case AZ::RHI::Format::BC1_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::BC1_UNORM:
                  return ePixelFormat_BC1;
              case AZ::RHI::Format::BC3_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::BC3_UNORM:
                  return ePixelFormat_BC3;
              case AZ::RHI::Format::BC4_UNORM:
                  return ePixelFormat_BC4;
              case AZ::RHI::Format::BC4_SNORM:
                  return ePixelFormat_BC4s;
              case AZ::RHI::Format::BC5_UNORM:
                  return ePixelFormat_BC5;
              case AZ::RHI::Format::BC5_SNORM:
                  return ePixelFormat_BC5s;
              case AZ::RHI::Format::BC6H_UF16:
                  return ePixelFormat_BC6UH;
              case AZ::RHI::Format::BC7_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::BC7_UNORM:
                  return ePixelFormat_BC7;
              case AZ::RHI::Format::R9G9B9E5_SHAREDEXP:
                  return ePixelFormat_R9G9B9E5;
              case AZ::RHI::Format::R32G32B32A32_FLOAT:
                  return ePixelFormat_R32G32B32A32F;
              case AZ::RHI::Format::R32G32_FLOAT:
                  return ePixelFormat_R32G32F;
              case AZ::RHI::Format::R32_FLOAT:
                  return ePixelFormat_R32F;
              case AZ::RHI::Format::R16G16B16A16_FLOAT:
                  return ePixelFormat_R16G16B16A16F;
              case AZ::RHI::Format::R16G16_FLOAT:
                  return ePixelFormat_R16G16F;
              case AZ::RHI::Format::R16_FLOAT:
                  return ePixelFormat_R16F;
              case AZ::RHI::Format::B8G8R8A8_UNORM_SRGB:
                  isSRGB = true;
              case AZ::RHI::Format::B8G8R8A8_UNORM:
                  return ePixelFormat_B8G8R8A8;
              case AZ::RHI::Format::R32_UINT:
                  return ePixelFormat_R32;
              default:
                  AZ_Warning("Image Processing", false, "Unknown pixel format");
                  return ePixelFormat_Unknown;
              }
          }
  
          AZ::RHI::Format PixelFormatToRHIFormat(EPixelFormat pixelFormat, bool isSrgb)
          {
              switch (pixelFormat)
              {
              case ePixelFormat_R8G8B8A8:
              case ePixelFormat_R8G8B8X8:
                  return isSrgb ? RHI::Format::R8G8B8A8_UNORM_SRGB : RHI::Format::R8G8B8A8_UNORM;
              case ePixelFormat_R8G8:
                  return RHI::Format::R8G8_UNORM;
              case ePixelFormat_R8:
                  return RHI::Format::R8_UNORM;
              case ePixelFormat_A8:
                  return RHI::Format::A8_UNORM;
              case ePixelFormat_R16G16B16A16:
                  return RHI::Format::R16G16B16A16_UNORM;
              case ePixelFormat_R16G16:
                  return RHI::Format::R16G16_UNORM;
              case ePixelFormat_R16:
                  return RHI::Format::R16_UNORM;
  
              case ePixelFormat_ASTC_4x4:
                  return isSrgb ? RHI::Format::ASTC_4x4_UNORM_SRGB : RHI::Format::ASTC_4x4_UNORM;
              case ePixelFormat_ASTC_5x4:
                  return isSrgb ? RHI::Format::ASTC_5x4_UNORM_SRGB : RHI::Format::ASTC_5x4_UNORM;
              case ePixelFormat_ASTC_5x5:
                  return isSrgb ? RHI::Format::ASTC_5x5_UNORM_SRGB : RHI::Format::ASTC_5x5_UNORM;
              case ePixelFormat_ASTC_6x5:
                  return isSrgb ? RHI::Format::ASTC_6x5_UNORM_SRGB : RHI::Format::ASTC_6x5_UNORM;
              case ePixelFormat_ASTC_6x6:
                  return isSrgb ? RHI::Format::ASTC_6x6_UNORM_SRGB : RHI::Format::ASTC_6x6_UNORM;
              case ePixelFormat_ASTC_8x5:
                  return isSrgb ? RHI::Format::ASTC_8x5_UNORM_SRGB : RHI::Format::ASTC_8x5_UNORM;
              case ePixelFormat_ASTC_8x6:
                  return isSrgb ? RHI::Format::ASTC_8x6_UNORM_SRGB : RHI::Format::ASTC_8x6_UNORM;
              case ePixelFormat_ASTC_8x8:
                  return isSrgb ? RHI::Format::ASTC_8x8_UNORM_SRGB : RHI::Format::ASTC_8x8_UNORM;
              case ePixelFormat_ASTC_10x5:
                  return isSrgb ? RHI::Format::ASTC_10x5_UNORM_SRGB : RHI::Format::ASTC_10x5_UNORM;
              case ePixelFormat_ASTC_10x6:
                  return isSrgb ? RHI::Format::ASTC_10x6_UNORM_SRGB : RHI::Format::ASTC_10x6_UNORM;
              case ePixelFormat_ASTC_10x8:
                  return isSrgb ? RHI::Format::ASTC_10x8_UNORM_SRGB : RHI::Format::ASTC_10x8_UNORM;
              case ePixelFormat_ASTC_10x10:
                  return isSrgb ? RHI::Format::ASTC_10x10_UNORM_SRGB : RHI::Format::ASTC_10x10_UNORM;
              case ePixelFormat_ASTC_12x10:
                  return isSrgb ? RHI::Format::ASTC_12x10_UNORM_SRGB : RHI::Format::ASTC_12x10_UNORM;
              case ePixelFormat_ASTC_12x12:
                  return isSrgb ? RHI::Format::ASTC_12x12_UNORM_SRGB : RHI::Format::ASTC_12x12_UNORM;
  
              case ePixelFormat_PVRTC2:
                  return isSrgb ? RHI::Format::PVRTC2_UNORM_SRGB : RHI::Format::PVRTC2_UNORM;
              case ePixelFormat_PVRTC4:
                  return isSrgb ? RHI::Format::PVRTC4_UNORM_SRGB : RHI::Format::PVRTC4_UNORM;
  
              case ePixelFormat_EAC_R11:
                  return RHI::Format::EAC_R11_UNORM;
              case ePixelFormat_EAC_RG11:
                  return RHI::Format::EAC_RG11_UNORM;
              case ePixelFormat_ETC2:
                  return isSrgb ? RHI::Format::ETC2_UNORM_SRGB : RHI::Format::ETC2_UNORM;
              case ePixelFormat_ETC2a:
                  return isSrgb ? RHI::Format::ETC2A_UNORM_SRGB : RHI::Format::ETC2A_UNORM;
              case ePixelFormat_ETC2a1:
                  return isSrgb ? RHI::Format::ETC2A1_UNORM_SRGB : RHI::Format::ETC2A1_UNORM;
  
              case ePixelFormat_BC1:
              case ePixelFormat_BC1a:
                  return isSrgb ? RHI::Format::BC1_UNORM_SRGB : RHI::Format::BC1_UNORM;
              case ePixelFormat_BC3:
              case ePixelFormat_BC3t:
                  return isSrgb ? RHI::Format::BC3_UNORM_SRGB : RHI::Format::BC3_UNORM;
              case ePixelFormat_BC4:
                  return RHI::Format::BC4_UNORM;
              case ePixelFormat_BC4s:
                  return RHI::Format::BC4_SNORM;
              case ePixelFormat_BC5:
                  return RHI::Format::BC5_UNORM;
              case ePixelFormat_BC5s:
                  return RHI::Format::BC5_SNORM;
              case ePixelFormat_BC6UH:
                  return RHI::Format::BC6H_UF16;
              case ePixelFormat_BC7:
              case ePixelFormat_BC7t:
                  return isSrgb ? RHI::Format::BC7_UNORM_SRGB : RHI::Format::BC7_UNORM;
              case ePixelFormat_R9G9B9E5:
                  return RHI::Format::R9G9B9E5_SHAREDEXP;
              case ePixelFormat_R32G32B32A32F:
                  return RHI::Format::R32G32B32A32_FLOAT;
              case ePixelFormat_R32G32F:
                  return RHI::Format::R32G32_FLOAT;
              case ePixelFormat_R32F:
                  return RHI::Format::R32_FLOAT;
              case ePixelFormat_R16G16B16A16F:
                  return RHI::Format::R16G16B16A16_FLOAT;
              case ePixelFormat_R16G16F:
                  return RHI::Format::R16G16_FLOAT;
              case ePixelFormat_R16F:
                  return RHI::Format::R16_FLOAT;
              case ePixelFormat_B8G8R8A8:
                  return isSrgb ? RHI::Format::B8G8R8A8_UNORM_SRGB : RHI::Format::B8G8R8A8_UNORM;
              case ePixelFormat_R32:
                  return RHI::Format::R32_UINT;
              default:
                  AZ_Warning("Image Processing", false, "Unknown pixel format");
                  return RHI::Format::Unknown;
              }
          }
  
          IImageObjectPtr LoadImageFromImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset)
          {
              if (!imageAsset.IsReady())
              {
                  return nullptr;
              }
  
              const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
  
              // create an image object based on the image descriptor to store all the mip data
              bool isSRGB;
              EPixelFormat format = RHIFormatToPixelFormat(imageDescriptor.m_format, isSRGB);
  
              u32 width = imageDescriptor.m_size.m_width;
              u32 height = imageDescriptor.m_size.m_height;
              u32 mipLevels = imageDescriptor.m_mipLevels;
              u32 arraySize = imageDescriptor.m_arraySize;
  
              if (imageDescriptor.m_isCubemap)
              {
                  height *= 6;
              }
  
              IImageObjectPtr outputImage = IImageObjectPtr(IImageObject::CreateImage(width, height, mipLevels, format));
  
              if (isSRGB)
              {
                  outputImage->AddImageFlags(EIF_SRGBRead);
              }
  
              if (imageDescriptor.m_isCubemap)
              {
                  outputImage->AddImageFlags(EIF_Cubemap);
              }
  
              // copy image data from asset to image object
              for (u32 mip = 0; mip < mipLevels; mip++)
              {
                  AZ::u8* imageBuf;
                  AZ::u32 pitch;
                  outputImage->GetImagePointer(mip, imageBuf, pitch);
  
                  for (u32 slice = 0; slice < arraySize; slice++)
                  {
                      AZStd::array_view<uint8_t> imageData = imageAsset->GetSubImageData(mip, slice);
                      memcpy(imageBuf + slice * imageData.size(), imageData.data(), imageData.size());
                  }
              }
              return outputImage;
          }
  
          AZ::Data::Asset<AZ::RPI::StreamingImageAsset> LoadImageAsset(const AZ::Data::AssetId& imageAssetId)
          {
              // Blocking loading streaming image asset with its mipchain assets
              AZ::Data::Asset<AZ::RPI::StreamingImageAsset> imageAsset = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::StreamingImageAsset>(imageAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
  
              imageAsset.BlockUntilLoadComplete();
  
              return imageAsset;
          }
  
          IImageObjectPtr LoadImageFromImageAsset(const AZ::Data::AssetId& imageAssetId)
          {
              auto imageAsset = LoadImageAsset(imageAssetId);
              return LoadImageFromImageAsset(imageAsset);
          }
  
          bool SaveImageToDdsFile(IImageObjectPtr image, AZStd::string_view filePath)
          {
              RHI::Format format = PixelFormatToRHIFormat(image->GetPixelFormat(), image->HasImageFlags(EIF_SRGBRead));
              IImageObjectPtr imageToSave = image;
  
              //Some compressed formats such as astc formats are not supported by dds. We need do some decompressing first.
              if (!AZ::DdsFile::DoesSupportFormat(format))
              {
                  // Convert to some uncompressed format
                  ImageToProcess imageToProcess(image);
                  imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
                  imageToSave = imageToProcess.Get();
              }
  
              DdsFile::DdsFileData ddsFileData;
              ddsFileData.m_size.m_width = imageToSave->GetWidth(0);
              ddsFileData.m_size.m_height = imageToSave->GetHeight(0);
              ddsFileData.m_format = PixelFormatToRHIFormat(imageToSave->GetPixelFormat(), imageToSave->HasImageFlags(EIF_SRGBRead));
              ddsFileData.m_mipLevels = imageToSave->GetMipCount();
  
              if (image->HasImageFlags(EIF_Cubemap))
              {
                  ddsFileData.m_size.m_height /= 6;
                  ddsFileData.m_isCubemap = true;
              }
              
              AZ::u32 bufferSize = 0;
              for (AZ::u32 mip = 0; mip < ddsFileData.m_mipLevels; mip++)
              {
                  bufferSize += imageToSave->GetMipBufSize(mip);
              }
  
              AZStd::vector<uint8_t> buffer;
              buffer.resize_no_construct(bufferSize);
  
              ddsFileData.m_buffer = &buffer;
  
              AZ::u32 faces = 1;
              if (imageToSave->HasImageFlags(EIF_Cubemap))
              {
                  faces = 6;
              }
  
              // The sub images are saved with this order:
              // face 1: mip 0, mip 1, mip2 ...
              // face 2: mip 0, mip 1, mip2 ...
              // ...
              // face 6: mip 0, mip 1, mip2 ...
  
              AZ::u8* currentPos = buffer.data();
  
              for (AZ::u32 face = 0; face < faces; face++)
              {
                  for (AZ::u32 mip = 0; mip < ddsFileData.m_mipLevels; mip++)
                  {
                      AZ::u32 mipBufferSize = imageToSave->GetMipBufSize(mip);
                      AZ::u32 subImageSize = mipBufferSize / faces;
  
                      AZ::u8* imageBuffer = nullptr;
                      AZ::u32 pitch = 0;
  
                      imageToSave->GetImagePointer(mip, imageBuffer, pitch);
  
                      memcpy(currentPos, imageBuffer + subImageSize * face, subImageSize);
                      currentPos += subImageSize;
                  }
              }            
  
              auto outcome = AZ::DdsFile::WriteFile(filePath, ddsFileData);
              if (!outcome)
              {
                  AZ_Warning("WriteDds", false, "%s", outcome.GetError().m_message.c_str());
                  return false;
              }
              return true;
          }
      }
  
  } // namespace ImageProcessingAtom
  