/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "GradientSignal_precompiled.h" #include #include namespace { namespace ChannelID { constexpr auto R = 0; constexpr auto G = 1; constexpr auto B = 2; constexpr auto A = 3; } ImageProcessing::EPixelFormat ExportFormatToPixelFormat(GradientSignal::ExportFormat format) { using namespace GradientSignal; using namespace ImageProcessing; switch (format) { case ExportFormat::U8: return ePixelFormat_R8; case ExportFormat::U16: return ePixelFormat_R16; case ExportFormat::U32: return ePixelFormat_R32; case ExportFormat::F32: return ePixelFormat_R32F; default: return EPixelFormat::ePixelFormat_Unknown; } } template struct Underlying {}; template <> struct Underlying { using type = AZ::u8; }; template <> struct Underlying { using type = AZ::u16; }; template <> struct Underlying { using type = AZ::u32; }; template <> struct Underlying { using type = float; }; template A Lerp(A a, B b, C c) { return aznumeric_cast((1 - c) * a + (c * b)); } template Target ScaleToTypeRange(double scaleFactor) { scaleFactor = AZStd::clamp(scaleFactor, 0.0, 1.0); if constexpr (AZStd::is_floating_point_v) { return aznumeric_cast(scaleFactor); } else { return aznumeric_cast(Lerp(aznumeric_cast(std::numeric_limits::lowest()), aznumeric_cast(std::numeric_limits::max()), scaleFactor)); } } template Target GetNormalScaled(T value, AZStd::pair range) { // Normalize a value between 0 and 1 double scaleFactor = (value - aznumeric_cast(range.first)) / (aznumeric_cast(range.second) - aznumeric_cast(range.first)); return ScaleToTypeRange(scaleFactor); } template AZStd::pair GetRange(const AZStd::vector& buffer) { auto bPtr = reinterpret_cast(buffer.data()); auto bEndPtr = reinterpret_cast(buffer.data() + buffer.size()); auto [min, max] = AZStd::minmax_element(bPtr, bEndPtr); return AZStd::make_pair(aznumeric_cast(*min), aznumeric_cast(*max)); } template void ConvertBufferType(AZStd::vector& buffer, bool autoScale, AZStd::pair userRange) { AZStd::size_t numElems = buffer.size() / sizeof(Old); AZStd::vector newBuffer(numElems * sizeof(New)); auto bPtr = reinterpret_cast(buffer.data()); auto bEndPtr = bPtr + numElems; auto nbPtr = reinterpret_cast(newBuffer.data()); if (autoScale) { userRange = GetRange(buffer); } else { // Validate user input userRange.first = AZStd::clamp(userRange.first, aznumeric_cast(std::numeric_limits::lowest()), aznumeric_cast(std::numeric_limits::max())); userRange.second = AZStd::clamp(userRange.second, aznumeric_cast(std::numeric_limits::lowest()), aznumeric_cast(std::numeric_limits::max())); userRange.second = AZStd::max(userRange.second, userRange.first); } // Account for case where range.first equals range.second to prevent division by 0. if (AZ::GetAbs(userRange.second - userRange.first) < std::numeric_limits::epsilon()) { if (!autoScale) { AZ_Warning("Buffer Type Conversion", false, "Check min and max ranges! Max cannot be less than or equal to min."); } while (bPtr != bEndPtr) { *nbPtr = ScaleToTypeRange(1.0); ++bPtr; ++nbPtr; } } else { while (bPtr != bEndPtr) { *nbPtr = GetNormalScaled(*bPtr, userRange); ++bPtr; ++nbPtr; } } buffer = AZStd::move(newBuffer); } template void ConvertBufferType(AZStd::vector& buffer, ImageProcessing::EPixelFormat newFormat, bool autoScale, AZStd::pair userRange) { using namespace ImageProcessing; switch (newFormat) { case ePixelFormat_R8: ConvertBufferType::type, Underlying::type>(buffer, autoScale, userRange); break; case ePixelFormat_R16: ConvertBufferType::type, Underlying::type>(buffer, autoScale, userRange); break; case ePixelFormat_R32: ConvertBufferType::type, Underlying::type>(buffer, autoScale, userRange); break; case ePixelFormat_R32F: ConvertBufferType::type, Underlying::type>(buffer, autoScale, userRange); break; default: break; } } ImageProcessing::EPixelFormat ConvertBufferType(AZStd::vector& buffer, ImageProcessing::EPixelFormat old, ImageProcessing::EPixelFormat newFormat, bool autoScale, AZStd::pair userRange) { using namespace ImageProcessing; switch (old) { case ePixelFormat_R8: ConvertBufferType(buffer, newFormat, autoScale, userRange); break; case ePixelFormat_R16: ConvertBufferType(buffer, newFormat, autoScale, userRange); break; case ePixelFormat_R32: ConvertBufferType(buffer, newFormat, autoScale, userRange); break; case ePixelFormat_R32F: ConvertBufferType(buffer, newFormat, autoScale, userRange); break; default: break; } return newFormat; } AZ::u8 IsActive(AZ::u8 index, AZ::u8 mask) { return (mask & (1 << index)) != 0; } template T AlphaOp(T val, const T* buffer, GradientSignal::AlphaExportTransform op) { switch (op) { case GradientSignal::AlphaExportTransform::MULTIPLY: { if constexpr (AZStd::is_floating_point_v) { return val * buffer[ChannelID::A]; } else { return aznumeric_cast(val * (aznumeric_cast(buffer[ChannelID::A]) / std::numeric_limits::max())); } } case GradientSignal::AlphaExportTransform::ADD: return aznumeric_cast(val + buffer[ChannelID::A]); case GradientSignal::AlphaExportTransform::SUBTRACT: return aznumeric_cast(val - buffer[ChannelID::A]); default: return val; } } template T GetMin(const T* arr, GradientSignal::ChannelMask mask, AZStd::size_t channels) { T min = std::numeric_limits::max(); for (AZStd::size_t i = 0; i < channels; ++i) { min = AZStd::min(min, Lerp(min, arr[i], IsActive(i, mask))); } return min; } template T GetMax(const T* arr, GradientSignal::ChannelMask mask, AZStd::size_t channels) { T max = std::numeric_limits::lowest(); for (AZStd::size_t i = 0; i < channels; ++i) { max = AZStd::max(max, Lerp(max, arr[i], IsActive(i, mask))); } return max; } template T GetAverage(const T* arr, GradientSignal::ChannelMask mask, AZStd::size_t channels) { double total = 0.0; AZStd::size_t active = 0; for (AZStd::size_t i = 0; i < channels; ++i) { AZ::u8 result = IsActive(i, mask); total += result * arr[i]; active += result; } total /= AZStd::max(active, aznumeric_cast(1)); return aznumeric_cast(total); } template T GetTerrarium(const T* arr, GradientSignal::ChannelMask mask, AZStd::size_t channels) { if (channels < 3 || !IsActive(ChannelID::R, mask) || !IsActive(ChannelID::G, mask) || !IsActive(ChannelID::B, mask)) { AZ_Error("GradientImageConversion", false, "RGB channels must be present and active!"); return aznumeric_cast(0); } else { /* "Terrarium" is an image-based terrain file format as defined here: https://www.mapzen.com/blog/terrain-tile-service/ According to the website: "Terrarium format PNG tiles contain raw elevation data in meters, in Mercator projection (EPSG:3857). All values are positive with a 32,768 offset, split into the red, green, and blue channels, with 16 bits of integer and 8 bits of fraction. To decode: (red * 256 + green + blue / 256) - 32768" This gives a range -32768 to 32768 meters at a constant 1/256 meter resolution. For reference, the lowest point on Earth (Mariana Trench) is at -10911 m, and the highest point (Mt Everest) is at 8848 m. */ return aznumeric_cast((arr[ChannelID::R] * 256.0) + arr[ChannelID::G] + (arr[ChannelID::B] / 256.0) - 32768.0); } } template void TransformBuffer(AZStd::size_t channels, GradientSignal::ChannelMask mask, GradientSignal::AlphaExportTransform alphaTransform, AZStd::vector& mem, const AZStd::function& transformFunc) { auto begin = reinterpret_cast(mem.data()); auto end = reinterpret_cast(mem.data() + mem.size()); auto out = begin; auto activeChannels = AZStd::min(channels, aznumeric_cast(3)); if (channels < 4 || !IsActive(ChannelID::A, mask)) { while (begin < end) { *out = transformFunc(begin, mask, activeChannels); std::advance(out, 1); std::advance(begin, channels); } } else { while (begin < end) { *out = AlphaOp(transformFunc(begin, mask, activeChannels), begin, alphaTransform); std::advance(out, 1); std::advance(begin, channels); } } mem.resize(mem.size() / channels); } AZStd::size_t GetChannels(ImageProcessing::EPixelFormat format) { using namespace ImageProcessing; switch (format) { case ePixelFormat_R8G8: case ePixelFormat_R16G16: case ePixelFormat_R32G32F: return 2; case ePixelFormat_R16G16B16A16: case ePixelFormat_R8G8B8A8: case ePixelFormat_R8G8B8X8: case ePixelFormat_R32G32B32A32F: return 4; default: return 0; } } template