-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #259 from Tom94/apple-hdr-gainmap
feat: support Apple HDR gain maps in HEIC/AVIF images
- Loading branch information
Showing
10 changed files
with
636 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* tev -- the EXR viewer | ||
* | ||
* Copyright (C) 2025 Thomas Müller <contact@tom94.net> | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License. | ||
* | ||
* This program 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 General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <tev/Common.h> | ||
|
||
#include <cstdint> | ||
#include <map> | ||
#include <stdexcept> | ||
#include <vector> | ||
|
||
namespace tev { | ||
|
||
bool isAppleMakernote(const uint8_t* data, size_t length); | ||
|
||
template <typename T> T read(const uint8_t* data, bool reverseEndianness) { | ||
if (reverseEndianness) { | ||
T result; | ||
for (size_t i = 0; i < sizeof(T); ++i) { | ||
reinterpret_cast<uint8_t*>(&result)[i] = data[sizeof(T) - i - 1]; | ||
} | ||
|
||
return result; | ||
} else { | ||
return *reinterpret_cast<const T*>(data); | ||
} | ||
} | ||
|
||
struct AppleMakerNoteEntry { | ||
enum class EFormat : uint16_t { | ||
Byte = 1, | ||
Ascii = 2, | ||
Short = 3, | ||
Long = 4, | ||
Rational = 5, | ||
Sbyte = 6, | ||
Undefined = 7, | ||
Sshort = 8, | ||
Slong = 9, | ||
Srational = 10, | ||
Float = 11, | ||
Double = 12, | ||
}; | ||
|
||
uint16_t tag; | ||
EFormat format; | ||
uint32_t nComponents; | ||
std::vector<uint8_t> data; | ||
|
||
static size_t formatSize(EFormat format) { | ||
switch (format) { | ||
case EFormat::Byte: | ||
case EFormat::Ascii: | ||
case EFormat::Sbyte: | ||
case EFormat::Undefined: return 1; | ||
case EFormat::Short: | ||
case EFormat::Sshort: return 2; | ||
case EFormat::Long: | ||
case EFormat::Slong: | ||
case EFormat::Float: return 4; | ||
case EFormat::Rational: | ||
case EFormat::Srational: | ||
case EFormat::Double: return 8; | ||
default: | ||
// The default size of 4 for unknown types is chosen to make parsing easier. Larger types would be stored at a remote | ||
// location with the 4 bytes interpreted as an offset, which may be invalid depending on the indended behavior of the | ||
// unknown type. Better play it safe and just read 4 bytes, leaving it to the user to know whether they represent an offset | ||
// or a meaningful value by themselves. | ||
return 4; | ||
} | ||
|
||
throw std::invalid_argument{std::string{"Unknown format: "} + std::to_string((uint32_t)format)}; | ||
} | ||
|
||
size_t size() const { return nComponents * formatSize(format); } | ||
}; | ||
|
||
class AppleMakerNote { | ||
public: | ||
AppleMakerNote(const uint8_t* data, size_t length); | ||
|
||
template <typename T> T getFloat(uint16_t tag) const { | ||
if (mTags.count(tag) == 0) { | ||
throw std::invalid_argument{"Requested tag does not exist."}; | ||
} | ||
|
||
const auto& entry = mTags.at(tag); | ||
const uint8_t* data = entry.data.data(); | ||
|
||
switch (entry.format) { | ||
case AppleMakerNoteEntry::EFormat::Byte: return static_cast<T>(*data); | ||
case AppleMakerNoteEntry::EFormat::Short: return static_cast<T>(read<uint16_t>(data, mReverseEndianess)); | ||
case AppleMakerNoteEntry::EFormat::Long: return static_cast<T>(read<uint32_t>(data, mReverseEndianess)); | ||
case AppleMakerNoteEntry::EFormat::Rational: { | ||
uint32_t numerator = read<uint32_t>(data, mReverseEndianess); | ||
uint32_t denominator = read<uint32_t>(data + sizeof(uint32_t), mReverseEndianess); | ||
return static_cast<T>(numerator) / static_cast<T>(denominator); | ||
} | ||
case AppleMakerNoteEntry::EFormat::Sbyte: return static_cast<T>(*reinterpret_cast<const int8_t*>(data)); | ||
case AppleMakerNoteEntry::EFormat::Sshort: return static_cast<T>(read<int16_t>(data, mReverseEndianess)); | ||
case AppleMakerNoteEntry::EFormat::Slong: return static_cast<T>(read<int32_t>(data, mReverseEndianess)); | ||
case AppleMakerNoteEntry::EFormat::Srational: { | ||
int32_t numerator = read<int32_t>(data, mReverseEndianess); | ||
int32_t denominator = read<int32_t>(data + sizeof(int32_t), mReverseEndianess); | ||
return static_cast<T>(numerator) / static_cast<T>(denominator); | ||
} | ||
case AppleMakerNoteEntry::EFormat::Float: return static_cast<T>(*reinterpret_cast<const float*>(data)); | ||
case AppleMakerNoteEntry::EFormat::Double: return static_cast<T>(*reinterpret_cast<const double*>(data)); | ||
case AppleMakerNoteEntry::EFormat::Ascii: | ||
case AppleMakerNoteEntry::EFormat::Undefined: throw std::invalid_argument{"Cannot convert this format to float."}; | ||
} | ||
|
||
throw std::invalid_argument{"Unknown format."}; | ||
} | ||
|
||
private: | ||
std::map<uint8_t, AppleMakerNoteEntry> mTags; | ||
bool mReverseEndianess = false; | ||
}; | ||
|
||
} // namespace tev |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* tev -- the EXR viewer | ||
* | ||
* Copyright (C) 2025 Thomas Müller <contact@tom94.net> | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License. | ||
* | ||
* This program 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 General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <tev/Common.h> | ||
#include <tev/Image.h> | ||
#include <tev/Task.h> | ||
|
||
class AppleMakerNote; | ||
|
||
namespace tev { | ||
|
||
Task<void> applyAppleGainMap(ImageData& image, const ImageData& gainMap, int priority, const AppleMakerNote& amn); | ||
|
||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
* tev -- the EXR viewer | ||
* | ||
* Copyright (C) 2025 Thomas Müller <contact@tom94.net> | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License. | ||
* | ||
* This program 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 General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#include <tev/Common.h> | ||
#include <tev/imageio/AppleMakerNote.h> | ||
|
||
using namespace std; | ||
|
||
namespace tev { | ||
|
||
static const uint8_t APPLE_SIGNATURE[] = {0x41, 0x70, 0x70, 0x6C, 0x65, 0x20, 0x69, 0x4F, 0x53, 0x00}; // "Apple iOS\0" | ||
static const size_t SIG_LENGTH = sizeof(APPLE_SIGNATURE); | ||
|
||
bool isAppleMakernote(const uint8_t* data, size_t length) { | ||
|
||
if (length < SIG_LENGTH) { | ||
return false; | ||
} | ||
|
||
return memcmp(data, APPLE_SIGNATURE, SIG_LENGTH) == 0; | ||
} | ||
|
||
// This whole function is one huge hack. It was pieced together by referencing the EXIF spec as well as the (non-functional) implementation | ||
// over at libexif. https://github.com/libexif/libexif/blob/master/libexif/apple/exif-mnote-data-apple.c That, plus quite a bit of trial and | ||
// error, finally got this to work. Who knows when Apple will break it. :) | ||
AppleMakerNote::AppleMakerNote(const uint8_t* data, size_t length) { | ||
mReverseEndianess = false; | ||
|
||
size_t ofs = 0; | ||
if ((data[ofs + 12] == 'M') && (data[ofs + 13] == 'M')) { | ||
mReverseEndianess = std::endian::little == std::endian::native; | ||
} else if ((data[ofs + 12] == 'I') && (data[ofs + 13] == 'I')) { | ||
mReverseEndianess = std::endian::big == std::endian::native; | ||
} else { | ||
throw invalid_argument{"Failed to determine byte order."}; | ||
} | ||
|
||
uint32_t tcount = read<uint16_t>(data + ofs + 14, mReverseEndianess); | ||
|
||
if (length < ofs + 16 + tcount * 12 + 4) { | ||
throw invalid_argument{"Too short"}; | ||
} | ||
|
||
ofs += 16; | ||
|
||
for (uint32_t i = 0; i < tcount; i++) { | ||
if (ofs + 12 > length) { | ||
throw invalid_argument{"Overflow"}; | ||
} | ||
|
||
AppleMakerNoteEntry entry; | ||
entry.tag = read<uint16_t>(data + ofs, mReverseEndianess); | ||
entry.format = read<AppleMakerNoteEntry::EFormat>(data + ofs + 2, mReverseEndianess); | ||
entry.nComponents = read<uint32_t>(data + ofs + 4, mReverseEndianess); | ||
|
||
if (ofs + 4 + entry.size() > length) { | ||
throw invalid_argument{"Elem overflow"}; | ||
} | ||
|
||
size_t entryOffset; | ||
if (entry.size() > 4) { | ||
// Entry is stored somewhere else, pointed to by the following | ||
entryOffset = read<uint32_t>(data + ofs + 8, mReverseEndianess); // -6? | ||
} else { | ||
entryOffset = ofs + 8; | ||
} | ||
|
||
entry.data = vector<uint8_t>(data + entryOffset, data + entryOffset + entry.size()); | ||
|
||
if (entryOffset + entry.size() > length) { | ||
throw invalid_argument{"Offset overflow"}; | ||
} | ||
|
||
ofs += 12; | ||
mTags[entry.tag] = entry; | ||
|
||
tlog::debug() << fmt::format("Tag: {} Format: {} Components: {}", entry.tag, (int)entry.format, entry.nComponents); | ||
} | ||
} | ||
|
||
} // namespace tev |
Oops, something went wrong.