From f1600070b39d7a99a62e0df716aed3e3972a50fa Mon Sep 17 00:00:00 2001
From: Nicholas Hayes <0xC0000054@users.noreply.github.com>
Date: Sun, 11 Apr 2021 21:06:58 -0600
Subject: [PATCH] Add the project
---
3rd-party/.gitignore | 1 +
3rd-party/README.md | 40 ++
src/.editorconfig | 10 +
src/common/AvifFormat.cpp | 202 +++++++++
src/common/AvifFormat.h | 126 ++++++
src/common/AvifFormat.r | 238 ++++++++++
src/common/AvifFormatTerminology.h | 67 +++
src/common/BigDocument.cpp | 57 +++
src/common/Common.cpp | 42 ++
src/common/Common.h | 66 +++
src/common/Estimate.cpp | 96 ++++
src/common/FileIO.cpp | 52 +++
src/common/FileIO.h | 36 ++
src/common/HostMetadata.cpp | 97 ++++
src/common/HostMetadata.h | 34 ++
src/common/LibHeifException.h | 71 +++
src/common/Memory.cpp | 107 +++++
src/common/OSErrException.h | 51 +++
src/common/Options.cpp | 85 ++++
src/common/Read.cpp | 326 ++++++++++++++
src/common/ReadMetadata.cpp | 287 ++++++++++++
src/common/ReadMetadata.h | 32 ++
src/common/ScopedBufferSuite.h | 126 ++++++
src/common/ScopedHandleSuite.h | 115 +++++
src/common/ScopedHeif.h | 54 +++
src/common/Scripting.cpp | 278 ++++++++++++
src/common/Utilities.cpp | 421 ++++++++++++++++++
src/common/Write.cpp | 682 +++++++++++++++++++++++++++++
src/common/WriteMetadata.cpp | 173 ++++++++
src/common/WriteMetadata.h | 32 ++
src/common/version.h | 27 ++
src/win/AvifFormat.rc | Bin 0 -> 10098 bytes
src/win/FileIOWin.cpp | 124 ++++++
src/win/FileIOWin.h | 36 ++
src/win/MemoryWin.cpp | 73 +++
src/win/MemoryWin.h | 29 ++
src/win/PiPL.rc | Bin 0 -> 54 bytes
src/win/UIWin.cpp | 600 +++++++++++++++++++++++++
src/win/dllmain.cpp | 41 ++
src/win/resource.h | 42 ++
src/win/version.rc | Bin 0 -> 4744 bytes
vs/AvifFormat.sln | 36 ++
vs/AvifFormat.sln.licenseheader | 21 +
vs/AvifFormat.vcxproj | 274 ++++++++++++
vs/AvifFormat.vcxproj.filters | 139 ++++++
45 files changed, 5446 insertions(+)
create mode 100644 3rd-party/.gitignore
create mode 100644 3rd-party/README.md
create mode 100644 src/.editorconfig
create mode 100644 src/common/AvifFormat.cpp
create mode 100644 src/common/AvifFormat.h
create mode 100644 src/common/AvifFormat.r
create mode 100644 src/common/AvifFormatTerminology.h
create mode 100644 src/common/BigDocument.cpp
create mode 100644 src/common/Common.cpp
create mode 100644 src/common/Common.h
create mode 100644 src/common/Estimate.cpp
create mode 100644 src/common/FileIO.cpp
create mode 100644 src/common/FileIO.h
create mode 100644 src/common/HostMetadata.cpp
create mode 100644 src/common/HostMetadata.h
create mode 100644 src/common/LibHeifException.h
create mode 100644 src/common/Memory.cpp
create mode 100644 src/common/OSErrException.h
create mode 100644 src/common/Options.cpp
create mode 100644 src/common/Read.cpp
create mode 100644 src/common/ReadMetadata.cpp
create mode 100644 src/common/ReadMetadata.h
create mode 100644 src/common/ScopedBufferSuite.h
create mode 100644 src/common/ScopedHandleSuite.h
create mode 100644 src/common/ScopedHeif.h
create mode 100644 src/common/Scripting.cpp
create mode 100644 src/common/Utilities.cpp
create mode 100644 src/common/Write.cpp
create mode 100644 src/common/WriteMetadata.cpp
create mode 100644 src/common/WriteMetadata.h
create mode 100644 src/common/version.h
create mode 100644 src/win/AvifFormat.rc
create mode 100644 src/win/FileIOWin.cpp
create mode 100644 src/win/FileIOWin.h
create mode 100644 src/win/MemoryWin.cpp
create mode 100644 src/win/MemoryWin.h
create mode 100644 src/win/PiPL.rc
create mode 100644 src/win/UIWin.cpp
create mode 100644 src/win/dllmain.cpp
create mode 100644 src/win/resource.h
create mode 100644 src/win/version.rc
create mode 100644 vs/AvifFormat.sln
create mode 100644 vs/AvifFormat.sln.licenseheader
create mode 100644 vs/AvifFormat.vcxproj
create mode 100644 vs/AvifFormat.vcxproj.filters
diff --git a/3rd-party/.gitignore b/3rd-party/.gitignore
new file mode 100644
index 0000000..355164c
--- /dev/null
+++ b/3rd-party/.gitignore
@@ -0,0 +1 @@
+*/
diff --git a/3rd-party/README.md b/3rd-party/README.md
new file mode 100644
index 0000000..12c875e
--- /dev/null
+++ b/3rd-party/README.md
@@ -0,0 +1,40 @@
+This directory contains the 3rd-party dependencies required to build the plug-in.
+
+### Adobe Photoshop CS5 SDK
+
+You will need to download the Adobe Photoshop CS5 SDK from http://www.adobe.com/devnet/photoshop/sdk.html and unzip it into this folder.
+
+### AOM
+
+Clone AOM from your preferred tag:
+
+`git clone -b v3.0.0 --depth 1 https://aomedia.googlesource.com/aom`
+
+Change into the `aom` directory and create a build directory.
+In this example a 64-bit build using Visual Studio 2019 is used.
+
+`cd aom`
+`mkdir build64 && cd build64`
+`cmake -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DENABLE_DOCS=0 -DENABLE_EXAMPLES=0 -DENABLE_TESTDATA=0 -DENABLE_TESTS=0 -DENABLE_TOOLS=0 ..`
+`cmake --build .`
+
+The generated AOM library should be located in `aom/build64/Release`, this library will be used when building `libheif`.
+
+### libheif
+
+Clone libheif from your preferred tag:
+
+`git clone -b v1.11.0 --depth 1 https://github.com/strukturag/libheif`
+
+Change into the `libheif` directory and create a build directory.
+In this example a 64-bit build using Visual Studio 2019 is used.
+
+`cd libheif`
+`mkdir build64 && cd build64`
+`cmake -G "Visual Studio 16 2019" -DBUILD_SHARED_LIBS=OFF -DWITH_EXAMPLES=OFF -DAOM_INCLUDE_DIR=..\..\aom -DAOM_LIBRARY=..\..\aom\build64\Release\aom.lib ..`
+`cmake --build .`
+
+You will need to add `LIBHEIF_STATIC_BUILD` to the preprocessor settings page in the libheif project properties,
+and remove the `HAS_VISIBILITY` definition if present.
+
+The generated libheif library should be located in `libheif/build64/Release`.
\ No newline at end of file
diff --git a/src/.editorconfig b/src/.editorconfig
new file mode 100644
index 0000000..415f182
--- /dev/null
+++ b/src/.editorconfig
@@ -0,0 +1,10 @@
+root = true
+
+[*]
+end_of_line = crlf
+
+[*.{cpp,h}]
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/src/common/AvifFormat.cpp b/src/common/AvifFormat.cpp
new file mode 100644
index 0000000..eab5c85
--- /dev/null
+++ b/src/common/AvifFormat.cpp
@@ -0,0 +1,202 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+#include "FileIO.h"
+#include
+
+namespace
+{
+ OSErr CreateGlobals(FormatRecordPtr formatRecord, intptr_t* data)
+ {
+ Handle handle;
+
+ OSErr e = NewPIHandle(formatRecord, sizeof(Globals), &handle);
+
+ if (e == noErr)
+ {
+ *data = reinterpret_cast(handle);
+ }
+
+ return e;
+ }
+
+ OSErr DoFilterFile(FormatRecordPtr formatRecord)
+ {
+ constexpr int bufferSize = 50;
+ uint8_t buffer[bufferSize] = {};
+
+ OSErr err = ReadData(formatRecord->dataFork, buffer, bufferSize);
+
+ if (err == noErr)
+ {
+ try
+ {
+ if (!heif_has_compatible_brand(buffer, bufferSize, "avif"))
+ {
+ err = formatCannotRead;
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ err = memFullErr;
+ }
+ catch (...)
+ {
+ err = formatCannotRead;
+ }
+ }
+
+ return err;
+ }
+
+ void InitGlobals(Globals* globals)
+ {
+ globals->context = nullptr;
+ globals->imageHandle = nullptr;
+ globals->image = nullptr;
+ globals->saveOptions.quality = 85;
+ globals->saveOptions.chromaSubsampling = ChromaSubsampling::Yuv422;
+ globals->saveOptions.compressionSpeed = CompressionSpeed::Default;
+ globals->saveOptions.lossless = false;
+ globals->saveOptions.imageBitDepth = ImageBitDepth::Twelve; // The save UI will default to 8-bit if the image is 8-bit.
+ globals->saveOptions.keepColorProfile = false;
+ globals->saveOptions.keepExif = false;
+ globals->saveOptions.keepXmp = false;
+ }
+}
+
+void PluginMain(const short selector, FormatRecordPtr formatRecord, intptr_t* data, short* result)
+{
+ if (selector == formatSelectorAbout)
+ {
+ DoAbout(reinterpret_cast(formatRecord));
+ *result = noErr;
+ }
+ else
+ {
+ if (formatRecord->HostSupports32BitCoordinates)
+ {
+ formatRecord->PluginUsing32BitCoordinates = true;
+ }
+
+ Globals* globals;
+ if (*data == 0)
+ {
+ *result = CreateGlobals(formatRecord, data);
+ if (*result != noErr)
+ {
+ return;
+ }
+
+ globals = reinterpret_cast(LockPIHandle(formatRecord, reinterpret_cast(*data), false));
+ InitGlobals(globals);
+ }
+ else
+ {
+ globals = reinterpret_cast(LockPIHandle(formatRecord, reinterpret_cast(*data), false));
+ }
+
+ switch (selector)
+ {
+ case formatSelectorReadPrepare:
+ *result = DoReadPrepare(formatRecord);
+ break;
+ case formatSelectorReadStart:
+ *result = DoReadStart(formatRecord, globals);
+ break;
+ case formatSelectorReadContinue:
+ *result = DoReadContinue(formatRecord, globals);
+ break;
+ case formatSelectorReadFinish:
+ *result = DoReadFinish(globals);
+ break;
+
+ case formatSelectorOptionsPrepare:
+ *result = DoOptionsPrepare(formatRecord);
+ break;
+ case formatSelectorOptionsStart:
+ *result = DoOptionsStart(formatRecord, globals);
+ break;
+ case formatSelectorOptionsContinue:
+ *result = DoOptionsContinue();
+ break;
+ case formatSelectorOptionsFinish:
+ *result = DoOptionsFinish();
+ break;
+
+ case formatSelectorEstimatePrepare:
+ *result = DoEstimatePrepare(formatRecord);
+ break;
+ case formatSelectorEstimateStart:
+ *result = DoEstimateStart(formatRecord);
+ break;
+ case formatSelectorEstimateContinue:
+ *result = DoEstimateContinue();
+ break;
+ case formatSelectorEstimateFinish:
+ *result = DoEstimateFinish();
+ break;
+
+ case formatSelectorWritePrepare:
+ *result = DoWritePrepare(formatRecord);
+ break;
+ case formatSelectorWriteStart:
+ *result = DoWriteStart(formatRecord, globals->saveOptions);
+ break;
+ case formatSelectorWriteContinue:
+ *result = DoWriteContinue();
+ break;
+ case formatSelectorWriteFinish:
+ *result = DoWriteFinish(formatRecord, globals->saveOptions);
+ break;
+
+ case formatSelectorFilterFile:
+ *result = DoFilterFile(formatRecord);
+ break;
+
+ default:
+ *result = formatBadParameters;
+ }
+
+ UnlockPIHandle(formatRecord, reinterpret_cast(*data));
+ }
+}
+
+OSErr HandleErrorMessage(FormatRecordPtr formatRecord, const char* const message, OSErr fallbackErrorCode)
+{
+ if (formatRecord->errorString != nullptr)
+ {
+ const size_t length = strlen(message);
+
+ if (length <= 254)
+ {
+ uint8* errorReportString = reinterpret_cast(formatRecord->errorString);
+
+ errorReportString[0] = static_cast(length);
+ memcpy(&errorReportString[1], message, length);
+ errorReportString[length + 1] = 0;
+
+ return errReportString;
+ }
+ }
+
+ return ShowErrorDialog(formatRecord, message, fallbackErrorCode);
+}
diff --git a/src/common/AvifFormat.h b/src/common/AvifFormat.h
new file mode 100644
index 0000000..f1556be
--- /dev/null
+++ b/src/common/AvifFormat.h
@@ -0,0 +1,126 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 AVIFFORMAT_H
+#define AVIFFORMAT_H
+
+#include "Common.h"
+
+enum class ChromaSubsampling
+{
+ Yuv420,
+ Yuv422,
+ Yuv444
+};
+
+enum class CompressionSpeed
+{
+ Fastest,
+ Default,
+ Slowest
+};
+
+enum class ImageBitDepth
+{
+ Eight,
+ Ten,
+ Twelve
+};
+
+struct SaveUIOptions
+{
+ int quality;
+ ChromaSubsampling chromaSubsampling;
+ CompressionSpeed compressionSpeed;
+ ImageBitDepth imageBitDepth;
+ bool lossless;
+ bool keepColorProfile;
+ bool keepExif;
+ bool keepXmp;
+};
+
+struct Globals
+{
+ heif_context* context;
+ heif_image_handle* imageHandle;
+ heif_image* image;
+
+ SaveUIOptions saveOptions;
+};
+
+DLLExport MACPASCAL void PluginMain(
+ const short selector,
+ FormatRecordPtr formatParamBlock,
+ intptr_t* data,
+ short* result);
+
+
+OSErr HandleErrorMessage(FormatRecordPtr formatRecord, const char* const message, OSErr fallbackErrorCode);
+
+VPoint GetImageSize(const FormatRecordPtr formatRecord);
+void SetRect(FormatRecordPtr formatRecord, int32 top, int32 left, int32 bottom, int32 right);
+
+OSErr NewPIHandle(const FormatRecordPtr formatRecord, int32 size, Handle* handle);
+void DisposePIHandle(const FormatRecordPtr formatRecord, Handle handle);
+Ptr LockPIHandle(const FormatRecordPtr formatRecord, Handle handle, Boolean moveHigh);
+void UnlockPIHandle(const FormatRecordPtr formatRecord, Handle handle);
+
+// Platform-specific UI methods
+
+void DoAbout(const AboutRecordPtr aboutRecord);
+bool DoSaveUI(const FormatRecordPtr formatRecord, SaveUIOptions& options);
+OSErr ShowErrorDialog(const FormatRecordPtr formatRecord, const char* const message, OSErr fallbackErrorCode);
+
+// File Format API callbacks
+
+OSErr DoReadPrepare(FormatRecordPtr formatRecord);
+OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals);
+OSErr DoReadContinue(FormatRecordPtr formatRecord, Globals* globals);
+OSErr DoReadFinish(Globals* globals);
+
+OSErr DoOptionsPrepare(FormatRecordPtr formatRecord);
+OSErr DoOptionsStart(FormatRecordPtr formatRecord, Globals* globals);
+OSErr DoOptionsContinue();
+OSErr DoOptionsFinish();
+
+OSErr DoEstimatePrepare(FormatRecordPtr formatRecord);
+OSErr DoEstimateStart(FormatRecordPtr formatRecord);
+OSErr DoEstimateContinue();
+OSErr DoEstimateFinish();
+
+OSErr DoWritePrepare(FormatRecordPtr formatRecord);
+OSErr DoWriteStart(FormatRecordPtr formatRecord, SaveUIOptions& options);
+OSErr DoWriteContinue();
+OSErr DoWriteFinish(FormatRecordPtr formatRecord, const SaveUIOptions& options);
+
+// Scripting
+
+OSErr ReadScriptParamsOnWrite(FormatRecordPtr formatRecord, SaveUIOptions& options, Boolean* showDialog);
+OSErr WriteScriptParamsOnWrite(FormatRecordPtr formatRecord, const SaveUIOptions& options);
+
+// Utility functions
+
+bool DescriptorSuiteIsAvaliable(const FormatRecordPtr formatRecord);
+bool HandleSuiteIsAvailable(const FormatRecordPtr formatRecord);
+bool HostImageModeSupported(const FormatRecordPtr formatRecord);
+bool HostSupportsRequiredFeatures(const FormatRecordPtr formatRecord);
+bool PropertySuiteIsAvailable(const FormatRecordPtr formatRecord);
+
+#endif // !AVIFFORMAT_H
diff --git a/src/common/AvifFormat.r b/src/common/AvifFormat.r
new file mode 100644
index 0000000..2c40ec5
--- /dev/null
+++ b/src/common/AvifFormat.r
@@ -0,0 +1,238 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 .
+ */
+
+//-------------------------------------------------------------------------------
+// Definitions -- Required by include files.
+//-------------------------------------------------------------------------------
+
+// The About box and resources are created in PIUtilities.r.
+// You can easily override them, if you like.
+
+#define plugInName "AV1 Image"
+
+
+//-------------------------------------------------------------------------------
+// Set up included files for Macintosh and Windows.
+//-------------------------------------------------------------------------------
+
+#include "PIDefines.h"
+
+#if __PIMac__
+ #include "Types.r"
+ #include "SysTypes.r"
+ #include "PIGeneral.r"
+ #include "PIUtilities.r"
+#elif defined(__PIWin__)
+ #define Rez
+ #include "PIGeneral.h"
+ #include "PIUtilities.r"
+#endif
+
+#include "AvifFormatTerminology.h"
+
+//-------------------------------------------------------------------------------
+// PiPL resource
+//-------------------------------------------------------------------------------
+
+resource 'PiPL' (ResourceID, plugInName " PiPL", purgeable)
+{
+ {
+ Kind { ImageFormat },
+ Name { plugInName },
+ Version { (latestFormatVersion << 16) | latestFormatSubVersion },
+
+ #ifdef __PIMac__
+ #if (defined(__x86_64__))
+ CodeMacIntel64 { "PluginMain" },
+ #endif
+ #if (defined(__i386__))
+ CodeMacIntel32 { "PluginMain" },
+ #endif
+ #else
+ #if defined(_WIN64)
+ CodeWin64X86 { "PluginMain" },
+ #else
+ CodeWin32X86 { "PluginMain" },
+ #endif
+ #endif
+
+ HasTerminology { plugInClassID,
+ plugInEventID,
+ ResourceID,
+ "597B1E83-4F1B-4A1E-9BF8-0768DA0D2763" },
+
+ SupportedModes
+ {
+ noBitmap,
+ noGrayScale,
+ noIndexedColor,
+ doesSupportRGBColor,
+ noCMYKColor,
+ noHSLColor,
+ noHSBColor,
+ noMultichannel,
+ noDuotone,
+ noLABColor
+ },
+
+ EnableInfo { "in (PSHOP_ImageMode, RGBMode, RGB48Mode)" },
+
+ PlugInMaxSize { 1073741824, 1073741824 },
+
+ FormatMaxSize { { 32767, 32767 } },
+
+ FormatMaxChannels { { 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4 } },
+
+ FmtFileType { 'AV1F', '8BIM' },
+ //ReadTypes { { '8B1F', ' ' } },
+ //FilteredTypes { { '8B1F', ' ' } },
+ ReadExtensions { { 'AVIF' } },
+ WriteExtensions { { 'AVIF' } },
+ //FilteredExtensions { { 'AVIF' } },
+ FormatFlags { fmtDoesNotSaveImageResources,
+ fmtCanRead,
+ fmtCanWrite,
+ fmtCanWriteIfRead,
+ fmtCanWriteTransparency,
+ fmtCannotCreateThumbnail }
+ }
+ };
+}
+
+resource 'aete' (ResourceID, plugInName " dictionary", purgeable)
+{
+ 1, 0, english, roman, /* aete version and language specifiers */
+ {
+ vendorName, /* vendor suite name */
+ "AVIF format plug-in", /* optional description */
+ plugInSuiteID, /* suite ID */
+ 1, /* suite code, must be 1 */
+ 1, /* suite level, must be 1 */
+ {}, /* structure for filters */
+ { /* non-filter plug-in class here */
+ vendorName " avifFormat", /* unique class name */
+ plugInClassID, /* class ID, must be unique or Suite ID */
+ plugInAETEComment, /* optional description */
+ { /* define inheritance */
+ "", /* must be exactly this */
+ keyInherits, /* must be keyInherits */
+ classFormat, /* parent: Format, Import, Export */
+ "parent class format", /* optional description */
+ flagsSingleProperty, /* if properties, list below */
+
+ "quality",
+ keyQuality,
+ typeInteger,
+ "",
+ flagsSingleProperty,
+
+ "compression speed",
+ keyCompressionSpeed,
+ typeCompressionSpeed,
+ "",
+ flagsEnumeratedParameter,
+
+ "lossless compression",
+ keyLosslessCompression,
+ typeBoolean,
+ "",
+ flagsSingleProperty,
+
+ "chroma sub-sampling",
+ keyChromaSubsampling,
+ typeChromaSubsampling,
+ "",
+ flagsEnumeratedParameter,
+
+ "color profile",
+ keyKeepColorProfile,
+ typeBoolean,
+ "",
+ flagsSingleProperty,
+
+ "EXIF",
+ keyKeepEXIF,
+ typeBoolean,
+ "",
+ flagsSingleProperty,
+
+ "XMP",
+ keyKeepXMP,
+ typeBoolean,
+ "",
+ flagsSingleProperty,
+
+ "image depth",
+ keyImageBitDepth,
+ typeImageBitDepth,
+ "",
+ flagsEnumeratedParameter,
+ },
+ {}, /* elements (not supported) */
+ /* class descriptions */
+ },
+ {}, /* comparison ops (not supported) */
+ { /* enumerations */
+
+ typeCompressionSpeed,
+ {
+ "fastest",
+ compressionSpeedFastest,
+ "",
+
+ "default",
+ compressionSpeedDefault,
+ "",
+
+ "slowest",
+ compressionSpeedSlowest,
+ ""
+ },
+ typeChromaSubsampling,
+ {
+ "4:2:0",
+ chromaSubsampling420,
+ "YUV 4:2:0 (best compression)",
+
+ "4:2:2",
+ chromaSubsampling422,
+ "YUV 4:2:2",
+
+ "4:4:4",
+ chromaSubsampling444,
+ "YUV 4:4:4 (best quality)"
+ },
+ typeImageBitDepth,
+ {
+ "8-bit",
+ imageBitDepthEight,
+ "",
+
+ "10-bit",
+ imageBitDepthTen,
+ "",
+
+ "12-bit",
+ imageBitDepthTwelve,
+ ""
+ }
+ }
+ }
+};
diff --git a/src/common/AvifFormatTerminology.h b/src/common/AvifFormatTerminology.h
new file mode 100644
index 0000000..ae935e4
--- /dev/null
+++ b/src/common/AvifFormatTerminology.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 AVIFFORMATTERMINOLOGY_H
+#define AVIFFORMATTERMINOLOGY_H
+
+#include "PITerminology.h"
+#include "PIActions.h"
+
+#ifndef NULLID
+#define NULLID 0
+#endif
+
+// Dictionary (aete) resources:
+
+#define vendorName "0xC0000054"
+#define plugInAETEComment "AV1 Image format module"
+
+#define plugInSuiteID 'av1F'
+#define plugInClassID plugInSuiteID
+#define plugInEventID typeNull // must be this
+
+// keyQuality is defined in PITerminology.h
+#define keyCompressionSpeed 'av1S'
+#define keyLosslessCompression 'av1L'
+#define keyChromaSubsampling 'av1C'
+#define keyKeepColorProfile 'kpmC'
+#define keyKeepEXIF 'kpmE'
+#define keyKeepXMP 'kpmX'
+#define keyImageBitDepth 'av1B'
+
+#define typeCompressionSpeed 'coSp'
+
+#define compressionSpeedFastest 'csP0'
+#define compressionSpeedDefault 'csP1'
+#define compressionSpeedSlowest 'csP2'
+
+#define typeChromaSubsampling 'chSu'
+
+#define chromaSubsampling420 'chS0'
+#define chromaSubsampling422 'chS1'
+#define chromaSubsampling444 'chS2'
+
+#define typeImageBitDepth 'imBd'
+
+#define imageBitDepthEight 'iBd0'
+#define imageBitDepthTen 'iBd1'
+#define imageBitDepthTwelve 'iBd2'
+
+#endif
diff --git a/src/common/BigDocument.cpp b/src/common/BigDocument.cpp
new file mode 100644
index 0000000..babdebd
--- /dev/null
+++ b/src/common/BigDocument.cpp
@@ -0,0 +1,57 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+
+VPoint GetImageSize(const FormatRecordPtr formatRecord)
+{
+ VPoint imageSize;
+
+ if (formatRecord->HostSupports32BitCoordinates && formatRecord->PluginUsing32BitCoordinates)
+ {
+ imageSize.h = formatRecord->imageSize32.h;
+ imageSize.v = formatRecord->imageSize32.v;
+ }
+ else
+ {
+ imageSize.h = formatRecord->imageSize.h;
+ imageSize.v = formatRecord->imageSize.v;
+ }
+
+ return imageSize;
+}
+
+void SetRect(FormatRecordPtr formatRecord, int32 top, int32 left, int32 bottom, int32 right)
+{
+ if (formatRecord->HostSupports32BitCoordinates && formatRecord->PluginUsing32BitCoordinates)
+ {
+ formatRecord->theRect32.top = top;
+ formatRecord->theRect32.left = left;
+ formatRecord->theRect32.bottom = bottom;
+ formatRecord->theRect32.right = right;
+ }
+ else
+ {
+ formatRecord->theRect.top = static_cast(top);
+ formatRecord->theRect.left = static_cast(left);
+ formatRecord->theRect.bottom = static_cast(bottom);
+ formatRecord->theRect.right = static_cast(right);
+ }
+}
diff --git a/src/common/Common.cpp b/src/common/Common.cpp
new file mode 100644
index 0000000..a2241ff
--- /dev/null
+++ b/src/common/Common.cpp
@@ -0,0 +1,42 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "Common.h"
+#include
+#include
+
+#if DEBUG_BUILD
+void DebugOut(const char* fmt, ...) noexcept
+{
+#if __PIWin__
+ va_list argp;
+ char dbg_out[4096] = {};
+
+ va_start(argp, fmt);
+ vsprintf_s(dbg_out, fmt, argp);
+ va_end(argp);
+
+ OutputDebugStringA(dbg_out);
+ OutputDebugStringA("\n");
+#else
+#error "Debug output has not been configured for this platform."
+#endif // __PIWin__
+}
+#endif // DEBUG_BUILD
diff --git a/src/common/Common.h b/src/common/Common.h
new file mode 100644
index 0000000..1a8b47f
--- /dev/null
+++ b/src/common/Common.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 COMMON_H
+#define COMMON_H
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+// Disable uninitialized variable warnings in the SDK headers.
+#pragma warning(disable: 26495)
+// Suppress C4121: 'FormatRecord': alignment of a member was sensitive to packing
+#pragma warning(disable: 4121)
+#endif // _MSC_VER)
+
+#include "PIDefines.h"
+#include "PITypes.h"
+#include "PIFormat.h"
+#include "PIAbout.h"
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+// PITypes.h may define a few C++ reserved keywords
+#if defined(false)
+#undef false
+#endif // defined(false)
+
+#if defined(true)
+#undef true
+#endif // defined(true)
+
+#include "libheif/heif.h"
+
+#if defined(DEBUG) || defined(_DEBUG)
+#define DEBUG_BUILD 1
+#else
+#define DEBUG_BUILD 0
+#endif
+
+#if DEBUG_BUILD
+void DebugOut(const char* fmt, ...) noexcept;
+#else
+#define DebugOut(fmt, ...)
+#endif // DEBUG_BUILD
+
+#define PrintFunctionName() DebugOut(__FUNCTION__)
+
+#endif // !COMMON_H
diff --git a/src/common/Estimate.cpp b/src/common/Estimate.cpp
new file mode 100644
index 0000000..d1758cc
--- /dev/null
+++ b/src/common/Estimate.cpp
@@ -0,0 +1,96 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+#include
+
+namespace
+{
+ int32 EstimateUncompressedSize(const FormatRecordPtr formatRecord)
+ {
+ VPoint imageSize = GetImageSize(formatRecord);
+
+ const unsigned64 width = static_cast(imageSize.h);
+ const unsigned64 height = static_cast(imageSize.v);
+
+ unsigned64 imageDataSize = width * height * static_cast(formatRecord->planes);
+
+ if (formatRecord->depth == 16)
+ {
+ imageDataSize *= 2; // 2 bytes per pixel.
+ }
+
+ // Assume that the AVIF format overhead is 512 bytes.
+
+ unsigned64 totalSize = 512 + imageDataSize;
+
+ return static_cast(std::min(totalSize, static_cast(std::numeric_limits::max())));
+ }
+}
+
+OSErr DoEstimatePrepare(FormatRecordPtr formatRecord)
+{
+ PrintFunctionName();
+
+ OSErr err = noErr;
+
+ if (!HostSupportsRequiredFeatures(formatRecord))
+ {
+ err = errPlugInHostInsufficient;
+ }
+ else if (!HostImageModeSupported(formatRecord))
+ {
+ err = formatBadParameters;
+ }
+
+ if (err == noErr)
+ {
+ formatRecord->maxData = 0;
+ }
+
+ return err;
+}
+
+OSErr DoEstimateStart(FormatRecordPtr formatRecord)
+{
+ PrintFunctionName();
+
+ const int32 uncompressedSize = EstimateUncompressedSize(formatRecord);
+
+ formatRecord->minDataBytes = uncompressedSize / 2;
+ formatRecord->maxDataBytes = uncompressedSize;
+ formatRecord->data = nullptr;
+
+ return noErr;
+}
+
+OSErr DoEstimateContinue()
+{
+ PrintFunctionName();
+
+ return noErr;
+}
+
+OSErr DoEstimateFinish()
+{
+ PrintFunctionName();
+
+ return noErr;
+}
diff --git a/src/common/FileIO.cpp b/src/common/FileIO.cpp
new file mode 100644
index 0000000..57aacf9
--- /dev/null
+++ b/src/common/FileIO.cpp
@@ -0,0 +1,52 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "FileIO.h"
+
+#if __PIWin__
+#include "FileIOWin.h"
+#else
+#error "Missing a native file I/O header for this platform."
+#endif
+
+OSErr GetFilePosition(intptr_t refNum, int64& position)
+{
+ return GetFilePositionNative(refNum, position);
+}
+
+OSErr GetFileSize(intptr_t refNum, int64& size)
+{
+ return GetFileSizeNative(refNum, size);
+}
+
+OSErr ReadData(intptr_t refNum, void* buffer, size_t size)
+{
+ return ReadDataNative(refNum, buffer, size);
+}
+
+OSErr SetFilePosition(intptr_t refNum, int64 position)
+{
+ return SetFilePositionNative(refNum, position);
+}
+
+OSErr WriteData(intptr_t refNum, const void* buffer, size_t size)
+{
+ return WriteDataNative(refNum, buffer, size);
+}
diff --git a/src/common/FileIO.h b/src/common/FileIO.h
new file mode 100644
index 0000000..5a7daf4
--- /dev/null
+++ b/src/common/FileIO.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 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 FILEIO_H
+#define FILEIO_H
+
+#include "Common.h"
+
+OSErr GetFilePosition(intptr_t refNum, int64& position);
+
+OSErr GetFileSize(intptr_t refNum, int64& size);
+
+OSErr ReadData(intptr_t refNum, void* buffer, size_t size);
+
+OSErr SetFilePosition(intptr_t refNum, int64 position);
+
+OSErr WriteData(intptr_t refNum, const void* buffer, size_t size);
+
+#endif // !FILEIO_H
diff --git a/src/common/HostMetadata.cpp b/src/common/HostMetadata.cpp
new file mode 100644
index 0000000..bf12c1f
--- /dev/null
+++ b/src/common/HostMetadata.cpp
@@ -0,0 +1,97 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "HostMetadata.h"
+#include "PIProperties.h"
+
+namespace
+{
+ Handle GetComplexProperty(const FormatRecordPtr formatRecord, int32 propertyKey)
+ {
+ Handle handle = nullptr;
+
+ if (formatRecord->propertyProcs->getPropertyProc(kPhotoshopSignature, propertyKey, 0, nullptr, &handle) != noErr)
+ {
+ handle = nullptr;
+ }
+
+ return handle;
+ }
+}
+
+ScopedHandleSuiteHandle GetExifMetadata(const FormatRecordPtr formatRecord)
+{
+ Handle exifHandle = nullptr;
+
+ if (HandleSuiteIsAvailable(formatRecord) && PropertySuiteIsAvailable(formatRecord))
+ {
+ exifHandle = GetComplexProperty(formatRecord, propEXIFData);
+ }
+
+ return ScopedHandleSuiteHandle(formatRecord->handleProcs, exifHandle);
+}
+
+ScopedHandleSuiteHandle GetXmpMetadata(const FormatRecordPtr formatRecord)
+{
+ Handle xmpHandle = nullptr;
+
+ if (HandleSuiteIsAvailable(formatRecord) && PropertySuiteIsAvailable(formatRecord))
+ {
+ xmpHandle = GetComplexProperty(formatRecord, propXMP);
+ }
+
+ return ScopedHandleSuiteHandle(formatRecord->handleProcs, xmpHandle);
+}
+
+bool HasColorProfileMetadata(const FormatRecordPtr formatRecord)
+{
+ return (HandleSuiteIsAvailable(formatRecord) &&
+ formatRecord->canUseICCProfiles &&
+ formatRecord->iCCprofileData != nullptr &&
+ formatRecord->iCCprofileSize > 0);
+}
+
+bool HasExifMetadata(const FormatRecordPtr formatRecord)
+{
+ bool result = false;
+
+ ScopedHandleSuiteHandle exif = GetExifMetadata(formatRecord);
+
+ if (exif != nullptr)
+ {
+ result = exif.GetSize() > 0;
+ }
+
+ return result;
+}
+
+bool HasXmpMetadata(const FormatRecordPtr formatRecord)
+{
+ bool result = false;
+
+ ScopedHandleSuiteHandle xmp = GetXmpMetadata(formatRecord);
+
+ if (xmp != nullptr)
+ {
+ result = xmp.GetSize() > 0;
+ }
+
+ return result;
+}
diff --git a/src/common/HostMetadata.h b/src/common/HostMetadata.h
new file mode 100644
index 0000000..3fc9d47
--- /dev/null
+++ b/src/common/HostMetadata.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 HOSTMETADATA_H
+#define HOSTMETADATA_H
+
+#include "AvifFormat.h"
+#include "ScopedHandleSuite.h"
+
+ScopedHandleSuiteHandle GetExifMetadata(const FormatRecordPtr formatRecord);
+ScopedHandleSuiteHandle GetXmpMetadata(const FormatRecordPtr formatRecord);
+
+bool HasColorProfileMetadata(const FormatRecordPtr formatRecord);
+bool HasExifMetadata(const FormatRecordPtr formatRecord);
+bool HasXmpMetadata(const FormatRecordPtr formatRecord);
+
+#endif // !HOSTMETADATA_H
diff --git a/src/common/LibHeifException.h b/src/common/LibHeifException.h
new file mode 100644
index 0000000..94e1a3d
--- /dev/null
+++ b/src/common/LibHeifException.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 LIBHEIFEXCEPTION_H
+#define LIBHEIFEXCEPTION_H
+
+#include
+#include
+
+class LibHeifException : public std::runtime_error
+{
+public:
+
+ LibHeifException(const heif_error& e) : std::runtime_error(e.message), code(e.code), subCode(e.subcode)
+ {
+ }
+
+ heif_error_code GetErrorCode() const
+ {
+ return code;
+ }
+
+ heif_suberror_code GetSubCode() const
+ {
+ return subCode;
+ }
+
+ static bool IsOutOfMemoryError(const heif_error& e)
+ {
+ return e.code == heif_error_Memory_allocation_error && e.subcode == heif_suberror_Unspecified;
+ }
+
+ static void ThrowIfError(const heif_error& e)
+ {
+ if (e.code != heif_error_Ok)
+ {
+ if (IsOutOfMemoryError(e))
+ {
+ throw std::bad_alloc();
+ }
+ else
+ {
+ throw LibHeifException(e);
+ }
+ }
+ }
+
+private:
+
+ heif_error_code code;
+ heif_suberror_code subCode;
+};
+
+#endif
diff --git a/src/common/Memory.cpp b/src/common/Memory.cpp
new file mode 100644
index 0000000..bf46595
--- /dev/null
+++ b/src/common/Memory.cpp
@@ -0,0 +1,107 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+
+#if __PIWin__
+#include "MemoryWin.h"
+#else
+#include "LowMem.h"
+#endif // __PIWin__
+
+// The following methods were adapted from the Host*Handle methods in the PS6 SDK:
+
+OSErr NewPIHandle(const FormatRecordPtr formatRecord, int32 size, Handle* handle)
+{
+ if (handle == nullptr)
+ {
+ return nilHandleErr;
+ }
+
+ if (HandleSuiteIsAvailable(formatRecord))
+ {
+ *handle = formatRecord->handleProcs->newProc(size);
+ }
+ else
+ {
+ *handle = NewHandle(size);
+ }
+
+ return *handle != nullptr ? noErr : memFullErr;
+}
+
+void DisposePIHandle(const FormatRecordPtr formatRecord, Handle handle)
+{
+ if (HandleSuiteIsAvailable(formatRecord))
+ {
+ formatRecord->handleProcs->disposeProc(handle);
+ }
+ else
+ {
+ DisposeHandle(handle);
+ }
+}
+
+Ptr LockPIHandle(const FormatRecordPtr formatRecord, Handle handle, Boolean moveHigh)
+{
+ if (HandleSuiteIsAvailable(formatRecord))
+ {
+ return formatRecord->handleProcs->lockProc(handle, moveHigh);
+ }
+ else
+ {
+ // Use OS routines:
+
+#ifdef __PIMac__
+ if (moveHigh)
+ MoveHHi(handle);
+ HLock(handle);
+
+ return *handle; // dereference and return pointer
+
+#else // Windows
+
+ return (Ptr)GlobalLock(handle);
+
+#endif
+ }
+}
+
+void UnlockPIHandle(const FormatRecordPtr formatRecord, Handle handle)
+{
+ if (HandleSuiteIsAvailable(formatRecord))
+ {
+ formatRecord->handleProcs->unlockProc(handle);
+ }
+ else
+ {
+ // Use OS routines:
+#ifdef __PIMac__
+
+ HUnlock(handle);
+
+#else // Windows
+
+ GlobalUnlock(handle);
+
+#endif
+ }
+}
+
diff --git a/src/common/OSErrException.h b/src/common/OSErrException.h
new file mode 100644
index 0000000..de68d8c
--- /dev/null
+++ b/src/common/OSErrException.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 OSERREXCEPTION_H
+#define OSERREXCEPTION_H
+
+#include "PITypes.h"
+#include
+
+class OSErrException : public std::exception
+{
+public:
+ explicit OSErrException(OSErr err) noexcept : error(err)
+ {
+ }
+
+ OSErr GetErrorCode() const noexcept
+ {
+ return error;
+ }
+
+ static void ThrowIfError(OSErr err)
+ {
+ if (err != noErr)
+ {
+ throw OSErrException(err);
+ }
+ }
+
+private:
+ OSErr error;
+};
+
+#endif // !OSERREXCEPTION_H
diff --git a/src/common/Options.cpp b/src/common/Options.cpp
new file mode 100644
index 0000000..2081ca6
--- /dev/null
+++ b/src/common/Options.cpp
@@ -0,0 +1,85 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+
+OSErr DoOptionsPrepare(FormatRecordPtr formatRecord)
+{
+ PrintFunctionName();
+
+ OSErr err = noErr;
+
+ if (!HostSupportsRequiredFeatures(formatRecord))
+ {
+ err = errPlugInHostInsufficient;
+ }
+ else if (!HostImageModeSupported(formatRecord))
+ {
+ err = formatBadParameters;
+ }
+
+ if (err == noErr)
+ {
+ formatRecord->maxData = 0;
+ }
+
+ return err;
+}
+
+OSErr DoOptionsStart(FormatRecordPtr formatRecord, Globals* globals)
+{
+ PrintFunctionName();
+
+ formatRecord->data = nullptr;
+ SetRect(formatRecord, 0, 0, 0, 0);
+
+ Boolean showDialog;
+ ReadScriptParamsOnWrite(formatRecord, globals->saveOptions, &showDialog);
+
+ OSErr err = noErr;
+
+ if (showDialog)
+ {
+ if (DoSaveUI(formatRecord, globals->saveOptions))
+ {
+ WriteScriptParamsOnWrite(formatRecord, globals->saveOptions);
+ }
+ else
+ {
+ err = userCanceledErr;
+ }
+ }
+
+ return err;
+}
+
+OSErr DoOptionsContinue()
+{
+ PrintFunctionName();
+
+ return noErr;
+}
+
+OSErr DoOptionsFinish()
+{
+ PrintFunctionName();
+
+ return noErr;
+}
diff --git a/src/common/Read.cpp b/src/common/Read.cpp
new file mode 100644
index 0000000..4c71c47
--- /dev/null
+++ b/src/common/Read.cpp
@@ -0,0 +1,326 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+#include "FileIO.h"
+#include "LibHeifException.h"
+#include "OSErrException.h"
+#include "ReadMetadata.h"
+#include "ScopedHeif.h"
+#include
+
+namespace
+{
+ int64_t heif_reader_get_position(void* userData)
+ {
+ int64_t position;
+
+ if (GetFilePosition(reinterpret_cast(userData), position) == noErr)
+ {
+ return position;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ int heif_reader_read(void* data, size_t size, void* userData)
+ {
+ if (ReadData(reinterpret_cast(userData), data, size) == noErr)
+ {
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ int heif_reader_seek(int64_t position, void* userData)
+ {
+ if (SetFilePosition(reinterpret_cast(userData), position) == noErr)
+ {
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ heif_reader_grow_status heif_reader_wait_for_file_size(int64_t target_size, void* userData)
+ {
+ int64 size;
+
+ if (GetFileSize(reinterpret_cast(userData), size) == noErr)
+ {
+ return target_size > size ? heif_reader_grow_status_size_beyond_eof : heif_reader_grow_status_size_reached;
+ }
+ else
+ {
+ return heif_reader_grow_status_size_beyond_eof;
+ }
+ }
+
+ static struct heif_reader readerCallbacks
+ {
+ 1,
+ heif_reader_get_position,
+ heif_reader_read,
+ heif_reader_seek,
+ heif_reader_wait_for_file_size
+ };
+
+ ScopedHeifImage DecodeImage(heif_image_handle* imageHandle, heif_colorspace colorSpace, heif_chroma chroma)
+ {
+ heif_image* tempImage;
+
+ LibHeifException::ThrowIfError(heif_decode_image(imageHandle, &tempImage, colorSpace, chroma, nullptr));
+
+ return ScopedHeifImage(tempImage);
+ }
+
+ ScopedHeifImageHandle GetPrimaryImageHandle(heif_context* context)
+ {
+ heif_image_handle* imageHandle;
+
+ LibHeifException::ThrowIfError(heif_context_get_primary_image_handle(context, &imageHandle));
+
+ return ScopedHeifImageHandle(imageHandle);
+ }
+}
+
+OSErr DoReadPrepare(FormatRecordPtr formatRecord)
+{
+ PrintFunctionName();
+
+ OSErr err = noErr;
+
+ if (HostSupportsRequiredFeatures(formatRecord))
+ {
+ formatRecord->maxData /= 2;
+ }
+ else
+ {
+ err = errPlugInHostInsufficient;
+ }
+
+ return err;
+}
+
+OSErr DoReadStart(FormatRecordPtr formatRecord, Globals* globals)
+{
+ PrintFunctionName();
+
+ globals->context = nullptr;
+ globals->imageHandle = nullptr;
+ globals->image = nullptr;
+
+ OSErr err = noErr;
+
+ try
+ {
+ ScopedHeifContext context(heif_context_alloc());
+
+ if (context == nullptr)
+ {
+ throw std::bad_alloc();
+ }
+
+ LibHeifException::ThrowIfError(heif_context_read_from_reader(
+ context.get(),
+ &readerCallbacks,
+ reinterpret_cast(formatRecord->dataFork),
+ nullptr));
+
+ ScopedHeifImageHandle primaryImage = GetPrimaryImageHandle(context.get());
+
+ const int width = heif_image_handle_get_width(primaryImage.get());
+ const int height = heif_image_handle_get_height(primaryImage.get());
+ const bool hasAlpha = heif_image_handle_has_alpha_channel(primaryImage.get());
+ const int lumaBitsPerPixel = heif_image_handle_get_luma_bits_per_pixel(primaryImage.get());
+
+ const heif_colorspace colorSpace = heif_colorspace_RGB;
+ heif_chroma chroma;
+
+ switch (lumaBitsPerPixel)
+ {
+ case 8:
+ formatRecord->imageMode = plugInModeRGBColor;
+ formatRecord->depth = 8;
+ formatRecord->planeBytes = 1;
+ chroma = hasAlpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB;
+ break;
+ case 10:
+ case 12:
+ formatRecord->imageMode = plugInModeRGB48;
+ formatRecord->depth = 16;
+ formatRecord->planeBytes = 2;
+ formatRecord->maxValue = (1 << lumaBitsPerPixel) - 1;
+#ifdef __PIMacPPC__
+ chroma = hasAlpha ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE;
+#else
+ chroma = hasAlpha ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBB_LE;
+#endif // __PIMacPPC__
+ break;
+ default:
+ throw OSErrException(formatCannotRead);
+ }
+
+ if (formatRecord->HostSupports32BitCoordinates && formatRecord->PluginUsing32BitCoordinates)
+ {
+ formatRecord->imageSize32.h = width;
+ formatRecord->imageSize32.v = height;
+ }
+ else
+ {
+ if (width > std::numeric_limits::max() || height > std::numeric_limits::max())
+ {
+ // The image is larger that the maximum value of a 16-bit signed integer.
+ throw OSErrException(formatCannotRead);
+ }
+
+ formatRecord->imageSize.h = static_cast(width);
+ formatRecord->imageSize.v = static_cast(height);
+ }
+
+ ScopedHeifImage image = DecodeImage(primaryImage.get(), colorSpace, chroma);
+
+ int stride;
+ uint8_t* data = heif_image_get_plane(image.get(), heif_channel_interleaved, &stride);
+
+ formatRecord->data = data;
+ formatRecord->planes = hasAlpha ? 4 : 3;
+ formatRecord->loPlane = 0;
+ formatRecord->hiPlane = formatRecord->planes - 1;
+ formatRecord->colBytes = static_cast(formatRecord->planes * formatRecord->planeBytes);
+ formatRecord->rowBytes = stride;
+
+ if (hasAlpha && formatRecord->transparencyPlane != 0)
+ {
+ formatRecord->transparencyPlane = 3;
+ }
+
+ SetRect(formatRecord, 0, 0, height, width);
+
+ // The context, image handle and image must remain valid until DoReadFinish is called.
+ // The host will read the image data when DoReadStart returns, and the meta-data will be set in DoReadContinue.
+ globals->context = context.release();
+ globals->imageHandle = primaryImage.release();
+ globals->image = image.release();
+ }
+ catch (const std::bad_alloc&)
+ {
+ err = memFullErr;
+ }
+ catch (const LibHeifException& e)
+ {
+ err = HandleErrorMessage(formatRecord, e.what(), readErr);
+ }
+ catch (const OSErrException& e)
+ {
+ err = e.GetErrorCode();
+ }
+ catch (const std::exception& e)
+ {
+ err = HandleErrorMessage(formatRecord, e.what(), readErr);
+ }
+ catch (...)
+ {
+ err = readErr;
+ }
+
+ return err;
+}
+
+OSErr DoReadContinue(FormatRecordPtr formatRecord, Globals* globals)
+{
+ PrintFunctionName();
+
+ formatRecord->data = nullptr;
+ SetRect(formatRecord, 0, 0, 0, 0);
+
+ OSErr err = noErr;
+
+ try
+ {
+ if (HandleSuiteIsAvailable(formatRecord))
+ {
+ if (PropertySuiteIsAvailable(formatRecord))
+ {
+ ReadExifMetadata(formatRecord, globals->imageHandle);
+ ReadXmpMetadata(formatRecord, globals->imageHandle);
+ }
+
+ if (formatRecord->canUseICCProfiles)
+ {
+ ReadIccProfileMetadata(formatRecord, globals->imageHandle);
+ }
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ err = memFullErr;
+ }
+ catch (const LibHeifException& e)
+ {
+ err = HandleErrorMessage(formatRecord, e.what(), readErr);
+ }
+ catch (const OSErrException& e)
+ {
+ err = e.GetErrorCode();
+ }
+ catch (const std::exception& e)
+ {
+ err = HandleErrorMessage(formatRecord, e.what(), readErr);
+ }
+ catch (...)
+ {
+ err = readErr;
+ }
+
+ return err;
+}
+
+OSErr DoReadFinish(Globals* globals)
+{
+ PrintFunctionName();
+
+ if (globals->image != nullptr)
+ {
+ heif_image_release(globals->image);
+ globals->image = nullptr;
+ }
+
+ if (globals->imageHandle != nullptr)
+ {
+ heif_image_handle_release(globals->imageHandle);
+ globals->imageHandle = nullptr;
+ }
+
+ if (globals->context != nullptr)
+ {
+ heif_context_free(globals->context);
+ globals->context = nullptr;
+ }
+
+ return noErr;
+}
diff --git a/src/common/ReadMetadata.cpp b/src/common/ReadMetadata.cpp
new file mode 100644
index 0000000..64b1ecf
--- /dev/null
+++ b/src/common/ReadMetadata.cpp
@@ -0,0 +1,287 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "ReadMetadata.h"
+#include "LibHeifException.h"
+#include "OSErrException.h"
+#include "PIProperties.h"
+#include "ScopedBufferSuite.h"
+#include
+#include
+
+namespace
+{
+ bool CheckTiffFileSignature(const uint8* data, size_t dataLength)
+ {
+ // We must have at least 4 bytes to check the TIFF file signature.
+ if (dataLength < 4)
+ {
+ return false;
+ }
+
+ const char* const bigEndianTiffSignature = "MM\0*";
+ const char* const littleEndianTiffSignature = "II*\0";
+
+ return memcmp(data, bigEndianTiffSignature, 4) == 0 || memcmp(data, littleEndianTiffSignature, 4) == 0;
+ }
+
+ bool HasIccProfile(const heif_image_handle* handle)
+ {
+ switch (heif_image_handle_get_color_profile_type(handle))
+ {
+ case heif_color_profile_type_prof:
+ case heif_color_profile_type_rICC:
+ return true;
+ case heif_color_profile_type_nclx:
+ case heif_color_profile_type_not_present:
+ default:
+ return false;
+ }
+ }
+
+ bool TryGetExifItemId(const heif_image_handle* handle, heif_item_id& exifId)
+ {
+ heif_item_id id;
+
+ if (heif_image_handle_get_list_of_metadata_block_IDs(handle, "Exif", &id, 1) == 1)
+ {
+ exifId = id;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool TryGetXmpItemId(const heif_image_handle* handle, heif_item_id& xmpId)
+ {
+ int mimeBlockCount = heif_image_handle_get_number_of_metadata_blocks(handle, "mime");
+
+ if (mimeBlockCount == 0)
+ {
+ return false;
+ }
+
+ std::vector ids(mimeBlockCount);
+
+ if (heif_image_handle_get_list_of_metadata_block_IDs(handle, "mime", ids.data(), mimeBlockCount) == mimeBlockCount)
+ {
+ for (size_t i = 0; i < ids.size(); i++)
+ {
+ const heif_item_id id = ids[i];
+ const char* contentType = heif_image_handle_get_metadata_content_type(handle, id);
+
+ if (strcmp(contentType, "application/rdf+xml") == 0)
+ {
+ xmpId = id;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
+
+void ReadExifMetadata(const FormatRecordPtr formatRecord, const heif_image_handle* handle)
+{
+ heif_item_id exifId;
+
+ if (TryGetExifItemId(handle, exifId))
+ {
+ const size_t size = heif_image_handle_get_metadata_size(handle, exifId);
+
+ // The EXIF data block has a header that indicates the number of bytes
+ // that come before the start of the TIFF header.
+ // See ISO/IEC 23008-12:2017 section A.2.1.
+ constexpr size_t exifHeaderSize = sizeof(uint32_t);
+
+ if (size > exifHeaderSize && size <= static_cast(std::numeric_limits::max()))
+ {
+ ScopedBufferSuiteBuffer exifBuffer(formatRecord->bufferProcs, static_cast(size));
+ uint8* exifBlock = static_cast(exifBuffer.Lock());
+
+ LibHeifException::ThrowIfError(heif_image_handle_get_metadata(handle, exifId, exifBlock));
+
+ uint32_t tiffHeaderOffset = (exifBlock[0] << 24) | (exifBlock[1] << 16) | (exifBlock[2] << 8) | exifBlock[3];
+
+ size_t headerStartOffset = static_cast(4) + tiffHeaderOffset;
+ size_t exifDataLength = size - headerStartOffset;
+
+ if (exifDataLength > 0 &&
+ exifDataLength <= static_cast(std::numeric_limits::max()) &&
+ CheckTiffFileSignature(exifBlock + headerStartOffset, exifDataLength))
+ {
+ Handle complexProperty = formatRecord->handleProcs->newProc(static_cast(exifDataLength));
+
+ if (complexProperty != nullptr)
+ {
+ Ptr ptr = formatRecord->handleProcs->lockProc(complexProperty, false);
+
+ if (ptr != nullptr)
+ {
+ memcpy(ptr, exifBlock + headerStartOffset, exifDataLength);
+
+ formatRecord->handleProcs->unlockProc(complexProperty);
+
+ OSErr err = formatRecord->propertyProcs->setPropertyProc(
+ kPhotoshopSignature,
+ propEXIFData,
+ 0,
+ 0,
+ complexProperty);
+
+ // The host takes ownership of the handle if the call succeeds, we dispose the handle if it fails.
+ if (err != noErr)
+ {
+ formatRecord->handleProcs->disposeProc(complexProperty);
+ }
+ }
+ else
+ {
+ formatRecord->handleProcs->disposeProc(complexProperty);
+ throw OSErrException(nilHandleErr);
+ }
+ }
+ else
+ {
+ throw std::bad_alloc();
+ }
+ }
+ }
+ }
+}
+
+void ReadIccProfileMetadata(const FormatRecordPtr formatRecord, const heif_image_handle* handle)
+{
+ if (HasIccProfile(handle))
+ {
+ const size_t iccProfileLength = heif_image_handle_get_raw_color_profile_size(handle);
+
+ if (iccProfileLength > 0 && iccProfileLength <= static_cast(std::numeric_limits::max()))
+ {
+ Handle iccProfile = formatRecord->handleProcs->newProc(static_cast(iccProfileLength));
+
+ if (iccProfile != nullptr)
+ {
+ Ptr ptr = formatRecord->handleProcs->lockProc(iccProfile, false);
+
+ if (ptr != nullptr)
+ {
+ heif_error error = heif_image_handle_get_raw_color_profile(handle, ptr);
+
+ formatRecord->handleProcs->unlockProc(iccProfile);
+
+ if (error.code == heif_error_Ok)
+ {
+ formatRecord->iCCprofileData = iccProfile;
+ formatRecord->iCCprofileSize = static_cast(iccProfileLength);
+ }
+ else
+ {
+ formatRecord->handleProcs->disposeProc(iccProfile);
+
+ if (LibHeifException::IsOutOfMemoryError(error))
+ {
+ throw std::bad_alloc();
+ }
+ else
+ {
+ throw LibHeifException(error);
+ }
+ }
+ }
+ else
+ {
+ formatRecord->handleProcs->disposeProc(iccProfile);
+ throw OSErrException(nilHandleErr);
+ }
+ }
+ else
+ {
+ throw std::bad_alloc();
+ }
+ }
+ }
+}
+
+void ReadXmpMetadata(const FormatRecordPtr formatRecord, const heif_image_handle* handle)
+{
+ heif_item_id xmpId;
+
+ if (TryGetXmpItemId(handle, xmpId))
+ {
+ const size_t xmpDataLength = heif_image_handle_get_metadata_size(handle, xmpId);
+
+ if (xmpDataLength > 0 && xmpDataLength <= static_cast(std::numeric_limits::max()))
+ {
+ Handle complexProperty = formatRecord->handleProcs->newProc(static_cast(xmpDataLength));
+
+ if (complexProperty != nullptr)
+ {
+ Ptr ptr = formatRecord->handleProcs->lockProc(complexProperty, false);
+
+ if (ptr != nullptr)
+ {
+ heif_error error = heif_image_handle_get_metadata(handle, xmpId, ptr);
+
+ formatRecord->handleProcs->unlockProc(complexProperty);
+
+ if (error.code == heif_error_Ok)
+ {
+ OSErr err = formatRecord->propertyProcs->setPropertyProc(
+ kPhotoshopSignature,
+ propXMP,
+ 0,
+ 0,
+ complexProperty);
+
+ // The host takes ownership of the handle if the call succeeds, we dispose the handle if it fails.
+ if (err != noErr)
+ {
+ formatRecord->handleProcs->disposeProc(complexProperty);
+ }
+ }
+ else
+ {
+ formatRecord->handleProcs->disposeProc(complexProperty);
+
+ if (LibHeifException::IsOutOfMemoryError(error))
+ {
+ throw std::bad_alloc();
+ }
+ else
+ {
+ throw LibHeifException(error);
+ }
+ }
+ }
+ else
+ {
+ formatRecord->handleProcs->disposeProc(complexProperty);
+ throw OSErrException(nilHandleErr);
+ }
+ }
+ else
+ {
+ throw std::bad_alloc();
+ }
+ }
+ }
+}
diff --git a/src/common/ReadMetadata.h b/src/common/ReadMetadata.h
new file mode 100644
index 0000000..3edd394
--- /dev/null
+++ b/src/common/ReadMetadata.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 READMETADATA_H
+#define READMETADATA_H
+
+#include "Common.h"
+
+void ReadExifMetadata(const FormatRecordPtr formatRecord, const heif_image_handle* handle);
+
+void ReadIccProfileMetadata(const FormatRecordPtr formatRecord, const heif_image_handle* handle);
+
+void ReadXmpMetadata(const FormatRecordPtr formatRecord, const heif_image_handle* handle);
+
+#endif // !READMETADATA_H
diff --git a/src/common/ScopedBufferSuite.h b/src/common/ScopedBufferSuite.h
new file mode 100644
index 0000000..189d67c
--- /dev/null
+++ b/src/common/ScopedBufferSuite.h
@@ -0,0 +1,126 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 SCOPEDBUFFERSUITE_H
+#define SCOPEDBUFFERSUITE_H
+
+#include "Common.h"
+#include "OSErrException.h"
+#include
+
+class ScopedBufferSuiteBuffer
+{
+public:
+ explicit ScopedBufferSuiteBuffer(BufferProcs* bufferProcs)
+ : bufferID(), bufferProcs(bufferProcs), bufferIDValid(false),
+ bufferDataPtr(nullptr), size(0)
+ {
+ }
+
+ explicit ScopedBufferSuiteBuffer(BufferProcs* bufferProcs, int32 bufferSize)
+ : bufferID(), bufferProcs(bufferProcs), bufferIDValid(false),
+ bufferDataPtr(nullptr), size(bufferSize)
+ {
+ OSErrException::ThrowIfError(bufferProcs->allocateProc(bufferSize, &bufferID));
+ bufferIDValid = true;
+ }
+
+ ScopedBufferSuiteBuffer(const ScopedBufferSuiteBuffer&) = delete;
+ ScopedBufferSuiteBuffer& operator=(const ScopedBufferSuiteBuffer&) = delete;
+
+ ~ScopedBufferSuiteBuffer()
+ {
+ Release();
+ }
+
+ int32 GetSize() const noexcept
+ {
+ return size;
+ }
+
+ void* Lock()
+ {
+ if (!bufferIDValid)
+ {
+ throw std::runtime_error("Cannot Lock an invalid buffer.");
+ }
+
+ if (bufferDataPtr == nullptr)
+ {
+ bufferDataPtr = bufferProcs->lockProc(bufferID, false);
+
+ if (bufferDataPtr == nullptr)
+ {
+ throw std::runtime_error("Unable to lock the BufferSuite buffer.");
+ }
+ }
+
+ return bufferDataPtr;
+ }
+
+ void Reset(int32 newBufferSize)
+ {
+ Release();
+
+ OSErrException::ThrowIfError(bufferProcs->allocateProc(newBufferSize, &bufferID));
+ bufferIDValid = true;
+ size = newBufferSize;
+ }
+
+ bool operator==(std::nullptr_t) const noexcept
+ {
+ return bufferIDValid;
+ }
+
+ bool operator!=(std::nullptr_t) const noexcept
+ {
+ return bufferIDValid;
+ }
+
+ explicit operator bool() const noexcept
+ {
+ return bufferIDValid;
+ }
+
+private:
+
+ void Release() noexcept
+ {
+ if (bufferIDValid)
+ {
+ bufferIDValid = false;
+ size = 0;
+ if (bufferDataPtr != nullptr)
+ {
+ bufferProcs->unlockProc(bufferID);
+ bufferDataPtr = nullptr;
+ }
+ bufferProcs->freeProc(bufferID);
+ }
+ }
+
+ const BufferProcs* const bufferProcs;
+ BufferID bufferID;
+ void* bufferDataPtr;
+ int32 size;
+ bool bufferIDValid;
+};
+
+#endif
diff --git a/src/common/ScopedHandleSuite.h b/src/common/ScopedHandleSuite.h
new file mode 100644
index 0000000..b9d23d5
--- /dev/null
+++ b/src/common/ScopedHandleSuite.h
@@ -0,0 +1,115 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 SCOPEDHANDLESUITE_H
+#define SCOPEDHANDLESUITE_H
+
+#include "AvifFormat.h"
+#include
+
+class ScopedHandleSuiteLock
+{
+public:
+ ScopedHandleSuiteLock(const HandleProcs* handleProcs, Handle handle)
+ : handleProcs(handleProcs), handle(handle),
+ ptr(handleProcs->lockProc(handle, false))
+ {
+ }
+
+ ~ScopedHandleSuiteLock()
+ {
+ if (ptr != nullptr)
+ {
+ handleProcs->unlockProc(handle);
+ ptr = nullptr;
+ }
+ }
+
+ Ptr Data() const noexcept
+ {
+ return ptr;
+ }
+
+private:
+ const HandleProcs* const handleProcs;
+ Handle handle;
+ Ptr ptr;
+};
+
+class ScopedHandleSuiteHandle
+{
+public:
+ ScopedHandleSuiteHandle(const HandleProcs* handleProcs, Handle handle) noexcept
+ : handleProcs(handleProcs), handle(handle)
+ {
+ }
+
+ ~ScopedHandleSuiteHandle()
+ {
+ if (handle != nullptr)
+ {
+ handleProcs->disposeProc(handle);
+ handle = nullptr;
+ }
+ }
+
+ int32 GetSize() const
+ {
+ int32 size = 0;
+
+ if (handle != nullptr)
+ {
+ size = handleProcs->getSizeProc(handle);
+ }
+
+ return size;
+ }
+
+ ScopedHandleSuiteLock Lock() const
+ {
+ if (handle == nullptr)
+ {
+ throw std::runtime_error("Cannot Lock an invalid handle.");
+ }
+
+ return ScopedHandleSuiteLock(handleProcs, handle);
+ }
+
+ bool operator==(std::nullptr_t) const noexcept
+ {
+ return handle == nullptr;
+ }
+
+ bool operator!=(std::nullptr_t) const noexcept
+ {
+ return handle != nullptr;
+ }
+
+ explicit operator bool() const noexcept
+ {
+ return handle != nullptr;
+ }
+
+private:
+ const HandleProcs* const handleProcs;
+ Handle handle;
+};
+
+#endif // !SCOPEDHANDLESUITE_H
diff --git a/src/common/ScopedHeif.h b/src/common/ScopedHeif.h
new file mode 100644
index 0000000..469ff13
--- /dev/null
+++ b/src/common/ScopedHeif.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 SCOPEDHEIF_H
+#define SCOPEDHEIF_H
+
+#include "libheif/heif.h"
+#include
+
+namespace detail
+{
+ struct context_deleter { void operator()(heif_context* h) noexcept { if (h) heif_context_free(h); } };
+
+ struct encoder_deleter { void operator()(heif_encoder* h) noexcept { if (h) heif_encoder_release(h); } };
+
+ struct encoding_options_deleter { void operator()(heif_encoding_options* h) noexcept { if (h) heif_encoding_options_free(h); } };
+
+ struct image_handle_deleter { void operator()(heif_image_handle* h) noexcept { if (h) heif_image_handle_release(h); } };
+
+ struct image_deleter { void operator()(heif_image* h) noexcept { if (h) heif_image_release(h); } };
+
+ struct nclx_profile_deleter { void operator()(heif_color_profile_nclx* h) noexcept { if (h) heif_nclx_color_profile_free(h); } };
+}
+
+using ScopedHeifContext = std::unique_ptr;
+
+using ScopedHeifEncoder = std::unique_ptr;
+
+using ScopedHeifEncodingOptions = std::unique_ptr;
+
+using ScopedHeifImageHandle = std::unique_ptr;
+
+using ScopedHeifImage = std::unique_ptr;
+
+using ScopedHeifNclxProfile = std::unique_ptr;
+
+#endif
diff --git a/src/common/Scripting.cpp b/src/common/Scripting.cpp
new file mode 100644
index 0000000..0e439bc
--- /dev/null
+++ b/src/common/Scripting.cpp
@@ -0,0 +1,278 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+#include "AvifFormatTerminology.h"
+
+namespace
+{
+ ChromaSubsampling ChromaSubsamplingFromDescriptor(DescriptorEnumID value)
+ {
+ switch (value)
+ {
+ case chromaSubsampling422:
+ return ChromaSubsampling::Yuv422;
+ case chromaSubsampling444:
+ return ChromaSubsampling::Yuv444;
+ case chromaSubsampling420:
+ default:
+ return ChromaSubsampling::Yuv420;
+ }
+ }
+
+ DescriptorEnumID ChromaSubsamplingToDescriptor(ChromaSubsampling value)
+ {
+ switch (value)
+ {
+ case ChromaSubsampling::Yuv422:
+ return chromaSubsampling422;
+ case ChromaSubsampling::Yuv444:
+ return chromaSubsampling444;
+ case ChromaSubsampling::Yuv420:
+ default:
+ return chromaSubsampling420;
+ }
+ }
+
+ CompressionSpeed CompressionSpeedFromDescriptor(DescriptorEnumID value)
+ {
+ switch (value)
+ {
+ case compressionSpeedFastest:
+ return CompressionSpeed::Fastest;
+ case compressionSpeedSlowest:
+ return CompressionSpeed::Slowest;
+ case compressionSpeedDefault:
+ default:
+ return CompressionSpeed::Default;
+ }
+ }
+
+ DescriptorEnumID CompressionSpeedToDescriptor(CompressionSpeed value)
+ {
+ switch (value)
+ {
+ case CompressionSpeed::Fastest:
+ return compressionSpeedFastest;
+ case CompressionSpeed::Slowest:
+ return compressionSpeedSlowest;
+ case CompressionSpeed::Default:
+ default:
+ return compressionSpeedDefault;
+ }
+ }
+
+ ImageBitDepth ImageBitDepthFromDescriptor(DescriptorEnumID value)
+ {
+ switch (value)
+ {
+ case imageBitDepthEight:
+ return ImageBitDepth::Eight;
+ case imageBitDepthTen:
+ return ImageBitDepth::Ten;
+ case imageBitDepthTwelve:
+ default:
+ return ImageBitDepth::Twelve;
+ }
+ }
+
+ DescriptorEnumID ImageBitDepthToDescriptor(ImageBitDepth value)
+ {
+ switch (value)
+ {
+ case ImageBitDepth::Eight:
+ return imageBitDepthEight;
+ case ImageBitDepth::Ten:
+ return imageBitDepthTen;
+ case ImageBitDepth::Twelve:
+ default:
+ return imageBitDepthTwelve;
+ }
+ }
+}
+
+OSErr ReadScriptParamsOnWrite(FormatRecordPtr formatRecord, SaveUIOptions& options, Boolean* showDialog)
+{
+ OSErr error = noErr;
+
+ if (showDialog)
+ {
+ *showDialog = true;
+ }
+
+ if (DescriptorSuiteIsAvaliable(formatRecord))
+ {
+ DescriptorKeyID key = 0;
+ DescriptorTypeID type = 0;
+ int32 flags = 0;
+ DescriptorKeyIDArray array =
+ {
+ keyQuality,
+ keyCompressionSpeed,
+ keyLosslessCompression,
+ keyChromaSubsampling,
+ keyKeepColorProfile,
+ keyKeepEXIF,
+ keyKeepXMP,
+ keyImageBitDepth,
+ NULLID
+ };
+
+ ReadDescriptorProcs* readProcs = formatRecord->descriptorParameters->readDescriptorProcs;
+
+ PIReadDescriptor token = readProcs->openReadDescriptorProc(formatRecord->descriptorParameters->descriptor, array);
+ if (token != nullptr)
+ {
+ DescriptorEnumID enumValue;
+ Boolean boolValue;
+ int32 intValue;
+
+ while (readProcs->getKeyProc(token, &key, &type, &flags))
+ {
+ switch (key)
+ {
+ case keyQuality:
+ if (readProcs->getIntegerProc(token, &intValue) == noErr)
+ {
+ options.quality = intValue;
+ }
+ break;
+ case keyCompressionSpeed:
+ if (readProcs->getEnumeratedProc(token, &enumValue) == noErr)
+ {
+ options.compressionSpeed = CompressionSpeedFromDescriptor(enumValue);
+ }
+ break;
+ case keyLosslessCompression:
+ if (readProcs->getBooleanProc(token, &boolValue) == noErr)
+ {
+ options.lossless = boolValue;
+ }
+ break;
+ case keyChromaSubsampling:
+ if (readProcs->getEnumeratedProc(token, &enumValue) == noErr)
+ {
+ options.chromaSubsampling = ChromaSubsamplingFromDescriptor(enumValue);
+ }
+ break;
+ case keyKeepColorProfile:
+ if (readProcs->getBooleanProc(token, &boolValue) == noErr)
+ {
+ options.keepColorProfile = boolValue;
+ }
+ break;
+ case keyKeepEXIF:
+ if (readProcs->getBooleanProc(token, &boolValue) == noErr)
+ {
+ options.keepExif = boolValue;
+ }
+ break;
+ case keyKeepXMP:
+ if (readProcs->getBooleanProc(token, &boolValue) == noErr)
+ {
+ options.keepXmp = boolValue;
+ }
+ break;
+ case typeImageBitDepth:
+ if (readProcs->getEnumeratedProc(token, &enumValue) == noErr)
+ {
+ options.imageBitDepth = ImageBitDepthFromDescriptor(enumValue);
+ }
+ break;
+ }
+ }
+
+ error = readProcs->closeReadDescriptorProc(token); // closes & disposes.
+
+ // Dispose the parameter block descriptor:
+ formatRecord->handleProcs->disposeProc(formatRecord->descriptorParameters->descriptor);
+ formatRecord->descriptorParameters->descriptor = nullptr;
+
+ if (showDialog != nullptr)
+ {
+ *showDialog = formatRecord->descriptorParameters->playInfo == plugInDialogDisplay;
+ }
+ }
+ }
+
+ return error;
+}
+
+OSErr WriteScriptParamsOnWrite(FormatRecordPtr formatRecord, const SaveUIOptions& options)
+{
+ OSErr error = noErr;
+
+ if (DescriptorSuiteIsAvaliable(formatRecord))
+ {
+ WriteDescriptorProcs* writeProcs = formatRecord->descriptorParameters->writeDescriptorProcs;
+
+ PIWriteDescriptor token = writeProcs->openWriteDescriptorProc();
+ if (token != nullptr)
+ {
+ DescriptorEnumID enumValue;
+
+ if (options.lossless)
+ {
+ writeProcs->putBooleanProc(token, keyLosslessCompression, options.lossless);
+ }
+ else
+ {
+ // Lossless compression overrides the quality and chroma sub-sampling settings.
+ writeProcs->putIntegerProc(token, keyQuality, options.quality);
+
+ if (options.chromaSubsampling != ChromaSubsampling::Yuv422)
+ {
+ enumValue = ChromaSubsamplingToDescriptor(options.chromaSubsampling);
+ writeProcs->putEnumeratedProc(token, keyChromaSubsampling, typeChromaSubsampling, enumValue);
+ }
+ }
+
+ if (options.compressionSpeed != CompressionSpeed::Default)
+ {
+ enumValue = CompressionSpeedToDescriptor(options.compressionSpeed);
+ writeProcs->putEnumeratedProc(token, keyCompressionSpeed, typeCompressionSpeed, enumValue);
+ }
+
+ if (options.keepColorProfile)
+ {
+ writeProcs->putBooleanProc(token, keyKeepColorProfile, options.keepColorProfile);
+ }
+
+ if (options.keepExif)
+ {
+ writeProcs->putBooleanProc(token, keyKeepEXIF, options.keepExif);
+ }
+
+ if (options.keepXmp)
+ {
+ writeProcs->putBooleanProc(token, keyKeepXMP, options.keepXmp);
+ }
+
+ enumValue = ImageBitDepthToDescriptor(options.imageBitDepth);
+ writeProcs->putEnumeratedProc(token, keyImageBitDepth, typeImageBitDepth, enumValue);
+
+ error = writeProcs->closeWriteDescriptorProc(token, &formatRecord->descriptorParameters->descriptor);
+ }
+ }
+
+ return error;
+}
+
+
diff --git a/src/common/Utilities.cpp b/src/common/Utilities.cpp
new file mode 100644
index 0000000..51b31e5
--- /dev/null
+++ b/src/common/Utilities.cpp
@@ -0,0 +1,421 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+
+namespace
+{
+ // Define the required suite versions and minimum callback routine counts here.
+ // This allows the plug-in to work in 3rd party hosts that do not have access to the post-6.0 Photoshop SDKs.
+
+ constexpr int16 RequiredBufferProcsVersion = 2;
+ constexpr int16 RequiredBufferProcsCount = 5;
+
+ constexpr int16 RequiredDescriptorParametersVerson = 0;
+ constexpr int16 RequiredReadDescriptorProcsVersion = 0;
+ constexpr int16 RequiredReadDescriptorProcsCount = 18;
+ constexpr int16 RequiredWriteDescriptorProcsVersion = 0;
+ constexpr int16 RequiredWriteDescriptorProcsCount = 16;
+
+ constexpr int16 RequiredHandleProcsVersion = 1;
+ constexpr int16 RequiredHandleProcsCount = 6;
+
+ constexpr int16 RequiredPropertyProcsVersion = 1;
+ constexpr int16 RequiredPropertyProcsCount = 2;
+
+ // Adapted from PIUtilities.cpp in the Photoshop 6 SDK
+
+ //-------------------------------------------------------------------------------
+ //
+ // HostBufferProcsAvailable
+ //
+ // Determines whether the BufferProcs callback is available.
+ //
+ // Inputs:
+ // BufferProcs* procs Pointer to BufferProcs structure.
+ //
+ // Outputs:
+ //
+ // returns TRUE If the BufferProcs callback is available.
+ // returns FALSE If the BufferProcs callback is absent.
+ //
+ //-------------------------------------------------------------------------------
+
+ bool HostBufferProcsAvailable(const BufferProcs* procs)
+ {
+#if DEBUG_BUILD
+ if (procs != nullptr)
+ {
+ DebugOut("bufferProcsVersion=%d numBufferProcs=%d allocateProc=%p lockProc=%p unlockProc=%p freeProc=%p spaceProc=%p",
+ procs->bufferProcsVersion,
+ procs->numBufferProcs,
+ procs->allocateProc,
+ procs->freeProc,
+ procs->lockProc,
+ procs->unlockProc,
+ procs->spaceProc);
+ }
+ else
+ {
+ DebugOut("BufferProcs == nullptr");
+ }
+#endif // DEBUG_BUILD
+
+ bool available = true; // assume docInfo are available
+
+ // We want to check for this stuff in a logical order, going from things
+ // that should always be present to things that "may" be present. It's
+ // always a danger checking things that "may" be present because some
+ // hosts may not leave them NULL if unavailable, instead pointing to
+ // other structures to save space. So first we'll check the main
+ // proc pointer, then the version, the number of routines, then some
+ // of the actual routines:
+
+ if (procs == nullptr)
+ {
+ available = false;
+ }
+ else if (procs->bufferProcsVersion != RequiredBufferProcsVersion)
+ {
+ available = false;
+ }
+ else if (procs->numBufferProcs < RequiredBufferProcsCount)
+ {
+ available = false;
+ }
+ else if (procs->allocateProc == nullptr ||
+ procs->lockProc == nullptr ||
+ procs->unlockProc == nullptr ||
+ procs->freeProc == nullptr ||
+ procs->spaceProc == nullptr)
+ {
+ available = false;
+ }
+
+ return available;
+
+ } // end HostBufferProcsAvailable
+
+ //-------------------------------------------------------------------------------
+ //
+ // HostDescriptorAvailable
+ //
+ // Determines whether the PIDescriptorParameters callback is available.
+ //
+ // The Descriptor Parameters suite are callbacks designed for
+ // scripting and automation. See PIActions.h.
+ //
+ // Inputs:
+ // PIDescriptorParameters* procs Pointer to Descriptor Parameters suite.
+ //
+ // Outputs:
+ //
+ // returns TRUE If PIDescriptorParameters is available.
+ // returns FALSE If PIDescriptorParameters is absent.
+ //
+ //-------------------------------------------------------------------------------
+
+ static Boolean HostDescriptorAvailable(const PIDescriptorParameters* procs)
+ {
+
+ Boolean available = TRUE; // assume procs are available
+ Boolean newerVersion = FALSE; // assume we're running under correct version
+
+ // We want to check for this stuff in a logical order, going from things
+ // that should always be present to things that "may" be present. It's
+ // always a danger checking things that "may" be present because some
+ // hosts may not leave them nullptr if unavailable, instead pointing to
+ // other structures to save space. So first we'll check the main
+ // proc pointer, then the version, the number of routines, then some
+ // of the actual routines:
+
+ if (procs == nullptr)
+ {
+ available = FALSE;
+ }
+ else if (procs->descriptorParametersVersion < RequiredDescriptorParametersVerson)
+ {
+ available = FALSE;
+ }
+ else if (procs->descriptorParametersVersion > RequiredDescriptorParametersVerson)
+ {
+ available = FALSE;
+ newerVersion = TRUE;
+ }
+ else if (procs->readDescriptorProcs == nullptr || procs->writeDescriptorProcs == nullptr)
+ {
+ available = FALSE;
+ }
+ else if (procs->readDescriptorProcs->readDescriptorProcsVersion < RequiredReadDescriptorProcsVersion)
+ {
+ available = FALSE;
+ }
+ else if (procs->readDescriptorProcs->readDescriptorProcsVersion > RequiredReadDescriptorProcsVersion)
+ {
+ available = FALSE;
+ newerVersion = TRUE;
+ }
+ else if (procs->readDescriptorProcs->numReadDescriptorProcs < RequiredReadDescriptorProcsCount)
+ {
+ available = FALSE;
+ }
+ else if (procs->writeDescriptorProcs->writeDescriptorProcsVersion < RequiredWriteDescriptorProcsVersion)
+ {
+ available = FALSE;
+ }
+ else if (procs->writeDescriptorProcs->writeDescriptorProcsVersion > RequiredWriteDescriptorProcsVersion)
+ {
+ available = FALSE;
+ newerVersion = TRUE;
+ }
+ else if (procs->writeDescriptorProcs->numWriteDescriptorProcs < RequiredWriteDescriptorProcsCount)
+ {
+ available = FALSE;
+ }
+ else if (procs->readDescriptorProcs->openReadDescriptorProc == nullptr ||
+ procs->readDescriptorProcs->closeReadDescriptorProc == nullptr ||
+ procs->readDescriptorProcs->getKeyProc == nullptr ||
+ procs->readDescriptorProcs->getIntegerProc == nullptr ||
+ procs->readDescriptorProcs->getFloatProc == nullptr ||
+ procs->readDescriptorProcs->getUnitFloatProc == nullptr ||
+ procs->readDescriptorProcs->getBooleanProc == nullptr ||
+ procs->readDescriptorProcs->getTextProc == nullptr ||
+ procs->readDescriptorProcs->getAliasProc == nullptr ||
+ procs->readDescriptorProcs->getEnumeratedProc == nullptr ||
+ procs->readDescriptorProcs->getClassProc == nullptr ||
+ procs->readDescriptorProcs->getSimpleReferenceProc == nullptr ||
+ procs->readDescriptorProcs->getObjectProc == nullptr ||
+ procs->readDescriptorProcs->getCountProc == nullptr ||
+ procs->readDescriptorProcs->getStringProc == nullptr ||
+ procs->readDescriptorProcs->getPinnedIntegerProc == nullptr ||
+ procs->readDescriptorProcs->getPinnedFloatProc == nullptr ||
+ procs->readDescriptorProcs->getPinnedUnitFloatProc == nullptr)
+ {
+ available = FALSE;
+ }
+ else if (procs->writeDescriptorProcs->openWriteDescriptorProc == nullptr ||
+ procs->writeDescriptorProcs->closeWriteDescriptorProc == nullptr ||
+ procs->writeDescriptorProcs->putIntegerProc == nullptr ||
+ procs->writeDescriptorProcs->putFloatProc == nullptr ||
+ procs->writeDescriptorProcs->putUnitFloatProc == nullptr ||
+ procs->writeDescriptorProcs->putBooleanProc == nullptr ||
+ procs->writeDescriptorProcs->putTextProc == nullptr ||
+ procs->writeDescriptorProcs->putAliasProc == nullptr ||
+ procs->writeDescriptorProcs->putEnumeratedProc == nullptr ||
+ procs->writeDescriptorProcs->putClassProc == nullptr ||
+ procs->writeDescriptorProcs->putSimpleReferenceProc == nullptr ||
+ procs->writeDescriptorProcs->putObjectProc == nullptr ||
+ procs->writeDescriptorProcs->putCountProc == nullptr ||
+ procs->writeDescriptorProcs->putStringProc == nullptr ||
+ procs->writeDescriptorProcs->putScopedClassProc == nullptr ||
+ procs->writeDescriptorProcs->putScopedObjectProc == nullptr)
+ {
+ available = FALSE;
+ }
+
+ return available;
+
+ } // end HostDescriptorAvailable
+
+ //-------------------------------------------------------------------------------
+ //
+ // HostHandleProcsAvailable
+ //
+ // Determines whether the HandleProcs callback is available.
+ //
+ // The HandleProcs are cross-platform master pointers that point to
+ // pointers that point to data that is allocated in the Photoshop
+ // virtual memory structure. They're reference counted and
+ // managed more efficiently than the operating system calls.
+ //
+ // WARNING: Do not mix operating system handle creation, deletion,
+ // and sizing routines with these callback routines. They
+ // operate differently, allocate memory differently, and,
+ // while you won't crash, you can cause memory to be
+ // allocated on the global heap and never deallocated.
+ //
+ // Inputs:
+ // HandeProcs* procs Pointer to HandleProcs structure.
+ //
+ // Outputs:
+ //
+ // returns TRUE If the HandleProcs callback is available.
+ // returns FALSE If the HandleProcs callback is absent.
+ //
+ //-------------------------------------------------------------------------------
+
+ bool HostHandleProcsAvailable(const HandleProcs* procs) noexcept
+ {
+#if DEBUG_BUILD
+ if (procs != nullptr)
+ {
+ DebugOut("handleProcsVersion=%d numHandleProcs=%d newProc=%p disposeProc=%p getSizeProc=%p setSizeProc=%p lockProc=%p unlockProc=%p",
+ procs->handleProcsVersion,
+ procs->numHandleProcs,
+ procs->newProc,
+ procs->disposeProc,
+ procs->getSizeProc,
+ procs->setSizeProc,
+ procs->lockProc,
+ procs->unlockProc);
+ }
+ else
+ {
+ DebugOut("HandleProcs == nullptr");
+ }
+#endif // DEBUG_BUILD
+
+ Boolean available = true; // assume docInfo are available
+
+ // We want to check for this stuff in a logical order, going from things
+ // that should always be present to things that "may" be present. It's
+ // always a danger checking things that "may" be present because some
+ // hosts may not leave them NULL if unavailable, instead pointing to
+ // other structures to save space. So first we'll check the main
+ // proc pointer, then the version, the number of routines, then some
+ // of the actual routines:
+
+ if (procs == nullptr)
+ {
+ available = false;
+ }
+ else if (procs->handleProcsVersion != RequiredHandleProcsVersion)
+ {
+ available = false;
+ }
+ else if (procs->numHandleProcs < RequiredHandleProcsCount)
+ {
+ available = false;
+ }
+ else if (procs->newProc == nullptr ||
+ procs->disposeProc == nullptr ||
+ procs->getSizeProc == nullptr ||
+ procs->setSizeProc == nullptr ||
+ procs->lockProc == nullptr ||
+ procs->unlockProc == nullptr)
+ {
+ available = false;
+ }
+
+ return available;
+
+ } // end HostHandleProcsAvailable
+
+ //-------------------------------------------------------------------------------
+ //
+ // HostPropertyProcsAvailable
+ //
+ // Determines whether the Property suite of callbacks is available.
+ //
+ // The Property suite callbacks are two callbacks, GetProperty and
+ // SetProperty, that manage a list of different data elements. See
+ // PIProperties.h.
+ //
+ // Inputs:
+ // PropertyProcs* procs Pointer to PropertyProcs structure.
+ //
+ // Outputs:
+ //
+ // returns TRUE If the Property suite is available.
+ // returns FALSE If the Property suite is absent.
+ //
+ //-------------------------------------------------------------------------------
+
+ bool HostPropertyProcsAvailable(const PropertyProcs* procs) noexcept
+ {
+#if DEBUG_BUILD
+ if (procs != nullptr)
+ {
+ DebugOut("propertyProcsVersion=%d numPropertyProcs=%d getPropertyProc=%p setPropertyProc=%p",
+ procs->propertyProcsVersion,
+ procs->numPropertyProcs,
+ procs->getPropertyProc,
+ procs->setPropertyProc);
+ }
+ else
+ {
+ DebugOut("PropertyProcs == nullptr");
+ }
+#endif // DEBUG_BUILD
+
+ bool available = true;
+
+ if (procs == nullptr)
+ {
+ available = false;
+ }
+ else if (procs->propertyProcsVersion != RequiredPropertyProcsVersion)
+ {
+ available = false;
+ }
+ else if (procs->numPropertyProcs < RequiredPropertyProcsCount)
+ {
+ available = false;
+ }
+ else if (procs->getPropertyProc == nullptr ||
+ procs->setPropertyProc == nullptr)
+ {
+ available = false;
+ }
+
+ return available;
+ }
+}
+
+
+
+bool DescriptorSuiteIsAvaliable(const FormatRecordPtr formatRecord)
+{
+ static bool descriptorSuiteAvailable = HostDescriptorAvailable(formatRecord->descriptorParameters);
+
+ return descriptorSuiteAvailable;
+}
+
+bool HandleSuiteIsAvailable(const FormatRecordPtr formatRecord)
+{
+ static bool handleSuiteAvailable = HostHandleProcsAvailable(formatRecord->handleProcs);
+
+ return handleSuiteAvailable;
+}
+
+bool HostImageModeSupported(const FormatRecordPtr formatRecord)
+{
+ switch (formatRecord->imageMode)
+ {
+ case plugInModeRGBColor:
+ case plugInModeRGB48:
+ return (formatRecord->depth == 8 || formatRecord->depth == 16);
+ default:
+ return false;
+ }
+}
+
+bool HostSupportsRequiredFeatures(const FormatRecordPtr formatRecord)
+{
+ return formatRecord->advanceState != nullptr && HostBufferProcsAvailable(formatRecord->bufferProcs);
+}
+
+bool PropertySuiteIsAvailable(const FormatRecordPtr formatRecord)
+{
+ static bool propertySuiteAvailable = HostPropertyProcsAvailable(formatRecord->propertyProcs);
+
+ return propertySuiteAvailable;
+}
+
diff --git a/src/common/Write.cpp b/src/common/Write.cpp
new file mode 100644
index 0000000..0e15b94
--- /dev/null
+++ b/src/common/Write.cpp
@@ -0,0 +1,682 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "AvifFormat.h"
+#include "FileIO.h"
+#include "LibHeifException.h"
+#include "OSErrException.h"
+#include "ScopedBufferSuite.h"
+#include "ScopedHeif.h"
+#include "WriteMetadata.h"
+#include
+#include
+
+namespace
+{
+ ScopedHeifImageHandle EncodeImage(
+ heif_context* context,
+ heif_image* image,
+ heif_encoder* encoder,
+ const heif_encoding_options* options)
+ {
+ heif_image_handle* encodedImageHandle;
+
+ LibHeifException::ThrowIfError(heif_context_encode_image(context, image, encoder, options, &encodedImageHandle));
+
+ return ScopedHeifImageHandle(encodedImageHandle);
+ }
+
+ ScopedHeifEncoder GetDefaultAV1Encoder(heif_context* context)
+ {
+ heif_encoder* tempEncoder;
+
+ LibHeifException::ThrowIfError(heif_context_get_encoder_for_format(context, heif_compression_AV1, &tempEncoder));
+
+ return ScopedHeifEncoder(tempEncoder);
+ }
+
+ heif_error heif_writer_write(
+ heif_context* /*context*/,
+ const void* data,
+ size_t size,
+ void* userdata)
+ {
+ static heif_error Success = { heif_error_Ok, heif_suberror_Unspecified, "Success" };
+ static heif_error WriteError = { heif_error_Encoding_error, heif_suberror_Cannot_write_output_data, "Write error" };
+
+ return WriteData(reinterpret_cast(userdata), data, size) == noErr ? Success : WriteError;
+ }
+
+ void WriteEncodedImage(const FormatRecordPtr formatRecord, heif_context* context)
+ {
+ static heif_writer writer = { 1, heif_writer_write };
+
+ LibHeifException::ThrowIfError(heif_context_write(context, &writer, reinterpret_cast(formatRecord->dataFork)));
+ }
+
+ std::string GetUnsupportedEncoderMessage(const char* encoderName)
+ {
+ const int requiredLength = std::snprintf(nullptr, 0, "Unsupported AV1 encoder %s.", encoderName);
+
+ if (requiredLength <= 0)
+ {
+ return "Unsupported AV1 encoder.";
+ }
+
+ const size_t lengthWithTerminator = static_cast(requiredLength) + 1;
+
+ auto buffer = std::make_unique(lengthWithTerminator);
+
+ const int writtenLength = std::snprintf(
+ buffer.get(),
+ lengthWithTerminator,
+ "Unsupported AV1 encoder %s.",
+ encoderName);
+
+ return std::string(buffer.get(), buffer.get() + writtenLength);
+ }
+
+ void EncodeAndSaveImage(
+ const FormatRecordPtr formatRecord,
+ heif_context* context,
+ heif_image* image,
+ const SaveUIOptions& saveOptions)
+ {
+ formatRecord->progressProc(50, 100);
+
+ AddColorProfileToImage(formatRecord, image, saveOptions);
+
+ ScopedHeifEncoder encoder = GetDefaultAV1Encoder(context);
+
+ if (saveOptions.lossless)
+ {
+ heif_encoder_set_lossy_quality(encoder.get(), 100);
+ heif_encoder_set_lossless(encoder.get(), true);
+ heif_encoder_set_parameter(encoder.get(), "chroma", "444");
+ }
+ else
+ {
+ heif_encoder_set_lossy_quality(encoder.get(), saveOptions.quality);
+ heif_encoder_set_lossless(encoder.get(), false);
+
+ switch (saveOptions.chromaSubsampling)
+ {
+ case ChromaSubsampling::Yuv420:
+ heif_encoder_set_parameter(encoder.get(), "chroma", "420");
+ break;
+ case ChromaSubsampling::Yuv422:
+ heif_encoder_set_parameter(encoder.get(), "chroma", "422");
+ break;
+ case ChromaSubsampling::Yuv444:
+ heif_encoder_set_parameter(encoder.get(), "chroma", "444");
+ break;
+ default:
+ throw OSErrException(formatBadParameters);
+ }
+ }
+
+ const char* encoderName = heif_encoder_get_name(encoder.get());
+
+ if (_strnicmp(encoderName, "AOM", 3) == 0)
+ {
+ switch (saveOptions.compressionSpeed)
+ {
+ case CompressionSpeed::Fastest:
+ heif_encoder_set_parameter_integer(encoder.get(), "speed", 6);
+ heif_encoder_set_parameter_boolean(encoder.get(), "realtime", true);
+ break;
+ case CompressionSpeed::Slowest:
+ heif_encoder_set_parameter_integer(encoder.get(), "speed", 1);
+ break;
+ case CompressionSpeed::Default:
+ heif_encoder_set_parameter_integer(encoder.get(), "speed", 4);
+ break;
+ default:
+ throw OSErrException(formatBadParameters);
+ }
+ }
+ else
+ {
+ throw std::runtime_error(GetUnsupportedEncoderMessage(encoderName));
+ }
+
+ unsigned int threadCount = std::thread::hardware_concurrency();
+
+ if (threadCount >= 1 && threadCount <= 16)
+ {
+ heif_encoder_set_parameter_integer(encoder.get(), "threads", static_cast(threadCount));
+ }
+
+ ScopedHeifEncodingOptions encodingOptions(heif_encoding_options_alloc());
+
+ if (encodingOptions == nullptr)
+ {
+ throw std::bad_alloc();
+ }
+
+ encodingOptions->save_two_colr_boxes_when_ICC_and_nclx_available = true;
+
+ // Check if cancellation has been requested before staring the encode.
+ // Unfortunately, most encoders do not provide a way to cancel an encode that is in progress.
+ if (formatRecord->abortProc())
+ {
+ throw OSErrException(userCanceledErr);
+ }
+
+ ScopedHeifImageHandle encodedImageHandle = EncodeImage(
+ context,
+ image,
+ encoder.get(),
+ encodingOptions.get());
+
+ formatRecord->progressProc(75, 100);
+ if (formatRecord->abortProc())
+ {
+ throw OSErrException(userCanceledErr);
+ }
+
+ if (saveOptions.keepExif)
+ {
+ AddExifMetadata(formatRecord, context, encodedImageHandle.get());
+ }
+
+ if (saveOptions.keepXmp)
+ {
+ AddXmpMetadata(formatRecord, context, encodedImageHandle.get());
+ }
+
+ WriteEncodedImage(formatRecord, context);
+
+ formatRecord->progressProc(100, 100);
+ }
+
+ ScopedHeifImage CreateHeifImage(int width, int height, heif_colorspace colorspace, heif_chroma chroma)
+ {
+ heif_image* tempImage;
+
+ LibHeifException::ThrowIfError(heif_image_create(width, height, colorspace, chroma, &tempImage));
+
+ return ScopedHeifImage(tempImage);
+ }
+
+ heif_chroma GetHeifImageChroma(ImageBitDepth bitDepth, bool hasAlpha)
+ {
+ heif_chroma chroma;
+
+ switch (bitDepth)
+ {
+ case ImageBitDepth::Eight:
+ chroma = hasAlpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB;
+ break;
+ case ImageBitDepth::Ten:
+ case ImageBitDepth::Twelve:
+#ifdef __PIMacPPC__
+ chroma = hasAlpha ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE;
+#else
+ chroma = hasAlpha ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBB_LE;
+#endif // __PIMacPPC__
+ break;
+ default:
+ throw OSErrException(formatCannotRead);
+ }
+
+ return chroma;
+ }
+
+ int GetHeifImageBitDepth(ImageBitDepth bitDepth)
+ {
+ int value;
+
+ switch (bitDepth)
+ {
+ case ImageBitDepth::Eight:
+ value = 8;
+ break;
+ case ImageBitDepth::Ten:
+ value = 10;
+ break;
+ case ImageBitDepth::Twelve:
+ value = 12;
+ break;
+ default:
+ throw OSErrException(formatCannotRead);
+ }
+
+ return value;
+ }
+
+ std::vector BuildEightBitToHeifImageLookup(int bitDepth)
+ {
+ std::vector lookupTable;
+ lookupTable.reserve(256);
+
+ const int maxValue = (1 << bitDepth) - 1;
+ const float maxValueFloat = static_cast(maxValue);
+
+ for (size_t i = 0; i < lookupTable.capacity(); i++)
+ {
+ int value = static_cast(((static_cast(i) / 255.0f) * maxValueFloat) + 0.5f);
+
+ if (value < 0)
+ {
+ value = 0;
+ }
+ else if (value > maxValue)
+ {
+ value = maxValue;
+ }
+
+ lookupTable.push_back(static_cast(value));
+ }
+
+ return lookupTable;
+ }
+
+ std::vector BuildSixteenBitToEightBitLookup()
+ {
+ std::vector lookupTable;
+ lookupTable.reserve(32769);
+
+ constexpr int maxValue = std::numeric_limits::max();
+ constexpr float maxValueFloat = static_cast(maxValue);
+
+ for (size_t i = 0; i < lookupTable.capacity(); i++)
+ {
+ int value = static_cast(((static_cast(i) / 32768.0f) * maxValueFloat) + 0.5f);
+
+ if (value < 0)
+ {
+ value = 0;
+ }
+ else if (value > maxValue)
+ {
+ value = maxValue;
+ }
+
+ lookupTable.push_back(static_cast(value));
+ }
+
+ return lookupTable;
+ }
+
+ std::vector BuildSixteenBitToHeifImageLookup(int bitDepth)
+ {
+ std::vector lookupTable;
+ lookupTable.reserve(32769);
+
+ const int maxValue = (1 << bitDepth) - 1;
+ const float maxValueFloat = static_cast(maxValue);
+
+ for (size_t i = 0; i < lookupTable.capacity(); i++)
+ {
+ int value = static_cast(((static_cast(i) / 32768.0f) * maxValueFloat) + 0.5f);
+
+ if (value < 0)
+ {
+ value = 0;
+ }
+ else if (value > maxValue)
+ {
+ value = maxValue;
+ }
+
+ lookupTable.push_back(static_cast(value));
+ }
+
+ return lookupTable;
+ }
+
+ void ConvertEightBitDataToHeifImage(
+ FormatRecordPtr formatRecord,
+ const VPoint& imageSize,
+ uint8_t* heifImageData,
+ int heifImageStride,
+ int heifImageBitDepth)
+ {
+ const int32 left = 0;
+ const int32 right = imageSize.h;
+
+ const int32 channelCount = formatRecord->planes;
+ const int32 rowLength = imageSize.h * channelCount;
+
+ if (heifImageBitDepth > 8)
+ {
+ // The 8-bit data must be converted to 10-bit or 12-bit when writing it to the heif_image.
+
+ std::vector lookupTable = BuildEightBitToHeifImageLookup(heifImageBitDepth);
+
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ if (formatRecord->abortProc())
+ {
+ throw OSErrException(userCanceledErr);
+ }
+
+ const int32 top = y;
+ const int32 bottom = std::min(top + 1, imageSize.v);
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+
+ const uint8* src = static_cast(formatRecord->data);
+ uint16* dst = reinterpret_cast(heifImageData + ((static_cast(y) * heifImageStride)));
+
+ for (int32 x = 0; x < rowLength; x += channelCount)
+ {
+ switch (channelCount)
+ {
+ case 3:
+ dst[x] = lookupTable[src[x]];
+ dst[x + 1] = lookupTable[src[x + 1]];
+ dst[x + 2] = lookupTable[src[x + 2]];
+ break;
+ case 4:
+ dst[x] = lookupTable[src[x]];
+ dst[x + 1] = lookupTable[src[x + 1]];
+ dst[x + 2] = lookupTable[src[x + 2]];
+ dst[x + 3] = lookupTable[src[x + 3]];
+ break;
+ default:
+ throw OSErrException(formatBadParameters);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ if (formatRecord->abortProc())
+ {
+ throw OSErrException(userCanceledErr);
+ }
+
+ const int32 top = y;
+ const int32 bottom = std::min(top + 1, imageSize.v);
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+
+ const uint8* src = static_cast(formatRecord->data);
+ uint8* dst = heifImageData + ((static_cast(y) * heifImageStride));
+
+ for (int32 x = 0; x < rowLength; x += channelCount)
+ {
+ switch (channelCount)
+ {
+ case 3:
+ dst[x] = src[x];
+ dst[x + 1] = src[x + 1];
+ dst[x + 2] = src[x + 2];
+ break;
+ case 4:
+ dst[x] = src[x];
+ dst[x + 1] = src[x + 1];
+ dst[x + 2] = src[x + 2];
+ dst[x + 3] = src[x + 3];
+ break;
+ default:
+ throw OSErrException(formatBadParameters);
+ }
+ }
+ }
+ }
+ }
+
+ void ConvertSixteenBitDataToHeifImage(
+ FormatRecordPtr formatRecord,
+ const VPoint& imageSize,
+ uint8_t* heifImageData,
+ int heifImageStride,
+ int heifImageBitDepth)
+ {
+ const int32 left = 0;
+ const int32 right = imageSize.h;
+
+ const int32 channelCount = formatRecord->planes;
+ const int32 rowLength = imageSize.h * channelCount;
+
+ if (heifImageBitDepth == 8)
+ {
+ // The 16-bit data must be converted to 8-bit when writing it to the heif_image.
+
+ std::vector lookupTable = BuildSixteenBitToEightBitLookup();
+
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ if (formatRecord->abortProc())
+ {
+ throw OSErrException(userCanceledErr);
+ }
+
+ const int32 top = y;
+ const int32 bottom = std::min(top + 1, imageSize.v);
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+
+ const uint16* src = static_cast(formatRecord->data);
+ uint8* dst = heifImageData + ((static_cast(y) * heifImageStride));
+
+ for (int32 x = 0; x < rowLength; x += channelCount)
+ {
+ switch (channelCount)
+ {
+ case 3:
+ dst[x] = lookupTable[src[x]];
+ dst[x + 1] = lookupTable[src[x + 1]];
+ dst[x + 2] = lookupTable[src[x + 2]];
+ break;
+ case 4:
+ dst[x] = lookupTable[src[x]];
+ dst[x + 1] = lookupTable[src[x + 1]];
+ dst[x + 2] = lookupTable[src[x + 2]];
+ dst[x + 3] = lookupTable[src[x + 3]];
+ break;
+ default:
+ throw OSErrException(formatBadParameters);
+ }
+ }
+ }
+ }
+ else
+ {
+ // The 16-bit data must be converted to 10-bit or 12-bit when writing it to the heif_image.
+
+ std::vector lookupTable = BuildSixteenBitToHeifImageLookup(heifImageBitDepth);
+
+ for (int32 y = 0; y < imageSize.v; y++)
+ {
+ if (formatRecord->abortProc())
+ {
+ throw OSErrException(userCanceledErr);
+ }
+
+ const int32 top = y;
+ const int32 bottom = std::min(top + 1, imageSize.v);
+
+ SetRect(formatRecord, top, left, bottom, right);
+
+ OSErrException::ThrowIfError(formatRecord->advanceState());
+
+ const uint16* src = static_cast(formatRecord->data);
+ uint16* dst = reinterpret_cast(heifImageData + ((static_cast(y) * heifImageStride)));
+
+ for (int32 x = 0; x < rowLength; x += channelCount)
+ {
+ switch (channelCount)
+ {
+ case 3:
+ dst[x] = lookupTable[src[x]];
+ dst[x + 1] = lookupTable[src[x + 1]];
+ dst[x + 2] = lookupTable[src[x + 2]];
+ break;
+ case 4:
+ dst[x] = lookupTable[src[x]];
+ dst[x + 1] = lookupTable[src[x + 1]];
+ dst[x + 2] = lookupTable[src[x + 2]];
+ dst[x + 3] = lookupTable[src[x + 3]];
+ break;
+ default:
+ throw OSErrException(formatBadParameters);
+ }
+ }
+ }
+ }
+ }
+}
+
+OSErr DoWritePrepare(FormatRecordPtr formatRecord)
+{
+ PrintFunctionName();
+
+ formatRecord->maxData /= 2;
+
+ return noErr;
+}
+
+OSErr DoWriteStart(FormatRecordPtr formatRecord, SaveUIOptions& options)
+{
+ PrintFunctionName();
+
+ OSErr err = noErr;
+
+ ReadScriptParamsOnWrite(formatRecord, options, nullptr);
+
+ try
+ {
+ formatRecord->progressProc(0, 100);
+
+ ScopedHeifContext context(heif_context_alloc());
+
+ if (context == nullptr)
+ {
+ throw std::bad_alloc();
+ }
+
+ const VPoint imageSize = GetImageSize(formatRecord);
+ const bool hasAlpha = formatRecord->planes == 4;
+
+ const heif_colorspace colorSpace = heif_colorspace_RGB;
+ const heif_chroma chroma = GetHeifImageChroma(options.imageBitDepth, hasAlpha);
+ const int heifImageBitDepth = GetHeifImageBitDepth(options.imageBitDepth);
+
+ ScopedHeifImage image = CreateHeifImage(imageSize.h, imageSize.v, colorSpace, chroma);
+
+ LibHeifException::ThrowIfError(heif_image_add_plane(
+ image.get(),
+ heif_channel_interleaved,
+ imageSize.h,
+ imageSize.v,
+ heifImageBitDepth));
+
+ formatRecord->planes = hasAlpha ? 4 : 3;
+ formatRecord->planeBytes = (formatRecord->depth + 7) / 8;
+ formatRecord->loPlane = 0;
+ formatRecord->hiPlane = formatRecord->planes - 1;
+ formatRecord->colBytes = static_cast(formatRecord->planes * formatRecord->planeBytes);
+
+ formatRecord->progressProc(25, 100);
+
+ const unsigned64 rowBytes = static_cast(imageSize.h) * static_cast(formatRecord->colBytes);
+
+ if (rowBytes > std::numeric_limits::max())
+ {
+ throw std::bad_alloc();
+ }
+ else
+ {
+ ScopedBufferSuiteBuffer buffer(formatRecord->bufferProcs, static_cast(rowBytes));
+
+ formatRecord->data = buffer.Lock();
+ formatRecord->rowBytes = static_cast(rowBytes);
+
+ // The image data is read into a temporary buffer, one row at a time.
+ // The host application may use a different stride value than the heif_image.
+
+ int heifImageStride;
+ uint8_t* heifImageData = heif_image_get_plane(image.get(), heif_channel_interleaved, &heifImageStride);
+
+ if (formatRecord->depth == 8)
+ {
+ ConvertEightBitDataToHeifImage(
+ formatRecord,
+ imageSize,
+ heifImageData,
+ heifImageStride,
+ heifImageBitDepth);
+ }
+ else
+ {
+ ConvertSixteenBitDataToHeifImage(
+ formatRecord,
+ imageSize,
+ heifImageData,
+ heifImageStride,
+ heifImageBitDepth);
+ }
+ }
+
+ EncodeAndSaveImage(formatRecord, context.get(), image.get(), options);
+ }
+ catch (const std::bad_alloc&)
+ {
+ err = memFullErr;
+ }
+ catch (const LibHeifException& e)
+ {
+ err = HandleErrorMessage(formatRecord, e.what(), writErr);
+ }
+ catch (const OSErrException& e)
+ {
+ err = e.GetErrorCode();
+ }
+ catch (const std::exception& e)
+ {
+ err = HandleErrorMessage(formatRecord, e.what(), writErr);
+ }
+ catch (...)
+ {
+ err = writErr;
+ }
+
+ formatRecord->data = nullptr;
+ SetRect(formatRecord, 0, 0, 0, 0);
+
+ return err;
+}
+
+OSErr DoWriteContinue()
+{
+ PrintFunctionName();
+
+ return noErr;
+}
+
+OSErr DoWriteFinish(FormatRecordPtr formatRecord, const SaveUIOptions& options)
+{
+ PrintFunctionName();
+
+ WriteScriptParamsOnWrite(formatRecord, options);
+ return noErr;
+}
diff --git a/src/common/WriteMetadata.cpp b/src/common/WriteMetadata.cpp
new file mode 100644
index 0000000..a0622b1
--- /dev/null
+++ b/src/common/WriteMetadata.cpp
@@ -0,0 +1,173 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "WriteMetadata.h"
+#include "HostMetadata.h"
+#include "LibHeifException.h"
+#include "ScopedBufferSuite.h"
+#include "ScopedHeif.h"
+
+namespace
+{
+ void SetNclxColorProfile(
+ heif_image* image,
+ heif_color_primaries primaries,
+ heif_transfer_characteristics transferCharacteristics,
+ heif_matrix_coefficients matrixCoefficients)
+ {
+ ScopedHeifNclxProfile nclxProfile(heif_nclx_color_profile_alloc());
+
+ if (nclxProfile == nullptr)
+ {
+ throw std::bad_alloc();
+ }
+
+ nclxProfile->version = 1;
+ nclxProfile->color_primaries = primaries;
+ nclxProfile->transfer_characteristics = transferCharacteristics;
+ nclxProfile->matrix_coefficients = matrixCoefficients;
+ nclxProfile->full_range_flag = true;
+
+ LibHeifException::ThrowIfError(heif_image_set_nclx_color_profile(image, nclxProfile.get()));
+ }
+
+ void SetIccColorProfile(const FormatRecordPtr formatRecord, heif_image* image, const SaveUIOptions& saveOptions)
+ {
+ const int32 dataSize = formatRecord->handleProcs->getSizeProc(formatRecord->iCCprofileData);
+
+ if (dataSize > 0)
+ {
+ ScopedHandleSuiteLock lock(formatRecord->handleProcs, formatRecord->iCCprofileData);
+
+ Ptr data = lock.Data();
+
+ if (data != nullptr)
+ {
+ LibHeifException::ThrowIfError(heif_image_set_raw_color_profile(
+ image,
+ "prof",
+ data,
+ static_cast(dataSize)));
+
+ if (saveOptions.lossless)
+ {
+ SetNclxColorProfile(
+ image,
+ heif_color_primaries_unspecified,
+ heif_transfer_characteristic_unspecified,
+ heif_matrix_coefficients_RGB_GBR);
+ }
+ }
+ }
+ }
+
+ bool GetExifDataWithHeader(const FormatRecordPtr formatRecord, ScopedBufferSuiteBuffer& buffer)
+ {
+ bool result = false;
+
+ ScopedHandleSuiteHandle exif = GetExifMetadata(formatRecord);
+
+ if (exif != nullptr)
+ {
+ const int32 exifSize = exif.GetSize();
+
+ // The EXIF data block has a header that indicates the number of bytes
+ // that come before the start of the TIFF header.
+ // See ISO/IEC 23008-12:2017 section A.2.1.
+ const int64 exifSizeWithHeader = static_cast(exifSize) + 4;
+
+ if (exifSizeWithHeader <= std::numeric_limits::max())
+ {
+ ScopedHandleSuiteLock lock = exif.Lock();
+ void* exifDataPtr = lock.Data();
+
+ if (exifDataPtr != nullptr)
+ {
+ buffer.Reset(static_cast(exifSizeWithHeader));
+
+ uint8* destinationBuffer = static_cast(buffer.Lock());
+
+ uint32_t* tiffHeaderOffset = reinterpret_cast(destinationBuffer);
+ *tiffHeaderOffset = 0;
+
+ memcpy(destinationBuffer + sizeof(uint32_t), exifDataPtr, static_cast(exifSize));
+ result = true;
+ }
+ }
+ }
+
+ return result;
+ }
+}
+
+void AddColorProfileToImage(const FormatRecordPtr formatRecord, heif_image* image, const SaveUIOptions& saveOptions)
+{
+ if (saveOptions.keepColorProfile && HasColorProfileMetadata(formatRecord))
+ {
+ SetIccColorProfile(formatRecord, image, saveOptions);
+ }
+ else
+ {
+ const heif_color_primaries primaries = heif_color_primaries_ITU_R_BT_709_5;
+ const heif_transfer_characteristics transferCharacteristics = heif_transfer_characteristic_IEC_61966_2_1;
+ heif_matrix_coefficients matrixCoefficients = heif_matrix_coefficients_ITU_R_BT_601_6;
+
+ if (saveOptions.lossless)
+ {
+ matrixCoefficients = heif_matrix_coefficients_RGB_GBR;
+ }
+
+ SetNclxColorProfile(image, primaries, transferCharacteristics, matrixCoefficients);
+ }
+}
+
+void AddExifMetadata(const FormatRecordPtr formatRecord, heif_context* context, heif_image_handle* imageHandle)
+{
+ ScopedBufferSuiteBuffer exif(formatRecord->bufferProcs);
+
+ if (GetExifDataWithHeader(formatRecord, exif))
+ {
+ const int32 bufferSize = exif.GetSize();
+ void* ptr = exif.Lock();
+
+ if (ptr != nullptr)
+ {
+ LibHeifException::ThrowIfError(heif_context_add_exif_metadata(context, imageHandle, ptr, bufferSize));
+ }
+ }
+}
+
+void AddXmpMetadata(const FormatRecordPtr formatRecord, heif_context* context, heif_image_handle* imageHandle)
+{
+ ScopedHandleSuiteHandle xmp = GetXmpMetadata(formatRecord);
+
+ if (xmp != nullptr)
+ {
+ const int32 xmpSize = xmp.GetSize();
+
+ ScopedHandleSuiteLock lock = xmp.Lock();
+ void* ptr = lock.Data();
+
+ if (ptr != nullptr)
+ {
+ LibHeifException::ThrowIfError(heif_context_add_XMP_metadata(context, imageHandle, ptr, xmpSize));
+ }
+ }
+}
diff --git a/src/common/WriteMetadata.h b/src/common/WriteMetadata.h
new file mode 100644
index 0000000..b8e396c
--- /dev/null
+++ b/src/common/WriteMetadata.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 WRITEMETADATA_H
+#define WRITEMETADATA_H
+
+#include "AvifFormat.h"
+
+void AddColorProfileToImage(const FormatRecordPtr formatRecord, heif_image* image, const SaveUIOptions& saveOptions);
+
+void AddExifMetadata(const FormatRecordPtr formatRecord, heif_context* context, heif_image_handle* imageHandle);
+
+void AddXmpMetadata(const FormatRecordPtr formatRecord, heif_context* context, heif_image_handle* imageHandle);
+
+#endif // !WRITEMETADATA_H
diff --git a/src/common/version.h b/src/common/version.h
new file mode 100644
index 0000000..b3a0939
--- /dev/null
+++ b/src/common/version.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 VERSION_H
+#define VERSION_H
+
+#define VI_VERSION 1,0,0,0
+#define VI_VERSION_STR "1.0.0.0"
+
+#endif
diff --git a/src/win/AvifFormat.rc b/src/win/AvifFormat.rc
new file mode 100644
index 0000000000000000000000000000000000000000..58a1e30a7dc27a614d4875773ca09956aa02f20d
GIT binary patch
literal 10098
zcmd^_`A!>G6vpr0mHG}-N0kzl1~4Q*6h$h|A_+ARtAu9F_!yP1v-J)EDYYw*7wR
za5FPra7;q$s2Yv$%w5m=opbN_@4u$QbV$N_I0*;gDqMt)?&Hu^-LC37;X1U#sou)r
zCtXQ+7mmWY?km-IRUWGDN+sj2^>m=;L~qP?5-!!}ari!br&Xq{e4yHMwVtZ?q4h2F
zjei{8hl2HHY~z@_6dIu!w!==SgnHNvt*{lUs;%gGBdmpe)im|H9+p+Jrsudm
zG)&$Vsf%S2RwS2R86QhZ7vW6&cO@O9sFkU^>KOm
zPoBggMQ$C}k`b&0flF8@1~raH2LBs`SP&-E7lu&F2a%1(GR
zzzRAUpE@yl{gCsmL(NzUHCdJWR8#)5Bkw9}<(8~#O&9b2p(`0Wa$9q3$%b1RwW0Bk
zY}GZ@Zm4!g<%-#9tnq#;n!7GveHo@ScT@eARpVoNd$iQ*UhNhDFT*VW|JW5Lt6H`4
z&0)b{FON1s$vrEX4eps(`?7dnF{Ge(a&rwv-Zx?927S?=ON>$A%3anNg!qNfk2i(z
zM!5Ax7VX%dQaOyGxjRxc?m7<)j}PT(V2od2#2~!sx3;LG$wHMZHBiWVEE8${#Nz&i
z@FvzqKI&?%$h%7xUw32^^snoEOgc_%+uL5-tPPEA2rU}9TJOg^;1hhM
z>n<9d1+BtNO^dasmH}1t`%bnAKFy22EQd4UcljVQ4feD(@#N7u+E2bLh_7Aq;<{pd
z+p;Y1o!Yvn19fZC$*D#Dnlgba>*skI*;q%d7xJAX{HSNBO!WhITjHoBDyG7m?xOBl
z>4I|6=#KP^rnIbeF2hHyc_KRBE9NBOPu)dHq-Z>BsO3N{7oxW3BUg4GW~qoN?Pkut6qz;pG5`oAaiuN9p~hBrnf`&^ft08VrOC{
z83pyw9(9mto~WCSvfA4@DN4G?8L7ZmF(+j*wVGKb5pP*mxUCD@cU}v6{*YTC)h3aQ
z85eX5MUKCQ(eaCDEQ|VY^*~y=NS^@d6cNK!L@nAH*jtEN~66e
zYx%E47dZr$?|XQjk;9f&EC^*dRp^SQj{Ksl_|w+=2YFjt&vT-K*|vpr_zipiZJ~&3
zJ=Qul5^q-3D{B!%B1VG34b8J)T#^kCCmkD})%@)2o*Yk{7h*)w=0|4KQByUkl`Il%
zN^VWb$Rh)>0nhOsWK5i_pp~pujn?&=B1K1+$JKl$K<3!gMU0|sY4w+SMo;K|S(5DP
zBEvz;uH#5;+Ba$<_4Gk{owt}df5W|FUzG7^t(JxEeiqY~RGJnCJJR!+WgbKs&l00H
zpbklrf05_1vjtPvWjT)S?3L4X-4dRfR(uB*rV!SkDQJcb2&fCZ@$Tw1%@2aNPdK`E2+Q@R0@;{g(P>D?AQnDxBrulAD
zbw|1ma~|eAkyW@wc?>}Q4b|Wgd6|>Bx|idDi-vCV(m|HS(KcR_9du-;P}m)to|>!z
zSwb;65W5{e;~eb+Y2R!%iRH|iHr?Z6wv)H{(R@|J(Xn_VTOOY;k7al(yNj>O*W3+~
zftN9liM}wJucr99&{@RY^aWnw1^1yZkKOtFEb=wlSJuT5XMu;hDYJ2weJ_5
z4xh+ye^CLhiGrL@@awzJalItvnY})_<6sVB>;3byC|<}T?&cjSKYJFf26N+W_s`3c
z>OUIKAqd6@T~Bzk$Ko^
zSbsJadnPz5ZzL}LJF$2)Zf4~+EZg*0%bvYId%u?a4-I+G#gk|5e7_>|^OHcL80U=p
z(o1?G82k7>VMQ%lYG09_Vg{1-*fQ*Rtqihi=YpPBb6VoDaMm&_vR`6+e(y_7_Jnk@
zo+EX!SDNYZgx$08sC|H
zr-_%+GFbX(GoCe!-l20|Fe9WYC}KR*^D}#UsJVUj;F)g!4)u{nKtuG@@mJ-`Yq~1Y
z@=NApR~zSp6W+31.
+ */
+
+#include "FileIOWin.h"
+#include
+
+OSErr GetFilePositionNative(intptr_t refNum, int64& position)
+{
+ LARGE_INTEGER distanceToMove;
+ distanceToMove.QuadPart = 0;
+
+ LARGE_INTEGER currentFilePosition;
+
+ if (!SetFilePointerEx(reinterpret_cast(refNum), distanceToMove, ¤tFilePosition, FILE_CURRENT))
+ {
+ return readErr;
+ }
+
+ position = currentFilePosition.QuadPart;
+ return noErr;
+}
+
+OSErr GetFileSizeNative(intptr_t refNum, int64& size)
+{
+ LARGE_INTEGER currentFileSize;
+
+ if (!GetFileSizeEx(reinterpret_cast(refNum), ¤tFileSize))
+ {
+ return readErr;
+ }
+
+ size = currentFileSize.QuadPart;
+ return noErr;
+}
+
+OSErr ReadDataNative(intptr_t refNum, void* buffer, size_t size)
+{
+ size_t totalBytesRead = 0;
+
+ HANDLE hFile = reinterpret_cast(refNum);
+
+ uint8_t* dataBuffer = static_cast(buffer);
+
+ while (totalBytesRead < size)
+ {
+ DWORD bytesToRead = static_cast(std::min(size - totalBytesRead, static_cast(2147483648)));
+
+ DWORD bytesRead;
+
+ if (!ReadFile(hFile, dataBuffer + totalBytesRead, bytesToRead, &bytesRead, nullptr))
+ {
+ return readErr;
+ }
+
+ if (bytesRead == 0)
+ {
+ return eofErr;
+ }
+
+ totalBytesRead += bytesRead;
+ }
+
+ return noErr;
+}
+
+OSErr SetFilePositionNative(intptr_t refNum, int64 position)
+{
+ LARGE_INTEGER distanceToMove;
+ distanceToMove.QuadPart = position;
+
+ if (!SetFilePointerEx(reinterpret_cast(refNum), distanceToMove, nullptr, FILE_BEGIN))
+ {
+ return readErr;
+ }
+
+ return noErr;
+}
+
+OSErr WriteDataNative(intptr_t refNum, const void* buffer, size_t size)
+{
+ size_t totalBytesWritten = 0;
+
+ HANDLE hFile = reinterpret_cast(refNum);
+
+ const uint8_t* dataBuffer = static_cast(buffer);
+
+ while (totalBytesWritten < size)
+ {
+ DWORD bytesToWrite = static_cast(std::min(size - totalBytesWritten, static_cast(2147483648)));
+
+ DWORD bytesWritten;
+
+ if (!WriteFile(hFile, dataBuffer + totalBytesWritten, bytesToWrite, &bytesWritten, nullptr))
+ {
+ return writErr;
+ }
+
+ if (bytesWritten != bytesToWrite)
+ {
+ return writErr;
+ }
+
+ totalBytesWritten += bytesWritten;
+ }
+
+ return noErr;
+}
diff --git a/src/win/FileIOWin.h b/src/win/FileIOWin.h
new file mode 100644
index 0000000..aa5e0e4
--- /dev/null
+++ b/src/win/FileIOWin.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 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 FILEIOWIN_H
+#define FILEIOWIN_H
+
+#include "Common.h"
+
+OSErr GetFilePositionNative(intptr_t refNum, int64& position);
+
+OSErr GetFileSizeNative(intptr_t refNum, int64& size);
+
+OSErr ReadDataNative(intptr_t refNum, void* buffer, size_t size);
+
+OSErr SetFilePositionNative(intptr_t refNum, int64 position);
+
+OSErr WriteDataNative(intptr_t refNum, const void* buffer, size_t size);
+
+#endif // !FILEIOWIN_H
diff --git a/src/win/MemoryWin.cpp b/src/win/MemoryWin.cpp
new file mode 100644
index 0000000..dffde7e
--- /dev/null
+++ b/src/win/MemoryWin.cpp
@@ -0,0 +1,73 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 "MemoryWin.h"
+#include
+#include
+
+// The following methods were adapted from WinUtilities.cpp in the PS6 SDK:
+
+#define signatureSize 4
+static char cSig[signatureSize] = { 'O', 'T', 'O', 'F' };
+
+#pragma warning(disable: 6387)
+#pragma warning(disable: 28183)
+
+Handle NewHandle(int32 size)
+{
+ Handle mHand;
+ char* p;
+
+ mHand = (Handle)GlobalAllocPtr(GHND, (sizeof(Handle) + signatureSize));
+
+ if (mHand)
+ *mHand = (Ptr)GlobalAllocPtr(GHND, size);
+
+ if (!mHand || !(*mHand))
+ {
+ return NULL;
+ }
+
+ // put the signature after the pointer
+ p = (char*)mHand;
+ p += sizeof(Handle);
+ memcpy(p, cSig, signatureSize);
+
+ return mHand;
+
+}
+
+void DisposeHandle(Handle handle)
+{
+ if (handle)
+ {
+ Ptr p;
+
+ p = *handle;
+
+ if (p)
+ GlobalFreePtr(p);
+
+ GlobalFreePtr((Ptr)handle);
+ }
+}
+
+#pragma warning(default: 6387)
+#pragma warning(default: 28183)
diff --git a/src/win/MemoryWin.h b/src/win/MemoryWin.h
new file mode 100644
index 0000000..d98e322
--- /dev/null
+++ b/src/win/MemoryWin.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 MEMORYWIN_H
+#define MEMORYWIN_H
+
+#include "Common.h"
+
+Handle NewHandle(int32 size);
+void DisposeHandle(Handle handle);
+
+#endif // MEMORYWIN_H
diff --git a/src/win/PiPL.rc b/src/win/PiPL.rc
new file mode 100644
index 0000000000000000000000000000000000000000..82797ba6f001f8df221769957447b136597843e2
GIT binary patch
literal 54
zcmWN{OAbIV6ac_Eiqzg6Y}`YXMB>vlI6mHXGT+-;9VVMwHftokCv-75RlCupQ^HET
F.
+ */
+
+#include "AvifFormat.h"
+#include "HostMetadata.h"
+#include "resource.h"
+#include "version.h"
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+ // The CenterDialog function was adapted from WinDialogUtils.cpp in the PS6 SDK.
+ /*******************************************************************************/
+ /* Centers a dialog template 1/3 of the way down on the main screen */
+
+ void CenterDialog(HWND hDlg)
+ {
+ int nHeight;
+ int nWidth;
+ int nTitleBits;
+ RECT rcDialog;
+ RECT rcParent;
+ int xOrigin;
+ int yOrigin;
+ int xScreen;
+ int yScreen;
+ HWND hParent = GetParent(hDlg);
+
+ if (hParent == NULL)
+ hParent = GetDesktopWindow();
+
+ GetClientRect(hParent, &rcParent);
+ ClientToScreen(hParent, (LPPOINT)&rcParent.left); // point(left, top)
+ ClientToScreen(hParent, (LPPOINT)&rcParent.right); // point(right, bottom)
+
+ // Center on Title: title bar has system menu, minimize, maximize bitmaps
+ // Width of title bar bitmaps - assumes 3 of them and dialog has a sysmenu
+ nTitleBits = GetSystemMetrics(SM_CXSIZE);
+
+ // If dialog has no sys menu compensate for odd# bitmaps by sub 1 bitwidth
+ if (!(GetWindowLong(hDlg, GWL_STYLE) & WS_SYSMENU))
+ nTitleBits -= nTitleBits / 3;
+
+ GetWindowRect(hDlg, &rcDialog);
+ nWidth = rcDialog.right - rcDialog.left;
+ nHeight = rcDialog.bottom - rcDialog.top;
+
+ xOrigin = std::max(rcParent.right - rcParent.left - nWidth, 0L) / 2
+ + rcParent.left - nTitleBits;
+ xScreen = GetSystemMetrics(SM_CXSCREEN);
+ if (xOrigin + nWidth > xScreen)
+ xOrigin = std::max(0, xScreen - nWidth);
+
+ yOrigin = std::max(rcParent.bottom - rcParent.top - nHeight, 0L) / 3
+ + rcParent.top;
+ yScreen = GetSystemMetrics(SM_CYSCREEN);
+ if (yOrigin + nHeight > yScreen)
+ yOrigin = std::max(0, yScreen - nHeight);
+
+ SetWindowPos(hDlg, NULL, xOrigin, yOrigin, nWidth, nHeight, SWP_NOZORDER);
+ }
+
+
+ // __ImageBase is a variable provided by the MSVC linker.
+ // See "Accessing the current module’s HINSTANCE from a static library" on Raymond Chen's blog:
+ // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483
+ EXTERN_C IMAGE_DOS_HEADER __ImageBase;
+
+ inline HINSTANCE GetModuleInstanceHandle()
+ {
+ return reinterpret_cast(&__ImageBase);
+ }
+
+ void InitAboutDialog(HWND hDlg) noexcept
+ {
+ TCHAR s[256]{}, format[256]{};
+
+ GetDlgItemText(hDlg, ABOUTFORMAT, format, 256);
+ _sntprintf_s(s, _countof(s), format, TEXT(VI_VERSION_STR));
+ SetDlgItemText(hDlg, ABOUTFORMAT, s);
+ }
+
+ INT_PTR CALLBACK AboutDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) noexcept
+ {
+ UNREFERENCED_PARAMETER(lParam);
+
+ int cmd;
+ LPNMHDR pNmhdr;
+
+ switch (wMsg)
+ {
+ case WM_INITDIALOG:
+ CenterDialog(hDlg);
+ InitAboutDialog(hDlg);
+ break;
+ case WM_LBUTTONUP:
+ EndDialog(hDlg, IDOK);
+ break;
+ case WM_COMMAND:
+ cmd = HIWORD(wParam);
+
+ if (cmd == BN_CLICKED)
+ {
+ EndDialog(hDlg, IDOK);
+ }
+ break;
+ case WM_NOTIFY:
+
+ pNmhdr = reinterpret_cast(lParam);
+ switch (pNmhdr->code)
+ {
+ case NM_CLICK: // Fall through to the next case.
+ case NM_RETURN:
+ {
+ if (pNmhdr->idFrom == IDC_PROJECT_HOMEPAGE_LINK)
+ {
+ ShellExecuteW(nullptr, L"open", L"https://github.com/0xC0000054/avif-format", nullptr, nullptr, SW_SHOW);
+ }
+ else if (pNmhdr->idFrom == IDC_CREDITS_LINK)
+ {
+ const PNMLINK link = reinterpret_cast(lParam);
+
+ switch (link->item.iLink)
+ {
+ case 0:
+ ShellExecuteW(nullptr, L"open", L"https://github.com/strukturag/libheif", nullptr, nullptr, SW_SHOW);
+ break;
+ case 1:
+ ShellExecuteW(nullptr, L"open", L"https://aomedia.googlesource.com/aom/", nullptr, nullptr, SW_SHOW);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ struct SaveDialogState
+ {
+ SaveUIOptions options;
+ const int16 hostImageDepth;
+ const bool hasColorProfile;
+ const bool hasExif;
+ const bool hasXmp;
+ bool losslessCheckboxEnabled; // Used to track state when changing the save bit-depth.
+
+ SaveDialogState(const FormatRecordPtr formatRecord, const SaveUIOptions& saveOptions) :
+ hostImageDepth(formatRecord->depth),
+ hasColorProfile(HasColorProfileMetadata(formatRecord)),
+ hasExif(HasExifMetadata(formatRecord)),
+ hasXmp(HasXmpMetadata(formatRecord)),
+ losslessCheckboxEnabled(formatRecord->depth == 8)
+ {
+ options.quality = saveOptions.quality;
+ options.chromaSubsampling = saveOptions.chromaSubsampling;
+ options.compressionSpeed = saveOptions.compressionSpeed;
+ options.imageBitDepth = formatRecord->depth == 8 ? ImageBitDepth::Eight : saveOptions.imageBitDepth;
+ options.lossless = saveOptions.lossless && losslessCheckboxEnabled;
+ options.keepColorProfile = saveOptions.keepColorProfile && hasColorProfile;
+ options.keepExif = saveOptions.keepExif && hasExif;
+ options.keepXmp = saveOptions.keepXmp && hasXmp;
+ }
+ };
+
+ void EnableLossyCompressionSettings(HWND hDlg, bool enabled)
+ {
+ EnableWindow(GetDlgItem(hDlg, IDC_QUALITY_SLIDER), enabled);
+ EnableWindow(GetDlgItem(hDlg, IDC_QUALITY_EDIT), enabled);
+ EnableWindow(GetDlgItem(hDlg, IDC_QUALITY_EDIT_SPIN), enabled);
+ EnableWindow(GetDlgItem(hDlg, IDC_CHROMA_SUBSAMPLING_COMBO), enabled);
+ }
+
+ void InitSaveDialog(HWND hDlg, const SaveDialogState* state) noexcept
+ {
+ const HINSTANCE hInstance = GetModuleInstanceHandle();
+
+ HWND qualitySlider = GetDlgItem(hDlg, IDC_QUALITY);
+ HWND qualityEditBox = GetDlgItem(hDlg, IDC_QUALITY_EDIT);
+ HWND qualityEditUpDown = GetDlgItem(hDlg, IDC_QUALITY_EDIT_SPIN);
+ HWND losslessCheckbox = GetDlgItem(hDlg, IDC_LOSSLESS_CHECK);
+ HWND chromaSubsamplingCombo = GetDlgItem(hDlg, IDC_CHROMA_SUBSAMPLING_COMBO);
+ HWND keepColorProfileCheckbox = GetDlgItem(hDlg, IDC_KEEP_COLOR_PROFILE_CHECK);
+ HWND keepExifCheckbox = GetDlgItem(hDlg, IDC_KEEP_EXIF_CHECK);
+ HWND keepXmpCheckbox = GetDlgItem(hDlg, IDC_KEEP_XMP_CHECK);
+ HWND pixelDepthCombo = GetDlgItem(hDlg, IDC_IMAGE_DEPTH_COMBO);
+
+ SendMessage(qualitySlider, TBM_SETRANGEMIN, FALSE, 0);
+ SendMessage(qualitySlider, TBM_SETRANGEMAX, FALSE, 100);
+ SendMessage(qualitySlider, TBM_SETPOS, TRUE, state->options.quality);
+ SendMessage(qualitySlider, TBM_SETBUDDY, FALSE, reinterpret_cast(qualityEditBox));
+
+ SendMessage(qualityEditBox, EM_LIMITTEXT, 3, 0);
+ SetDlgItemInt(hDlg, IDC_QUALITY_EDIT, static_cast(state->options.quality), false);
+
+ SendMessage(qualityEditUpDown, UDM_SETBUDDY, reinterpret_cast(qualityEditBox), 0);
+ SendMessage(qualityEditUpDown, UDM_SETRANGE, 0, MAKELPARAM(100, 0));
+
+ // The AVIF format only supports 10-bit and 12-bit data, so saving a 16-bit image may be lossy.
+ if (state->hostImageDepth == 8)
+ {
+ Button_SetCheck(losslessCheckbox, state->options.lossless);
+ EnableWindow(losslessCheckbox, true);
+ EnableLossyCompressionSettings(hDlg, !state->options.lossless);
+ }
+ else
+ {
+ Button_SetCheck(losslessCheckbox, false);
+ EnableWindow(losslessCheckbox, false);
+ }
+
+ // Swap the tab order of the Chroma Subsampling combo box and the Default compression speed radio button.
+ SetWindowPos(chromaSubsamplingCombo, GetDlgItem(hDlg, IDC_COMPRESSION_SPEED_DEFAULT_RADIO), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+
+ std::array chromaSubsamplingResIds = { IDS_CHROMA_SUBSAMPLING_420,
+ IDS_CHROMA_SUBSAMPLING_422,
+ IDS_CHROMA_SUBSAMPLING_444 };
+
+ constexpr int resourceBufferLength = 256;
+ TCHAR resourceBuffer[resourceBufferLength]{};
+
+ for (size_t i = 0; i < chromaSubsamplingResIds.size(); i++)
+ {
+ UINT id = chromaSubsamplingResIds[i];
+
+ if (LoadString(hInstance, id, resourceBuffer, resourceBufferLength) > 0)
+ {
+ SendMessage(chromaSubsamplingCombo, CB_ADDSTRING, 0, reinterpret_cast(resourceBuffer));
+ }
+ }
+
+ int selectedChromaSubsamplingIndex;
+ switch (state->options.chromaSubsampling)
+ {
+ case ChromaSubsampling::Yuv422:
+ selectedChromaSubsamplingIndex = 1;
+ break;
+ case ChromaSubsampling::Yuv444:
+ selectedChromaSubsamplingIndex = 2;
+ break;
+ case ChromaSubsampling::Yuv420:
+ default:
+ selectedChromaSubsamplingIndex = 0;
+ break;
+ }
+
+ ComboBox_SetCurSel(chromaSubsamplingCombo, selectedChromaSubsamplingIndex);
+
+ int selectedCompressionSpeed;
+ switch (state->options.compressionSpeed)
+ {
+ case CompressionSpeed::Fastest:
+ selectedCompressionSpeed = IDC_COMPRESSION_SPEED_FASTEST_RADIO;
+ break;
+ case CompressionSpeed::Slowest:
+ selectedCompressionSpeed = IDC_COMPRESSION_SPEED_SLOWEST_RADIO;
+ break;
+ case CompressionSpeed::Default:
+ default:
+ selectedCompressionSpeed = IDC_COMPRESSION_SPEED_DEFAULT_RADIO;
+ break;
+ }
+
+ CheckRadioButton(hDlg, IDC_COMPRESSION_SPEED_FASTEST_RADIO, IDC_COMPRESSION_SPEED_SLOWEST_RADIO, selectedCompressionSpeed);
+
+ if (state->hasColorProfile)
+ {
+ Button_SetCheck(keepColorProfileCheckbox, state->options.keepColorProfile);
+ EnableWindow(keepColorProfileCheckbox, true);
+ }
+ else
+ {
+ Button_SetCheck(keepColorProfileCheckbox, false);
+ EnableWindow(keepColorProfileCheckbox, false);
+ }
+
+ if (state->hasExif)
+ {
+ Button_SetCheck(keepExifCheckbox, state->options.keepExif);
+ EnableWindow(keepExifCheckbox, true);
+ }
+ else
+ {
+ Button_SetCheck(keepExifCheckbox, false);
+ EnableWindow(keepExifCheckbox, false);
+ }
+
+ if (state->hasXmp)
+ {
+ Button_SetCheck(keepXmpCheckbox, state->options.keepXmp);
+ EnableWindow(keepXmpCheckbox, true);
+ }
+ else
+ {
+ Button_SetCheck(keepXmpCheckbox, false);
+ EnableWindow(keepXmpCheckbox, false);
+ }
+
+ SendMessage(pixelDepthCombo, CB_ADDSTRING, 0, reinterpret_cast(TEXT("8-bit")));
+ SendMessage(pixelDepthCombo, CB_ADDSTRING, 0, reinterpret_cast(TEXT("10-bit")));
+ SendMessage(pixelDepthCombo, CB_ADDSTRING, 0, reinterpret_cast(TEXT("12-bit")));
+
+ if (state->hostImageDepth == 8)
+ {
+ ComboBox_SetCurSel(pixelDepthCombo, 0);
+ }
+ else
+ {
+ ComboBox_SetCurSel(pixelDepthCombo, state->options.imageBitDepth == ImageBitDepth::Ten ? 1 : 2);
+ }
+ }
+
+ INT_PTR CALLBACK SaveDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) noexcept
+ {
+ static SaveDialogState* state;
+
+ int item;
+ int cmd;
+ int value;
+ HWND controlHwnd;
+
+ switch (wMsg)
+ {
+ case WM_INITDIALOG:
+ state = reinterpret_cast(lParam);
+
+ CenterDialog(hDlg);
+ InitSaveDialog(hDlg, state);
+ break;
+ case WM_COMMAND:
+ item = LOWORD(wParam);
+ cmd = HIWORD(wParam);
+
+ if (cmd == BN_CLICKED)
+ {
+ controlHwnd = reinterpret_cast(lParam);
+
+ switch (item)
+ {
+ case IDC_COMPRESSION_SPEED_FASTEST_RADIO:
+ if (Button_GetCheck(controlHwnd) == BST_CHECKED)
+ {
+ CheckRadioButton(
+ hDlg,
+ IDC_COMPRESSION_SPEED_FASTEST_RADIO,
+ IDC_COMPRESSION_SPEED_SLOWEST_RADIO,
+ IDC_COMPRESSION_SPEED_FASTEST_RADIO);
+ state->options.compressionSpeed = CompressionSpeed::Fastest;
+ }
+ break;
+ case IDC_COMPRESSION_SPEED_DEFAULT_RADIO:
+ if (Button_GetCheck(controlHwnd) == BST_CHECKED)
+ {
+ CheckRadioButton(
+ hDlg,
+ IDC_COMPRESSION_SPEED_FASTEST_RADIO,
+ IDC_COMPRESSION_SPEED_SLOWEST_RADIO,
+ IDC_COMPRESSION_SPEED_DEFAULT_RADIO);
+ state->options.compressionSpeed = CompressionSpeed::Default;
+ }
+ break;
+ case IDC_COMPRESSION_SPEED_SLOWEST_RADIO:
+ if (Button_GetCheck(controlHwnd) == BST_CHECKED)
+ {
+ CheckRadioButton(
+ hDlg,
+ IDC_COMPRESSION_SPEED_FASTEST_RADIO,
+ IDC_COMPRESSION_SPEED_SLOWEST_RADIO,
+ IDC_COMPRESSION_SPEED_SLOWEST_RADIO);
+ state->options.compressionSpeed = CompressionSpeed::Slowest;
+ }
+ break;
+ case IDC_KEEP_COLOR_PROFILE_CHECK:
+ state->options.keepColorProfile = Button_GetCheck(controlHwnd) == BST_CHECKED;
+ break;
+ case IDC_KEEP_EXIF_CHECK:
+ state->options.keepExif = Button_GetCheck(controlHwnd) == BST_CHECKED;
+ break;
+ case IDC_KEEP_XMP_CHECK:
+ state->options.keepXmp = Button_GetCheck(controlHwnd) == BST_CHECKED;
+ break;
+ case IDC_LOSSLESS_CHECK:
+ state->options.lossless = Button_GetCheck(controlHwnd) == BST_CHECKED;
+ EnableLossyCompressionSettings(hDlg, !state->options.lossless);
+ break;
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hDlg, item);
+ break;
+ default:
+ break;
+ }
+ }
+ else if (cmd == CBN_SELCHANGE)
+ {
+ controlHwnd = reinterpret_cast(lParam);
+
+ value = ComboBox_GetCurSel(controlHwnd);
+
+ if (item == IDC_CHROMA_SUBSAMPLING_COMBO)
+ {
+ switch (value)
+ {
+ case 0:
+ state->options.chromaSubsampling = ChromaSubsampling::Yuv420;
+ break;
+ case 1:
+ state->options.chromaSubsampling = ChromaSubsampling::Yuv422;
+ break;
+ case 2:
+ state->options.chromaSubsampling = ChromaSubsampling::Yuv444;
+ break;
+ default:
+ state->options.chromaSubsampling = ChromaSubsampling::Yuv420;
+ break;
+ }
+ }
+ else if (item == IDC_IMAGE_DEPTH_COMBO)
+ {
+ switch (value)
+ {
+ case 0:
+ state->options.imageBitDepth = ImageBitDepth::Eight;
+ break;
+ case 1:
+ state->options.imageBitDepth = ImageBitDepth::Ten;
+ break;
+ case 2:
+ state->options.imageBitDepth = ImageBitDepth::Twelve;
+ break;
+ default:
+ state->options.imageBitDepth = state->hostImageDepth == 8 ? ImageBitDepth::Eight : ImageBitDepth::Twelve;
+ break;
+ }
+
+ if (state->hostImageDepth == 8)
+ {
+ // Lossless mode is only supported when the host image depth and output
+ // image depth are both 8-bits-per-channel.
+
+ if (state->options.imageBitDepth != ImageBitDepth::Eight)
+ {
+ if (state->losslessCheckboxEnabled)
+ {
+ state->losslessCheckboxEnabled = false;
+ HWND losslessCheck = GetDlgItem(hDlg, IDC_LOSSLESS_CHECK);
+
+ if (Button_GetCheck(losslessCheck) == BST_CHECKED)
+ {
+ Button_SetCheck(losslessCheck, BST_UNCHECKED);
+ state->options.lossless = false;
+ EnableLossyCompressionSettings(hDlg, true);
+ }
+
+ EnableWindow(losslessCheck, false);
+ }
+ }
+ else
+ {
+ if (!state->losslessCheckboxEnabled)
+ {
+ state->losslessCheckboxEnabled = true;
+
+ EnableWindow(GetDlgItem(hDlg, IDC_LOSSLESS_CHECK), true);
+ }
+ }
+ }
+ }
+ }
+ else if (item == IDC_QUALITY_EDIT && cmd == EN_CHANGE)
+ {
+ BOOL translated;
+ value = static_cast(GetDlgItemInt(hDlg, IDC_QUALITY_EDIT, &translated, true));
+
+ if (translated && value >= 0 && value <= 100 && state->options.quality != value)
+ {
+ state->options.quality = value;
+ SendMessage(GetDlgItem(hDlg, IDC_QUALITY_SLIDER), TBM_SETPOS, TRUE, value);
+ }
+ }
+ break;
+ case WM_HSCROLL:
+ controlHwnd = reinterpret_cast(lParam);
+
+ switch (LOWORD(wParam))
+ {
+ case TB_LINEUP:
+ case TB_LINEDOWN:
+ case TB_PAGEUP:
+ case TB_PAGEDOWN:
+ case TB_THUMBTRACK:
+ case TB_TOP:
+ case TB_BOTTOM:
+ case TB_ENDTRACK:
+ value = static_cast(SendMessage(controlHwnd, TBM_GETPOS, 0, 0));
+
+ if (state->options.quality != value)
+ {
+ state->options.quality = value;
+ SetDlgItemInt(hDlg, IDC_QUALITY_EDIT, static_cast(value), true);
+ }
+ break;
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+}
+
+void DoAbout(const AboutRecordPtr aboutRecord)
+{
+ PlatformData* platform = static_cast(aboutRecord->platformData);
+
+ HWND parent = platform != nullptr ? reinterpret_cast(platform->hwnd) : nullptr;
+
+ DialogBoxParam(GetModuleInstanceHandle(), MAKEINTRESOURCE(IDD_ABOUT), parent, AboutDlgProc, 0);
+}
+
+bool DoSaveUI(const FormatRecordPtr formatRecord, SaveUIOptions& options)
+{
+ PlatformData* platform = static_cast(formatRecord->platformData);
+
+ HWND parent = platform != nullptr ? reinterpret_cast(platform->hwnd) : nullptr;
+
+ SaveDialogState state(formatRecord, options);
+
+ if (DialogBoxParam(
+ GetModuleInstanceHandle(),
+ MAKEINTRESOURCE(IDD_SAVE),
+ parent,
+ SaveDlgProc,
+ reinterpret_cast(&state)) == IDOK)
+ {
+ options.quality = state.options.quality;
+ options.chromaSubsampling = state.options.chromaSubsampling;
+ options.compressionSpeed = state.options.compressionSpeed;
+ options.lossless = state.options.lossless;
+ options.imageBitDepth = state.options.imageBitDepth;
+ options.keepColorProfile = state.options.keepColorProfile;
+ options.keepExif = state.options.keepExif;
+ options.keepXmp = state.options.keepXmp;
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+OSErr ShowErrorDialog(const FormatRecordPtr formatRecord, const char* const message, OSErr fallbackErrorCode)
+{
+ PlatformData* platformData = static_cast(formatRecord->platformData);
+
+ HWND parent = platformData != nullptr ? reinterpret_cast(platformData->hwnd) : nullptr;
+
+ if (MessageBoxA(parent, message, "AV1 Image File Format", MB_OK | MB_ICONERROR) == IDOK)
+ {
+ // Any positive number is a plug-in handled error message.
+ return 1;
+ }
+ else
+ {
+ return fallbackErrorCode;
+ }
+}
+
diff --git a/src/win/dllmain.cpp b/src/win/dllmain.cpp
new file mode 100644
index 0000000..3f14485
--- /dev/null
+++ b/src/win/dllmain.cpp
@@ -0,0 +1,41 @@
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 .
+ */
+
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+// Windows Header Files
+#include
+
+BOOL APIENTRY DllMain( HMODULE /*hModule*/,
+ DWORD ul_reason_for_call,
+ LPVOID /*lpReserved*/
+ )
+{
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+}
+
diff --git a/src/win/resource.h b/src/win/resource.h
new file mode 100644
index 0000000..b335983
--- /dev/null
+++ b/src/win/resource.h
@@ -0,0 +1,42 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by AvifFormat.rc
+//
+#define IDD_SAVE 101
+#define IDD_ABOUT 102
+#define IDS_CHROMA_SUBSAMPLING_420 105
+#define IDS_CHROMA_SUBSAMPLING_422 106
+#define IDS_CHROMA_SUBSAMPLING_444 107
+#define IDC_QUALITY 1000
+#define IDC_QUALITY_SLIDER 1000
+#define ABOUTFORMAT 1001
+#define IDC_QUALITYGB 1001
+#define IDC_ABOUTOK 1002
+#define IDC_QUALITY_EDIT 1003
+#define IDC_QUALITY_EDIT_SPIN 1004
+#define IDC_LOSSLESS_CHECK 1005
+#define IDC_COMPRESSION_SPEED_GROUP 1006
+#define IDC_COMPRESSION_SPEED_FASTEST_RADIO 1007
+#define IDC_COMPRESSION_SPEED_DEFAULT_RADIO 1008
+#define IDC_COMPRESSION_SPEED_SLOWEST_RADIO 1009
+#define IDC_CHROMA_SUBSAMPLING_GROUP 1010
+#define IDC_CHROMA_SUBSAMPLING_COMBO 1011
+#define IDC_METADATA_GROUP 1012
+#define IDC_KEEP_COLOR_PROFILE_CHECK 1013
+#define IDC_KEEP_EXIF_CHECK 1014
+#define IDC_KEEP_XMP_CHECK 1015
+#define IDC_IMAGE_DEPTH_GROUP 1016
+#define IDC_IMAGE_DEPTH_COMBO 1017
+#define IDC_PROJECT_HOMEPAGE_LINK 1018
+#define IDC_CREDITS_LINK 1019
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 106
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1020
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/src/win/version.rc b/src/win/version.rc
new file mode 100644
index 0000000000000000000000000000000000000000..e97ed06a1db9a6d6f46085d6cefbdf9793397d07
GIT binary patch
literal 4744
zcmdUy$!;1!5QggX6+5)NMzbOY!g8XUnam{tDNa6Z)Ju
zM0%jrDO*lv*PS68Cw6Hyt6RhJRvSg
z4J)3mi0l%%`rh6my9V7UE1qYWqQ$Py>V+<=BS87lM3WDkn*Z-=L3Wmhn9!)-0XADr
zd5Y#mS0_Xp&7SNUeQo=*IxX*cdx=X`W&IPfZibwtYfa9ssae-Vv%e!27+2vrdJ>*>
zPuX&nxJ{0!y7))cA*<4)<1Bb*v+7A4jSy-cMs*1Bn>efoO`7Cg3z`BMUURo^g}F38
zC&R=Y-wEsDj-V)!(S;=%@sv_BJ;x=dREfa`6{x-jUpVR9RgfY`FQ_CnDnLYZf43vN
zS#~-rKD+bVLp82I-D!|5Nz4mLUc$kgze7EqV6(^R_haE*oY`SCv4_p<@f2{EinU2<5|bw^}V&e|3D?%*4N;0EtR^cXkDlc&}}RGlGE
zen{)!bIsy$_Z-?^a@Gi|J%D_gvTt@|ucJ#+{&PgiqFV@&lU_;Ig(h|zm~BI+Kjb
zI?_}LCeF`Oyso_Vaj&ZHtB`8L0g<5!8;|$}+QpmpS9bu<(~ZY(G}Anc|N7Ofe)RFL?qJI}ip_c#!qxFUB%gGDroXY%ewU;_L7G1-
literal 0
HcmV?d00001
diff --git a/vs/AvifFormat.sln b/vs/AvifFormat.sln
new file mode 100644
index 0000000..26f6711
--- /dev/null
+++ b/vs/AvifFormat.sln
@@ -0,0 +1,36 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31129.286
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AvifFormat", "AvifFormat.vcxproj", "{78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B4C7A3AD-33C9-40BF-A6D4-570726DC1DF0}"
+ ProjectSection(SolutionItems) = preProject
+ ..\src\.editorconfig = ..\src\.editorconfig
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Debug|x64.ActiveCfg = Debug|x64
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Debug|x64.Build.0 = Debug|x64
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Debug|x86.ActiveCfg = Debug|Win32
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Debug|x86.Build.0 = Debug|Win32
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Release|x64.ActiveCfg = Release|x64
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Release|x64.Build.0 = Release|x64
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Release|x86.ActiveCfg = Release|Win32
+ {78CFC9B1-79F2-4B05-8DD8-852E32B06EF6}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FD40C7D7-77FB-45F8-AC02-7325480C6804}
+ EndGlobalSection
+EndGlobal
diff --git a/vs/AvifFormat.sln.licenseheader b/vs/AvifFormat.sln.licenseheader
new file mode 100644
index 0000000..5f1fc01
--- /dev/null
+++ b/vs/AvifFormat.sln.licenseheader
@@ -0,0 +1,21 @@
+extensions: designer.cs generated.cs
+extensions: .cs .cpp .h
+/*
+ * This file is part of avif-format, an AV1 Image (AVIF) file format
+ * plug-in for Adobe Photoshop(R).
+ *
+ * Copyright (c) 2021 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 .
+ */
diff --git a/vs/AvifFormat.vcxproj b/vs/AvifFormat.vcxproj
new file mode 100644
index 0000000..e02c95e
--- /dev/null
+++ b/vs/AvifFormat.vcxproj
@@ -0,0 +1,274 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {78cfc9b1-79f2-4b05-8dd8-852e32b06ef6}
+ AvifFormat
+ 10.0
+
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ Av1Image
+ .8bi
+
+
+ false
+ Av1Image
+ .8bi
+
+
+ true
+ Av1Image
+ .8bi
+
+
+ false
+ Av1Image
+ .8bi
+
+
+
+ Level4
+ true
+ WIN32;_DEBUG;AVIFFORMAT_EXPORTS;_WINDOWS;_USRDLL;NOMINMAX;LIBHEIF_STATIC_BUILD;%(PreprocessorDefinitions)
+ true
+ ..\src\win;..\src\common;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\photoshop;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\pica_sp;..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\common\includes;..\3rd-party\libheif;..\3rd-party\libheif\build32;%(AdditionalIncludeDirectories)
+ MultiThreadedDebug
+ 26812
+
+
+ Windows
+ true
+ false
+ aom.lib;heif.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ ..\3rd-party\libheif\build32\libheif\Debug;..\3rd-party\aom\build32\Debug
+
+
+ ..\src\common;$(IntDir)
+
+
+
+
+
+
+
+
+ Level4
+ true
+ true
+ true
+ WIN32;NDEBUG;AVIFFORMAT_EXPORTS;_WINDOWS;_USRDLL;NOMINMAX;LIBHEIF_STATIC_BUILD;%(PreprocessorDefinitions)
+ true
+ ..\src\win;..\src\common;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\photoshop;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\pica_sp;..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\common\includes;..\3rd-party\libheif;..\3rd-party\libheif\build32;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ 26812
+
+
+ Windows
+ true
+ true
+ true
+ false
+ aom.lib;heif.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ ..\3rd-party\libheif\build32\libheif\Release;..\3rd-party\aom\build32\Release
+
+
+ ..\src\common;$(IntDir)
+
+
+
+
+
+
+
+
+ Level4
+ true
+ _DEBUG;AVIFFORMAT_EXPORTS;_WINDOWS;_USRDLL;WIN32;NOMINMAX;LIBHEIF_STATIC_BUILD;%(PreprocessorDefinitions)
+ true
+ ..\src\win;..\src\common;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\photoshop;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\pica_sp;..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\common\includes;..\3rd-party\libheif;..\3rd-party\libheif\build64;%(AdditionalIncludeDirectories)
+ MultiThreadedDebug
+ 26812
+
+
+ Windows
+ true
+ false
+ ..\3rd-party\libheif\build64\libheif\Debug;..\3rd-party\aom\build64\Debug
+ aom.lib;heif.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+
+
+ ..\src\common;$(IntDir)
+
+
+
+
+
+
+ copy "$(TargetPath)" "D:\Adobe CC Apps\Adobe Photoshop 2020\Plug-ins\File Formats" /y
+
+
+
+
+ Level4
+ true
+ true
+ true
+ NDEBUG;AVIFFORMAT_EXPORTS;_WINDOWS;_USRDLL;WIN32;NOMINMAX;LIBHEIF_STATIC_BUILD;%(PreprocessorDefinitions)
+ true
+ ..\src\win;..\src\common;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\photoshop;..\3rd-party\adobe_photoshop_cs5_sdk_win\photoshopapi\pica_sp;..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\common\includes;..\3rd-party\libheif;..\3rd-party\libheif\build64;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ 26812
+
+
+ Windows
+ true
+ true
+ true
+ false
+ ..\3rd-party\libheif\build64\libheif\Release;..\3rd-party\aom\build64\Release
+ aom.lib;heif.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+
+
+ ..\src\common;$(IntDir)
+
+
+
+
+
+
+ copy "$(TargetPath)" "D:\Adobe CC Apps\Adobe Photoshop 2020\Plug-ins\File Formats" /y
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Document
+ cl /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Includes /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\Photoshop /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\PICA_SP /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\resources /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Resources /EP /DMSWindows=1 /DWIN32=1 /Tc"%(FullPath)" > "$(IntDir)%(Filename).rr"
+..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\resources\cnvtpipl.exe "$(IntDir)%(Filename).rr" "$(IntDir)%(Filename).pipl"
+del "$(IntDir)%(Filename).rr"
+ $(IntDir)%(Filename).pipl;%(Outputs)
+ cl /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Includes /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\Photoshop /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\PICA_SP /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\resources /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Resources /EP /DMSWindows=1 /DWIN32=1 /Tc"%(FullPath)" > "$(IntDir)%(Filename).rr"
+..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\resources\cnvtpipl.exe "$(IntDir)%(Filename).rr" "$(IntDir)%(Filename).pipl"
+del "$(IntDir)%(Filename).rr"
+ $(IntDir)%(Filename).pipl;%(Outputs)
+ cl /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Includes /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\Photoshop /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\PICA_SP /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\resources /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Resources /EP /DMSWindows=1 /DWIN32=1 /Tc"%(FullPath)" > "$(IntDir)%(Filename).rr"
+..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\resources\cnvtpipl.exe "$(IntDir)%(Filename).rr" "$(IntDir)%(Filename).pipl"
+del "$(IntDir)%(Filename).rr"
+ $(IntDir)%(Filename).pipl;%(Outputs)
+ cl /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Includes /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\Photoshop /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\PICA_SP /I..\3rd-party\adobe_photoshop_cs5_sdk_win\PhotoshopAPI\resources /I..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\Common\Resources /EP /DMSWindows=1 /DWIN32=1 /Tc"%(FullPath)" > "$(IntDir)%(Filename).rr"
+..\3rd-party\adobe_photoshop_cs5_sdk_win\samplecode\resources\cnvtpipl.exe "$(IntDir)%(Filename).rr" "$(IntDir)%(Filename).pipl"
+del "$(IntDir)%(Filename).rr"
+ $(IntDir)%(Filename).pipl;%(Outputs)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vs/AvifFormat.vcxproj.filters b/vs/AvifFormat.vcxproj.filters
new file mode 100644
index 0000000..595ca1a
--- /dev/null
+++ b/vs/AvifFormat.vcxproj.filters
@@ -0,0 +1,139 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Resource Files
+
+
+ Resource Files
+
+
+ Resource Files
+
+
+
+
+ Resource Files
+
+
+
\ No newline at end of file