1f9b284de2
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> # Conflicts: # Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h # Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h # Code/Editor/Plugins/EditorCommon/stdafx.cpp # Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h # Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h # Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h # Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h # Code/Tools/AssetProcessor/native/precompiled.h # Code/Tools/Standalone/StandaloneTools_precompiled.h # Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h # Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h # Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h # Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h # Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h # Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h # Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake # Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h # Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h # Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp # Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake # Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h # Gems/Blast/Code/Source/StdAfx.cpp # Gems/Camera/Code/Source/Camera_precompiled.h # Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h # Gems/FastNoise/Code/Source/FastNoise_precompiled.h # Gems/Gestures/Code/Source/Gestures_precompiled.h # Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h # Gems/GraphCanvas/Code/precompiled.h # Gems/ImGui/Code/Source/ImGui_precompiled.h # Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h # Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h # Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp # Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h # Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h # Gems/LyShine/Code/Source/LyShine_precompiled.h # Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h # Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h # Gems/Maestro/Code/Source/Maestro_precompiled.h # Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h # Gems/Metastream/Code/Source/Metastream_precompiled.h # Gems/Microphone/Code/Source/Microphone_precompiled.h # Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h # Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h # Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h # Gems/PhysX/Code/Source/PhysX_precompiled.h # Gems/PhysX/Code/physx_unsupported_files.cmake # Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h # Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h # Gems/ScriptCanvas/Code/Editor/precompiled.h # Gems/ScriptCanvas/Code/Source/precompiled.h # Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h # Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h # Gems/ScriptEvents/Code/Source/precompiled.h # Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp # Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h # Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h # Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h # Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h # Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h # Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h # Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h # Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h # Gems/Twitch/Code/Source/Twitch_precompiled.h # Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h # Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h # Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
389 lines
15 KiB
C++
389 lines
15 KiB
C++
/*
|
|
* 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/ImageProcessing/ImageObject.h>
|
|
#include <Processing/ImageToProcess.h>
|
|
#include <Processing/PixelFormatInfo.h>
|
|
|
|
#include <Compressors/CTSquisher.h>
|
|
|
|
namespace ImageProcessingAtom
|
|
{
|
|
struct CrySquisherCallbackUserData
|
|
{
|
|
IImageObjectPtr m_pImageObject;
|
|
AZ::u8* m_dstMem;
|
|
AZ::u32 m_dstOffset;
|
|
};
|
|
|
|
// callbacks for the CryTextureSquisher
|
|
void CrySquisherOutputCallback(const CryTextureSquisher::CompressorParameters& compress, const void* data, AZ::u32 size, AZ::u32 oy, AZ::u32 ox)
|
|
{
|
|
CrySquisherCallbackUserData* const pUserData = (CrySquisherCallbackUserData*)compress.userPtr;
|
|
|
|
AZ::u32 stride = (compress.width + 3) >> 2;
|
|
AZ::u32 blocks = (compress.height + 3) >> 2;
|
|
memcpy(pUserData->m_dstMem + size * (stride * oy + ox), data, size);
|
|
pUserData->m_dstOffset = size * (stride * blocks);
|
|
}
|
|
|
|
void CrySquisherInputCallback(const CryTextureSquisher::DecompressorParameters& decompress, void* data, AZ::u32 size, AZ::u32 oy, AZ::u32 ox)
|
|
{
|
|
CrySquisherCallbackUserData* const pUserData = (CrySquisherCallbackUserData*)decompress.userPtr;
|
|
AZ::u32 stride = (decompress.width + 3) >> 2;
|
|
AZ::u32 blocks = (decompress.height + 3) >> 2;
|
|
|
|
//assert(CPixelFormats::GetPixelFormatInfo(pUserData->m_pImageObject->GetPixelFormat())->bCompressed);
|
|
|
|
memcpy(data, pUserData->m_dstMem + size * (stride * oy + ox), size);
|
|
|
|
pUserData->m_dstOffset = size * (stride * blocks);
|
|
}
|
|
|
|
|
|
CryTextureSquisher::ECodingPreset CTSquisher::GetCompressPreset(EPixelFormat compressFmt, EPixelFormat uncompressFmt)
|
|
{
|
|
CryTextureSquisher::ECodingPreset preset = CryTextureSquisher::eCompressorPreset_Num;
|
|
switch (compressFmt)
|
|
{
|
|
case ePixelFormat_BC1:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC1U;
|
|
break;
|
|
case ePixelFormat_BC1a:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC1Ua;
|
|
break;
|
|
case ePixelFormat_BC3:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC3U;
|
|
break;
|
|
case ePixelFormat_BC3t:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC3Ut;
|
|
break;
|
|
case ePixelFormat_BC4:
|
|
preset = (CPixelFormats::GetInstance().IsFormatSingleChannel(uncompressFmt)
|
|
? CryTextureSquisher::eCompressorPreset_BC4Ua // a-channel
|
|
: CryTextureSquisher::eCompressorPreset_BC4U); // r-channel
|
|
break;
|
|
case ePixelFormat_BC4s:
|
|
preset = (CPixelFormats::GetInstance().IsFormatSingleChannel(uncompressFmt)
|
|
? CryTextureSquisher::eCompressorPreset_BC4Sa // a-channel
|
|
: CryTextureSquisher::eCompressorPreset_BC4S); // r-channel
|
|
break;
|
|
case ePixelFormat_BC5:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC5Un;
|
|
break;
|
|
case ePixelFormat_BC5s:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC5Sn;
|
|
break;
|
|
case ePixelFormat_BC6UH:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC6UH;
|
|
break;
|
|
case ePixelFormat_BC7:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC7U;
|
|
break;
|
|
case ePixelFormat_BC7t:
|
|
preset = CryTextureSquisher::eCompressorPreset_BC7Ut;
|
|
break;
|
|
default:
|
|
AZ_Assert(false, "%s: Unexpected pixel format (in compressing an image). Inform an RC programmer.", __FUNCTION__);
|
|
}
|
|
|
|
return preset;
|
|
}
|
|
|
|
bool CTSquisher::IsCompressedPixelFormatSupported(EPixelFormat fmt)
|
|
{
|
|
switch (fmt)
|
|
{
|
|
case ePixelFormat_BC1:
|
|
case ePixelFormat_BC1a:
|
|
case ePixelFormat_BC3:
|
|
case ePixelFormat_BC3t:
|
|
case ePixelFormat_BC4:
|
|
case ePixelFormat_BC4s:
|
|
case ePixelFormat_BC5:
|
|
case ePixelFormat_BC5s:
|
|
case ePixelFormat_BC6UH:
|
|
case ePixelFormat_BC7:
|
|
case ePixelFormat_BC7t:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CTSquisher::IsUncompressedPixelFormatSupported(EPixelFormat fmt)
|
|
{
|
|
switch (fmt)
|
|
{
|
|
case ePixelFormat_R8:
|
|
case ePixelFormat_A8:
|
|
case ePixelFormat_R8G8B8A8:
|
|
case ePixelFormat_R8G8B8X8:
|
|
case ePixelFormat_R32F:
|
|
case ePixelFormat_R32G32B32A32F:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CTSquisher::DoesSupportDecompress([[maybe_unused]] EPixelFormat fmtDst)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
ColorSpace CTSquisher::GetSupportedColorSpace([[maybe_unused]] EPixelFormat compressFormat) const
|
|
{
|
|
return ColorSpace::autoSelect;
|
|
}
|
|
|
|
EPixelFormat CTSquisher::GetSuggestedUncompressedFormat(EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const
|
|
{
|
|
//special cases
|
|
if (compressedfmt == ePixelFormat_BC6UH || compressedfmt == ePixelFormat_BC5 || compressedfmt == ePixelFormat_BC5s)
|
|
{
|
|
return ePixelFormat_R32G32B32A32F;
|
|
}
|
|
|
|
if (IsUncompressedPixelFormatSupported(uncompressedfmt))
|
|
{
|
|
return uncompressedfmt;
|
|
}
|
|
|
|
//for fmt dont support, convert to supported uncompressed formats: ePixelFormat_A8, ePixelFormat_R8, ePixelFormat_A8R8G8B8,
|
|
// ePixelFormat_X8R8G8B8, ePixelFormat_R32F, ePixelFormat_A32B32G32R32F
|
|
|
|
switch (uncompressedfmt)
|
|
{
|
|
case ePixelFormat_R8G8:
|
|
case ePixelFormat_R16G16:
|
|
case ePixelFormat_R8G8B8:
|
|
case ePixelFormat_B8G8R8:
|
|
return ePixelFormat_R8G8B8X8;
|
|
case ePixelFormat_R16:
|
|
return ePixelFormat_R8;
|
|
case ePixelFormat_R16G16B16A16:
|
|
case ePixelFormat_B8G8R8A8:
|
|
return ePixelFormat_R8G8B8A8;
|
|
case ePixelFormat_R9G9B9E5:
|
|
case ePixelFormat_R32G32F:
|
|
case ePixelFormat_R16G16B16A16F:
|
|
case ePixelFormat_R16G16F:
|
|
return ePixelFormat_R32G32B32A32F;
|
|
case ePixelFormat_R16F:
|
|
return ePixelFormat_R32F;
|
|
default:
|
|
//this shouldn't happen. but we could handle it with uncompressed data anyway
|
|
if (CPixelFormats::GetInstance().IsPixelFormatWithoutAlpha(uncompressedfmt))
|
|
{
|
|
return ePixelFormat_R8G8B8X8;
|
|
}
|
|
else
|
|
{
|
|
return ePixelFormat_R8G8B8A8;
|
|
}
|
|
}
|
|
}
|
|
|
|
IImageObjectPtr CTSquisher::DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const
|
|
{
|
|
// Decompressing
|
|
// the output pixel format could only have one channel or four channels. need to find out more
|
|
EPixelFormat fmtSrc = srcImage->GetPixelFormat();
|
|
|
|
//src format need to be compressed and dst format need to uncompressed.
|
|
if (!IsCompressedPixelFormatSupported(fmtSrc) || !IsUncompressedPixelFormatSupported(fmtDst))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst));
|
|
|
|
//clear the dstImage to (0, 0, 0, 1) since some compression format only write to certain channels
|
|
dstImage->ClearColor(0, 0, 0, 1);
|
|
|
|
//for each mipmap
|
|
const AZ::u32 mipCount = srcImage->GetMipCount();
|
|
for (AZ::u32 dwMip = 0; dwMip < mipCount; ++dwMip)
|
|
{
|
|
const AZ::u32 dwLocalWidth = srcImage->GetWidth(dwMip);
|
|
const AZ::u32 dwLocalHeight = srcImage->GetHeight(dwMip);
|
|
|
|
AZ::u8* pSrcMem;
|
|
AZ::u32 dwSrcPitch;
|
|
srcImage->GetImagePointer(dwMip, pSrcMem, dwSrcPitch);
|
|
|
|
AZ::u8* pDstMem;
|
|
AZ::u32 dwDstPitch;
|
|
dstImage->GetImagePointer(dwMip, pDstMem, dwDstPitch);
|
|
|
|
CrySquisherCallbackUserData userData;
|
|
userData.m_pImageObject = srcImage;
|
|
userData.m_dstOffset = 0;
|
|
userData.m_dstMem = pSrcMem;
|
|
|
|
CryTextureSquisher::DecompressorParameters decompress;
|
|
|
|
decompress.dstBuffer = pDstMem;
|
|
decompress.width = dwLocalWidth;
|
|
decompress.height = dwLocalHeight;
|
|
decompress.pitch = dwDstPitch;
|
|
|
|
decompress.dstType = (CPixelFormats::GetInstance().IsFormatFloatingPoint(fmtDst, true) ?
|
|
CryTextureSquisher::eBufferType_ufloat : CryTextureSquisher::eBufferType_uint8);
|
|
|
|
if (CPixelFormats::GetInstance().IsFormatSigned(fmtSrc))
|
|
{
|
|
decompress.dstType = (decompress.dstType == CryTextureSquisher::eBufferType_ufloat ?
|
|
CryTextureSquisher::eBufferType_sfloat : CryTextureSquisher::eBufferType_sint8);
|
|
}
|
|
|
|
decompress.userPtr = &userData;
|
|
decompress.userInputFunction = CrySquisherInputCallback;
|
|
decompress.preset = GetCompressPreset(fmtSrc, fmtDst);
|
|
|
|
CryTextureSquisher::Decompress(decompress);
|
|
}
|
|
|
|
// CTsquish operates on native normal vectors when floating-point
|
|
// buffers are used. Apply bias and scale when returning a normal-map.
|
|
if (fmtSrc == ePixelFormat_BC5 || fmtSrc == ePixelFormat_BC5s)
|
|
{
|
|
if (fmtDst == ePixelFormat_R32G32B32A32F)
|
|
{
|
|
//conver from [-1, 1] to [0, 1]. And set alpha to 1.
|
|
dstImage->ScaleAndBiasChannels(0, 100,
|
|
AZ::Vector4(0.5f, 0.5f, 0.5f, 0.0f),
|
|
AZ::Vector4(0.5f, 0.5f, 0.5f, 1.0f));
|
|
}
|
|
}
|
|
|
|
return dstImage;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
IImageObjectPtr CTSquisher::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst,
|
|
const CompressOption* compressOption) const
|
|
{
|
|
// Compressing
|
|
EPixelFormat fmtSrc = srcImage->GetPixelFormat();
|
|
|
|
//src format need to be uncompressed and dst format need to compressed.
|
|
if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst));
|
|
|
|
//passing compress option
|
|
ICompressor::EQuality quality = ICompressor::eQuality_Normal;
|
|
AZ::Vector3 weights = AZ::Vector3(0.3333f, 0.3334f, 0.3333f);
|
|
if (compressOption)
|
|
{
|
|
quality = compressOption->compressQuality;
|
|
weights = compressOption->rgbWeight;
|
|
}
|
|
|
|
//do some clamp for float
|
|
if (fmtSrc == ePixelFormat_R32G32B32A32F)
|
|
{
|
|
const uint32 nMips = srcImage->GetMipCount();
|
|
|
|
// NOTES:
|
|
// - all incoming images are unsigned, even normal maps
|
|
// - all mipmaps of incoming images can contain out-of-range values from mipmap filtering
|
|
// - 3Dc/BC5 is synonymous with "is a normal map" because they are not tagged explicitly as such
|
|
if (fmtDst == ePixelFormat_BC5 || fmtDst == ePixelFormat_BC5s)
|
|
{
|
|
srcImage->ScaleAndBiasChannels(0, nMips,
|
|
AZ::Vector4(2.0f, 2.0f, 2.0f, 1.0f),
|
|
AZ::Vector4(-1.0f, -1.0f, -1.0f, 0.0f));
|
|
srcImage->ClampChannels(0, nMips,
|
|
AZ::Vector4(-1.0f, -1.0f, -1.0f, -1.0f),
|
|
AZ::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
|
}
|
|
else if (fmtDst == ePixelFormat_BC6UH)
|
|
{
|
|
srcImage->ClampChannels(0, nMips,
|
|
AZ::Vector4(0.0f, 0.0f, 0.0f, 0.0f),
|
|
AZ::Vector4(FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX));
|
|
}
|
|
else
|
|
{
|
|
srcImage->ClampChannels(0, nMips,
|
|
AZ::Vector4(0.0f, 0.0f, 0.0f, 0.0f),
|
|
AZ::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
|
}
|
|
}
|
|
|
|
const uint32 mipCount = dstImage->GetMipCount();
|
|
for (uint32 dwMip = 0; dwMip < mipCount; ++dwMip)
|
|
{
|
|
uint32 dwLocalWidth = srcImage->GetWidth(dwMip);
|
|
uint32 dwLocalHeight = srcImage->GetHeight(dwMip);
|
|
|
|
uint8* pSrcMem;
|
|
uint32 dwSrcPitch;
|
|
srcImage->GetImagePointer(dwMip, pSrcMem, dwSrcPitch);
|
|
|
|
uint8* pDstMem;
|
|
uint32 dwDstPitch;
|
|
dstImage->GetImagePointer(dwMip, pDstMem, dwDstPitch);
|
|
|
|
{
|
|
CrySquisherCallbackUserData userData;
|
|
userData.m_pImageObject = dstImage;
|
|
userData.m_dstOffset = 0;
|
|
userData.m_dstMem = pDstMem;
|
|
|
|
CryTextureSquisher::CompressorParameters compress;
|
|
|
|
compress.srcBuffer = pSrcMem;
|
|
compress.width = dwLocalWidth;
|
|
compress.height = dwLocalHeight;
|
|
compress.pitch = dwSrcPitch;
|
|
|
|
compress.srcType = (CPixelFormats::GetInstance().IsFormatFloatingPoint(fmtSrc, true) ?
|
|
CryTextureSquisher::eBufferType_ufloat : CryTextureSquisher::eBufferType_uint8);
|
|
if (CPixelFormats::GetInstance().IsFormatSigned(fmtDst))
|
|
{
|
|
compress.srcType = (compress.srcType == CryTextureSquisher::eBufferType_ufloat ?
|
|
CryTextureSquisher::eBufferType_sfloat : CryTextureSquisher::eBufferType_sint8);
|
|
}
|
|
|
|
const AZ::Vector3 uniform = AZ::Vector3(0.3333f, 0.3334f, 0.3333f);
|
|
|
|
compress.weights[0] = weights.GetX();
|
|
compress.weights[1] = weights.GetY();
|
|
compress.weights[2] = weights.GetZ();
|
|
|
|
compress.perceptual =
|
|
(compress.weights[0] != uniform.GetX()) ||
|
|
(compress.weights[1] != uniform.GetY()) ||
|
|
(compress.weights[2] != uniform.GetZ());
|
|
|
|
compress.quality =
|
|
(quality == eQuality_Preview ? CryTextureSquisher::eQualityProfile_Low :
|
|
(quality == eQuality_Fast ? CryTextureSquisher::eQualityProfile_Low :
|
|
(quality == eQuality_Slow ? CryTextureSquisher::eQualityProfile_High :
|
|
CryTextureSquisher::eQualityProfile_Medium)));
|
|
|
|
compress.userPtr = &userData;
|
|
compress.userOutputFunction = CrySquisherOutputCallback;
|
|
compress.preset = GetCompressPreset(fmtDst, fmtSrc);
|
|
|
|
CryTextureSquisher::Compress(compress);
|
|
}
|
|
} // for: all mips
|
|
|
|
return dstImage;
|
|
}
|
|
}; //namespace ImageProcessingAtom
|