diff --git a/src/common/AvifFormat.cpp b/src/common/AvifFormat.cpp
index d8328e9..ca9c2e2 100644
--- a/src/common/AvifFormat.cpp
+++ b/src/common/AvifFormat.cpp
@@ -77,7 +77,9 @@ namespace
{
globals->context = nullptr;
globals->imageHandle = nullptr;
+ globals->imageHandleNclxProfile = nullptr;
globals->image = nullptr;
+ globals->imageHandleProfileType = heif_color_profile_type_not_present;
globals->saveOptions.quality = 85;
globals->saveOptions.chromaSubsampling = ChromaSubsampling::Yuv422;
globals->saveOptions.compressionSpeed = CompressionSpeed::Default;
diff --git a/src/common/AvifFormat.h b/src/common/AvifFormat.h
index a3bbe84..38dfa44 100644
--- a/src/common/AvifFormat.h
+++ b/src/common/AvifFormat.h
@@ -63,7 +63,9 @@ struct Globals
{
heif_context* context;
heif_image_handle* imageHandle;
+ heif_color_profile_nclx* imageHandleNclxProfile;
heif_image* image;
+ heif_color_profile_type imageHandleProfileType;
SaveUIOptions saveOptions;
bool libheifInitialized;
diff --git a/src/common/AvifFormat.r b/src/common/AvifFormat.r
index 96ec144..8490f3a 100644
--- a/src/common/AvifFormat.r
+++ b/src/common/AvifFormat.r
@@ -95,7 +95,7 @@ resource 'PiPL' (ResourceID, plugInName " PiPL", purgeable)
noLABColor
},
- EnableInfo { "in (PSHOP_ImageMode, RGBMode, RGB48Mode, GrayScaleMode, Gray16Mode)" },
+ EnableInfo { "in (PSHOP_ImageMode, RGBMode, RGB48Mode, GrayScaleMode, Gray16Mode, RGB96Mode, Gray32Mode)" },
PlugInMaxSize { 1073741824, 1073741824 },
diff --git a/src/common/ColorProfileGeneration.cpp b/src/common/ColorProfileGeneration.cpp
index f38e4a9..43f4e93 100644
--- a/src/common/ColorProfileGeneration.cpp
+++ b/src/common/ColorProfileGeneration.cpp
@@ -345,7 +345,10 @@ void SetIccProfileFromNclx(FormatRecord* formatRecord, const heif_color_profile_
toneCurve.reset(cmsBuildParametricToneCurve(context.get(), 4, Parameters));
description = L"Grayscale (sRGB TRC)";
}
- else if (transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_709_5)
+ else if (transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_709_5 ||
+ transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_601_6 ||
+ transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_2020_2_10bit ||
+ transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_2020_2_12bit)
{
cmsFloat64Number Parameters[5]
{
@@ -357,7 +360,26 @@ void SetIccProfileFromNclx(FormatRecord* formatRecord, const heif_color_profile_
};
toneCurve.reset(cmsBuildParametricToneCurve(context.get(), 4, Parameters));
- description = L"Grayscale (Rec. 709 TRC)";
+
+ switch (primaries)
+ {
+ case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
+ description = L"Grayscale (BT. 2020)";
+ break;
+ case heif_color_primaries_ITU_R_BT_709_5:
+ case heif_color_primaries_ITU_R_BT_470_6_System_M:
+ case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
+ case heif_color_primaries_ITU_R_BT_601_6:
+ case heif_color_primaries_SMPTE_240M:
+ case heif_color_primaries_generic_film:
+ case heif_color_primaries_SMPTE_ST_428_1:
+ case heif_color_primaries_SMPTE_RP_431_2:
+ case heif_color_primaries_SMPTE_EG_432_1:
+ case heif_color_primaries_EBU_Tech_3213_E:
+ default:
+ description = L"Grayscale (Rec. 709 TRC)";
+ break;
+ }
}
if (toneCurve && description != nullptr)
@@ -437,6 +459,51 @@ void SetIccProfileFromNclx(FormatRecord* formatRecord, const heif_color_profile_
description);
}
}
+ else if (primaries == heif_color_primaries_ITU_R_BT_2020_2_and_2100_0)
+ {
+ const cmsCIExyY whitepoint = { 0.3127, 0.3290, 1.0f }; // D65
+ const cmsCIExyYTRIPLE rgbPrimaries =
+ {
+ { 0.708, 0.292, 1.0 },
+ { 0.170, 0.797, 1.0 },
+ { 0.131, 0.046, 1.0 }
+ };
+
+ ScopedLcmsToneCurve toneCurve;
+ const wchar_t* description = nullptr;
+
+ if (linear)
+ {
+ toneCurve.reset(cmsBuildGamma(context.get(), 1.0));
+ description = L"BT. 2020 (Linear RGB Profile)";
+ }
+ else if (transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_2020_2_10bit ||
+ transferCharacteristics == heif_transfer_characteristic_ITU_R_BT_2020_2_12bit)
+ {
+ // BT. 2020 uses the same transfer curve as Rec. 709.
+ cmsFloat64Number Parameters[5]
+ {
+ 1.0 / 0.45,
+ 1.0 / 1.099296826809442,
+ 1.0 - 1 / 1.099296826809442,
+ 1.0 / 4.5,
+ 4.5 * 0.018053968510807,
+ };
+
+ toneCurve.reset(cmsBuildParametricToneCurve(context.get(), 4, Parameters));
+ description = L"BT. 2020";
+ }
+
+ if (toneCurve && description != nullptr)
+ {
+ profile = BuildRGBProfile(
+ context.get(),
+ &whitepoint,
+ &rgbPrimaries,
+ toneCurve.get(),
+ description);
+ }
+ }
if (profile)
{
diff --git a/src/common/ColorTransfer.cpp b/src/common/ColorTransfer.cpp
new file mode 100644
index 0000000..c2f459c
--- /dev/null
+++ b/src/common/ColorTransfer.cpp
@@ -0,0 +1,99 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021, 2022 Nicholas Hayes
+ *
+ * avif-format is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * avif-format is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with avif-format. If not, see .
+ */
+
+#include "ColorTransfer.h"
+#include
+
+namespace
+{
+ // The sRGB reference viewing environment has a maximum luminance level of 80 nits
+ // See the 'Screen luminance level' value in the sRGB reference viewing environment table
+ // https://en.wikipedia.org/wiki/SRGB#Viewing_environment
+ constexpr float srgbMaxLuminanceLevel = 80.0f;
+ // PQ (SMPTE ST 2084) has a maximum luminance level of 10000 nits
+ // https://en.wikipedia.org/wiki/Perceptual_quantizer
+ constexpr float pqMaxLuminanceLevel = 10000;
+
+ inline float LinearToPQ(float normalizedLinearValue)
+ {
+ // These constant values are taken from the Perceptual quantizer article on Wikipedia
+ // https://en.wikipedia.org/wiki/Perceptual_quantizer
+ constexpr float m1 = 2610.0f / 16384.0f;
+ constexpr float m2 = 2523.0f / 4096.0f * 128.0f;
+ constexpr float c1 = 3424.0f / 4096.0f; // c3 - c2 + 1
+ constexpr float c2 = 2413.0f / 4096.0f * 32.0f;
+ constexpr float c3 = 2392.0f / 4096.0f * 32.0f;
+
+ if (normalizedLinearValue < 0.0f)
+ {
+ return 0.0f;
+ }
+
+ const float x = powf(normalizedLinearValue, m1);
+ const float pq = powf((c1 + c2 * x) / (1.0f + c3 * x), m2);
+
+ return pq;
+ }
+
+ inline float PQToLinear(float value)
+ {
+ // These constant values are taken from the Perceptual quantizer article on Wikipedia
+ // https://en.wikipedia.org/wiki/Perceptual_quantizer
+ constexpr float m1 = 2610.0f / 16384.0f;
+ constexpr float m2 = 2523.0f / 4096.0f * 128.0f;
+ constexpr float c1 = 3424.0f / 4096.0f; // c3 - c2 + 1
+ constexpr float c2 = 2413.0f / 4096.0f * 32.0f;
+ constexpr float c3 = 2392.0f / 4096.0f * 32.0f;
+
+ if (value < 0.0f)
+ {
+ return 0.0f;
+ }
+
+ const float x = powf(value, 1.0f / m2);
+ const float normalizedLinear = powf(std::max(x - c1, 0.0f) / (c2 - c3 * x), 1.0f / m1);
+
+ // We have to adjust for the difference in the maximum luminance level between
+ // PQ and sRGB, otherwise the image is too dark.
+ return normalizedLinear * (pqMaxLuminanceLevel / srgbMaxLuminanceLevel);
+ }
+}
+
+float TransferCurveToLinear(float value, ColorTransferCurveType curveType)
+{
+ switch (curveType)
+ {
+ case ColorTransferCurveType::PQ:
+ return PQToLinear(value);
+ default:
+ throw std::runtime_error("Unsupported color transfer curve type.");
+ }
+}
+
+float LinearToTransferCurve(float value, ColorTransferCurveType curveType)
+{
+ switch (curveType)
+ {
+ case ColorTransferCurveType::PQ:
+ return LinearToPQ(value);
+ default:
+ throw std::runtime_error("Unsupported color transfer curve type.");
+ }
+}
diff --git a/src/common/ColorTransfer.h b/src/common/ColorTransfer.h
new file mode 100644
index 0000000..84fc5f2
--- /dev/null
+++ b/src/common/ColorTransfer.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021, 2022 Nicholas Hayes
+ *
+ * avif-format is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * avif-format is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with avif-format. If not, see .
+ */
+
+#ifndef COLORTRANSFER_H
+#define COLORTRANSFER_H
+
+#include
+#include
+
+enum class ColorTransferCurveType
+{
+ PQ
+};
+
+float TransferCurveToLinear(float value, ColorTransferCurveType curveType);
+
+float LinearToTransferCurve(float value, ColorTransferCurveType curveType);
+
+#endif // !COLORTRANSFER_H
diff --git a/src/common/Read.cpp b/src/common/Read.cpp
index 95f4504..432969c 100644
--- a/src/common/Read.cpp
+++ b/src/common/Read.cpp
@@ -169,6 +169,18 @@ namespace
return alphaState;
}
+
+ bool IsHDRImage(const heif_color_profile_nclx* nclx)
+ {
+ bool result = false;
+
+ if (nclx != nullptr)
+ {
+ result = nclx->transfer_characteristics == heif_transfer_characteristic_ITU_R_BT_2100_0_PQ;
+ }
+
+ return result;
+ }
}
OSErr DoReadPrepare(FormatRecordPtr formatRecord)
@@ -245,6 +257,15 @@ OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals)
formatRecord->imageSize.v = static_cast(height);
}
+ const heif_color_profile_type imageHandleProfileType = heif_image_handle_get_color_profile_type(primaryImage.get());
+
+ ScopedHeifNclxProfile imageHandleNclxProfile;
+
+ if (imageHandleProfileType == heif_color_profile_type_nclx)
+ {
+ imageHandleNclxProfile = GetNclxColorProfile(primaryImage.get());
+ }
+
ScopedHeifImage image = DecodeImage(primaryImage.get(), heif_colorspace_undefined, heif_chroma_undefined);
const heif_colorspace colorSpace = heif_image_get_colorspace(image.get());
@@ -265,8 +286,16 @@ OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals)
break;
case 10:
case 12:
- formatRecord->imageMode = plugInModeGray16;
- formatRecord->depth = 16;
+ if (IsHDRImage(imageHandleNclxProfile.get()))
+ {
+ formatRecord->imageMode = plugInModeGrayScale;
+ formatRecord->depth = 32;
+ }
+ else
+ {
+ formatRecord->imageMode = plugInModeGray16;
+ formatRecord->depth = 16;
+ }
break;
default:
throw OSErrException(formatCannotRead);
@@ -288,8 +317,16 @@ OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals)
break;
case 10:
case 12:
- formatRecord->imageMode = plugInModeRGB48;
- formatRecord->depth = 16;
+ if (IsHDRImage(imageHandleNclxProfile.get()))
+ {
+ formatRecord->imageMode = plugInModeRGBColor;
+ formatRecord->depth = 32;
+ }
+ else
+ {
+ formatRecord->imageMode = plugInModeRGB48;
+ formatRecord->depth = 16;
+ }
break;
default:
throw OSErrException(formatCannotRead);
@@ -311,8 +348,16 @@ OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals)
break;
case 10:
case 12:
- formatRecord->imageMode = plugInModeRGB48;
- formatRecord->depth = 16;
+ if (IsHDRImage(imageHandleNclxProfile.get()))
+ {
+ formatRecord->imageMode = plugInModeRGBColor;
+ formatRecord->depth = 32;
+ }
+ else
+ {
+ formatRecord->imageMode = plugInModeRGB48;
+ formatRecord->depth = 16;
+ }
break;
default:
throw OSErrException(formatCannotRead);
@@ -334,7 +379,9 @@ OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals)
// The image data and meta-data will be set in DoReadContinue.
globals->context = context.release();
globals->imageHandle = primaryImage.release();
+ globals->imageHandleNclxProfile = imageHandleNclxProfile.release();
globals->image = image.release();
+ globals->imageHandleProfileType = imageHandleProfileType;
}
catch (const std::bad_alloc&)
{
@@ -368,26 +415,56 @@ OSErr DoReadContinue(FormatRecordPtr formatRecord, Globals* globals)
try
{
- ScopedHeifNclxProfile imageNclxProfile = GetNclxColorProfile(globals->image);
+ ScopedHeifNclxProfile imageNclxProfile;
+
+ const heif_color_profile_nclx* nclxProfile = globals->imageHandleNclxProfile;
+
+ if (nclxProfile == nullptr)
+ {
+ // If the image handle does not have color information from a NCLX 'colr' box
+ // try to get the color information from the image bitstream.
+ imageNclxProfile = GetNclxColorProfile(globals->image);
+ if (imageNclxProfile)
+ {
+ nclxProfile = imageNclxProfile.get();
+ }
+ }
const AlphaState alphaState = GetAlphaState(globals->imageHandle);
- switch (formatRecord->imageMode)
+ if (IsMonochromeImage(formatRecord))
+ {
+ switch (formatRecord->depth)
+ {
+ case 8:
+ ReadHeifImageGrayEightBit(globals->image, alphaState, nclxProfile, formatRecord);
+ break;
+ case 16:
+ ReadHeifImageGraySixteenBit(globals->image, alphaState, nclxProfile, formatRecord);
+ break;
+ case 32:
+ ReadHeifImageGrayThirtyTwoBit(globals->image, alphaState, nclxProfile, formatRecord);
+ break;
+ default:
+ throw std::runtime_error("Unsupported host bit depth");
+ }
+ }
+ else
{
- case plugInModeGrayScale:
- ReadHeifImageGrayEightBit(globals->image, alphaState, imageNclxProfile.get(), formatRecord);
- break;
- case plugInModeGray16:
- ReadHeifImageGraySixteenBit(globals->image, alphaState, imageNclxProfile.get(), formatRecord);
- break;
- case plugInModeRGBColor:
- ReadHeifImageRGBEightBit(globals->image, alphaState, imageNclxProfile.get(), formatRecord);
- break;
- case plugInModeRGB48:
- ReadHeifImageRGBSixteenBit(globals->image, alphaState, imageNclxProfile.get(), formatRecord);
- break;
- default:
- throw OSErrException(formatCannotRead);
+ switch (formatRecord->depth)
+ {
+ case 8:
+ ReadHeifImageRGBEightBit(globals->image, alphaState, nclxProfile, formatRecord);
+ break;
+ case 16:
+ ReadHeifImageRGBSixteenBit(globals->image, alphaState, nclxProfile, formatRecord);
+ break;
+ case 32:
+ ReadHeifImageRGBThirtyTwoBit(globals->image, alphaState, nclxProfile, formatRecord);
+ break;
+ default:
+ throw std::runtime_error("Unsupported host bit depth");
+ }
}
SetRect(formatRecord, 0, 0, 0, 0);
@@ -403,7 +480,7 @@ OSErr DoReadContinue(FormatRecordPtr formatRecord, Globals* globals)
if (formatRecord->canUseICCProfiles)
{
- const heif_color_profile_type imageHandleProfileType = heif_image_handle_get_color_profile_type(globals->imageHandle);
+ const heif_color_profile_type imageHandleProfileType = globals->imageHandleProfileType;
if (imageHandleProfileType == heif_color_profile_type_prof ||
imageHandleProfileType == heif_color_profile_type_rICC)
@@ -412,15 +489,9 @@ OSErr DoReadContinue(FormatRecordPtr formatRecord, Globals* globals)
}
else
{
- if (imageNclxProfile)
- {
- SetIccProfileFromNclx(formatRecord, imageNclxProfile.get());
- }
- else if (imageHandleProfileType == heif_color_profile_type_nclx)
+ if (nclxProfile != nullptr)
{
- ScopedHeifNclxProfile imageHandleNclxProfile = GetNclxColorProfile(globals->imageHandle);
-
- SetIccProfileFromNclx(formatRecord, imageHandleNclxProfile.get());
+ SetIccProfileFromNclx(formatRecord, nclxProfile);
}
}
}
@@ -466,6 +537,12 @@ OSErr DoReadFinish(Globals* globals)
globals->imageHandle = nullptr;
}
+ if (globals->imageHandleNclxProfile != nullptr)
+ {
+ heif_nclx_color_profile_free(globals->imageHandleNclxProfile);
+ globals->imageHandleNclxProfile = nullptr;
+ }
+
if (globals->context != nullptr)
{
heif_context_free(globals->context);
diff --git a/src/common/ReadHeifImage.cpp b/src/common/ReadHeifImage.cpp
index c5e0db9..2a9b441 100644
--- a/src/common/ReadHeifImage.cpp
+++ b/src/common/ReadHeifImage.cpp
@@ -19,10 +19,12 @@
*/
#include "ReadHeifImage.h"
+#include "ColorTransfer.h"
#include "PremultipliedAlpha.h"
#include "ScopedBufferSuite.h"
#include "Utilities.h"
#include "YUVDecode.h"
+#include
namespace
{
@@ -284,6 +286,125 @@ namespace
}
}
}
+
+ void ReadHeifImageYUVThirtyTwoBit(
+ const heif_image* image,
+ AlphaState alphaState,
+ const heif_color_profile_nclx* nclxProfile,
+ FormatRecordPtr formatRecord,
+ ColorTransferCurveType curveType)
+ {
+ const heif_chroma chroma = heif_image_get_chroma_format(image);
+
+ const int lumaBitsPerPixel = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
+
+ if (heif_image_get_bits_per_pixel_range(image, heif_channel_Cb) != lumaBitsPerPixel ||
+ heif_image_get_bits_per_pixel_range(image, heif_channel_Cr) != lumaBitsPerPixel)
+ {
+ throw std::runtime_error("The chroma channel bit depth does not match the main image.");
+ }
+
+ const VPoint imageSize = GetImageSize(formatRecord);
+ const bool hasAlpha = alphaState != AlphaState::None;
+
+ SetupFormatRecord(formatRecord, imageSize);
+
+ int yPlaneStride;
+ const uint8_t* yPlaneScan0 = heif_image_get_plane_readonly(image, heif_channel_Y, &yPlaneStride);
+
+ int cbPlaneStride;
+ const uint8_t* cbPlaneScan0 = heif_image_get_plane_readonly(image, heif_channel_Cb, &cbPlaneStride);
+
+ int crPlaneStride;
+ const uint8_t* crPlaneScan0 = heif_image_get_plane_readonly(image, heif_channel_Cr, &crPlaneStride);
+
+ ScopedBufferSuiteBuffer buffer(formatRecord->bufferProcs, formatRecord->rowBytes);
+
+ formatRecord->data = buffer.Lock();
+
+ const int32 left = 0;
+ const int32 right = imageSize.h;
+
+ YUVCoefficiants yuvCoefficiants{};
+
+ GetYUVCoefficiants(nclxProfile, yuvCoefficiants);
+
+ const YUVLookupTables tables(nclxProfile, lumaBitsPerPixel, false, hasAlpha);
+
+ int32 xChromaShift, yChromaShift;
+
+ GetChromaShift(chroma, xChromaShift, yChromaShift);
+
+ if (hasAlpha)
+ {
+ if (heif_image_get_bits_per_pixel_range(image, heif_channel_Alpha) != lumaBitsPerPixel)
+ {
+ throw std::runtime_error("The alpha channel bit depth does not match the main image channels.");
+ }
+
+ int alphaStride;
+ const uint8_t* alphaScan0 = heif_image_get_plane_readonly(image, heif_channel_Alpha, &alphaStride);
+ const bool alphaPremultiplied = alphaState == AlphaState::Premultiplied;
+
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ const int32 uvJ = y >> yChromaShift;
+ const uint16_t* srcY = reinterpret_cast(yPlaneScan0 + (static_cast(y) * yPlaneStride));
+ const uint16_t* srcCb = reinterpret_cast(cbPlaneScan0 + (static_cast(uvJ) * cbPlaneStride));
+ const uint16_t* srcCr = reinterpret_cast(crPlaneScan0 + (static_cast(uvJ) * crPlaneStride));
+
+ const uint16_t* srcAlpha = reinterpret_cast(alphaScan0 + (static_cast(y) * alphaStride));
+ float* dst = static_cast(formatRecord->data);
+
+ DecodeYUV16RowToRGBA32(srcY, srcCb, srcCr, srcAlpha, alphaPremultiplied,
+ dst, imageSize.h, xChromaShift, yuvCoefficiants, tables, curveType);
+
+ const int32 top = y;
+ const int32 bottom = y + 1;
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+ }
+ }
+ else
+ {
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ const int32 uvJ = y >> yChromaShift;
+ const uint16_t* srcY = reinterpret_cast(yPlaneScan0 + (static_cast(y) * yPlaneStride));
+ const uint16_t* srcCb = reinterpret_cast(cbPlaneScan0 + (static_cast(uvJ) * cbPlaneStride));
+ const uint16_t* srcCr = reinterpret_cast(crPlaneScan0 + (static_cast(uvJ) * crPlaneStride));
+
+ float* dst = static_cast(formatRecord->data);
+
+ DecodeYUV16RowToRGB32(srcY, srcCb, srcCr, dst, imageSize.h, xChromaShift,
+ yuvCoefficiants, tables, curveType);
+
+ const int32 top = y;
+ const int32 bottom = y + 1;
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+ }
+ }
+ }
+
+ ::std::vector BuildUnormToFloatLookupTable(int bitDepth)
+ {
+ const size_t count = static_cast(1) << bitDepth;
+ const float maxValue = static_cast(count - 1);
+
+ ::std::vector table(count);
+
+ for (size_t i = 0; i < count; i++)
+ {
+ table[i] = static_cast(i) / maxValue;
+ }
+
+ return table;
+ }
}
void ReadHeifImageGrayEightBit(
@@ -730,3 +851,260 @@ void ReadHeifImageRGBSixteenBit(
}
}
}
+
+void ReadHeifImageGrayThirtyTwoBit(
+ const heif_image* image,
+ AlphaState alphaState,
+ const heif_color_profile_nclx* nclxProfile,
+ FormatRecordPtr formatRecord)
+{
+ if (nclxProfile == nullptr)
+ {
+ throw std::runtime_error("The nclxProfile is null.");
+ }
+
+ const VPoint imageSize = GetImageSize(formatRecord);
+ const bool hasAlpha = alphaState != AlphaState::None;
+
+ SetupFormatRecord(formatRecord, imageSize);
+
+ const int lumaBitsPerPixel = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
+
+ int grayStride;
+ const uint8_t* grayScan0 = heif_image_get_plane_readonly(image, heif_channel_Y, &grayStride);
+
+ ScopedBufferSuiteBuffer buffer(formatRecord->bufferProcs, formatRecord->rowBytes);
+
+ formatRecord->data = buffer.Lock();
+
+ const int32 left = 0;
+ const int32 right = imageSize.h;
+
+ const YUVLookupTables tables(nclxProfile, lumaBitsPerPixel, true, hasAlpha);
+ ColorTransferCurveType curveType = ColorTransferCurveType::PQ;
+
+ if (nclxProfile->transfer_characteristics != heif_transfer_characteristic_ITU_R_BT_2100_0_PQ)
+ {
+ throw std::runtime_error("Unsupported NCLX transfer characteristic.");
+ }
+
+ if (hasAlpha)
+ {
+ if (heif_image_get_bits_per_pixel_range(image, heif_channel_Alpha) != lumaBitsPerPixel)
+ {
+ throw std::runtime_error("The alpha channel bit depth does not match the main image channels.");
+ }
+
+ int alphaStride;
+ const uint8_t* alphaScan0 = heif_image_get_plane_readonly(image, heif_channel_Alpha, &alphaStride);
+ const bool alphaPremultiplied = alphaState == AlphaState::Premultiplied;
+
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ const uint16_t* srcGray = reinterpret_cast(grayScan0 + (static_cast(y) * grayStride));
+ const uint16_t* srcAlpha = reinterpret_cast(alphaScan0 + (static_cast(y) * alphaStride));
+ float* dst = static_cast(formatRecord->data);
+
+ DecodeY16RowToGrayAlpha32(srcGray, srcAlpha, alphaPremultiplied, dst, imageSize.h, tables, curveType);
+
+ const int32 top = y;
+ const int32 bottom = y + 1;
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+ }
+ }
+ else
+ {
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ const uint16_t* srcGray = reinterpret_cast(grayScan0 + (static_cast(y) * grayStride));
+ float* dst = static_cast(formatRecord->data);
+
+ DecodeY16RowToGray32(srcGray, dst, imageSize.h, tables, curveType);
+
+ const int32 top = y;
+ const int32 bottom = y + 1;
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+ }
+ }
+}
+
+void ReadHeifImageRGBThirtyTwoBit(
+ const heif_image* image,
+ AlphaState alphaState,
+ const heif_color_profile_nclx* nclxProfile,
+ FormatRecordPtr formatRecord)
+{
+ if (nclxProfile == nullptr)
+ {
+ throw std::runtime_error("The nclxProfile is null.");
+ }
+
+ ColorTransferCurveType curveType = ColorTransferCurveType::PQ;
+
+ if (nclxProfile->transfer_characteristics != heif_transfer_characteristic_ITU_R_BT_2100_0_PQ)
+ {
+ throw std::runtime_error("Unsupported NCLX transfer characteristic.");
+ }
+
+ const heif_colorspace colorspace = heif_image_get_colorspace(image);
+
+ // The image color space can be either YCbCr or RGB.
+ if (colorspace == heif_colorspace_YCbCr)
+ {
+ ReadHeifImageYUVThirtyTwoBit(image, alphaState, nclxProfile, formatRecord, curveType);
+ return;
+ }
+ else if (colorspace != heif_colorspace_RGB)
+ {
+ throw std::runtime_error("Unsupported image color space, expected RGB.");
+ }
+
+ const VPoint imageSize = GetImageSize(formatRecord);
+ const bool hasAlpha = alphaState != AlphaState::None;
+
+ const int redBitsPerPixel = heif_image_get_bits_per_pixel_range(image, heif_channel_R);
+
+ if (heif_image_get_bits_per_pixel_range(image, heif_channel_G) != redBitsPerPixel ||
+ heif_image_get_bits_per_pixel_range(image, heif_channel_B) != redBitsPerPixel)
+ {
+ throw std::runtime_error("The color channel bit depths do not match.");
+ }
+
+ SetupFormatRecord(formatRecord, imageSize);
+
+ int rPlaneStride;
+ const uint8_t* rPlaneScan0 = heif_image_get_plane_readonly(image, heif_channel_R, &rPlaneStride);
+
+ int gPlaneStride;
+ const uint8_t* gPlaneScan0 = heif_image_get_plane_readonly(image, heif_channel_G, &gPlaneStride);
+
+ int bPlaneStride;
+ const uint8_t* bPlaneScan0 = heif_image_get_plane_readonly(image, heif_channel_B, &bPlaneStride);
+
+ ScopedBufferSuiteBuffer buffer(formatRecord->bufferProcs, formatRecord->rowBytes);
+
+ formatRecord->data = buffer.Lock();
+
+ const int32 left = 0;
+ const int32 right = imageSize.h;
+
+ const ::std::vector unormToFloatTable = BuildUnormToFloatLookupTable(redBitsPerPixel);
+
+ if (hasAlpha)
+ {
+ if (heif_image_get_bits_per_pixel_range(image, heif_channel_Alpha) != redBitsPerPixel)
+ {
+ throw std::runtime_error("The alpha channel bit depth does not match the main image channels.");
+ }
+
+ int alphaStride;
+ const uint8_t* alphaScan0 = heif_image_get_plane_readonly(image, heif_channel_Alpha, &alphaStride);
+ const bool alphaPremultiplied = alphaState == AlphaState::Premultiplied;
+
+ const uint16_t rgbMaxValue = (1 << redBitsPerPixel) - 1;
+
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ const uint16_t* srcR = reinterpret_cast(rPlaneScan0 + (static_cast(y) * rPlaneStride));
+ const uint16_t* srcG = reinterpret_cast(gPlaneScan0 + (static_cast(y) * gPlaneStride));
+ const uint16_t* srcB = reinterpret_cast(bPlaneScan0 + (static_cast(y) * bPlaneStride));
+ const uint16_t* srcAlpha = reinterpret_cast(alphaScan0 + (static_cast(y) * alphaStride));
+
+ float* dst = static_cast(formatRecord->data);
+
+ for (int32 x = 0; x < imageSize.h; x++)
+ {
+ uint16_t unormR = *srcR;
+ uint16_t unormG = *srcG;
+ uint16_t unormB = *srcB;
+ const uint16_t unormA = *srcAlpha;
+
+ if (alphaPremultiplied)
+ {
+ if (unormA < rgbMaxValue)
+ {
+ if (unormA == 0)
+ {
+ unormR = 0;
+ unormG = 0;
+ unormB = 0;
+ }
+ else
+ {
+ unormR = UnpremultiplyColor(unormR, unormA, rgbMaxValue);
+ unormG = UnpremultiplyColor(unormG, unormA, rgbMaxValue);
+ unormB = UnpremultiplyColor(unormB, unormA, rgbMaxValue);
+ }
+ }
+ }
+
+ const float r = unormToFloatTable[unormR];
+ const float g = unormToFloatTable[unormG];
+ const float b = unormToFloatTable[unormB];
+ const float a = unormToFloatTable[unormA];
+
+ dst[0] = TransferCurveToLinear(r, curveType);
+ dst[1] = TransferCurveToLinear(g, curveType);
+ dst[2] = TransferCurveToLinear(b, curveType);
+ dst[3] = a;
+
+ srcR++;
+ srcG++;
+ srcB++;
+ srcAlpha++;
+ dst += 4;
+ }
+
+ const int32 top = y;
+ const int32 bottom = y + 1;
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+ }
+ }
+ else
+ {
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ const uint16_t* srcR = reinterpret_cast(rPlaneScan0 + (static_cast(y) * rPlaneStride));
+ const uint16_t* srcG = reinterpret_cast(gPlaneScan0 + (static_cast(y) * gPlaneStride));
+ const uint16_t* srcB = reinterpret_cast(bPlaneScan0 + (static_cast(y) * bPlaneStride));
+
+ float* dst = static_cast(formatRecord->data);
+
+ for (int32 x = 0; x < imageSize.h; x++)
+ {
+ const uint16_t unormR = *srcR;
+ const uint16_t unormG = *srcG;
+ const uint16_t unormB = *srcB;
+
+ const float r = unormToFloatTable[unormR];
+ const float g = unormToFloatTable[unormG];
+ const float b = unormToFloatTable[unormB];
+
+ dst[0] = TransferCurveToLinear(r, curveType);
+ dst[1] = TransferCurveToLinear(g, curveType);
+ dst[2] = TransferCurveToLinear(b, curveType);
+
+ srcR++;
+ srcG++;
+ srcB++;
+ dst += 3;
+ }
+
+ const int32 top = y;
+ const int32 bottom = y + 1;
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+ }
+ }
+}
diff --git a/src/common/ReadHeifImage.h b/src/common/ReadHeifImage.h
index 89e22e7..06b5de1 100644
--- a/src/common/ReadHeifImage.h
+++ b/src/common/ReadHeifImage.h
@@ -48,4 +48,16 @@ void ReadHeifImageRGBSixteenBit(
const heif_color_profile_nclx* nclxProfile,
FormatRecordPtr formatRecord);
+void ReadHeifImageGrayThirtyTwoBit(
+ const heif_image* image,
+ AlphaState alphaState,
+ const heif_color_profile_nclx* nclxProfile,
+ FormatRecordPtr formatRecord);
+
+void ReadHeifImageRGBThirtyTwoBit(
+ const heif_image* image,
+ AlphaState alphaState,
+ const heif_color_profile_nclx* nclxProfile,
+ FormatRecordPtr formatRecord);
+
#endif // !READHEIFIMAGE_H
diff --git a/src/common/Utilities.cpp b/src/common/Utilities.cpp
index f030e7e..95b8a70 100644
--- a/src/common/Utilities.cpp
+++ b/src/common/Utilities.cpp
@@ -421,9 +421,11 @@ bool HasAlphaChannel(const FormatRecordPtr formatRecord)
{
case plugInModeGrayScale:
case plugInModeGray16:
+ case plugInModeGray32:
return formatRecord->planes == 2;
case plugInModeRGBColor:
case plugInModeRGB48:
+ case plugInModeRGB96:
return formatRecord->planes == 4;
default:
return false;
@@ -436,6 +438,7 @@ bool IsMonochromeImage(const FormatRecordPtr formatRecord)
{
case plugInModeGrayScale:
case plugInModeGray16:
+ case plugInModeGray32:
return true;
default:
return false;
diff --git a/src/common/YUVDecode.h b/src/common/YUVDecode.h
index f9c15fc..70f8641 100644
--- a/src/common/YUVDecode.h
+++ b/src/common/YUVDecode.h
@@ -21,6 +21,7 @@
#ifndef YUVDECODE_H
#define YUVDECODE_H
+#include "ColorTransfer.h"
#include "YUVCoefficiants.h"
#include "YUVLookupTables.h"
@@ -46,12 +47,28 @@ void DecodeY16RowToGray16(
void DecodeY16RowToGrayAlpha16(
const uint16_t* yPlane,
- const uint16_t* alphaPlane,
+ const uint16_t* alphaPlane,
bool alphaPremultiplied,
uint16_t* grayaRow,
int32 rowWidth,
const YUVLookupTables& tables);
+void DecodeY16RowToGray32(
+ const uint16_t* yPlane,
+ float* grayRow,
+ int32 rowWidth,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType);
+
+void DecodeY16RowToGrayAlpha32(
+ const uint16_t* yPlane,
+ const uint16_t* alphaPlane,
+ bool alphaPremultiplied,
+ float* grayaRow,
+ int32 rowWidth,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType);
+
void DecodeYUV8RowToRGB8(
const uint8_t* yPlane,
const uint8_t* uPlane,
@@ -96,4 +113,28 @@ void DecodeYUV16RowToRGBA16(
const YUVCoefficiants& yuvCoefficiants,
const YUVLookupTables& tables);
+void DecodeYUV16RowToRGB32(
+ const uint16_t* yPlane,
+ const uint16_t* uPlane,
+ const uint16_t* vPlane,
+ float* rgbRow,
+ int32 rowWidth,
+ int32 xChromaShift,
+ const YUVCoefficiants& yuvCoefficiants,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType);
+
+void DecodeYUV16RowToRGBA32(
+ const uint16_t* yPlane,
+ const uint16_t* uPlane,
+ const uint16_t* vPlane,
+ const uint16_t* alphaPlane,
+ bool alphaPremultiplied,
+ float* rgbaRow,
+ int32 rowWidth,
+ int32 xChromaShift,
+ const YUVCoefficiants& yuvCoefficiants,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType);
+
#endif // !YUVDECODE_H
diff --git a/src/common/YuvDecode.cpp b/src/common/YuvDecode.cpp
index 6e6cc61..adde480 100644
--- a/src/common/YuvDecode.cpp
+++ b/src/common/YuvDecode.cpp
@@ -45,6 +45,7 @@
*/
#include "YUVDecode.h"
+#include "ColorTransfer.h"
#include "PremultipliedAlpha.h"
#include
#include
@@ -190,6 +191,76 @@ void DecodeY16RowToGrayAlpha16(
}
}
+void DecodeY16RowToGray32(
+ const uint16_t* yPlane,
+ float* grayRow,
+ int32 rowWidth,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType)
+{
+ float* dstPtr = grayRow;
+
+ const uint16_t yuvMaxChannel = static_cast(tables.yuvMaxChannel);
+
+ for (int32 x = 0; x < rowWidth; ++x)
+ {
+ // Unpack Y into unorm
+ const uint16_t unormY = std::min(yPlane[x], yuvMaxChannel);
+
+ // Convert unorm to float
+ const float Y = tables.unormFloatTableY[unormY];
+
+ dstPtr[0] = TransferCurveToLinear(Y, curveType);
+
+ dstPtr++;
+ }
+}
+
+void DecodeY16RowToGrayAlpha32(
+ const uint16_t* yPlane,
+ const uint16_t* alphaPlane,
+ bool alphaPremultiplied,
+ float* grayaRow,
+ int32 rowWidth,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType)
+{
+ float* dstPtr = grayaRow;
+
+ const uint16_t yuvMaxChannel = static_cast(tables.yuvMaxChannel);
+
+ for (int32 x = 0; x < rowWidth; ++x)
+ {
+ // Unpack Y into unorm
+ uint16_t unormY = std::min(yPlane[x], yuvMaxChannel);
+ const uint16_t unormA = std::min(alphaPlane[x], yuvMaxChannel);
+
+ if (alphaPremultiplied)
+ {
+ if (unormA < tables.yuvMaxChannel)
+ {
+ if (unormA == 0)
+ {
+ unormY = 0;
+ }
+ else
+ {
+ unormY = UnpremultiplyColor(unormY, unormA, yuvMaxChannel);
+ }
+ }
+ }
+
+ // Convert unorm to float
+ const float Y = tables.unormFloatTableY[unormY];
+ const float A = tables.unormFloatTableAlpha[unormA];
+
+ dstPtr[0] = TransferCurveToLinear(Y, curveType);
+ dstPtr[1] = A;
+
+ dstPtr += 2;
+ }
+}
+
void DecodeYUV8RowToRGB8(
const uint8_t* yPlane,
const uint8_t* uPlane,
@@ -429,3 +500,123 @@ void DecodeYUV16RowToRGBA16(
dstPtr += 4;
}
}
+
+void DecodeYUV16RowToRGB32(
+ const uint16_t* yPlane,
+ const uint16_t* uPlane,
+ const uint16_t* vPlane,
+ float* rgbRow,
+ int32 rowWidth,
+ int32 xChromaShift,
+ const YUVCoefficiants& yuvCoefficiants,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType)
+{
+ const float kr = yuvCoefficiants.kr;
+ const float kg = yuvCoefficiants.kg;
+ const float kb = yuvCoefficiants.kb;
+
+ const uint16_t yuvMaxChannel = static_cast(tables.yuvMaxChannel);
+
+ float* dstPtr = rgbRow;
+
+ for (int32 x = 0; x < rowWidth; ++x)
+ {
+ // Unpack YUV into unorm
+ const int32_t uvI = x >> xChromaShift;
+ const uint16_t unormY = std::min(yPlane[x], yuvMaxChannel);
+ const uint16_t unormU = std::min(uPlane[uvI], yuvMaxChannel);
+ const uint16_t unormV = std::min(vPlane[uvI], yuvMaxChannel);
+
+ // Convert unorm to float
+ const float Y = tables.unormFloatTableY[unormY];
+ const float Cb = tables.unormFloatTableUV[unormU];
+ const float Cr = tables.unormFloatTableUV[unormV];
+
+ float R = Y + (2 * (1 - kr)) * Cr;
+ float B = Y + (2 * (1 - kb)) * Cb;
+ float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
+
+ R = std::clamp(R, 0.0f, 1.0f);
+ G = std::clamp(G, 0.0f, 1.0f);
+ B = std::clamp(B, 0.0f, 1.0f);
+
+ dstPtr[0] = TransferCurveToLinear(R, curveType);
+ dstPtr[1] = TransferCurveToLinear(G, curveType);
+ dstPtr[2] = TransferCurveToLinear(B, curveType);
+
+ dstPtr += 3;
+ }
+}
+
+void DecodeYUV16RowToRGBA32(
+ const uint16_t* yPlane,
+ const uint16_t* uPlane,
+ const uint16_t* vPlane,
+ const uint16_t* alphaPlane,
+ bool alphaPremultiplied,
+ float* rgbaRow,
+ int32 rowWidth,
+ int32 xChromaShift,
+ const YUVCoefficiants& yuvCoefficiants,
+ const YUVLookupTables& tables,
+ ColorTransferCurveType curveType)
+{
+ const float kr = yuvCoefficiants.kr;
+ const float kg = yuvCoefficiants.kg;
+ const float kb = yuvCoefficiants.kb;
+
+ const uint16_t yuvMaxChannel = static_cast(tables.yuvMaxChannel);
+
+ float* dstPtr = rgbaRow;
+
+ for (int32 x = 0; x < rowWidth; ++x)
+ {
+ // Unpack YUV into unorm
+ const int32_t uvI = x >> xChromaShift;
+ const uint16_t unormY = std::min(yPlane[x], yuvMaxChannel);
+ const uint16_t unormU = std::min(uPlane[uvI], yuvMaxChannel);
+ const uint16_t unormV = std::min(vPlane[uvI], yuvMaxChannel);
+ const uint16_t unormA = std::min(alphaPlane[x], yuvMaxChannel);
+
+ // Convert unorm to float
+ const float Y = tables.unormFloatTableY[unormY];
+ const float Cb = tables.unormFloatTableUV[unormU];
+ const float Cr = tables.unormFloatTableUV[unormV];
+
+ float R = Y + (2 * (1 - kr)) * Cr;
+ float B = Y + (2 * (1 - kb)) * Cb;
+ float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
+
+ R = std::clamp(R, 0.0f, 1.0f);
+ G = std::clamp(G, 0.0f, 1.0f);
+ B = std::clamp(B, 0.0f, 1.0f);
+ const float A = tables.unormFloatTableAlpha[unormA];
+
+ if (alphaPremultiplied)
+ {
+ if (unormA < tables.yuvMaxChannel)
+ {
+ if (unormA == 0)
+ {
+ R = 0;
+ G = 0;
+ B = 0;
+ }
+ else
+ {
+ R = UnpremultiplyColor(R, A, 1.0f);
+ G = UnpremultiplyColor(G, A, 1.0f);
+ B = UnpremultiplyColor(B, A, 1.0f);
+ }
+ }
+ }
+
+ dstPtr[0] = TransferCurveToLinear(R, curveType);
+ dstPtr[1] = TransferCurveToLinear(G, curveType);
+ dstPtr[2] = TransferCurveToLinear(B, curveType);
+ dstPtr[3] = A;
+
+ dstPtr += 4;
+ }
+}
diff --git a/vs/AvifFormat.vcxproj b/vs/AvifFormat.vcxproj
index 928550c..d038a3f 100644
--- a/vs/AvifFormat.vcxproj
+++ b/vs/AvifFormat.vcxproj
@@ -332,6 +332,7 @@
+
@@ -358,6 +359,7 @@
+
diff --git a/vs/AvifFormat.vcxproj.filters b/vs/AvifFormat.vcxproj.filters
index 7a619b3..00b9243 100644
--- a/vs/AvifFormat.vcxproj.filters
+++ b/vs/AvifFormat.vcxproj.filters
@@ -93,6 +93,9 @@
Header Files
+
+ Header Files
+
@@ -170,6 +173,9 @@
Source Files
+
+ Source Files
+