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 0000000..58a1e30 Binary files /dev/null and b/src/win/AvifFormat.rc differ diff --git a/src/win/FileIOWin.cpp b/src/win/FileIOWin.cpp new file mode 100644 index 0000000..c1adee5 --- /dev/null +++ b/src/win/FileIOWin.cpp @@ -0,0 +1,124 @@ +/* + * 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 "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 0000000..82797ba Binary files /dev/null and b/src/win/PiPL.rc differ diff --git a/src/win/UIWin.cpp b/src/win/UIWin.cpp new file mode 100644 index 0000000..7fd8484 --- /dev/null +++ b/src/win/UIWin.cpp @@ -0,0 +1,600 @@ +/* + * 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 "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 0000000..e97ed06 Binary files /dev/null and b/src/win/version.rc differ 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