diff --git a/.clang-tidy b/.clang-tidy
index adee37064..a10a9f5be 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -50,5 +50,5 @@ CheckOptions:
- key: modernize-use-default-member-init.UseAssignment
value: 1
- key: cppcoreguidelines-macro-usage.AllowedRegexp
- value: 'DEBUG|_GLIBCXX_SANITIZE_VECTOR|RAWSPEED_SOURCE_DIR|STR|XSTR|BSWAP16|BSWAP32|BSWAP64|ThrowExceptionHelper|ThrowIOE|ThrowRSE|ThrowCME|ThrowRDE|ThrowRPE|ThrowTPE|ThrowFIE|ThrowCPE|ThrowFPE|DECODER|fuji_quant_gradient|JPEG_MEMSRC|RLVTABLE|PRECISION_MIN|PRECISION_MAX|MARKER_BAND_END|SQR|RS_CAMERAS_XML_PATH|FULLDECODE|IMPL|IMPL0|IMPL1|PUMP|DECODE|PARSER|GEN_E|GEN_PFS|GEN_PSS|BENCHMARK_CAPTURE_NAME|OMPSHAREDCLAUSE|RAWSPEED_UNLIKELY_FUNCTION|RAWSPEED_NOINLINE'
+ value: 'DEBUG|_GLIBCXX_SANITIZE_VECTOR|RAWSPEED_SOURCE_DIR|STR|XSTR|BSWAP16|BSWAP32|BSWAP64|ThrowExceptionHelper|ThrowIOE|ThrowIPE|ThrowRSE|ThrowCME|ThrowRDE|ThrowRPE|ThrowTPE|ThrowFIE|ThrowCPE|ThrowFPE|DECODER|fuji_quant_gradient|JPEG_MEMSRC|RLVTABLE|PRECISION_MIN|PRECISION_MAX|MARKER_BAND_END|SQR|RS_CAMERAS_XML_PATH|FULLDECODE|IMPL|IMPL0|IMPL1|PUMP|DECODE|PARSER|GEN_E|GEN_PFS|GEN_PSS|BENCHMARK_CAPTURE_NAME|OMPFIRSTPRIVATECLAUSE|OMPSHAREDCLAUSE|RAWSPEED_UNLIKELY_FUNCTION|RAWSPEED_NOINLINE'
...
diff --git a/data/cameras.xml b/data/cameras.xml
index 41645599d..035c97658 100644
--- a/data/cameras.xml
+++ b/data/cameras.xml
@@ -154,6 +154,28 @@
+
+ Canon EOS 250D
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+ 9079 -1923 -1236
+ -4677 12454 2492
+ -922 2319 5565
+
+
+
Canon EOS 300D
@@ -550,12 +572,12 @@
Canon EOS 80D
-
- RED
- GREEN
- GREEN
- BLUE
-
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
@@ -621,6 +643,23 @@
+
+ Canon EOS 90D
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
Canon EOS 700D
@@ -701,6 +740,33 @@
+
+ Canon EOS 850D
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+ Canon EOS Rebel T8i
+
+
+
+
+
+
+ 9079 -1923 -1236
+ -4677 12454 2492
+ -922 2319 5565
+
+
+
Canon EOS 760D
@@ -1704,6 +1770,31 @@
+
+ Canon EOS M6 Mark II
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+
+
+ 11498 -3759 -1516
+ -5073 12954 2349
+ -892 1867 6118
+
+
+
Canon EOS M10
@@ -1757,6 +1848,30 @@
+
+ Canon EOS M200
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+
+ 10463 -2173 -1437
+ -4856 12635 2482
+ -1216 2915 7237
+
+
+
Canon EOS-1D
@@ -2134,6 +2249,176 @@
+
+ Canon EOS-1D X Mark III
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+
+
+ 8971 -2022 -1242
+ -5405 13249 2380
+ -1280 2483 6072
+
+
+
+
+ Canon EOS R
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+
+ 8293 -1789 -1094
+ -5025 12925 2327
+ -1199 2769 6108
+
+
+
+
+ Canon EOS RP
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+
+ 8608 -2097 -1178
+ -5425 13265 2383
+ -1149 2238 5680
+
+
+
+
+ Canon EOS R5
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+ 9766 -2953 -1254
+ -4276 12116 2433
+ -437 1336 5131
+
+
+
+
+ Canon EOS R6
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+ 8293 -1611 -1132
+ -4759 12711 2275
+ -1013 2415 5509
+
+
+
+
+ Canon EOS M50
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+ Canon EOS KISS M
+
+
+
+
+
+
+ 8532 -701 -1167
+ -4095 11879 2508
+ -797 2424 7010
+
+
+
+
+ Canon EOS M50 Mark II
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+
+
+
+
+ 10463 -2173 -1437
+ -4856 12635 2482
+ -1216 2915 7237
+
+
+
Canon PowerShot Pro1
@@ -2310,6 +2595,26 @@
+
+ Canon PowerShot G5 X Mark II
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+ 11629 -5713 -914
+ -2706 11090 1842
+ -206 1225 5515
+
+
+
Canon PowerShot G6
@@ -2386,6 +2691,26 @@
+
+ Canon PowerShot G7 X Mark III
+
+ RED
+ GREEN
+ GREEN
+ BLUE
+
+
+
+
+
+
+
+ 11629 -5713 -914
+ -2706 11090 1842
+ -206 1225 5515
+
+
+
Canon PowerShot G1 X
diff --git a/fuzz/all-fuzzers.txt b/fuzz/all-fuzzers.txt
index a83873089..254ac9cea 100644
--- a/fuzz/all-fuzzers.txt
+++ b/fuzz/all-fuzzers.txt
@@ -67,6 +67,8 @@ HuffmanTableVectorFuzzer-BitPumpMSB-FullDecode
HuffmanTableVectorFuzzer-BitPumpMSB-NoFullDecode
HuffmanTableVectorFuzzer-BitPumpMSB32-FullDecode
HuffmanTableVectorFuzzer-BitPumpMSB32-NoFullDecode
+IsoMParserFuzzer-GetDecoder
+IsoMParserFuzzer-GetDecoder-Decode
KodakDecompressorFuzzer
LJpegDecompressorFuzzer
NikonDecompressorFuzzer
diff --git a/fuzz/librawspeed/decoders/TiffDecoders/main.cpp b/fuzz/librawspeed/decoders/TiffDecoders/main.cpp
index 3aa01c7b8..5fbb89a9c 100644
--- a/fuzz/librawspeed/decoders/TiffDecoders/main.cpp
+++ b/fuzz/librawspeed/decoders/TiffDecoders/main.cpp
@@ -25,6 +25,7 @@
#include "common/RawspeedException.h" // for RawspeedException
#include "decoders/ArwDecoder.h" // IWYU pragma: keep
#include "decoders/Cr2Decoder.h" // IWYU pragma: keep
+#include "decoders/Cr3Decoder.h" // IWYU pragma: keep
#include "decoders/DcrDecoder.h" // IWYU pragma: keep
#include "decoders/DcsDecoder.h" // IWYU pragma: keep
#include "decoders/DngDecoder.h" // IWYU pragma: keep
diff --git a/fuzz/librawspeed/parsers/CMakeLists.txt b/fuzz/librawspeed/parsers/CMakeLists.txt
index 4ffae2a48..ac335a9dc 100644
--- a/fuzz/librawspeed/parsers/CMakeLists.txt
+++ b/fuzz/librawspeed/parsers/CMakeLists.txt
@@ -19,6 +19,7 @@ endfunction()
set(PARSERS
"Ciff"
"Fiff"
+ "IsoM"
"Raw"
"Tiff"
)
diff --git a/fuzz/librawspeed/parsers/main.cpp b/fuzz/librawspeed/parsers/main.cpp
index a44fc5938..6db139411 100644
--- a/fuzz/librawspeed/parsers/main.cpp
+++ b/fuzz/librawspeed/parsers/main.cpp
@@ -1,7 +1,7 @@
/*
RawSpeed - RAW file decoder.
- Copyright (C) 2017 Roman Lebedev
+ Copyright (C) 2017-2018 Roman Lebedev
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -34,6 +34,7 @@
#include "io/IOException.h" // for IOException
#include "parsers/CiffParser.h" // IWYU pragma: keep
#include "parsers/FiffParser.h" // IWYU pragma: keep
+#include "parsers/IsoMParser.h" // IWYU pragma: keep
#include "parsers/RawParser.h" // IWYU pragma: keep
#include "parsers/RawParserException.h" // for RawParserException
#include "parsers/TiffParser.h" // IWYU pragma: keep
diff --git a/src/librawspeed/common/Range.h b/src/librawspeed/common/Range.h
index 58208a0fe..5296001dc 100644
--- a/src/librawspeed/common/Range.h
+++ b/src/librawspeed/common/Range.h
@@ -55,6 +55,15 @@ constexpr bool __attribute__((const)) RangeContains(const Tr& r, Tv pos) {
return r.end() > pos;
}
+template
+inline constexpr bool __attribute__((const))
+RangesAreNested(const Tro& outer, const Tri& inner) {
+ // 1. The inner range must not begin before the outer range, and
+ // 2. The outer range must not end before the inner range
+ // Same begin and/or end is ok.
+ return outer.begin() <= inner.begin() && inner.end() <= outer.end();
+}
+
template
constexpr bool __attribute__((const))
RangesOverlap(const T& lhs, const T& rhs) {
diff --git a/src/librawspeed/decoders/CMakeLists.txt b/src/librawspeed/decoders/CMakeLists.txt
index 654116331..acb267543 100644
--- a/src/librawspeed/decoders/CMakeLists.txt
+++ b/src/librawspeed/decoders/CMakeLists.txt
@@ -5,6 +5,8 @@ FILE(GLOB SOURCES
"ArwDecoder.h"
"Cr2Decoder.cpp"
"Cr2Decoder.h"
+ "Cr3Decoder.cpp"
+ "Cr3Decoder.h"
"CrwDecoder.cpp"
"CrwDecoder.h"
"DcrDecoder.cpp"
diff --git a/src/librawspeed/decoders/Cr3Decoder.cpp b/src/librawspeed/decoders/Cr3Decoder.cpp
new file mode 100644
index 000000000..d45bd52f6
--- /dev/null
+++ b/src/librawspeed/decoders/Cr3Decoder.cpp
@@ -0,0 +1,729 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ Information about CR3 file structure and BMFF boxes
+ provided by Laurent Clévy and contributors
+ via https://github.com/lclevy/canon_cr3
+
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "decoders/Cr3Decoder.h" // for Cr3Decoder
+#include "decompressors/CrxDecompressor.h" // for CrxDecompressor
+#include "parsers/IsoMParserException.h" // for ThrowIPE
+#include "parsers/TiffParser.h" // for TiffParser
+
+namespace rawspeed {
+
+const FourCharStr IsoMBoxCanonTypes::CNCV;
+const FourCharStr IsoMBoxCanonTypes::CCTP;
+const FourCharStr IsoMBoxCanonTypes::CTBO;
+const FourCharStr IsoMBoxCanonTypes::CMT1;
+const FourCharStr IsoMBoxCanonTypes::CMT2;
+const FourCharStr IsoMBoxCanonTypes::CMT3;
+const FourCharStr IsoMBoxCanonTypes::CMT4;
+const FourCharStr IsoMBoxCanonTypes::THMB;
+const FourCharStr IsoMBoxCanonTypes::CRAW;
+const FourCharStr IsoMBoxCanonTypes::CMP1;
+const FourCharStr IsoMBoxCanonTypes::CDI1;
+const FourCharStr IsoMBoxCanonTypes::IAD1;
+const FourCharStr IsoMBoxCanonTypes::CTMD;
+
+const AbstractIsoMBox::UuidType CanonBoxUuid = {
+ 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0,
+ 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48};
+
+void IsoMCanonBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMCanonCodecVersionBox::BoxType == box.boxType) {
+ if (cncvBox)
+ ThrowIPE("duplicate cncv box found.");
+ cncvBox = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMCanonCCTPBox::BoxType == box.boxType) {
+ if (cctpBox)
+ ThrowIPE("duplicate CCTP box found.");
+ cctpBox = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMCanonCTBOBox::BoxType == box.boxType) {
+ if (ctboBox)
+ ThrowIPE("duplicate CTBO box found.");
+ ctboBox = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMCanonCMT1Box::BoxType == box.boxType) {
+ if (cmt1Box)
+ ThrowIPE("duplicate CMT1 box found.");
+ cmt1Box = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMCanonCMT2Box::BoxType == box.boxType) {
+ if (cmt2Box)
+ ThrowIPE("duplicate CMT2 box found.");
+ cmt2Box = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMCanonCMT3Box::BoxType == box.boxType) {
+ if (cmt3Box)
+ ThrowIPE("duplicate CMT3 box found.");
+ cmt3Box = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMCanonCMT4Box::BoxType == box.boxType) {
+ if (cmt4Box)
+ ThrowIPE("duplicate CMT4 box found.");
+ cmt4Box = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+
+ if (IsoMCanonThumbnailBox::BoxType == box.boxType) {
+ if (thmbBox)
+ ThrowIPE("duplicate THMB box found.");
+ thmbBox = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+const std::unique_ptr& IsoMCanonBox::CNCV() const {
+ if (cncvBox)
+ return cncvBox;
+ else
+ ThrowIPE("CNCV box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::CCTP() const {
+ if (cctpBox)
+ return cctpBox;
+ else
+ ThrowIPE("CCTP box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::CTBO() const {
+ if (ctboBox)
+ return ctboBox;
+ else
+ ThrowIPE("CTBO box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::CMT1() const {
+ if (cmt1Box)
+ return cmt1Box;
+ else
+ ThrowIPE("CMT1 box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::CMT2() const {
+ if (cmt2Box)
+ return cmt2Box;
+ else
+ ThrowIPE("CMT2 box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::CMT3() const {
+ if (cmt3Box)
+ return cmt3Box;
+ else
+ ThrowIPE("CMT3 box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::CMT4() const {
+ if (cmt4Box)
+ return cmt4Box;
+ else
+ ThrowIPE("CMT4 box not available");
+}
+
+const std::unique_ptr& IsoMCanonBox::THMB() const {
+ if (thmbBox)
+ return thmbBox;
+ else
+ ThrowIPE("THMB box not available");
+}
+
+IsoMCanonBox::operator bool() const {
+ if (!cncvBox)
+ ThrowIPE("no CNCV box found.");
+ if (!cctpBox)
+ ThrowIPE("no CCTP box found.");
+ if (!ctboBox)
+ ThrowIPE("no CTBO box found.");
+ if (!cmt1Box)
+ ThrowIPE("no CMT1 box found.");
+ if (!cmt2Box)
+ ThrowIPE("no CMT2 box found.");
+ if (!cmt3Box)
+ ThrowIPE("no CMT3 box found.");
+ if (!cmt4Box)
+ ThrowIPE("no CMT4 box found.");
+
+ return true; // OK!
+}
+
+IsoMCanonCodecVersionBox::IsoMCanonCodecVersionBox(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ assert(data.getRemainSize() == 30); // Payload string is exactly 30 bytes long
+ auto payload = data.getBuffer(30);
+ compressorVersion = std::string(payload.begin(), payload.end());
+ assert(data.getRemainSize() == 0);
+}
+
+IsoMCanonCMT1Box::IsoMCanonCMT1Box(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ NORangesSet rs;
+ auto payload =
+ DataBuffer(data.getBuffer(data.getRemainSize()), Endianness::little);
+ mRootIFD0 = TiffParser::parse(nullptr, payload);
+}
+
+IsoMCanonCMT2Box::IsoMCanonCMT2Box(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ NORangesSet rs;
+ auto payload =
+ DataBuffer(data.getBuffer(data.getRemainSize()), Endianness::little);
+ mRootIFD0 = TiffParser::parse(nullptr, payload);
+}
+
+IsoMCanonCMT3Box::IsoMCanonCMT3Box(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ NORangesSet rs;
+ auto payload =
+ DataBuffer(data.getBuffer(data.getRemainSize()), Endianness::little);
+ mRootIFD0 = TiffParser::parse(nullptr, payload);
+}
+
+IsoMCanonCMT4Box::IsoMCanonCMT4Box(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ NORangesSet rs;
+ auto payload =
+ DataBuffer(data.getBuffer(data.getRemainSize()), Endianness::little);
+ mRootIFD0 = TiffParser::parse(nullptr, payload);
+}
+
+IsoMCanonTimedMetadataBox::IsoMCanonTimedMetadataBox(
+ const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ // Set position after box `size` and `boxtype` fields, so we
+ // can parse the custom SampleEntry ourself.
+ data.setPosition(8);
+
+ for (auto& c : reserved1)
+ c = data.getByte();
+ dataReferenceIndex = data.getU16();
+
+ const auto entryCount = data.getU32();
+
+ // Can't check/reserve entryCount.
+ std::generate_n(std::back_inserter(recDescs), entryCount,
+ [this]() { return RecordDesc(&data); });
+ assert(recDescs.size() == entryCount);
+
+ assert(data.getRemainSize() == 0);
+
+ // Validate.
+ operator bool();
+}
+
+IsoMCanonTimedMetadataBox::operator bool() const {
+ // This CTMD box is not used for decoding, since record type and size
+ // are available in MDAT data for CTMD, too.
+ return true; // OK!
+}
+
+IsoMCanonTimedMetadataBox::RecordDesc::RecordDesc(ByteStream* bs) {
+ recType = bs->getU32();
+ recSize = bs->getU32();
+}
+
+IsoMCanonCrawBox::IsoMCanonCrawBox(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ // Set position after box `size` and `boxtype` fields, so we
+ // can parse the custom SampleEntry ourself.
+ data.setPosition(8);
+
+ for (auto& c : reserved1)
+ c = data.getByte();
+ dataReferenceIndex = data.getU16();
+ for (auto& c : reserved2)
+ c = data.getByte();
+ width = data.getU16();
+ height = data.getU16();
+ xResolution = static_cast(data.getU16()) << 16 | data.getU16();
+ yResolution = static_cast(data.getU16()) << 16 | data.getU16();
+ reserved3 = data.getU32();
+ reserved4 = data.getU16();
+ for (auto& c : reserved5)
+ c = data.getByte();
+ bitDepth = data.getU16();
+ reserved6 = data.getU16();
+ flags = data.getU16();
+ formatInd = data.getU16();
+
+ // Change this if Canon adds more fields to CRAW box
+ assert(data.getPosition() == 90);
+
+ // After fields, there are embedded boxes
+ cmp1Box = std::make_unique(AbstractIsoMBox(&data));
+ cdi1Box = std::make_unique(AbstractIsoMBox(&data));
+ cdi1Box->IsoMContainer::parse();
+ // There is a 'free' box after CDI1 which we ignore
+
+ // Validate.
+ operator bool();
+}
+
+IsoMCanonCrawBox::operator bool() const {
+ // For JPEG trak, CRAW has no CMP1/CDI1 boxes. But as we
+ // decode RAW, not JPEG, CMP1 and CDI1 are required.
+ if (!cmp1Box)
+ ThrowIPE("no CMP1 box found.");
+ if (!cdi1Box)
+ ThrowIPE("no CDI1 box found.");
+
+ return true; // OK!
+}
+
+const std::unique_ptr& IsoMCanonCrawBox::CMP1() const {
+ if (cmp1Box)
+ return cmp1Box;
+ else
+ ThrowIPE("CMP1 box not available");
+}
+
+const std::unique_ptr& IsoMCanonCrawBox::CDI1() const {
+ if (cdi1Box)
+ return cdi1Box;
+ else
+ ThrowIPE("CDI1 box not available");
+}
+
+IsoMCanonCmp1Box::IsoMCanonCmp1Box(const AbstractIsoMBox& base)
+ : IsoMBox(base) {
+ // Set position after box `size` and `boxtype` fields, so we
+ // can parse the custom SampleEntry ourself.
+ data.setPosition(8);
+ // This fields mainly used in the decoding process.
+ reserved1 = data.getU16();
+ headerSize = data.getU16();
+ assert(headerSize == 0x30);
+ version = data.getI16();
+ versionSub = data.getI16();
+ f_width = data.getI32();
+ f_height = data.getI32();
+ tileWidth = data.getI32();
+ tileHeight = data.getI32();
+ nBits = data.get();
+ nPlanes = data.peek() >> 4;
+ cfaLayout = data.get() & 0xF;
+ encType = data.peek() >> 4;
+ imageLevels = data.get() & 0xF;
+ hasTileCols = data.peek() >> 7;
+ hasTileRows = data.get() & 1;
+ mdatHdrSize = data.getI32();
+ // Some reserved fields, unknown.
+ reserved2 = data.getI32();
+ for (auto& c : reserved3)
+ c = data.getByte();
+
+ // we assume this is fixed, until Canon makes CMP1 flexible
+ assert(data.getPosition() == 44 + 16);
+ // headerSize should match position
+ assert((data.getPosition() - 2 - 2 - 8) == headerSize);
+ assert(data.getRemainSize() == 0);
+
+ // Validate.
+ operator bool();
+}
+
+IsoMCanonCmp1Box::operator bool() const {
+ // validation based on libraw decoder requirements
+ if (version != 0x100 && version != 0x200) {
+ ThrowRDE("Unsupported version in CMP1");
+ }
+ if (!mdatHdrSize) {
+ ThrowRDE("CMP1 describes an empty MDAT header");
+ }
+ if (encType == 1) {
+ if (nBits > 15)
+ ThrowRDE("Unknown encoding bit count in CMP1");
+ } else {
+ if (encType && encType != 3)
+ ThrowRDE("Unknown encType in CMP1");
+ if (nBits > 14)
+ ThrowRDE("Unknown encoding bit count in CMP1");
+ }
+ if (nPlanes == 1) {
+ if (cfaLayout || encType || nBits != 8)
+ ThrowRDE("Unknown encoding parameters in CMP1");
+ } else if (nPlanes != 4 || f_width & 1 || f_height & 1 || tileWidth & 1 ||
+ tileHeight & 1 || cfaLayout > 3 || nBits == 8)
+ ThrowRDE("Unknown encoding parameters in CMP1");
+
+ if (tileWidth > f_width || tileHeight > f_height)
+ ThrowRDE("Unknown encoding parameters in CMP1");
+
+ if (imageLevels > 3 || hasTileCols > 1 || hasTileRows > 1)
+ ThrowRDE("Unknown encoding parameters in CMP1");
+
+ return true; // OK!
+}
+
+void IsoMCanonCdi1Box::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMCanonIad1Box::BoxType == box.boxType) {
+ if (iad1Box)
+ ThrowIPE("duplicate IAD1 box found.");
+ iad1Box = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+IsoMCanonCdi1Box::operator bool() const {
+ if (!iad1Box)
+ ThrowIPE("no IAD1 box found.");
+
+ return true; // OK!
+}
+
+const std::unique_ptr& IsoMCanonCdi1Box::IAD1() const {
+ if (iad1Box)
+ return iad1Box;
+ else
+ ThrowIPE("IAD1 box not available");
+}
+
+IsoMCanonIad1Box::IsoMCanonIad1Box(const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ sensorWidth = data.get();
+ sensorHeight = data.get();
+ reserved1 = data.get();
+ ind = data.get();
+ reserved2 = data.get();
+ reserved3 = data.get();
+
+ if (2 == ind) { // ind is 2 for big images
+ cropLeftOffset = data.get();
+ cropTopOffset = data.get();
+ cropRightOffset = data.get();
+ cropBottomOffset = data.get();
+
+ leftOpticalBlackLeftOffset = data.get();
+ leftOpticalBlackTopOffset = data.get();
+ leftOpticalBlackRightOffset = data.get();
+ leftOpticalBlackBottomOffset = data.get();
+
+ topOpticalBlackLeftOffset = data.get();
+ topOpticalBlackTopOffset = data.get();
+ topOpticalBlackRightOffset = data.get();
+ topOpticalBlackBottomOffset = data.get();
+
+ activeAreaLeftOffset = data.get();
+ activeAreaTopOffset = data.get();
+ activeAreaRightOffset = data.get();
+ activeAreaBottomOffset = data.get();
+ } else {
+ // We hit a small image box?!
+ ThrowRDE("IAD1 box contains small image information, but big image expected");
+ }
+
+ writeLog(DEBUG_PRIO::EXTRA,
+ "IAD1 sensor width: %d, height: %d, crop: %u, %u, %u, %u, black "
+ "area left: %u, top: %u",
+ sensorWidth, sensorHeight, cropLeftOffset, cropTopOffset,
+ cropRightOffset, cropBottomOffset, leftOpticalBlackRightOffset,
+ topOpticalBlackBottomOffset);
+
+ // Validate.
+ operator bool();
+}
+
+IsoMCanonIad1Box::operator bool() const {
+ if(!sensorWidth || !sensorHeight)
+ ThrowIPE("IAD1 sensor size unknown");
+ if(!cropRect().isThisInside(sensorRect()))
+ ThrowIPE("IAD1 crop rect is outside sensor rect");
+ return true; // OK!
+}
+
+iRectangle2D IsoMCanonIad1Box::sensorRect() const {
+ return iRectangle2D(0, 0, sensorWidth, sensorHeight);
+}
+
+iRectangle2D IsoMCanonIad1Box::cropRect() const {
+ return iRectangle2D(
+ cropLeftOffset,
+ cropTopOffset,
+ (cropRightOffset+1)-cropLeftOffset,
+ (cropBottomOffset+1)-cropTopOffset);
+}
+
+iRectangle2D IsoMCanonIad1Box::leftOpticalBlackRect() const {
+ return iRectangle2D(
+ leftOpticalBlackLeftOffset,
+ leftOpticalBlackTopOffset,
+ (leftOpticalBlackRightOffset+1)-leftOpticalBlackLeftOffset,
+ (leftOpticalBlackBottomOffset+1)-leftOpticalBlackTopOffset);
+}
+
+iRectangle2D IsoMCanonIad1Box::topOpticalBlackRect() const {
+ return iRectangle2D(
+ topOpticalBlackLeftOffset,
+ topOpticalBlackTopOffset,
+ (topOpticalBlackRightOffset+1)-topOpticalBlackLeftOffset,
+ (topOpticalBlackBottomOffset+1)-topOpticalBlackTopOffset);
+}
+
+iRectangle2D IsoMCanonIad1Box::activeArea() const {
+ return iRectangle2D(
+ activeAreaLeftOffset,
+ activeAreaTopOffset,
+ (activeAreaRightOffset+1)-activeAreaLeftOffset,
+ (activeAreaBottomOffset+1)-activeAreaTopOffset);
+}
+
+
+CanonTimedMetadata::CanonTimedMetadata::Record::Record(ByteStream* bs) {
+ assert(bs->getByteOrder() == Endianness::little);
+ auto origPos = bs->getPosition();
+ recSize = bs->getU32();
+ recType = bs->getU16();
+ reserved1 = bs->get();
+ reserved2 = bs->get();
+ reserved3 = bs->get();
+ reserved4 = bs->get();
+ payload = bs->getStream(recSize - (bs->getPosition() - origPos));
+}
+
+CanonTimedMetadata::CanonTimedMetadata(const ByteStream* bs) : data(*bs) {
+
+ // CTMD is little-endian, force stream to correct endianness
+ data.setByteOrder(Endianness::little);
+
+ while (data.getRemainSize() > 0) {
+ auto rec = Record(&data);
+ // No record type can exists multiple times
+ assert(records.find(rec.recType) == records.end());
+ records[rec.recType] = rec;
+ }
+ assert(data.getRemainSize() == 0);
+}
+
+bool Cr3Decoder::isAppropriateDecoder(const IsoMRootBox& box) {
+ return box.ftyp()->majorBrand == FourCharStr({'c', 'r', 'x', ' '});
+}
+
+RawImage Cr3Decoder::decodeRawInternal() {
+ /*
+ ByteStream biggestImage;
+
+ for (const auto& track : rootBox->moov()->tracks) {
+ for (const auto& chunk : track.mdia->minf->stbl->chunks) {
+ if (chunk->getSize() > biggestImage.getSize())
+ biggestImage = *chunk;
+ }
+ }
+ */
+
+ assert(crawBox);
+ ByteStream biggestImage(
+ *rootBox->moov()->tracks[2].mdia->minf->stbl->chunks[0]);
+
+ // Setup image dimensions
+ const auto& cmp1 = crawBox->CMP1();
+
+ mRaw->dim = iPoint2D(cmp1->f_width, cmp1->f_height);
+ mRaw->setCpp(1);
+ mRaw->createData();
+
+ assert(mRaw->getBpp() == 2);
+
+ CrxDecompressor u(mRaw);
+ u.decode(*cmp1, biggestImage);
+
+ return mRaw;
+}
+
+bool Cr3Decoder::isCodecSupported(const std::string& compressorVersion) const {
+ if (compressorVersion == "CanonHEIF001/10.00.00/00.00.00"
+ || compressorVersion == "CanonHEIF001/10.00.01/00.00.00") {
+ writeLog(DEBUG_PRIO::WARNING, "HEIF CNCV: '%s' is not supported",
+ compressorVersion.c_str());
+ }
+ if (compressorVersion == "CanonCR3_001/01.09.00/01.00.00") {
+ writeLog(DEBUG_PRIO::WARNING, "Raw-burst roll CNCV: '%s' is not supported",
+ compressorVersion.c_str());
+ }
+ if (compressorVersion == "CanonCRM0001/02.09.00/00.00.00") {
+ writeLog(DEBUG_PRIO::WARNING, "CRM movies CNCV: '%s' is not supported",
+ compressorVersion.c_str());
+ }
+
+ return compressorVersion ==
+ "CanonCR3_001/00.10.00/00.00.00" // EOS R5, R6 and 1DX Mark III
+ // (raw)
+ || compressorVersion ==
+ "CanonCR3_003/00.10.00/00.00.00" // R6 (craw with HDR preview),
+ // R5 (craw HDR, FW 1.2.0)
+ || compressorVersion ==
+ "CanonCR3_002/00.10.00/00.00.00" // CR3 of 1DX Mark III (craw)
+ || compressorVersion ==
+ "CanonCR3_001/01.09.00/00.00.00" // SX70 HS, G5 Mark II and G7 Mark III
+ || compressorVersion ==
+ "CanonCR3_001/00.09.00/00.00.00"; // EOS R, EOS RP, M50, 250D,
+ // 90D, M6 Mark II, M200, M50m2 and 250D
+}
+
+void Cr3Decoder::checkSupportInternal(const CameraMetaData* meta) {
+ // Get Canon UUID box and parse
+ canonBox =
+ std::make_unique(rootBox->moov()->getBox(CanonBoxUuid));
+ canonBox->parse();
+
+ // Check compressor version string
+ auto compressorVersion = canonBox->CNCV()->compressorVersion;
+ writeLog(DEBUG_PRIO::ERROR, "Compressor Version: %s",
+ compressorVersion.c_str());
+ if (!isCodecSupported(compressorVersion)) {
+ ThrowRDE("CR3 compressor version (CNCV: %s) is not supported",
+ compressorVersion.c_str());
+ }
+
+ // CMT1 contains a TIFF file with EXIF information
+ auto camId = canonBox->CMT1()->mRootIFD0->getID();
+ writeLog(DEBUG_PRIO::EXTRA, "CMT1 EXIF make: %s", camId.make.c_str());
+ writeLog(DEBUG_PRIO::EXTRA, "CMT1 EXIF model: %s", camId.model.c_str());
+
+ // Load CRAW box
+ auto& stsd = rootBox->moov()->tracks[2].mdia->minf->stbl->stsd;
+ crawBox = std::make_unique(stsd->dscs[0]);
+
+ checkCameraSupported(meta, camId.make, camId.model, mode);
+}
+
+void Cr3Decoder::decodeMetaDataInternal(const CameraMetaData* meta) {
+ const auto camId = canonBox->CMT1()->mRootIFD0->getID();
+
+ uint32_t iso = 0;
+ if (canonBox->CMT2()->mRootIFD0->hasEntryRecursive(TiffTag::ISOSPEEDRATINGS)) {
+ iso = canonBox->CMT2()
+ ->mRootIFD0->getEntryRecursive(TiffTag::ISOSPEEDRATINGS)
+ ->getU32();
+ }
+ if(65535 == iso) {
+ // ISOSPEEDRATINGS is a SHORT EXIF value. For larger values, we have to look
+ // at RECOMMENDED_EXPOSURE_INDEX (maybe Canon specific).
+ if (canonBox->CMT2()->mRootIFD0->hasEntryRecursive(TiffTag::RECOMMENDEDEXPOSUREINDEX))
+ iso = canonBox->CMT2()
+ ->mRootIFD0->getEntryRecursive(TiffTag::RECOMMENDEDEXPOSUREINDEX)
+ ->getU32();
+ }
+
+ // Big raw image is always in track 4
+ assert(rootBox->moov()->tracks.size() >= 4);
+ auto& track3Mdia = rootBox->moov()->tracks[3].mdia;
+
+ // CTMD
+ auto& CTMD_stsd = track3Mdia->minf->stbl->stsd;
+ assert(!CTMD_stsd->dscs.empty());
+
+ // Get Sample and rebuild a CTMD
+ IsoMCanonTimedMetadataBox ctmd =
+ IsoMCanonTimedMetadataBox(CTMD_stsd->dscs[0]);
+
+ // CTMD MDAT
+ assert(!track3Mdia->minf->stbl->chunks.empty());
+ auto ctmd_chunk = track3Mdia->minf->stbl->chunks[0];
+
+ Buffer ctmd_chunk_buf = ctmd_chunk->getSubView(0);
+
+ auto ctmd_recs = CanonTimedMetadata(ctmd_chunk);
+
+ // Record 8 contains EXIF data with CANONCOLORDATA tag
+ auto rec8 = ctmd_recs.records[8].payload.getSubView(8);
+
+ NORangesSet rs;
+
+ // Rec. 8 contains TIFF data, but with corrupt IFD1 index. We
+ // parse it manually.
+ TiffRootIFD IFD_ctmd_rec8(nullptr, &rs, DataBuffer(rec8, Endianness::little),
+ 8); // skip TIFF header
+
+ if (IFD_ctmd_rec8.hasEntryRecursive(TiffTag::CANONCOLORDATA)) {
+ TiffEntry* wb = IFD_ctmd_rec8.getEntryRecursive(TiffTag::CANONCOLORDATA);
+ // this entry is a big table, and different cameras store used WB in
+ // different parts, so find the offset, default is the most common one.
+ // The wb_offset values in cameras.xml are extracted from:
+ // https://github.com/exiftool/exiftool/blob/ceff3cbc4564e93518f3d2a2e00d8ae203ff54af/lib/Image/ExifTool/Canon.pm#L1910
+ int offset = hints.get("wb_offset", 126);
+
+ wb_coeffs[0] = static_cast(wb->getU16(offset + 0)) / 1024.0;
+ wb_coeffs[1] = static_cast(wb->getU16(offset + 1)) / 1024.0;
+ wb_coeffs[2] = 0; // GG
+ wb_coeffs[3] = static_cast(wb->getU16(offset + 3)) / 1024.0;
+
+ writeLog(DEBUG_PRIO::EXTRA, "wb_coeffs:, 0: %f, 1: %f, 2: %f, 3: %f\n",
+ wb_coeffs[0], wb_coeffs[1], wb_coeffs[2], wb_coeffs[3]);
+
+ } else {
+ writeLog(DEBUG_PRIO::EXTRA, "no wb_coeffs found");
+ }
+
+ // No CR3 camera has swapped_wb so far, but who knows...
+ if (hints.has("swapped_wb")) {
+ mRaw->metadata.wbCoeffs[0] = wb_coeffs[2];
+ mRaw->metadata.wbCoeffs[1] = wb_coeffs[0];
+ mRaw->metadata.wbCoeffs[2] = wb_coeffs[1];
+ } else {
+ mRaw->metadata.wbCoeffs[0] = wb_coeffs[0];
+ mRaw->metadata.wbCoeffs[1] = wb_coeffs[1];
+ mRaw->metadata.wbCoeffs[2] = wb_coeffs[3];
+ }
+
+ setMetaData(meta, camId.make, camId.model, mode, iso);
+ writeLog(DEBUG_PRIO::EXTRA, "blacklevel for ISO %d is %d", mRaw->metadata.isoSpeed, mRaw->blackLevel);
+
+ // IAD1 describes sensor constraints
+ const auto& iad1 = crawBox->CDI1()->IAD1();
+
+ if (mRaw->blackAreas.empty()) {
+ // IAD1 stores the rectangles for black areas.
+ auto leftOpticalBlack = iad1->leftOpticalBlackRect();
+ auto topOpticalBlack = iad1->topOpticalBlackRect();
+ if(leftOpticalBlack.dim.x >= 12+4) {
+ // if left optical black has >= 12+4 pixels, we reduce them by 12 as some
+ // models (EOS RP is known) has white pixels in this area.
+ // Yes, this is hacky, but IAD1 reports offset=0 which is either wrong or the white pixels
+ // are a camera bug and must be resolved in software.
+ leftOpticalBlack.pos.x += 12;
+ leftOpticalBlack.dim.x -= 12;
+ }
+ if(topOpticalBlack.dim.y >= 12+4) {
+ // Same must be done for horizontal pixels
+ topOpticalBlack.pos.y += 12;
+ topOpticalBlack.dim.y -= 12;
+ }
+ mRaw->blackAreas.push_back(BlackArea(leftOpticalBlack.pos.x, leftOpticalBlack.dim.x, true));
+ mRaw->blackAreas.push_back(BlackArea(topOpticalBlack.pos.y, topOpticalBlack.pos.y, false));
+ }
+
+ if (applyCrop) {
+ mRaw->subFrame(iad1->cropRect());
+ }
+}
+
+} // namespace rawspeed
diff --git a/src/librawspeed/decoders/Cr3Decoder.h b/src/librawspeed/decoders/Cr3Decoder.h
new file mode 100644
index 000000000..8924feb72
--- /dev/null
+++ b/src/librawspeed/decoders/Cr3Decoder.h
@@ -0,0 +1,381 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "common/RawImage.h" // for RawImage
+#include "decoders/RawDecoder.h" // for RawDecoder
+#include "tiff/IsoMBox.h" // for IsoMRootBox
+#include // for unique_ptr
+#include // for move
+
+#include "tiff/TiffIFD.h" // for TiffRootIFDOwner
+
+namespace rawspeed {
+
+class CameraMetaData;
+
+class Buffer;
+
+
+struct IsoMBoxCanonTypes final {
+ static constexpr FourCharStr CNCV = FourCharStr({'C', 'N', 'C', 'V'});
+ static constexpr FourCharStr CCTP = FourCharStr({'C', 'C', 'T', 'P'});
+ static constexpr FourCharStr CTBO = FourCharStr({'C', 'T', 'B', 'O'});
+ static constexpr FourCharStr CMT1 = FourCharStr({'C', 'M', 'T', '1'});
+ static constexpr FourCharStr CMT2 = FourCharStr({'C', 'M', 'T', '2'});
+ static constexpr FourCharStr CMT3 = FourCharStr({'C', 'M', 'T', '3'});
+ static constexpr FourCharStr CMT4 = FourCharStr({'C', 'M', 'T', '4'});
+ static constexpr FourCharStr THMB = FourCharStr({'T', 'H', 'M', 'B'});
+
+ static constexpr FourCharStr CRAW = FourCharStr({'C', 'R', 'A', 'W'});
+ static constexpr FourCharStr CMP1 = FourCharStr({'C', 'M', 'P', '1'});
+ static constexpr FourCharStr CDI1 = FourCharStr({'C', 'D', 'I', '1'});
+ static constexpr FourCharStr IAD1 = FourCharStr({'I', 'A', 'D', '1'});
+
+ static constexpr FourCharStr CTMD = FourCharStr({'C', 'T', 'M', 'D'});
+};
+
+
+
+
+class IsoMCanonCodecVersionBox final : public IsoMBox {
+public:
+ std::string compressorVersion;
+ explicit IsoMCanonCodecVersionBox(const AbstractIsoMBox& base);
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+
+class IsoMCanonCTBOBox final : public IsoMBox {
+public:
+ explicit IsoMCanonCTBOBox(const AbstractIsoMBox& base) : IsoMBox(base) {}
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+
+class IsoMCanonCCTPBox final : public IsoMBox {
+public:
+ explicit IsoMCanonCCTPBox(const AbstractIsoMBox& base) : IsoMBox(base){}
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+
+
+class IsoMCanonCMT1Box final : public IsoMBox {
+public:
+ TiffRootIFDOwner mRootIFD0;
+
+ explicit IsoMCanonCMT1Box(const AbstractIsoMBox& base);
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+class IsoMCanonCMT2Box final : public IsoMBox {
+public:
+ TiffRootIFDOwner mRootIFD0;
+
+ explicit IsoMCanonCMT2Box(const AbstractIsoMBox& base);
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+
+class IsoMCanonCMT3Box final : public IsoMBox {
+public:
+ TiffRootIFDOwner mRootIFD0;
+
+ explicit IsoMCanonCMT3Box(const AbstractIsoMBox& base);
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+class IsoMCanonCMT4Box final : public IsoMBox {
+public:
+ TiffRootIFDOwner mRootIFD0;
+
+ explicit IsoMCanonCMT4Box(const AbstractIsoMBox& base);
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+class IsoMCanonThumbnailBox final : public IsoMBox {
+public:
+ explicit IsoMCanonThumbnailBox(const AbstractIsoMBox& base) : IsoMBox(base) {}
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+
+
+
+
+class IsoMCanonBox final : public IsoMContainerBox {
+ void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const override;
+
+ std::unique_ptr cncvBox;
+ std::unique_ptr cctpBox;
+ std::unique_ptr ctboBox;
+ std::unique_ptr cmt1Box;
+ std::unique_ptr cmt2Box;
+ std::unique_ptr cmt3Box;
+ std::unique_ptr cmt4Box;
+ std::unique_ptr thmbBox;
+
+public:
+ explicit IsoMCanonBox(const AbstractIsoMBox& base)
+ : IsoMContainerBox(base) {}
+
+ const std::unique_ptr& CNCV() const;
+ const std::unique_ptr& CCTP() const;
+ const std::unique_ptr& CTBO() const;
+ const std::unique_ptr& CMT1() const;
+ const std::unique_ptr& CMT2() const;
+ const std::unique_ptr& CMT3() const;
+ const std::unique_ptr& CMT4() const;
+ const std::unique_ptr& THMB() const;
+};
+
+
+
+
+
+
+
+class IsoMCanonCmp1Box final : public IsoMBox {
+ //void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const;
+
+public:
+ uint16_t reserved1; // unknown, -1?
+ uint16_t headerSize;
+ int16_t version;
+ int16_t versionSub; // Always 00 00
+ int32_t f_width;
+ int32_t f_height;
+ int32_t tileWidth;
+ int32_t tileHeight;
+ int32_t nBits;
+ int32_t nPlanes;
+ int32_t cfaLayout;
+ int32_t encType;
+ int32_t imageLevels;
+ int32_t hasTileCols;
+ int32_t hasTileRows;
+ int32_t mdatHdrSize;
+ int32_t reserved2; // unknown
+ std::array reserved3; // unknown
+
+ explicit IsoMCanonCmp1Box(const AbstractIsoMBox& base);
+};
+
+
+
+
+class IsoMCanonIad1Box final : public IsoMFullBox {
+ //void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const;
+
+public:
+ // IAD1 data is not required to decode the image.
+ // We skip parsing IAD1.
+
+ uint16_t sensorWidth;
+ uint16_t sensorHeight;
+ uint16_t reserved1;
+ uint16_t ind; // 0 = small, 1 = big
+ uint16_t reserved2;
+ uint16_t reserved3;
+
+ // Big image fields (we ignore small image, not needed for decoding)
+
+ // Crop rectangle
+ uint16_t cropLeftOffset;
+ uint16_t cropTopOffset;
+ uint16_t cropRightOffset;
+ uint16_t cropBottomOffset;
+
+ // Left optical black rectangle
+ uint16_t leftOpticalBlackLeftOffset;
+ uint16_t leftOpticalBlackTopOffset;
+ uint16_t leftOpticalBlackRightOffset;
+ uint16_t leftOpticalBlackBottomOffset;
+
+ // Top optical black rectangle
+ uint16_t topOpticalBlackLeftOffset;
+ uint16_t topOpticalBlackTopOffset;
+ uint16_t topOpticalBlackRightOffset;
+ uint16_t topOpticalBlackBottomOffset;
+
+ // Active area rectangle
+ uint16_t activeAreaLeftOffset;
+ uint16_t activeAreaTopOffset;
+ uint16_t activeAreaRightOffset;
+ uint16_t activeAreaBottomOffset;
+
+ explicit IsoMCanonIad1Box(const AbstractIsoMBox& base);
+
+ iRectangle2D sensorRect() const;
+ iRectangle2D cropRect() const;
+ iRectangle2D leftOpticalBlackRect() const;
+ iRectangle2D topOpticalBlackRect() const;
+ iRectangle2D activeArea() const;
+};
+
+
+
+class IsoMCanonCdi1Box final : public IsoMContainerFullBox {
+ void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const override;
+public:
+ std::unique_ptr iad1Box;
+ explicit IsoMCanonCdi1Box(const AbstractIsoMBox& base)
+ : IsoMContainerFullBox(base) {
+ }
+
+ const std::unique_ptr& IAD1() const;
+};
+
+
+// This is derived from SampleEntry
+class IsoMCanonCrawBox final : public IsoMBox {
+ //void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const;
+
+ std::unique_ptr cmp1Box;
+ std::unique_ptr cdi1Box;
+
+public:
+ std::array reserved1;
+ uint16_t dataReferenceIndex;
+ std::array reserved2; // unknown, all zero
+ uint16_t width;
+ uint16_t height;
+ uint32_t xResolution; // stored as 0072 0000 fixed point
+ uint32_t yResolution; // stored as 0072 0000 fixed point
+ uint32_t reserved3; // unknown
+ uint16_t reserved4; // unknown
+ std::array reserved5; // unknown
+ uint16_t bitDepth;
+ uint16_t reserved6; // unknown
+ uint16_t flags; // unknown, 3 for Jpeg, 1 for craw/raw
+ uint16_t formatInd; // 0 for jpeg, 1 for craw/raw
+
+ explicit IsoMCanonCrawBox(const AbstractIsoMBox& base);
+
+ const std::unique_ptr& CMP1() const;
+ const std::unique_ptr& CDI1() const;
+};
+
+
+
+
+
+// Derived from SampleEntry
+class IsoMCanonTimedMetadataBox final : public IsoMBox {
+ struct RecordDesc final {
+ uint32_t recType;
+ uint32_t recSize;
+
+ RecordDesc() = default;
+
+ explicit RecordDesc(ByteStream* bs);
+ };
+
+ //void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const;
+
+public:
+ std::array reserved1;
+ uint16_t dataReferenceIndex;
+ std::vector recDescs;
+
+ explicit IsoMCanonTimedMetadataBox(const AbstractIsoMBox& base);
+
+};
+
+
+
+
+
+
+
+// TODO: List of records?
+class CanonTimedMetadata final {
+ struct Record final {
+ uint32_t recSize;
+ uint16_t recType;
+ uint8_t reserved1;
+ uint8_t reserved2;
+ uint16_t reserved3;
+ uint16_t reserved4;
+ ByteStream payload;
+
+ Record() = default;
+
+ explicit Record(ByteStream* bs);
+ };
+
+ ByteStream data;
+
+ //void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const;
+
+public:
+ std::map records; // type + record
+
+ explicit CanonTimedMetadata(const ByteStream* bs);
+
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+class Cr3Decoder final : public RawDecoder {
+ std::unique_ptr rootBox;
+ std::unique_ptr canonBox;
+ std::unique_ptr crawBox;
+
+ std::array wb_coeffs = {{NAN, NAN, NAN, NAN}};
+
+ std::string mode;
+
+public:
+ static bool isAppropriateDecoder(const IsoMRootBox& box);
+
+ Cr3Decoder(std::unique_ptr rootBox_, const Buffer& file)
+ : RawDecoder(file), rootBox(std::move(rootBox_)) {}
+
+ RawImage decodeRawInternal() override;
+ void checkSupportInternal(const CameraMetaData* meta) override;
+ void decodeMetaDataInternal(const CameraMetaData* meta) override;
+
+protected:
+ int getDecoderVersion() const override { return 0; }
+ bool isCodecSupported(const std::string& compressorVersion) const;
+};
+
+} // namespace rawspeed
diff --git a/src/librawspeed/decompressors/CMakeLists.txt b/src/librawspeed/decompressors/CMakeLists.txt
index e723c6761..fb2d327e9 100644
--- a/src/librawspeed/decompressors/CMakeLists.txt
+++ b/src/librawspeed/decompressors/CMakeLists.txt
@@ -11,6 +11,8 @@ FILE(GLOB SOURCES
"Cr2Decompressor.h"
"CrwDecompressor.cpp"
"CrwDecompressor.h"
+ "CrxDecompressor.cpp"
+ "CrxDecompressor.h"
"DeflateDecompressor.cpp"
"DeflateDecompressor.h"
"FujiDecompressor.cpp"
diff --git a/src/librawspeed/decompressors/CrxDecompressor.cpp b/src/librawspeed/decompressors/CrxDecompressor.cpp
new file mode 100644
index 000000000..400535f2b
--- /dev/null
+++ b/src/librawspeed/decompressors/CrxDecompressor.cpp
@@ -0,0 +1,2507 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018-2019 Alexey Danilchenko
+ Copyright (C) 2019 Alex Tutubalin, LibRaw LLC
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This code is ported from libraw:
+ https://github.com/LibRaw/LibRaw/blob/637b935bef384f6a96056c950696468d34335495/src/decoders/crx.cpp
+ For code compatibility, libraw routines like sgetn() are keept.
+ File reading was entirely replaced by rawspeed Buffer class.
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "decompressors/CrxDecompressor.h" // For CrxDecompressor
+#include "common/Array2DRef.h" // for Array2DRef
+#include "common/Point.h" // for iPoint2D, iPoint2D::area_type
+#include "common/RawImage.h" // for RawImage, RawImageData
+#include "decoders/Cr3Decoder.h" // for Cr3Decoder
+#include "decoders/RawDecoderException.h" // for ThrowRDE
+#include "decompressors/UncompressedDecompressor.h" // For UncompressedDecompresser
+#include "io/Endianness.h" // For Endianness
+#include // for copy_n, min
+#include // for array
+#include // for assert
+#include // for initializer_list
+
+// this should be divisible by 4
+#define CRX_BUF_SIZE (uint64_t)0x10000
+
+#define crx_constrain(x, l, u) ((x) < (l) ? (l) : ((x) > (u) ? (u) : (x)))
+
+#if !defined(_WIN32) || \
+ (defined(__GNUC__) && !defined(__INTRINSIC_SPECIAL__BitScanReverse))
+/* __INTRINSIC_SPECIAL__BitScanReverse found in MinGW32-W64 v7.30 headers, may
+ * be there is a better solution? */
+typedef uint32_t DWORD;
+inline void crx_BitScanReverse(DWORD* Index, unsigned long Mask) {
+ *Index = sizeof(unsigned long) * 8 - 1 - __builtin_clzl(Mask);
+}
+#else
+typedef uint32_t DWORD;
+inline void crx_BitScanReverse(DWORD* Index, unsigned long Mask) {
+ _BitScanReverse(Index, Mask);
+}
+#endif
+
+namespace rawspeed {
+
+static inline unsigned sgetn(int n, const uint8_t* s) {
+ unsigned result = 0;
+ while (n-- > 0)
+ result = (result << 8) | (*s++);
+ return result;
+}
+
+struct CrxBitstream {
+ std::vector mdatBuf;
+ uint64_t mdatSize;
+ uint64_t curBufOffset;
+ uint32_t curPos;
+ uint32_t curBufSize;
+ uint32_t bitData;
+ int32_t bitsLeft;
+ rawspeed::Buffer crxRawData;
+};
+
+struct CrxBandParam {
+ CrxBitstream bitStream;
+ int16_t subbandWidth;
+ int16_t subbandHeight;
+ int32_t roundedBitsMask;
+ int32_t roundedBits;
+ int16_t curLine;
+ int32_t* lineBuf0;
+ int32_t* lineBuf1;
+ int32_t* lineBuf2;
+ int32_t sParam;
+ int32_t kParam;
+ int32_t* paramData;
+ int32_t* nonProgrData;
+ bool supportsPartial;
+};
+
+struct CrxWaveletTransform {
+ int32_t* subband0Buf;
+ int32_t* subband1Buf;
+ int32_t* subband2Buf;
+ int32_t* subband3Buf;
+ int32_t* lineBuf[8];
+ int16_t curLine;
+ int16_t curH;
+ int8_t fltTapH;
+ int16_t height;
+ int16_t width;
+};
+
+struct CrxSubband {
+ CrxBandParam* bandParam;
+ uint64_t mdatOffset;
+ uint8_t* bandBuf;
+ uint16_t width;
+ uint16_t height;
+ int32_t qParam;
+ int32_t kParam;
+ uint32_t qStepBase;
+ uint32_t qStepMult;
+ bool supportsPartial;
+ int32_t bandSize;
+ uint64_t dataSize;
+ int64_t dataOffset;
+ int16_t rowStartAddOn;
+ int16_t rowEndAddOn;
+ int16_t colStartAddOn;
+ int16_t colEndAddOn;
+ int16_t levelShift;
+};
+
+struct CrxPlaneComp {
+ uint8_t* compBuf;
+ CrxSubband* subBands;
+ CrxWaveletTransform* wvltTransform;
+ int8_t compNumber;
+ int64_t dataOffset;
+ int32_t compSize;
+ bool supportsPartial;
+ int32_t roundedBitsMask;
+ int8_t tileFlag;
+};
+
+struct CrxQStep {
+ uint32_t* qStepTbl;
+ int width;
+ int height;
+};
+
+struct CrxTile {
+ CrxPlaneComp* comps;
+ int8_t tileFlag;
+ int8_t tileNumber;
+ int64_t dataOffset;
+ int32_t tileSize;
+ uint16_t width;
+ uint16_t height;
+ bool hasQPData;
+ CrxQStep* qStep;
+ uint32_t mdatQPDataSize;
+ uint16_t mdatExtraSize;
+};
+
+struct CrxImage {
+ uint8_t nPlanes;
+ uint16_t planeWidth;
+ uint16_t planeHeight;
+ uint8_t samplePrecision;
+ uint8_t subbandCount;
+ uint8_t levels;
+ uint8_t nBits;
+ uint8_t encType;
+ uint16_t tileCols;
+ uint16_t tileRows;
+ CrxTile* tiles;
+ uint64_t mdatOffset;
+ int32_t mdatHdrSize;
+ int16_t* outBufs[4]; // one per plane
+ int16_t* planeBuf;
+ Buffer crxRawData;
+};
+
+enum TileFlags {
+ E_HAS_TILES_ON_THE_RIGHT = 1,
+ E_HAS_TILES_ON_THE_LEFT = 2,
+ E_HAS_TILES_ON_THE_BOTTOM = 4,
+ E_HAS_TILES_ON_THE_TOP = 8
+};
+
+static int32_t exCoefNumTbl[144] = {
+ 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 1, 0, 0,
+ 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 1, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 1,
+ 1, 1, 1, 2, 2, 1, 1, 0, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+
+static int32_t q_step_tbl[8] = {0x28, 0x2D, 0x33, 0x39, 0x40, 0x48};
+
+static uint32_t JS[32] = {1, 1, 1, 1, 2, 2, 2, 2,
+ 4, 4, 4, 4, 8, 8, 8, 8,
+ 0x10, 0x10, 0x20, 0x20, 0x40, 0x40, 0x80, 0x80,
+ 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000};
+
+static uint32_t J[32] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
+ 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 9, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
+
+static inline void crxFillBuffer(CrxBitstream* bitStrm) {
+ if (bitStrm->curPos >= bitStrm->curBufSize && bitStrm->mdatSize) {
+ bitStrm->curPos = 0;
+ bitStrm->curBufOffset += bitStrm->curBufSize;
+
+ auto sub = bitStrm->crxRawData.getSubView(bitStrm->curBufOffset);
+
+ bitStrm->mdatBuf.resize(CRX_BUF_SIZE);
+ auto bytesToRead = std::min(bitStrm->mdatSize, CRX_BUF_SIZE);
+
+ if (sub.getSize() >= bytesToRead) {
+ auto data = sub.getData(0, bytesToRead);
+ assert(!bitStrm->mdatBuf.empty());
+ ::memcpy(bitStrm->mdatBuf.data(), data, bytesToRead);
+ bitStrm->curBufSize = bytesToRead;
+ }
+
+ if (bitStrm->curBufSize < 1) // nothing read
+ ThrowRDE("Error reading more bytes");
+ bitStrm->mdatSize -= bitStrm->curBufSize;
+ }
+}
+
+inline int crxBitstreamGetZeros(CrxBitstream* bitStrm) {
+ uint32_t nonZeroBit = 0;
+ uint64_t nextData = 0;
+ int32_t result = 0;
+
+ if (bitStrm->bitData) {
+ crx_BitScanReverse(static_cast(&nonZeroBit), static_cast(bitStrm->bitData));
+ result = 31 - nonZeroBit;
+ bitStrm->bitData <<= 32 - nonZeroBit;
+ bitStrm->bitsLeft -= 32 - nonZeroBit;
+ } else {
+ uint32_t bitsLeft = bitStrm->bitsLeft;
+ while (true) {
+ while (bitStrm->curPos + 4 <= bitStrm->curBufSize) {
+ nextData = getByteSwapped(
+ *reinterpret_cast(bitStrm->mdatBuf.data() + bitStrm->curPos));
+ bitStrm->curPos += 4;
+ crxFillBuffer(bitStrm);
+ if (nextData) {
+ crx_BitScanReverse(static_cast(&nonZeroBit), static_cast(nextData));
+ result = bitsLeft + 31 - nonZeroBit;
+ bitStrm->bitData = nextData << (32 - nonZeroBit);
+ bitStrm->bitsLeft = nonZeroBit;
+ return result;
+ }
+ bitsLeft += 32;
+ }
+ if (bitStrm->curBufSize < bitStrm->curPos + 1)
+ break; // error
+ nextData = bitStrm->mdatBuf[bitStrm->curPos++];
+ crxFillBuffer(bitStrm);
+ if (nextData)
+ break;
+ bitsLeft += 8;
+ }
+ crx_BitScanReverse(static_cast(&nonZeroBit), static_cast(nextData));
+ result = static_cast(bitsLeft + 7 - nonZeroBit);
+ bitStrm->bitData = nextData << (32 - nonZeroBit);
+ bitStrm->bitsLeft = nonZeroBit;
+ }
+ return result;
+}
+
+inline uint32_t crxBitstreamGetBits(CrxBitstream* bitStrm, int bits) {
+ int bitsLeft = bitStrm->bitsLeft;
+ uint32_t bitData = bitStrm->bitData;
+ uint32_t nextWord;
+ uint8_t nextByte;
+ uint32_t result;
+
+ if (bitsLeft < bits) {
+ // get them from stream
+ if (bitStrm->curPos + 4 <= bitStrm->curBufSize) {
+ nextWord = getByteSwapped(
+ *reinterpret_cast(bitStrm->mdatBuf.data() + bitStrm->curPos));
+ bitStrm->curPos += 4;
+ crxFillBuffer(bitStrm);
+ bitStrm->bitsLeft = 32 - (bits - bitsLeft);
+ result = ((nextWord >> bitsLeft) | bitData) >> (32 - bits);
+ bitStrm->bitData = nextWord << (bits - bitsLeft);
+ return result;
+ }
+ // less than a word left - read byte at a time
+ do {
+ if (bitStrm->curPos >= bitStrm->curBufSize)
+ break; // error
+ bitsLeft += 8;
+ nextByte = bitStrm->mdatBuf[bitStrm->curPos++];
+ crxFillBuffer(bitStrm);
+ bitData |= nextByte << (32 - bitsLeft);
+ } while (bitsLeft < bits);
+ }
+ result = bitData >> (32 - bits); // 32-bits
+ bitStrm->bitData = bitData << bits;
+ bitStrm->bitsLeft = bitsLeft - bits;
+ return result;
+}
+
+inline int32_t crxPrediction(int32_t left, int32_t top, int32_t deltaH,
+ int32_t deltaV) {
+ int32_t symb[4] = {left + deltaH, left + deltaH, left, top};
+
+ return symb[(((deltaV < 0) ^ (deltaH < 0)) << 1) +
+ ((left < top) ^ (deltaH < 0))];
+}
+
+inline int32_t crxPredictKParameter(int32_t prevK, int32_t bitCode,
+ int32_t maxVal = 0) {
+ int32_t newKParam = prevK - (bitCode < (1 << prevK >> 1)) +
+ ((bitCode >> prevK) > 2) + ((bitCode >> prevK) > 5);
+
+ return !maxVal || newKParam < maxVal ? newKParam : maxVal;
+}
+
+inline void crxDecodeSymbolL1(CrxBandParam* param, int32_t doMedianPrediction,
+ int32_t notEOL = 0) {
+ if (doMedianPrediction) {
+ int32_t symb[4];
+
+ int32_t delta = param->lineBuf0[1] - param->lineBuf0[0];
+ symb[2] = param->lineBuf1[0];
+ symb[0] = symb[1] = delta + symb[2];
+ symb[3] = param->lineBuf0[1];
+
+ param->lineBuf1[1] =
+ symb[(((param->lineBuf0[0] < param->lineBuf1[0]) ^ (delta < 0)) << 1) +
+ ((param->lineBuf1[0] < param->lineBuf0[1]) ^ (delta < 0))];
+ } else
+ param->lineBuf1[1] = param->lineBuf0[1];
+
+ // get next error symbol
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+
+ // add converted (+/-) error code to predicted value
+ param->lineBuf1[1] += -(bitCode & 1) ^ (bitCode >> 1);
+
+ // for not end of the line - use one symbol ahead to estimate next K
+ if (notEOL) {
+ int32_t nextDelta = (param->lineBuf0[2] - param->lineBuf0[1]) << 1;
+ bitCode = (bitCode + std::abs(nextDelta)) >> 1;
+ ++param->lineBuf0;
+ }
+
+ // update K parameter
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+
+ ++param->lineBuf1;
+}
+
+static int crxDecodeLine(CrxBandParam* param) {
+ int length = param->subbandWidth;
+
+ param->lineBuf1[0] = param->lineBuf0[1];
+ for (; length > 1; --length) {
+ if (param->lineBuf1[0] != param->lineBuf0[1] ||
+ param->lineBuf1[0] != param->lineBuf0[2]) {
+ crxDecodeSymbolL1(param, 1, 1);
+ } else {
+ int nSyms = 0;
+ if (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms = 1;
+ while (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms += JS[param->sParam];
+ if (nSyms > length) {
+ nSyms = length;
+ break;
+ }
+ if (param->sParam < 31)
+ ++param->sParam;
+ if (nSyms == length)
+ break;
+ }
+
+ if (nSyms < length) {
+ if (J[param->sParam])
+ nSyms += crxBitstreamGetBits(¶m->bitStream, J[param->sParam]);
+ if (param->sParam > 0)
+ --param->sParam;
+ if (nSyms > length)
+ return -1;
+ }
+
+ length -= nSyms;
+
+ // copy symbol nSyms times
+ param->lineBuf0 += nSyms;
+
+ // copy symbol nSyms times
+ while (nSyms-- > 0) {
+ param->lineBuf1[1] = param->lineBuf1[0];
+ ++param->lineBuf1;
+ }
+ }
+
+ if (length > 0)
+ crxDecodeSymbolL1(param, 0, (length > 1));
+ }
+ }
+
+ if (length == 1)
+ crxDecodeSymbolL1(param, 1, 0);
+
+ param->lineBuf1[1] = param->lineBuf1[0] + 1;
+
+ return 0;
+}
+
+inline void crxDecodeSymbolL1Rounded(CrxBandParam* param, int32_t doSym = 1,
+ int32_t doCode = 1) {
+ int32_t sym = param->lineBuf0[1];
+
+ if (doSym) {
+ // calculate the next symbol gradient
+ int32_t symb[4];
+ int32_t deltaH = param->lineBuf0[1] - param->lineBuf0[0];
+ symb[2] = param->lineBuf1[0];
+ symb[0] = symb[1] = deltaH + symb[2];
+ symb[3] = param->lineBuf0[1];
+ sym =
+ symb[(((param->lineBuf0[0] < param->lineBuf1[0]) ^ (deltaH < 0)) << 1) +
+ ((param->lineBuf1[0] < param->lineBuf0[1]) ^ (deltaH < 0))];
+ }
+
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ int32_t code = -(bitCode & 1) ^ (bitCode >> 1);
+ param->lineBuf1[1] = param->roundedBitsMask * 2 * code + (code >> 31) + sym;
+
+ if (doCode) {
+ if (param->lineBuf0[2] > param->lineBuf0[1]) {
+ code = (param->lineBuf0[2] - param->lineBuf0[1] + param->roundedBitsMask -
+ 1) >>
+ param->roundedBits;
+ } else {
+ code = -(
+ (param->lineBuf0[1] - param->lineBuf0[2] + param->roundedBitsMask) >>
+ param->roundedBits);
+ }
+
+ param->kParam = crxPredictKParameter(param->kParam,
+ (bitCode + 2 * std::abs(code)) >> 1, 15);
+ } else
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+
+ ++param->lineBuf1;
+}
+
+static int crxDecodeLineRounded(CrxBandParam* param) {
+ int32_t valueReached = 0;
+
+ param->lineBuf0[0] = param->lineBuf0[1];
+ param->lineBuf1[0] = param->lineBuf0[1];
+ int32_t length = param->subbandWidth;
+
+ for (; length > 1; --length) {
+ if (std::abs(param->lineBuf0[2] - param->lineBuf0[1]) >
+ param->roundedBitsMask) {
+ crxDecodeSymbolL1Rounded(param);
+ ++param->lineBuf0;
+ valueReached = 1;
+ } else if (valueReached || std::abs(param->lineBuf0[0] - param->lineBuf1[0]) >
+ param->roundedBitsMask) {
+ crxDecodeSymbolL1Rounded(param);
+ ++param->lineBuf0;
+ valueReached = 0;
+ } else {
+ int nSyms = 0;
+ if (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms = 1;
+ while (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms += JS[param->sParam];
+ if (nSyms > length) {
+ nSyms = length;
+ break;
+ }
+ if (param->sParam < 31)
+ ++param->sParam;
+ if (nSyms == length)
+ break;
+ }
+ if (nSyms < length) {
+ if (J[param->sParam])
+ nSyms += crxBitstreamGetBits(¶m->bitStream, J[param->sParam]);
+ if (param->sParam > 0)
+ --param->sParam;
+ }
+ if (nSyms > length)
+ return -1;
+ }
+ length -= nSyms;
+
+ // copy symbol nSyms times
+ param->lineBuf0 += nSyms;
+
+ // copy symbol nSyms times
+ while (nSyms-- > 0) {
+ param->lineBuf1[1] = param->lineBuf1[0];
+ ++param->lineBuf1;
+ }
+
+ if (length > 1) {
+ crxDecodeSymbolL1Rounded(param, 0);
+ ++param->lineBuf0;
+ valueReached = std::abs(param->lineBuf0[1] - param->lineBuf0[0]) >
+ param->roundedBitsMask;
+ } else if (length == 1)
+ crxDecodeSymbolL1Rounded(param, 0, 0);
+ }
+ }
+ if (length == 1)
+ crxDecodeSymbolL1Rounded(param, 1, 0);
+
+ param->lineBuf1[1] = param->lineBuf1[0] + 1;
+
+ return 0;
+}
+
+static int crxDecodeLineNoRefPrevLine(CrxBandParam* param) {
+ int32_t i = 0;
+
+ for (; i < param->subbandWidth - 1; i++) {
+ if (param->lineBuf0[i + 2] | param->lineBuf0[i + 1] | param->lineBuf1[i]) {
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[i + 1] = -(bitCode & 1) ^ (bitCode >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode);
+ if (param->lineBuf2[i + 1] - param->kParam <= 1) {
+ if (param->kParam >= 15)
+ param->kParam = 15;
+ } else
+ ++param->kParam;
+ } else {
+ int nSyms = 0;
+ if (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms = 1;
+ if (i != param->subbandWidth - 1) {
+ while (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms += JS[param->sParam];
+ if (i + nSyms > param->subbandWidth) {
+ nSyms = param->subbandWidth - i;
+ break;
+ }
+ if (param->sParam < 31)
+ ++param->sParam;
+ if (i + nSyms == param->subbandWidth)
+ break;
+ }
+ if (i + nSyms < param->subbandWidth) {
+ if (J[param->sParam])
+ nSyms += crxBitstreamGetBits(¶m->bitStream, J[param->sParam]);
+ if (param->sParam > 0)
+ --param->sParam;
+ }
+ if (i + nSyms > param->subbandWidth)
+ return -1;
+ }
+ } else if (i > param->subbandWidth)
+ return -1;
+
+ if (nSyms > 0) {
+ memset(param->lineBuf1 + i + 1, 0, nSyms * sizeof(int32_t));
+ memset(param->lineBuf2 + i, 0, nSyms * sizeof(int32_t));
+ i += nSyms;
+ }
+
+ if (i >= param->subbandWidth - 1) {
+ if (i == param->subbandWidth - 1) {
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[i + 1] = -((bitCode + 1) & 1) ^ ((bitCode + 1) >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ param->lineBuf2[i] = param->kParam;
+ }
+ continue;
+ } else {
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[i + 1] = -((bitCode + 1) & 1) ^ ((bitCode + 1) >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode);
+ if (param->lineBuf2[i + 1] - param->kParam <= 1) {
+ if (param->kParam >= 15)
+ param->kParam = 15;
+ } else
+ ++param->kParam;
+ }
+ }
+ param->lineBuf2[i] = param->kParam;
+ }
+ if (i == param->subbandWidth - 1) {
+ int32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[i + 1] = -(bitCode & 1) ^ (bitCode >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ param->lineBuf2[i] = param->kParam;
+ }
+
+ return 0;
+}
+
+static int crxDecodeTopLine(CrxBandParam* param) {
+ param->lineBuf1[0] = 0;
+
+ int32_t length = param->subbandWidth;
+
+ // read the line from bitstream
+ for (; length > 1; --length) {
+ if (param->lineBuf1[0])
+ param->lineBuf1[1] = param->lineBuf1[0];
+ else {
+ int nSyms = 0;
+ if (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms = 1;
+ while (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms += JS[param->sParam];
+ if (nSyms > length) {
+ nSyms = length;
+ break;
+ }
+ if (param->sParam < 31)
+ ++param->sParam;
+ if (nSyms == length)
+ break;
+ }
+ if (nSyms < length) {
+ if (J[param->sParam])
+ nSyms += crxBitstreamGetBits(¶m->bitStream, J[param->sParam]);
+ if (param->sParam > 0)
+ --param->sParam;
+ if (nSyms > length)
+ return -1;
+ }
+
+ length -= nSyms;
+
+ // copy symbol nSyms times
+ while (nSyms-- > 0) {
+ param->lineBuf1[1] = param->lineBuf1[0];
+ ++param->lineBuf1;
+ }
+
+ if (length <= 0)
+ break;
+ }
+
+ param->lineBuf1[1] = 0;
+ }
+
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[1] += -(bitCode & 1) ^ (bitCode >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ ++param->lineBuf1;
+ }
+
+ if (length == 1) {
+ param->lineBuf1[1] = param->lineBuf1[0];
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[1] += -(bitCode & 1) ^ (bitCode >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ ++param->lineBuf1;
+ }
+
+ param->lineBuf1[1] = param->lineBuf1[0] + 1;
+
+ return 0;
+}
+
+static int crxDecodeTopLineRounded(CrxBandParam* param) {
+ param->lineBuf1[0] = 0;
+
+ int32_t length = param->subbandWidth;
+
+ // read the line from bitstream
+ for (; length > 1; --length) {
+ if (std::abs(param->lineBuf1[0]) > param->roundedBitsMask)
+ param->lineBuf1[1] = param->lineBuf1[0];
+ else {
+ int nSyms = 0;
+ if (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms = 1;
+ while (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms += JS[param->sParam];
+ if (nSyms > length) {
+ nSyms = length;
+ break;
+ }
+ if (param->sParam < 31)
+ ++param->sParam;
+ if (nSyms == length)
+ break;
+ }
+ if (nSyms < length) {
+ if (J[param->sParam])
+ nSyms += crxBitstreamGetBits(¶m->bitStream, J[param->sParam]);
+ if (param->sParam > 0)
+ --param->sParam;
+ if (nSyms > length)
+ return -1;
+ }
+ }
+
+ length -= nSyms;
+
+ // copy symbol nSyms times
+ while (nSyms-- > 0) {
+ param->lineBuf1[1] = param->lineBuf1[0];
+ ++param->lineBuf1;
+ }
+
+ if (length <= 0)
+ break;
+
+ param->lineBuf1[1] = 0;
+ }
+
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+
+ int32_t sVal = -(bitCode & 1) ^ (bitCode >> 1);
+ param->lineBuf1[1] += param->roundedBitsMask * 2 * sVal + (sVal >> 31);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ ++param->lineBuf1;
+ }
+
+ if (length == 1) {
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ int32_t sVal = -(bitCode & 1) ^ (bitCode >> 1);
+ param->lineBuf1[1] += param->roundedBitsMask * 2 * sVal + (sVal >> 31);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ ++param->lineBuf1;
+ }
+
+ param->lineBuf1[1] = param->lineBuf1[0] + 1;
+
+ return 0;
+}
+
+static int crxDecodeTopLineNoRefPrevLine(CrxBandParam* param) {
+ param->lineBuf0[0] = 0;
+ param->lineBuf1[0] = 0;
+ int32_t length = param->subbandWidth;
+ for (; length > 1; --length) {
+ if (param->lineBuf1[0]) {
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[1] = -(bitCode & 1) ^ (bitCode >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ } else {
+ int nSyms = 0;
+ if (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms = 1;
+ while (crxBitstreamGetBits(¶m->bitStream, 1)) {
+ nSyms += JS[param->sParam];
+ if (nSyms > length) {
+ nSyms = length;
+ break;
+ }
+ if (param->sParam < 31)
+ ++param->sParam;
+ if (nSyms == length)
+ break;
+ }
+ if (nSyms < length) {
+ if (J[param->sParam])
+ nSyms += crxBitstreamGetBits(¶m->bitStream, J[param->sParam]);
+ if (param->sParam > 0)
+ --param->sParam;
+ if (nSyms > length)
+ return -1;
+ }
+ }
+
+ length -= nSyms;
+
+ // copy symbol nSyms times
+ while (nSyms-- > 0) {
+ param->lineBuf2[0] = 0;
+ param->lineBuf1[1] = 0;
+ ++param->lineBuf1;
+ ++param->lineBuf2;
+ }
+
+ if (length <= 0)
+ break;
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[1] = -((bitCode + 1) & 1) ^ ((bitCode + 1) >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ }
+ param->lineBuf2[0] = param->kParam;
+ ++param->lineBuf2;
+ ++param->lineBuf1;
+ }
+
+ if (length == 1) {
+ uint32_t bitCode = crxBitstreamGetZeros(¶m->bitStream);
+ if (bitCode >= 41)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, 21);
+ else if (param->kParam)
+ bitCode = crxBitstreamGetBits(¶m->bitStream, param->kParam) |
+ (bitCode << param->kParam);
+ param->lineBuf1[1] = -(bitCode & 1) ^ (bitCode >> 1);
+ param->kParam = crxPredictKParameter(param->kParam, bitCode, 15);
+ param->lineBuf2[0] = param->kParam;
+ ++param->lineBuf1;
+ }
+
+ param->lineBuf1[1] = 0;
+
+ return 0;
+}
+
+static int crxDecodeLine(CrxBandParam* param, uint8_t* bandBuf) {
+ if (!param || !bandBuf)
+ return -1;
+ if (param->curLine >= param->subbandHeight)
+ return -1;
+
+ if (param->curLine == 0) {
+ int32_t lineLength = param->subbandWidth + 2;
+
+ param->sParam = 0;
+ param->kParam = 0;
+ if (param->supportsPartial) {
+ if (param->roundedBitsMask <= 0) {
+ param->lineBuf0 = param->paramData;
+ param->lineBuf1 = param->lineBuf0 + lineLength;
+ int32_t* lineBuf = param->lineBuf1 + 1;
+ if (crxDecodeTopLine(param))
+ return -1;
+ memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
+ ++param->curLine;
+ } else {
+ param->roundedBits = 1;
+ if (param->roundedBitsMask & ~1) {
+ while (param->roundedBitsMask >> param->roundedBits)
+ ++param->roundedBits;
+ }
+ param->lineBuf0 = param->paramData;
+ param->lineBuf1 = param->lineBuf0 + lineLength;
+ int32_t* lineBuf = param->lineBuf1 + 1;
+ if (crxDecodeTopLineRounded(param))
+ return -1;
+ memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
+ ++param->curLine;
+ }
+ } else {
+ param->lineBuf2 = param->nonProgrData;
+ param->lineBuf0 = param->paramData;
+ param->lineBuf1 = param->lineBuf0 + lineLength;
+ int32_t* lineBuf = param->lineBuf1 + 1;
+ if (crxDecodeTopLineNoRefPrevLine(param))
+ return -1;
+ memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
+ ++param->curLine;
+ }
+ } else if (!param->supportsPartial) {
+ int32_t lineLength = param->subbandWidth + 2;
+ param->lineBuf2 = param->nonProgrData;
+ if (param->curLine & 1) {
+ param->lineBuf1 = param->paramData;
+ param->lineBuf0 = param->lineBuf1 + lineLength;
+ } else {
+ param->lineBuf0 = param->paramData;
+ param->lineBuf1 = param->lineBuf0 + lineLength;
+ }
+ int32_t* lineBuf = param->lineBuf1 + 1;
+ if (crxDecodeLineNoRefPrevLine(param))
+ return -1;
+ memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
+ ++param->curLine;
+ } else if (param->roundedBitsMask <= 0) {
+ int32_t lineLength = param->subbandWidth + 2;
+ if (param->curLine & 1) {
+ param->lineBuf1 = param->paramData;
+ param->lineBuf0 = param->lineBuf1 + lineLength;
+ } else {
+ param->lineBuf0 = param->paramData;
+ param->lineBuf1 = param->lineBuf0 + lineLength;
+ }
+ int32_t* lineBuf = param->lineBuf1 + 1;
+ if (crxDecodeLine(param))
+ return -1;
+ memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
+ ++param->curLine;
+ } else {
+ int32_t lineLength = param->subbandWidth + 2;
+ if (param->curLine & 1) {
+ param->lineBuf1 = param->paramData;
+ param->lineBuf0 = param->lineBuf1 + lineLength;
+ } else {
+ param->lineBuf0 = param->paramData;
+ param->lineBuf1 = param->lineBuf0 + lineLength;
+ }
+ int32_t* lineBuf = param->lineBuf1 + 1;
+ if (crxDecodeLineRounded(param))
+ return -1;
+ memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
+ ++param->curLine;
+ }
+ return 0;
+}
+
+static int crxUpdateQparam(CrxSubband* subband) {
+ uint32_t bitCode = crxBitstreamGetZeros(&subband->bandParam->bitStream);
+ if (bitCode >= 23)
+ bitCode = crxBitstreamGetBits(&subband->bandParam->bitStream, 8);
+ else if (subband->kParam)
+ bitCode =
+ crxBitstreamGetBits(&subband->bandParam->bitStream, subband->kParam) |
+ (bitCode << subband->kParam);
+
+ subband->qParam +=
+ -(bitCode & 1) ^ (bitCode >> 1); // converting encoded to signed integer
+ subband->kParam = crxPredictKParameter(subband->kParam, bitCode);
+ if (subband->kParam > 7)
+ return -1;
+ return 0;
+}
+
+inline int getSubbandRow(CrxSubband* band, int row) {
+ return row < band->rowStartAddOn ? 0
+ : (row < band->height - band->rowEndAddOn
+ ? row - band->rowEndAddOn
+ : band->height - band->rowEndAddOn -
+ band->rowStartAddOn - 1);
+}
+
+static int crxDecodeLineWithIQuantization(CrxSubband* band, CrxQStep* qStep) {
+ if (!band->dataSize) {
+ memset(band->bandBuf, 0, band->bandSize);
+ return 0;
+ }
+
+ if (band->supportsPartial && !qStep && crxUpdateQparam(band))
+ return -1;
+ if (crxDecodeLine(band->bandParam, band->bandBuf))
+ return -1;
+
+ if (band->width <= 0)
+ return 0;
+
+ // update band buffers
+ int32_t* bandBuf = reinterpret_cast(band->bandBuf);
+ if (qStep) {
+ // new version
+ uint32_t* qStepTblPtr =
+ &qStep->qStepTbl[qStep->width *
+ getSubbandRow(band, band->bandParam->curLine - 1)];
+
+ for (int i = 0; i < band->colStartAddOn; ++i) {
+ uint32_t quantVal =
+ band->qStepBase + ((qStepTblPtr[0] * band->qStepMult) >> 3);
+ bandBuf[i] *= crx_constrain(quantVal, 1, 0x168000);
+ }
+
+ for (int i = band->colStartAddOn; i < band->width - band->colEndAddOn;
+ ++i) {
+ uint32_t quantVal =
+ band->qStepBase +
+ ((qStepTblPtr[(i - band->colStartAddOn) >> band->levelShift] *
+ band->qStepMult) >>
+ 3);
+ bandBuf[i] *= crx_constrain(quantVal, 1, 0x168000);
+ }
+ int lastIdx = (band->width - band->colEndAddOn - band->colStartAddOn - 1) >>
+ band->levelShift;
+ for (int i = band->width - band->colEndAddOn; i < band->width; ++i) {
+ uint32_t quantVal =
+ band->qStepBase + ((qStepTblPtr[lastIdx] * band->qStepMult) >> 3);
+ bandBuf[i] *= crx_constrain(quantVal, 1, 0x168000);
+ }
+ } else {
+ // prev. version
+ int32_t qScale = q_step_tbl[band->qParam % 6] >> (6 - band->qParam / 6);
+ if (band->qParam / 6 >= 6)
+ qScale = q_step_tbl[band->qParam % 6] * (1 << (band->qParam / 6 + 26));
+
+ if (qScale != 1)
+ for (int32_t i = 0; i < band->width; ++i)
+ bandBuf[i] *= qScale;
+ }
+
+ return 0;
+}
+
+static void crxHorizontal53(int32_t* lineBufLA, int32_t* lineBufLB,
+ CrxWaveletTransform* wavelet, uint32_t tileFlag) {
+ int32_t* band0Buf = wavelet->subband0Buf;
+ int32_t* band1Buf = wavelet->subband1Buf;
+ int32_t* band2Buf = wavelet->subband2Buf;
+ int32_t* band3Buf = wavelet->subband3Buf;
+
+ if (wavelet->width <= 1) {
+ lineBufLA[0] = band0Buf[0];
+ lineBufLB[0] = band2Buf[0];
+ } else {
+ if (tileFlag & E_HAS_TILES_ON_THE_LEFT) {
+ lineBufLA[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufLB[0] = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ ++band1Buf;
+ ++band3Buf;
+ } else {
+ lineBufLA[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+ lineBufLB[0] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
+ }
+ ++band0Buf;
+ ++band2Buf;
+
+ for (int i = 0; i < wavelet->width - 3; i += 2) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufLA[1] = band1Buf[0] + ((delta + lineBufLA[0]) >> 1);
+ lineBufLA[2] = delta;
+
+ delta = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ lineBufLB[1] = band3Buf[0] + ((delta + lineBufLB[0]) >> 1);
+ lineBufLB[2] = delta;
+
+ ++band0Buf;
+ ++band1Buf;
+ ++band2Buf;
+ ++band3Buf;
+ lineBufLA += 2;
+ lineBufLB += 2;
+ }
+ if (tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
+ int32_t deltaA = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufLA[1] = band1Buf[0] + ((deltaA + lineBufLA[0]) >> 1);
+
+ int32_t deltaB = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ lineBufLB[1] = band3Buf[0] + ((deltaB + lineBufLB[0]) >> 1);
+
+ if (wavelet->width & 1) {
+ lineBufLA[2] = deltaA;
+ lineBufLB[2] = deltaB;
+ }
+ } else if (wavelet->width & 1) {
+ lineBufLA[1] =
+ band1Buf[0] +
+ ((lineBufLA[0] + band0Buf[0] - ((band1Buf[0] + 1) >> 1)) >> 1);
+ lineBufLA[2] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+
+ lineBufLB[1] =
+ band3Buf[0] +
+ ((lineBufLB[0] + band2Buf[0] - ((band3Buf[0] + 1) >> 1)) >> 1);
+ lineBufLB[2] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
+ } else {
+ lineBufLA[1] = lineBufLA[0] + band1Buf[0];
+ lineBufLB[1] = lineBufLB[0] + band3Buf[0];
+ }
+ }
+}
+
+static int32_t* crxIdwt53FilterGetLine(CrxPlaneComp* comp, int32_t level) {
+ int32_t* result =
+ comp->wvltTransform[level].lineBuf[(comp->wvltTransform[level].fltTapH -
+ comp->wvltTransform[level].curH + 5) %
+ 5 +
+ 3];
+ comp->wvltTransform[level].curH--;
+ return result;
+}
+
+static int crxIdwt53FilterDecode(CrxPlaneComp* comp, int32_t level, CrxQStep* qStep) {
+ if (comp->wvltTransform[level].curH)
+ return 0;
+
+ CrxSubband* sband = comp->subBands + 3 * level;
+ CrxQStep* qStepLevel = qStep ? qStep + level : nullptr;
+
+ if (comp->wvltTransform[level].height - 3 <=
+ comp->wvltTransform[level].curLine &&
+ !(comp->tileFlag & E_HAS_TILES_ON_THE_BOTTOM)) {
+ if (comp->wvltTransform[level].height & 1) {
+ if (level) {
+ if (crxIdwt53FilterDecode(comp, level - 1, qStep))
+ return -1;
+ } else if (crxDecodeLineWithIQuantization(sband, qStepLevel))
+ return -1;
+
+ if (crxDecodeLineWithIQuantization(sband + 1, qStepLevel))
+ return -1;
+ }
+ } else {
+ if (level) {
+ if (crxIdwt53FilterDecode(comp, level - 1, qStep))
+ return -1;
+ } else if (crxDecodeLineWithIQuantization(sband, qStepLevel)) // LL band
+ return -1;
+
+ if (crxDecodeLineWithIQuantization(sband + 1, qStepLevel) || // HL band
+ crxDecodeLineWithIQuantization(sband + 2, qStepLevel) || // LH band
+ crxDecodeLineWithIQuantization(sband + 3, qStepLevel)) // HH band
+ return -1;
+ }
+
+ return 0;
+}
+
+static int crxIdwt53FilterTransform(CrxPlaneComp* comp, uint32_t level) {
+ CrxWaveletTransform* wavelet = comp->wvltTransform + level;
+
+ if (wavelet->curH)
+ return 0;
+
+ if (wavelet->curLine >= wavelet->height - 3) {
+ if (!(comp->tileFlag & E_HAS_TILES_ON_THE_BOTTOM)) {
+ if (wavelet->height & 1) {
+ if (level) {
+ if (!wavelet[-1].curH)
+ if (crxIdwt53FilterTransform(comp, level - 1))
+ return -1;
+ wavelet->subband0Buf = crxIdwt53FilterGetLine(comp, level - 1);
+ }
+ int32_t* band0Buf = wavelet->subband0Buf;
+ int32_t* band1Buf = wavelet->subband1Buf;
+ int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
+ int32_t* lineBufH1 = wavelet->lineBuf[(wavelet->fltTapH + 1) % 5 + 3];
+ int32_t* lineBufH2 = wavelet->lineBuf[(wavelet->fltTapH + 2) % 5 + 3];
+
+ int32_t* lineBufL0 = wavelet->lineBuf[0];
+ int32_t* lineBufL1 = wavelet->lineBuf[1];
+ wavelet->lineBuf[1] = wavelet->lineBuf[2];
+ wavelet->lineBuf[2] = lineBufL1;
+
+ // process L bands
+ if (wavelet->width <= 1) {
+ lineBufL0[0] = band0Buf[0];
+ } else {
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT) {
+ lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ ++band1Buf;
+ } else {
+ lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+ }
+ ++band0Buf;
+ for (int i = 0; i < wavelet->width - 3; i += 2) {
+ int32_t delta =
+ band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufL0[1] = band1Buf[0] + ((lineBufL0[0] + delta) >> 1);
+ lineBufL0[2] = delta;
+ ++band0Buf;
+ ++band1Buf;
+ lineBufL0 += 2;
+ }
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
+ int32_t delta =
+ band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufL0[1] = band1Buf[0] + ((lineBufL0[0] + delta) >> 1);
+ if (wavelet->width & 1)
+ lineBufL0[2] = delta;
+ } else if (wavelet->width & 1) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+ lineBufL0[1] = band1Buf[0] + ((lineBufL0[0] + delta) >> 1);
+ lineBufL0[2] = delta;
+ } else
+ lineBufL0[1] = band1Buf[0] + lineBufL0[0];
+ }
+
+ // process H bands
+ lineBufL0 = wavelet->lineBuf[0];
+ lineBufL1 = wavelet->lineBuf[1];
+ for (int32_t i = 0; i < wavelet->width; i++) {
+ int32_t delta = lineBufL0[i] - ((lineBufL1[i] + 1) >> 1);
+ lineBufH1[i] = lineBufL1[i] + ((delta + lineBufH0[i]) >> 1);
+ lineBufH2[i] = delta;
+ }
+ wavelet->curH += 3;
+ wavelet->curLine += 3;
+ wavelet->fltTapH = (wavelet->fltTapH + 3) % 5;
+ } else {
+ int32_t* lineBufL2 = wavelet->lineBuf[2];
+ int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
+ int32_t* lineBufH1 = wavelet->lineBuf[(wavelet->fltTapH + 1) % 5 + 3];
+ wavelet->lineBuf[1] = lineBufL2;
+ wavelet->lineBuf[2] = wavelet->lineBuf[1];
+
+ for (int32_t i = 0; i < wavelet->width; i++)
+ lineBufH1[i] = lineBufH0[i] + lineBufL2[i];
+
+ wavelet->curH += 2;
+ wavelet->curLine += 2;
+ wavelet->fltTapH = (wavelet->fltTapH + 2) % 5;
+ }
+ }
+ } else {
+ if (level) {
+ if (!wavelet[-1].curH && crxIdwt53FilterTransform(comp, level - 1))
+ return -1;
+ wavelet->subband0Buf = crxIdwt53FilterGetLine(comp, level - 1);
+ }
+
+ int32_t* band0Buf = wavelet->subband0Buf;
+ int32_t* band1Buf = wavelet->subband1Buf;
+ int32_t* band2Buf = wavelet->subband2Buf;
+ int32_t* band3Buf = wavelet->subband3Buf;
+
+ int32_t* lineBufL0 = wavelet->lineBuf[0];
+ int32_t* lineBufL1 = wavelet->lineBuf[1];
+ int32_t* lineBufL2 = wavelet->lineBuf[2];
+ int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
+ int32_t* lineBufH1 = wavelet->lineBuf[(wavelet->fltTapH + 1) % 5 + 3];
+ int32_t* lineBufH2 = wavelet->lineBuf[(wavelet->fltTapH + 2) % 5 + 3];
+
+ wavelet->lineBuf[1] = wavelet->lineBuf[2];
+ wavelet->lineBuf[2] = lineBufL1;
+
+ // process L bands
+ if (wavelet->width <= 1) {
+ lineBufL0[0] = band0Buf[0];
+ lineBufL1[0] = band2Buf[0];
+ } else {
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT) {
+ lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufL1[0] = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ ++band1Buf;
+ ++band3Buf;
+ } else {
+ lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+ lineBufL1[0] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
+ }
+ ++band0Buf;
+ ++band2Buf;
+ for (int i = 0; i < wavelet->width - 3; i += 2) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufL0[1] = band1Buf[0] + ((delta + lineBufL0[0]) >> 1);
+ lineBufL0[2] = delta;
+
+ delta = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ lineBufL1[1] = band3Buf[0] + ((delta + lineBufL1[0]) >> 1);
+ lineBufL1[2] = delta;
+
+ ++band0Buf;
+ ++band1Buf;
+ ++band2Buf;
+ ++band3Buf;
+ lineBufL0 += 2;
+ lineBufL1 += 2;
+ }
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
+ int32_t deltaA = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufL0[1] = band1Buf[0] + ((deltaA + lineBufL0[0]) >> 1);
+
+ int32_t deltaB = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ lineBufL1[1] = band3Buf[0] + ((deltaB + lineBufL1[0]) >> 1);
+
+ if (wavelet->width & 1) {
+ lineBufL0[2] = deltaA;
+ lineBufL1[2] = deltaB;
+ }
+ } else if (wavelet->width & 1) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+ lineBufL0[1] = band1Buf[0] + ((delta + lineBufL0[0]) >> 1);
+ lineBufL0[2] = delta;
+
+ delta = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
+ lineBufL1[1] = band3Buf[0] + ((delta + lineBufL1[0]) >> 1);
+ lineBufL1[2] = delta;
+ } else {
+ lineBufL0[1] = lineBufL0[0] + band1Buf[0];
+ lineBufL1[1] = lineBufL1[0] + band3Buf[0];
+ }
+ }
+
+ // process H bands
+ lineBufL0 = wavelet->lineBuf[0];
+ lineBufL1 = wavelet->lineBuf[1];
+ lineBufL2 = wavelet->lineBuf[2];
+ for (int32_t i = 0; i < wavelet->width; i++) {
+ int32_t delta = lineBufL0[i] - ((lineBufL2[i] + lineBufL1[i] + 2) >> 2);
+ lineBufH1[i] = lineBufL1[i] + ((delta + lineBufH0[i]) >> 1);
+ lineBufH2[i] = delta;
+ }
+ if (wavelet->curLine >= wavelet->height - 3 && wavelet->height & 1) {
+ wavelet->curH += 3;
+ wavelet->curLine += 3;
+ wavelet->fltTapH = (wavelet->fltTapH + 3) % 5;
+ } else {
+ wavelet->curH += 2;
+ wavelet->curLine += 2;
+ wavelet->fltTapH = (wavelet->fltTapH + 2) % 5;
+ }
+ }
+
+ return 0;
+}
+
+static int crxIdwt53FilterInitialize(CrxPlaneComp* comp, int32_t level,
+ CrxQStep* qStep) {
+ if (level == 0)
+ return 0;
+
+ for (int curLevel = 0, curBand = 0; curLevel < level;
+ curLevel++, curBand += 3) {
+ CrxQStep* qStepLevel = qStep ? qStep + curLevel : nullptr;
+ CrxWaveletTransform* wavelet = comp->wvltTransform + curLevel;
+ if (curLevel)
+ wavelet[0].subband0Buf = crxIdwt53FilterGetLine(comp, curLevel - 1);
+ else if (crxDecodeLineWithIQuantization(comp->subBands + curBand,
+ qStepLevel))
+ return -1;
+
+ int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
+ if (wavelet->height > 1) {
+ if (crxDecodeLineWithIQuantization(comp->subBands + curBand + 1,
+ qStepLevel) ||
+ crxDecodeLineWithIQuantization(comp->subBands + curBand + 2,
+ qStepLevel) ||
+ crxDecodeLineWithIQuantization(comp->subBands + curBand + 3,
+ qStepLevel))
+ return -1;
+
+ int32_t* lineBufL0 = wavelet->lineBuf[0];
+ int32_t* lineBufL1 = wavelet->lineBuf[1];
+ int32_t* lineBufL2 = wavelet->lineBuf[2];
+
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_TOP) {
+ crxHorizontal53(lineBufL0, wavelet->lineBuf[1], wavelet,
+ comp->tileFlag);
+ if (crxDecodeLineWithIQuantization(comp->subBands + curBand + 3,
+ qStepLevel) ||
+ crxDecodeLineWithIQuantization(comp->subBands + curBand + 2,
+ qStepLevel))
+ return -1;
+
+ int32_t* band2Buf = wavelet->subband2Buf;
+ int32_t* band3Buf = wavelet->subband3Buf;
+
+ // process L band
+ if (wavelet->width <= 1)
+ lineBufL2[0] = band2Buf[0];
+ else {
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT) {
+ lineBufL2[0] = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ ++band3Buf;
+ } else
+ lineBufL2[0] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
+
+ ++band2Buf;
+
+ for (int i = 0; i < wavelet->width - 3; i += 2) {
+ int32_t delta =
+ band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ lineBufL2[1] = band3Buf[0] + ((lineBufL2[0] + delta) >> 1);
+ lineBufL2[2] = delta;
+
+ ++band2Buf;
+ ++band3Buf;
+ lineBufL2 += 2;
+ }
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
+ int32_t delta =
+ band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
+ lineBufL2[1] = band3Buf[0] + ((lineBufL2[0] + delta) >> 1);
+ if (wavelet->width & 1)
+ lineBufL2[2] = delta;
+ } else if (wavelet->width & 1) {
+ int32_t delta = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
+
+ lineBufL2[1] = band3Buf[0] + ((lineBufL2[0] + delta) >> 1);
+ lineBufL2[2] = delta;
+ } else {
+ lineBufL2[1] = band3Buf[0] + lineBufL2[0];
+ }
+ }
+
+ // process H band
+ for (int32_t i = 0; i < wavelet->width; i++)
+ lineBufH0[i] =
+ lineBufL0[i] - ((lineBufL1[i] + lineBufL2[i] + 2) >> 2);
+ } else {
+ crxHorizontal53(lineBufL0, wavelet->lineBuf[2], wavelet,
+ comp->tileFlag);
+ for (int i = 0; i < wavelet->width; i++)
+ lineBufH0[i] = lineBufL0[i] - ((lineBufL2[i] + 1) >> 1);
+ }
+
+ if (crxIdwt53FilterDecode(comp, curLevel, qStep) ||
+ crxIdwt53FilterTransform(comp, curLevel))
+ return -1;
+ } else {
+ if (crxDecodeLineWithIQuantization(comp->subBands + curBand + 1,
+ qStepLevel))
+ return -1;
+
+ int32_t* band0Buf = wavelet->subband0Buf;
+ int32_t* band1Buf = wavelet->subband1Buf;
+
+ // process H band
+ if (wavelet->width <= 1)
+ lineBufH0[0] = band0Buf[0];
+ else {
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT) {
+ lineBufH0[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ ++band1Buf;
+ } else
+ lineBufH0[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+
+ ++band0Buf;
+
+ for (int i = 0; i < wavelet->width - 3; i += 2) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufH0[1] = band1Buf[0] + ((lineBufH0[0] + delta) >> 1);
+ lineBufH0[2] = delta;
+
+ ++band0Buf;
+ ++band1Buf;
+ lineBufH0 += 2;
+ }
+
+ if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
+ lineBufH0[1] = band1Buf[0] + ((lineBufH0[0] + delta) >> 1);
+ lineBufH0[2] = delta;
+ } else if (wavelet->width & 1) {
+ int32_t delta = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
+ lineBufH0[1] = band1Buf[0] + ((lineBufH0[0] + delta) >> 1);
+ lineBufH0[2] = delta;
+ } else {
+ lineBufH0[1] = band1Buf[0] + lineBufH0[0];
+ }
+ }
+ ++wavelet->curLine;
+ ++wavelet->curH;
+ wavelet->fltTapH = (wavelet->fltTapH + 1) % 5;
+ }
+ }
+
+ return 0;
+}
+
+static void crxFreeSubbandData(CrxImage* image, CrxPlaneComp* comp) {
+ if (comp->compBuf) {
+ free(comp->compBuf);
+ comp->compBuf = nullptr;
+ }
+
+ if (!comp->subBands)
+ return;
+
+ for (int32_t i = 0; i < image->subbandCount; i++) {
+ if (comp->subBands[i].bandParam) {
+ free(comp->subBands[i].bandParam);
+ comp->subBands[i].bandParam = nullptr;
+ }
+
+ comp->subBands[i].bandBuf = nullptr;
+ comp->subBands[i].bandSize = 0;
+ }
+}
+
+static void crxConvertPlaneLine(CrxImage* img, int imageRow, int imageCol = 0,
+ int plane = 0, int32_t* lineData = nullptr,
+ int lineLength = 0) {
+ if (lineData) {
+ uint64_t rawOffset = 4 * img->planeWidth * imageRow + 2 * imageCol;
+ if (img->encType == 1) {
+ int32_t maxVal = 1 << (img->nBits - 1);
+ int32_t minVal = -maxVal;
+ --maxVal;
+ for (int i = 0; i < lineLength; i++)
+ img->outBufs[plane][rawOffset + 2 * i] =
+ crx_constrain(lineData[i], minVal, maxVal);
+ } else if (img->encType == 3) {
+ // copy to intermediate planeBuf
+ rawOffset = plane * img->planeWidth * img->planeHeight +
+ img->planeWidth * imageRow + imageCol;
+ for (int i = 0; i < lineLength; i++)
+ img->planeBuf[rawOffset + i] = lineData[i];
+ } else if (img->nPlanes == 4) {
+ int32_t median = 1 << (img->nBits - 1);
+ int32_t maxVal = (1 << img->nBits) - 1;
+ for (int i = 0; i < lineLength; i++)
+ img->outBufs[plane][rawOffset + 2 * i] =
+ crx_constrain(median + lineData[i], 0, maxVal);
+ } else if (img->nPlanes == 1) {
+ int32_t maxVal = (1 << img->nBits) - 1;
+ int32_t median = 1 << (img->nBits - 1);
+ rawOffset = img->planeWidth * imageRow + imageCol;
+ for (int i = 0; i < lineLength; i++)
+ img->outBufs[0][rawOffset + i] =
+ crx_constrain(median + lineData[i], 0, maxVal);
+ }
+ } else if (img->encType == 3 && img->planeBuf) {
+ int32_t planeSize = img->planeWidth * img->planeHeight;
+ int16_t* plane0 = img->planeBuf + imageRow * img->planeWidth;
+ int16_t* plane1 = plane0 + planeSize;
+ int16_t* plane2 = plane1 + planeSize;
+ int16_t* plane3 = plane2 + planeSize;
+
+ int32_t median = 1 << (img->nBits - 1) << 10;
+ int32_t maxVal = (1 << img->nBits) - 1;
+ uint32_t rawLineOffset = 4 * img->planeWidth * imageRow;
+
+ // for this stage - all except imageRow is ignored
+ for (int i = 0; i < img->planeWidth; i++) {
+ int32_t gr =
+ median + (plane0[i] << 10) - 168 * plane1[i] - 585 * plane3[i];
+ int32_t val = 0;
+ if (gr < 0)
+ gr = -(((std::abs(gr) + 512) >> 9) & ~1);
+ else
+ gr = ((std::abs(gr) + 512) >> 9) & ~1;
+
+ // Essentially R = round(median + P0 + 1.474*P3)
+ val = (median + (plane0[i] << 10) + 1510 * plane3[i] + 512) >> 10;
+ img->outBufs[0][rawLineOffset + 2 * i] = crx_constrain(val, 0, maxVal);
+ // Essentially G1 = round(median + P0 + P2 - 0.164*P1 - 0.571*P3)
+ val = (plane2[i] + gr + 1) >> 1;
+ img->outBufs[1][rawLineOffset + 2 * i] = crx_constrain(val, 0, maxVal);
+ // Essentially G2 = round(median + P0 - P2 - 0.164*P1 - 0.571*P3)
+ val = (gr - plane2[i] + 1) >> 1;
+ img->outBufs[2][rawLineOffset + 2 * i] = crx_constrain(val, 0, maxVal);
+ // Essentially B = round(median + P0 + 1.881*P1)
+ val = (median + (plane0[i] << 10) + 1927 * plane1[i] + 512) >> 10;
+ img->outBufs[3][rawLineOffset + 2 * i] = crx_constrain(val, 0, maxVal);
+ }
+ }
+}
+
+static int crxParamInit(CrxImage* img, CrxBandParam** param,
+ uint64_t subbandMdatOffset, uint64_t subbandDataSize,
+ uint32_t subbandWidth, uint32_t subbandHeight,
+ bool supportsPartial, uint32_t roundedBitsMask) {
+ int32_t progrDataSize = supportsPartial ? 0 : sizeof(int32_t) * subbandWidth;
+ int32_t paramLength = 2 * subbandWidth + 4;
+ uint8_t* paramBuf = nullptr;
+
+ paramBuf = (uint8_t*)calloc(
+ 1, sizeof(CrxBandParam) + sizeof(int32_t) * paramLength + progrDataSize);
+
+ if (!paramBuf)
+ return -1;
+
+ *param = reinterpret_cast(paramBuf);
+
+ paramBuf += sizeof(CrxBandParam);
+
+ (*param)->paramData = reinterpret_cast(paramBuf);
+ (*param)->nonProgrData =
+ progrDataSize ? (*param)->paramData + paramLength : nullptr;
+ (*param)->subbandWidth = subbandWidth;
+ (*param)->subbandHeight = subbandHeight;
+ (*param)->roundedBits = 0;
+ (*param)->curLine = 0;
+ (*param)->roundedBitsMask = roundedBitsMask;
+ (*param)->supportsPartial = supportsPartial;
+ (*param)->bitStream.bitData = 0;
+ (*param)->bitStream.bitsLeft = 0;
+ (*param)->bitStream.mdatSize = subbandDataSize;
+ (*param)->bitStream.curPos = 0;
+ (*param)->bitStream.curBufSize = 0;
+ (*param)->bitStream.curBufOffset = subbandMdatOffset;
+ (*param)->bitStream.crxRawData = img->crxRawData;
+
+ crxFillBuffer(&(*param)->bitStream);
+
+ return 0;
+}
+
+static int crxSetupSubbandData(CrxImage* img, CrxPlaneComp* planeComp,
+ const CrxTile* tile, uint32_t mdatOffset) {
+ int64_t compDataSize = 0;
+ int64_t waveletDataOffset = 0;
+ int64_t compCoeffDataOffset = 0;
+ int32_t toSubbands = 3 * img->levels + 1;
+ int32_t transformWidth = 0;
+
+ CrxSubband* subbands = planeComp->subBands;
+
+ // calculate sizes
+ for (int32_t subbandNum = 0; subbandNum < toSubbands; subbandNum++) {
+ subbands[subbandNum].bandSize =
+ subbands[subbandNum].width * sizeof(int32_t); // 4bytes
+ compDataSize += subbands[subbandNum].bandSize;
+ }
+
+ if (img->levels) {
+ int32_t encLevels = img->levels ? img->levels : 1;
+ waveletDataOffset = (compDataSize + 7) & ~7;
+ compDataSize =
+ (sizeof(CrxWaveletTransform) * encLevels + waveletDataOffset + 7) & ~7;
+ compCoeffDataOffset = compDataSize;
+
+ // calc wavelet line buffer sizes (always at one level up from current)
+ for (int level = 0; level < img->levels; ++level) {
+ if (level < img->levels - 1) {
+ compDataSize += 8 * sizeof(int32_t) *
+ planeComp->subBands[3 * (level + 1) + 2].width;
+ } else {
+ compDataSize += 8 * sizeof(int32_t) * tile->width;
+ }
+ }
+ }
+
+ // buffer allocation
+ planeComp->compBuf = (uint8_t*)malloc(compDataSize);
+
+ if (!planeComp->compBuf)
+ return -1;
+
+ // subbands buffer and sizes initialisation
+ uint64_t subbandMdatOffset = img->mdatOffset + mdatOffset;
+ uint8_t* subbandBuf = planeComp->compBuf;
+
+ for (int32_t subbandNum = 0; subbandNum < toSubbands; subbandNum++) {
+ subbands[subbandNum].bandBuf = subbandBuf;
+ subbandBuf += subbands[subbandNum].bandSize;
+ subbands[subbandNum].mdatOffset =
+ subbandMdatOffset + subbands[subbandNum].dataOffset;
+ }
+
+ // wavelet data initialisation
+ if (img->levels) {
+ auto waveletTransforms =
+ reinterpret_cast(planeComp->compBuf + waveletDataOffset);
+ auto paramData = reinterpret_cast(planeComp->compBuf + compCoeffDataOffset);
+
+ planeComp->wvltTransform = waveletTransforms;
+ waveletTransforms[0].subband0Buf = reinterpret_cast(subbands->bandBuf);
+
+ for (int level = 0; level < img->levels; ++level) {
+ int32_t band = 3 * level + 1;
+
+ if (level >= img->levels - 1) {
+ waveletTransforms[level].height = tile->height;
+ transformWidth = tile->width;
+ } else {
+ waveletTransforms[level].height = subbands[band + 3].height;
+ transformWidth = subbands[band + 4].width;
+ }
+ waveletTransforms[level].width = transformWidth;
+ waveletTransforms[level].lineBuf[0] = paramData;
+ waveletTransforms[level].lineBuf[1] =
+ waveletTransforms[level].lineBuf[0] + transformWidth;
+ waveletTransforms[level].lineBuf[2] =
+ waveletTransforms[level].lineBuf[1] + transformWidth;
+ waveletTransforms[level].lineBuf[3] =
+ waveletTransforms[level].lineBuf[2] + transformWidth;
+ waveletTransforms[level].lineBuf[4] =
+ waveletTransforms[level].lineBuf[3] + transformWidth;
+ waveletTransforms[level].lineBuf[5] =
+ waveletTransforms[level].lineBuf[4] + transformWidth;
+ waveletTransforms[level].lineBuf[6] =
+ waveletTransforms[level].lineBuf[5] + transformWidth;
+ waveletTransforms[level].lineBuf[7] =
+ waveletTransforms[level].lineBuf[6] + transformWidth;
+ waveletTransforms[level].curLine = 0;
+ waveletTransforms[level].curH = 0;
+ waveletTransforms[level].fltTapH = 0;
+ waveletTransforms[level].subband1Buf = reinterpret_cast(subbands[band].bandBuf);
+ waveletTransforms[level].subband2Buf =
+ reinterpret_cast(subbands[band + 1].bandBuf);
+ waveletTransforms[level].subband3Buf =
+ reinterpret_cast(subbands[band + 2].bandBuf);
+
+ paramData = waveletTransforms[level].lineBuf[7] + transformWidth;
+ }
+ }
+
+ // decoding params and bitstream initialisation
+ for (int32_t subbandNum = 0; subbandNum < toSubbands; subbandNum++) {
+ if (subbands[subbandNum].dataSize) {
+ bool supportsPartial = false;
+ uint32_t roundedBitsMask = 0;
+
+ if (planeComp->supportsPartial && subbandNum == 0) {
+ roundedBitsMask = planeComp->roundedBitsMask;
+ supportsPartial = true;
+ }
+ if (crxParamInit(img, &subbands[subbandNum].bandParam,
+ subbands[subbandNum].mdatOffset,
+ subbands[subbandNum].dataSize,
+ subbands[subbandNum].width, subbands[subbandNum].height,
+ supportsPartial, roundedBitsMask))
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int CrxDecompressor::crxDecodePlane(void* p, uint32_t planeNumber) {
+ auto img = static_cast(p);
+ int imageRow = 0;
+ for (int tRow = 0; tRow < img->tileRows; tRow++) {
+ int imageCol = 0;
+ for (int tCol = 0; tCol < img->tileCols; tCol++) {
+ CrxTile* tile = img->tiles + tRow * img->tileCols + tCol;
+ CrxPlaneComp* planeComp = tile->comps + planeNumber;
+ uint64_t tileMdatOffset = tile->dataOffset + tile->mdatQPDataSize +
+ tile->mdatExtraSize + planeComp->dataOffset;
+
+ // decode single tile
+ if (crxSetupSubbandData(img, planeComp, tile, tileMdatOffset))
+ return -1;
+
+ if (img->levels) {
+ if (crxIdwt53FilterInitialize(planeComp, img->levels, tile->qStep))
+ return -1;
+ for (int i = 0; i < tile->height; ++i) {
+ if (crxIdwt53FilterDecode(planeComp, img->levels - 1, tile->qStep) ||
+ crxIdwt53FilterTransform(planeComp, img->levels - 1))
+ return -1;
+ int32_t* lineData =
+ crxIdwt53FilterGetLine(planeComp, img->levels - 1);
+ crxConvertPlaneLine(img, imageRow + i, imageCol, planeNumber,
+ lineData, tile->width);
+ }
+ } else {
+ // we have the only subband in this case
+ if (!planeComp->subBands->dataSize) {
+ memset(planeComp->subBands->bandBuf, 0,
+ planeComp->subBands->bandSize);
+ return 0;
+ }
+
+ for (int i = 0; i < tile->height; ++i) {
+ if (crxDecodeLine(planeComp->subBands->bandParam,
+ planeComp->subBands->bandBuf))
+ return -1;
+ auto lineData = reinterpret_cast(planeComp->subBands->bandBuf);
+ crxConvertPlaneLine(img, imageRow + i, imageCol, planeNumber,
+ lineData, tile->width);
+ }
+ }
+ imageCol += tile->width;
+ }
+ imageRow += img->tiles[tRow * img->tileCols].height;
+ }
+
+ return 0;
+}
+
+static uint32_t crxReadQP(CrxBitstream* bitStrm, int32_t kParam) {
+ uint32_t qp = crxBitstreamGetZeros(bitStrm);
+ if (qp >= 23)
+ qp = crxBitstreamGetBits(bitStrm, 8);
+ else if (kParam)
+ qp = crxBitstreamGetBits(bitStrm, kParam) | (qp << kParam);
+
+ return qp;
+}
+
+static void crxDecodeGolombTop(CrxBitstream* bitStrm, int32_t width, int32_t* lineBuf,
+ int32_t* kParam) {
+ lineBuf[0] = 0;
+ while (width-- > 0) {
+ lineBuf[1] = lineBuf[0];
+ uint32_t qp = crxReadQP(bitStrm, *kParam);
+ lineBuf[1] += -(qp & 1) ^ (qp >> 1);
+ *kParam = crxPredictKParameter(*kParam, qp, 7);
+ ++lineBuf;
+ }
+ lineBuf[1] = lineBuf[0] + 1;
+}
+
+static void crxDecodeGolombNormal(CrxBitstream* bitStrm, int32_t width,
+ int32_t* lineBuf0, int32_t* lineBuf1,
+ int32_t* kParam) {
+ lineBuf1[0] = lineBuf0[1];
+ int32_t deltaH = lineBuf0[1] - lineBuf0[0];
+ while (width-- > 0) {
+ lineBuf1[1] = crxPrediction(lineBuf1[0], lineBuf0[1], deltaH,
+ lineBuf0[0] - lineBuf1[0]);
+ uint32_t qp = crxReadQP(bitStrm, *kParam);
+ lineBuf1[1] += -(qp & 1) ^ (qp >> 1);
+ if (width) {
+ deltaH = lineBuf0[2] - lineBuf0[1];
+ *kParam = crxPredictKParameter(*kParam, (qp + 2 * std::abs(deltaH)) >> 1, 7);
+ ++lineBuf0;
+ } else
+ *kParam = crxPredictKParameter(*kParam, qp, 7);
+ ++lineBuf1;
+ }
+ lineBuf1[1] = lineBuf1[0] + 1;
+}
+
+static int crxMakeQStep(CrxImage* img, CrxTile* tile, int32_t* qpTable,
+ uint32_t totalQP) {
+ if (img->levels > 3 || img->levels < 1)
+ return -1;
+ int qpWidth = (tile->width >> 3) + ((tile->width & 7) != 0);
+ int qpHeight = (tile->height >> 1) + (tile->height & 1);
+ int qpHeight4 = (tile->height >> 2) + ((tile->height & 3) != 0);
+ int qpHeight8 = (tile->height >> 3) + ((tile->height & 7) != 0);
+ uint32_t totalHeight = qpHeight;
+ if (img->levels > 1)
+ totalHeight += qpHeight4;
+ if (img->levels > 2)
+ totalHeight += qpHeight8;
+
+ size_t const qStepSize = totalHeight * size_t(qpWidth) * sizeof(uint32_t) +
+ img->levels * sizeof(CrxQStep);
+ tile->qStep = (CrxQStep*)malloc(qStepSize);
+
+ if (!tile->qStep)
+ return -1;
+ auto qStepTbl = reinterpret_cast(tile->qStep + img->levels);
+ CrxQStep* qStep = tile->qStep;
+ uint8_t curr_level = img->levels;
+ while (curr_level > 0) {
+ switch (curr_level) {
+ case 3:
+ qStep->qStepTbl = qStepTbl;
+ qStep->width = qpWidth;
+ qStep->height = qpHeight8;
+ for (int qpRow = 0; qpRow < qpHeight8; ++qpRow) {
+ int row0Idx = qpWidth * std::min(4 * qpRow, qpHeight - 1);
+ int row1Idx = qpWidth * std::min(4 * qpRow + 1, qpHeight - 1);
+ int row2Idx = qpWidth * std::min(4 * qpRow + 2, qpHeight - 1);
+ int row3Idx = qpWidth * std::min(4 * qpRow + 3, qpHeight - 1);
+
+ for (int qpCol = 0; qpCol < qpWidth; ++qpCol, ++qStepTbl) {
+ int32_t quantVal = qpTable[row0Idx++] + qpTable[row1Idx++] +
+ qpTable[row2Idx++] + qpTable[row3Idx++];
+ // not sure about this nonsense - why is it not just avg like with 2
+ // levels?
+ quantVal = ((quantVal < 0) * 3 + quantVal) >> 2;
+ if (quantVal / 6 >= 6)
+ *qStepTbl = q_step_tbl[quantVal % 6] * (1 << (quantVal / 6 + 26));
+ else
+ *qStepTbl = q_step_tbl[quantVal % 6] >> (6 - quantVal / 6);
+ }
+ }
+ // continue to the next level - we always decode all levels
+ ++qStep;
+ --curr_level;
+ break;
+ case 2:
+ qStep->qStepTbl = qStepTbl;
+ qStep->width = qpWidth;
+ qStep->height = qpHeight4;
+ for (int qpRow = 0; qpRow < qpHeight4; ++qpRow) {
+ int row0Idx = qpWidth * std::min(2 * qpRow, qpHeight - 1);
+ int row1Idx = qpWidth * std::min(2 * qpRow + 1, qpHeight - 1);
+
+ for (int qpCol = 0; qpCol < qpWidth; ++qpCol, ++qStepTbl) {
+ int32_t quantVal = (qpTable[row0Idx++] + qpTable[row1Idx++]) / 2;
+ if (quantVal / 6 >= 6)
+ *qStepTbl = q_step_tbl[quantVal % 6] * (1 << (quantVal / 6 + 26));
+ else
+ *qStepTbl = q_step_tbl[quantVal % 6] >> (6 - quantVal / 6);
+ }
+ }
+ // continue to the next level - we always decode all levels
+ ++qStep;
+ --curr_level;
+ break;
+ case 1:
+ qStep->qStepTbl = qStepTbl;
+ qStep->width = qpWidth;
+ qStep->height = qpHeight;
+ for (int qpRow = 0; qpRow < qpHeight; ++qpRow) {
+ for (int qpCol = 0; qpCol < qpWidth; ++qpCol, ++qStepTbl, ++qpTable) {
+ if (*qpTable / 6 >= 6) {
+ *qStepTbl = q_step_tbl[*qpTable % 6] * (1 << (*qpTable / 6 + 26));
+ } else {
+ *qStepTbl = q_step_tbl[*qpTable % 6] >> (6 - *qpTable / 6);
+ }
+ }
+ }
+ --curr_level;
+ break;
+ default:
+ break; // oops, more then 3 levels?
+ }
+ }
+ return 0;
+}
+
+inline void crxSetupSubbandIdx(const IsoMCanonCmp1Box* hdr, CrxImage* img,
+ CrxSubband* band, int level, int16_t colStartIdx,
+ int16_t bandWidthExCoef, int16_t rowStartIdx,
+ int16_t bandHeightExCoef) {
+ if (hdr->version == 0x200) {
+ band->rowStartAddOn = rowStartIdx;
+ band->rowEndAddOn = bandHeightExCoef;
+ band->colStartAddOn = colStartIdx;
+ band->colEndAddOn = bandWidthExCoef;
+ band->levelShift = 3 - level;
+ } else {
+ band->rowStartAddOn = 0;
+ band->rowEndAddOn = 0;
+ band->colStartAddOn = 0;
+ band->colEndAddOn = 0;
+ band->levelShift = 0;
+ }
+}
+
+static int crxProcessSubbands(const IsoMCanonCmp1Box* hdr, CrxImage* img,
+ CrxTile* tile, CrxPlaneComp* comp) {
+ CrxSubband* band = comp->subBands + img->subbandCount - 1; // set to last band
+ uint32_t bandHeight = tile->height;
+ uint32_t bandWidth = tile->width;
+ int32_t bandWidthExCoef = 0;
+ int32_t bandHeightExCoef = 0;
+ if (img->levels) {
+ // Build up subband sequences to crxDecode to a level in a header
+
+ // Coefficient structure is a bit unclear and convoluted:
+ // 3 levels max - 8 groups (for tile width rounded to 8 bytes)
+ // of 3 band per level 4 sets of coefficients for each
+ int32_t* rowExCoef =
+ exCoefNumTbl + 0x30 * (img->levels - 1) + 6 * (tile->width & 7);
+ int32_t* colExCoef =
+ exCoefNumTbl + 0x30 * (img->levels - 1) + 6 * (tile->height & 7);
+ for (int level = 0; level < img->levels; ++level) {
+ int32_t widthOddPixel = bandWidth & 1;
+ int32_t heightOddPixel = bandHeight & 1;
+ bandWidth = (widthOddPixel + bandWidth) >> 1;
+ bandHeight = (heightOddPixel + bandHeight) >> 1;
+
+ int32_t bandWidthExCoef0 = 0;
+ int32_t bandWidthExCoef1 = 0;
+ int32_t bandHeightExCoef0 = 0;
+ int32_t bandHeightExCoef1 = 0;
+ int32_t colStartIdx = 0;
+ int32_t rowStartIdx = 0;
+ if (tile->tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
+ bandWidthExCoef0 = rowExCoef[2 * level];
+ bandWidthExCoef1 = rowExCoef[2 * level + 1];
+ }
+ if (tile->tileFlag & E_HAS_TILES_ON_THE_LEFT) {
+ ++bandWidthExCoef0;
+ colStartIdx = 1;
+ }
+
+ if (tile->tileFlag & E_HAS_TILES_ON_THE_BOTTOM) {
+ bandHeightExCoef0 = colExCoef[2 * level];
+ bandHeightExCoef1 = colExCoef[2 * level + 1];
+ }
+ if (tile->tileFlag & E_HAS_TILES_ON_THE_TOP) {
+ ++bandHeightExCoef0;
+ rowStartIdx = 1;
+ }
+
+ band[0].width = bandWidth + bandWidthExCoef0 - widthOddPixel;
+ band[0].height = bandHeight + bandHeightExCoef0 - heightOddPixel;
+ crxSetupSubbandIdx(hdr, img, band, level + 1, colStartIdx,
+ bandWidthExCoef0 - colStartIdx, rowStartIdx,
+ bandHeightExCoef0 - rowStartIdx);
+
+ band[-1].width = bandWidth + bandWidthExCoef1;
+ band[-1].height = bandHeight + bandHeightExCoef0 - heightOddPixel;
+
+ crxSetupSubbandIdx(hdr, img, band - 1, level + 1, 0, bandWidthExCoef1,
+ rowStartIdx, bandHeightExCoef0 - rowStartIdx);
+
+ band[-2].width = bandWidth + bandWidthExCoef0 - widthOddPixel;
+ band[-2].height = bandHeight + bandHeightExCoef1;
+ crxSetupSubbandIdx(hdr, img, band - 2, level + 1, colStartIdx,
+ bandWidthExCoef0 - colStartIdx, 0, bandHeightExCoef1);
+
+ band -= 3;
+ }
+ bandWidthExCoef = bandHeightExCoef = 0;
+ if (tile->tileFlag & E_HAS_TILES_ON_THE_RIGHT)
+ bandWidthExCoef = rowExCoef[2 * img->levels - 1];
+ if (tile->tileFlag & E_HAS_TILES_ON_THE_BOTTOM)
+ bandHeightExCoef = colExCoef[2 * img->levels - 1];
+ }
+ band->width = bandWidthExCoef + bandWidth;
+ band->height = bandHeightExCoef + bandHeight;
+ if (img->levels)
+ crxSetupSubbandIdx(hdr, img, band, img->levels, 0, bandWidthExCoef, 0,
+ bandHeightExCoef);
+
+ return 0;
+}
+
+static int crxReadSubbandHeaders(const IsoMCanonCmp1Box* hdr, CrxImage* img,
+ CrxTile* tile, CrxPlaneComp* comp,
+ const uint8_t** subbandMdatPtr, int32_t* mdatSize) {
+ if (!img->subbandCount)
+ return 0;
+ int32_t subbandOffset = 0;
+ CrxSubband* band = comp->subBands;
+ for (int curSubband = 0; curSubband < img->subbandCount;
+ curSubband++, band++) {
+ if (*mdatSize < 4)
+ return -1;
+
+ int hdrSign = sgetn(2, *subbandMdatPtr);
+ int hdrSize = sgetn(2, *subbandMdatPtr + 2);
+ if (*mdatSize < hdrSize + 4)
+ return -1;
+ if ((hdrSign != 0xFF03 || hdrSize != 8) &&
+ (hdrSign != 0xFF13 || hdrSize != 16))
+ return -1;
+
+ int32_t subbandSize = sgetn(4, *subbandMdatPtr + 4);
+
+ if (curSubband != ((*subbandMdatPtr)[8] & 0xF0) >> 4) {
+ band->dataSize = subbandSize;
+ return -1;
+ }
+
+ band->dataOffset = subbandOffset;
+ band->kParam = 0;
+ band->bandParam = nullptr;
+ band->bandBuf = nullptr;
+ band->bandSize = 0;
+
+ if (hdrSign == 0xFF03) {
+ // old header
+ uint32_t bitData = sgetn(4, *subbandMdatPtr + 8);
+ band->dataSize = subbandSize - (bitData & 0x7FFFF);
+ band->supportsPartial = bitData & 0x8000000;
+ band->qParam = (bitData >> 19) & 0xFF;
+ band->qStepBase = 0;
+ band->qStepMult = 0;
+ } else {
+ // new header
+ if (sgetn(2, *subbandMdatPtr + 8) & 0xFFF)
+ // partial and qParam are not supported
+ return -1;
+ if (sgetn(2, *subbandMdatPtr + 18))
+ // new header terninated by 2 zero bytes
+ return -1;
+ band->supportsPartial = false;
+ band->qParam = 0;
+ band->dataSize = subbandSize - sgetn(2, *subbandMdatPtr + 16);
+ band->qStepBase = sgetn(4, *subbandMdatPtr + 12);
+ band->qStepMult = sgetn(2, *subbandMdatPtr + 10);
+ }
+
+ subbandOffset += subbandSize;
+
+ *subbandMdatPtr += hdrSize + 4;
+ *mdatSize -= hdrSize + 4;
+ }
+
+ return 0;
+}
+
+static int crxReadImageHeaders(const IsoMCanonCmp1Box* hdr, CrxImage* img) {
+ int nTiles = img->tileRows * img->tileCols;
+
+ if (!nTiles)
+ ThrowRDE("Crx decompression error");
+
+ if (!img->tiles) {
+ img->tiles = static_cast(calloc(
+ sizeof(CrxTile) * nTiles +
+ sizeof(CrxPlaneComp) * nTiles * img->nPlanes +
+ sizeof(CrxSubband) * nTiles * img->nPlanes * img->subbandCount,
+ 1));
+
+ if (!img->tiles)
+ ThrowRDE("Crx decompression error");
+
+ // memory areas in allocated chunk
+ CrxTile* tile = img->tiles;
+ auto comps = reinterpret_cast(tile + nTiles);
+ auto bands = reinterpret_cast(comps + img->nPlanes * nTiles);
+
+ for (int curTile = 0; curTile < nTiles; curTile++, tile++) {
+ tile->tileFlag = 0; // tile neighbouring flags
+ tile->tileNumber = curTile;
+ tile->tileSize = 0;
+ tile->comps = comps + curTile * img->nPlanes;
+
+ if ((curTile + 1) % img->tileCols) {
+ // not the last tile in a tile row
+ tile->width = hdr->tileWidth;
+ if (img->tileCols > 1) {
+ tile->tileFlag = E_HAS_TILES_ON_THE_RIGHT;
+ if (curTile % img->tileCols)
+ // not the first tile in tile row
+ tile->tileFlag |= E_HAS_TILES_ON_THE_LEFT;
+ }
+ } else {
+ // last tile in a tile row
+ tile->width = img->planeWidth - hdr->tileWidth * (img->tileCols - 1);
+ if (img->tileCols > 1)
+ tile->tileFlag = E_HAS_TILES_ON_THE_LEFT;
+ }
+ if (curTile < nTiles - img->tileCols) {
+ // in first tile row
+ tile->height = hdr->tileHeight;
+ if (img->tileRows > 1) {
+ tile->tileFlag |= E_HAS_TILES_ON_THE_BOTTOM;
+ if (curTile >= img->tileCols)
+ tile->tileFlag |= E_HAS_TILES_ON_THE_TOP;
+ }
+ } else {
+ // non first tile row
+ tile->height = img->planeHeight - hdr->tileHeight * (img->tileRows - 1);
+ if (img->tileRows > 1)
+ tile->tileFlag |= E_HAS_TILES_ON_THE_TOP;
+ }
+ if (img->nPlanes) {
+ CrxPlaneComp* comp = tile->comps;
+ CrxSubband* band = bands + curTile * img->nPlanes * img->subbandCount;
+
+ for (int curComp = 0; curComp < img->nPlanes; curComp++, comp++) {
+ comp->compNumber = curComp;
+ comp->supportsPartial = true;
+ comp->tileFlag = tile->tileFlag;
+ comp->subBands = band;
+ comp->compBuf = nullptr;
+ comp->wvltTransform = nullptr;
+ if (img->subbandCount) {
+ for (int curBand = 0; curBand < img->subbandCount;
+ curBand++, band++) {
+ band->supportsPartial = false;
+ band->qParam = 4;
+ band->bandParam = nullptr;
+ band->dataSize = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ uint32_t tileOffset = 0;
+ Buffer mdatHdr = img->crxRawData.getSubView(0, img->mdatHdrSize);
+ int32_t dataSize = mdatHdr.getSize();
+ const uint8_t* dataPtr = mdatHdr.getData(0, dataSize);
+ CrxTile* tile = img->tiles;
+
+ for (int curTile = 0; curTile < nTiles; ++curTile, ++tile) {
+ if (dataSize < 4)
+ ThrowRDE("Crx decompression error");
+
+ int hdrSign = sgetn(2, dataPtr);
+ int hdrSize = sgetn(2, dataPtr + 2);
+ if ((hdrSign != 0xFF01 || hdrSize != 8) &&
+ (hdrSign != 0xFF11 || (hdrSize != 8 && hdrSize != 16)))
+ ThrowRDE("Crx decompression error");
+ if (dataSize < hdrSize + 4)
+ ThrowRDE("Crx decompression error");
+ int tailSign = sgetn(2, dataPtr + 10);
+ if ((hdrSize == 8 && tailSign) || (hdrSize == 16 && tailSign != 0x4000))
+ ThrowRDE("Crx decompression error");
+ if (sgetn(2, dataPtr + 8) != (unsigned)curTile)
+ ThrowRDE("Crx decompression error");
+
+ dataSize -= hdrSize + 4;
+
+ tile->tileSize = sgetn(4, dataPtr + 4);
+ tile->dataOffset = tileOffset;
+ tile->qStep = nullptr;
+ if (hdrSize == 16) {
+ // extended header data - terminated by 0 bytes
+ if (sgetn(2, dataPtr + 18) != 0)
+ return -1;
+ tile->hasQPData = true;
+ tile->mdatQPDataSize = sgetn(4, dataPtr + 12);
+ tile->mdatExtraSize = sgetn(2, dataPtr + 16);
+ } else {
+ tile->hasQPData = false;
+ tile->mdatQPDataSize = 0;
+ tile->mdatExtraSize = 0;
+ }
+
+ dataPtr += hdrSize + 4;
+ tileOffset += tile->tileSize;
+
+ uint32_t compOffset = 0;
+ CrxPlaneComp* comp = tile->comps;
+
+ for (int compNum = 0; compNum < img->nPlanes; ++compNum, ++comp) {
+ if (dataSize < 0xC)
+ ThrowRDE("Crx decompression error");
+ hdrSign = sgetn(2, dataPtr);
+ hdrSize = sgetn(2, dataPtr + 2);
+ if ((hdrSign != 0xFF02 && hdrSign != 0xFF12) || hdrSize != 8)
+ ThrowRDE("Crx decompression error");
+ if (compNum != dataPtr[8] >> 4)
+ ThrowRDE("Crx decompression error");
+ if (sgetn(3, dataPtr + 9) != 0)
+ ThrowRDE("Crx decompression error");
+
+ comp->compSize = sgetn(4, dataPtr + 4);
+
+ int32_t compHdrRoundedBits = (dataPtr[8] >> 1) & 3;
+ comp->supportsPartial = (dataPtr[8] & 8) != 0;
+
+ comp->dataOffset = compOffset;
+ comp->tileFlag = tile->tileFlag;
+
+ compOffset += comp->compSize;
+ dataSize -= 0xC;
+ dataPtr += 0xC;
+
+ comp->roundedBitsMask = 0;
+
+ if (compHdrRoundedBits) {
+ if (img->levels || !comp->supportsPartial)
+ ThrowRDE("Crx decompression error");
+
+ comp->roundedBitsMask = 1 << (compHdrRoundedBits - 1);
+ }
+
+ if (crxReadSubbandHeaders(hdr, img, tile, comp, &dataPtr, &dataSize) ||
+ crxProcessSubbands(hdr, img, tile, comp))
+ ThrowRDE("Crx decompression error");
+ }
+ }
+
+ if (hdr->version != 0x200)
+ return 0;
+
+ tile = img->tiles;
+ for (int curTile = 0; curTile < nTiles; ++curTile, ++tile) {
+ if (tile->hasQPData) {
+ CrxBitstream bitStrm;
+ bitStrm.bitData = 0;
+ bitStrm.bitsLeft = 0;
+ bitStrm.curPos = 0;
+ bitStrm.curBufSize = 0;
+ bitStrm.mdatSize = tile->mdatQPDataSize;
+ bitStrm.curBufOffset = img->mdatOffset + tile->dataOffset;
+ bitStrm.crxRawData = img->crxRawData;
+
+ crxFillBuffer(&bitStrm);
+
+ uint32_t qpWidth = (tile->width >> 3) + ((tile->width & 7) != 0);
+ uint32_t qpHeight = (tile->height >> 1) + (tile->height & 1);
+ uint64_t totalQP = static_cast(qpHeight) * static_cast(qpWidth);
+
+ try {
+ std::vector qpTable(totalQP + 2 * (qpWidth + 2));
+ int32_t* qpCurElem = qpTable.data();
+ // 2 lines padded with extra pixels at the start and at the end
+ int32_t* qpLineBuf = qpTable.data() + totalQP;
+ int32_t kParam = 0;
+ for (unsigned qpRow = 0; qpRow < qpHeight; ++qpRow) {
+ int32_t* qpLine0 = qpRow & 1 ? qpLineBuf + qpWidth + 2 : qpLineBuf;
+ int32_t* qpLine1 = qpRow & 1 ? qpLineBuf : qpLineBuf + qpWidth + 2;
+
+ if (qpRow)
+ crxDecodeGolombNormal(&bitStrm, qpWidth, qpLine0, qpLine1, &kParam);
+ else
+ crxDecodeGolombTop(&bitStrm, qpWidth, qpLine1, &kParam);
+
+ for (unsigned qpCol = 0; qpCol < qpWidth; ++qpCol)
+ *qpCurElem++ = qpLine1[qpCol + 1] + 4;
+ }
+
+ // now we read QP data - build tile QStep
+ if (crxMakeQStep(img, tile, qpTable.data(), totalQP))
+ ThrowRDE("Crx decompression error");
+ } catch (...) {
+ ThrowRDE("Crx decompression error");
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int crxSetupImageData(const IsoMCanonCmp1Box* hdr, CrxImage* img,
+ int16_t* outBuf) {
+ int IncrBitTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0};
+
+ img->planeWidth = hdr->f_width;
+ img->planeHeight = hdr->f_height;
+
+ if (hdr->tileWidth < 0x16 || hdr->tileHeight < 0x16 ||
+ img->planeWidth > 0x7FFF || img->planeHeight > 0x7FFF)
+ ThrowRDE("Crx decompression error");
+
+ img->tileCols = (img->planeWidth + hdr->tileWidth - 1) / hdr->tileWidth;
+ img->tileRows = (img->planeHeight + hdr->tileHeight - 1) / hdr->tileHeight;
+
+ if (img->tileCols > 0xFF || img->tileRows > 0xFF ||
+ img->planeWidth - hdr->tileWidth * (img->tileCols - 1) < 0x16 ||
+ img->planeHeight - hdr->tileHeight * (img->tileRows - 1) < 0x16)
+ ThrowRDE("Crx decompression error");
+
+ img->tiles = nullptr;
+ img->levels = hdr->imageLevels;
+ img->subbandCount = 3 * img->levels + 1; // 3 bands per level + one last LL
+ img->nPlanes = hdr->nPlanes;
+ img->nBits = hdr->nBits;
+ img->encType = hdr->encType;
+ img->samplePrecision = hdr->nBits + IncrBitTable[4 * hdr->encType + 2] + 1;
+ img->mdatOffset = hdr->mdatHdrSize; // after header, plane data follows
+ img->mdatHdrSize = hdr->mdatHdrSize;
+ img->planeBuf = nullptr;
+ img->outBufs[0] = img->outBufs[1] = img->outBufs[2] = img->outBufs[3] = nullptr;
+
+ // The encoding type 3 needs all 4 planes to be decoded to generate row of
+ // RGGB values. It seems to be using some other colour space for raw encoding
+ // It is a massive buffer so ideallly it will need a different approach:
+ // decode planes line by line and convert single line then without
+ // intermediate plane buffer. At the moment though it's too many changes so
+ // left as is.
+ if (img->encType == 3 && img->nPlanes == 4 && img->nBits > 8) {
+ size_t const planeBufSize = img->planeHeight * img->planeWidth * img->nPlanes *
+ (size_t(img->samplePrecision + 7) >> 3);
+ img->planeBuf = (int16_t*)malloc(planeBufSize);
+ if (!img->planeBuf)
+ ThrowRDE("Crx decompression error");
+ }
+
+ int32_t rowSize = 2 * img->planeWidth;
+
+ if (img->nPlanes == 1)
+ img->outBufs[0] = outBuf;
+ else
+ switch (hdr->cfaLayout) {
+ case 0:
+ // R G
+ // G B
+ img->outBufs[0] = outBuf;
+ img->outBufs[1] = outBuf + 1;
+ img->outBufs[2] = outBuf + rowSize;
+ img->outBufs[3] = img->outBufs[2] + 1;
+ break;
+ case 1:
+ // G R
+ // B G
+ img->outBufs[1] = outBuf;
+ img->outBufs[0] = outBuf + 1;
+ img->outBufs[3] = outBuf + rowSize;
+ img->outBufs[2] = img->outBufs[3] + 1;
+ break;
+ case 2:
+ // G B
+ // R G
+ img->outBufs[2] = outBuf;
+ img->outBufs[3] = outBuf + 1;
+ img->outBufs[0] = outBuf + rowSize;
+ img->outBufs[1] = img->outBufs[0] + 1;
+ break;
+ case 3:
+ // B G
+ // G R
+ img->outBufs[3] = outBuf;
+ img->outBufs[2] = outBuf + 1;
+ img->outBufs[1] = outBuf + rowSize;
+ img->outBufs[0] = img->outBufs[1] + 1;
+ break;
+ }
+
+ // read header
+ return crxReadImageHeaders(hdr, img);
+}
+
+static int crxFreeImageData(CrxImage* img) {
+ CrxTile* tile = img->tiles;
+ int nTiles = img->tileRows * img->tileCols;
+
+ if (img->tiles) {
+ for (int32_t curTile = 0; curTile < nTiles; curTile++) {
+ if (tile[curTile].comps)
+ for (int32_t curPlane = 0; curPlane < img->nPlanes; curPlane++)
+ crxFreeSubbandData(img, tile[curTile].comps + curPlane);
+ if (tile[curTile].qStep)
+ free(tile[curTile].qStep);
+ }
+ free(img->tiles);
+ img->tiles = nullptr;
+ }
+
+ if (img->planeBuf) {
+ free(img->planeBuf);
+ img->planeBuf = nullptr;
+ }
+
+ return 0;
+}
+
+void CrxDecompressor::crxLoadDecodeLoop(void* img, int nPlanes) {
+ int results[4]; // nPlanes is always <= 4
+
+#ifdef HAVE_OPENMP
+#pragma omp for schedule(static)
+#endif
+ for (int32_t plane = 0; plane < nPlanes; ++plane)
+ results[plane] = crxDecodePlane(img, plane);
+
+ for (int32_t plane = 0; plane < nPlanes; ++plane)
+ if (results[plane])
+ ThrowRDE("Crx decompression error");
+}
+
+void CrxDecompressor::crxConvertPlaneLineDf(void* p, int imageRow) {
+ crxConvertPlaneLine((CrxImage*)p, imageRow);
+}
+
+void CrxDecompressor::crxLoadFinalizeLoopE3(void* p, int planeHeight) {
+#ifdef HAVE_OPENMP
+#pragma omp for schedule(static)
+#endif
+ for (int i = 0; i < planeHeight; ++i)
+ crxConvertPlaneLineDf(p, i);
+}
+
+void CrxDecompressor::decode(const IsoMCanonCmp1Box& cmp1Box,
+ Buffer& crxRawData) {
+ CrxImage img;
+ img.crxRawData = crxRawData;
+
+ // Local copy because we must must modify some values for decompression
+ IsoMCanonCmp1Box hdr = cmp1Box;
+
+ // Bytes required for decompression output
+ Buffer::size_type bufLen =
+ size_t(cmp1Box.f_height) * size_t(cmp1Box.f_width) * sizeof(uint16_t);
+
+ // update sizes for the planes
+ if (hdr.nPlanes == 4) {
+ hdr.f_width >>= 1;
+ hdr.f_height >>= 1;
+ hdr.tileWidth >>= 1;
+ hdr.tileHeight >>= 1;
+ }
+
+ auto storage = rawspeed::Buffer::Create(bufLen);
+ const rawspeed::Buffer outBuf(storage.get(), bufLen);
+
+ if (crxSetupImageData(&hdr, &img, reinterpret_cast(storage.get()))) {
+ ThrowRDE("Crx image setup failed");
+ }
+ crxLoadDecodeLoop(&img, hdr.nPlanes);
+
+ if (img.encType == 3)
+ crxLoadFinalizeLoopE3(&img, img.planeHeight);
+
+ crxFreeImageData(&img);
+
+ ByteStream input(DataBuffer(outBuf, Endianness::big));
+ UncompressedDecompressor u(input, mRaw);
+
+ // align output bytes
+ u.decodeRawUnpacked<16, Endianness::little>(cmp1Box.f_width,
+ cmp1Box.f_height);
+}
+
+CrxDecompressor::CrxDecompressor(const RawImage& img)
+ : AbstractDecompressor(), mRaw(img) {
+ if (mRaw->getDataType() != RawImageType::UINT16)
+ ThrowRDE("Unexpected data type");
+
+ if (!((mRaw->getCpp() == 1 && mRaw->getBpp() == sizeof(uint16_t))))
+ ThrowRDE("Unexpected cpp: %u", mRaw->getCpp());
+}
+
+} // namespace rawspeed
diff --git a/src/librawspeed/decompressors/CrxDecompressor.h b/src/librawspeed/decompressors/CrxDecompressor.h
new file mode 100644
index 000000000..fc80717e3
--- /dev/null
+++ b/src/librawspeed/decompressors/CrxDecompressor.h
@@ -0,0 +1,51 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "common/RawImage.h" // for RawImage
+#include "decoders/RawDecoderException.h" // for ThrowRDE
+#include "decompressors/AbstractDecompressor.h" // for AbstractDecompressor
+#include // for assert
+#include // for uint16_t
+
+namespace rawspeed {
+
+class Buffer;
+class RawImage;
+class IsoMCanonCmp1Box;
+
+class CrxDecompressor final : public AbstractDecompressor {
+ RawImage mRaw;
+
+public:
+ CrxDecompressor(const RawImage& img);
+
+ void decode(const IsoMCanonCmp1Box& cmp1Box, Buffer& crxRawData);
+
+private:
+ int crxDecodePlane(void* p, uint32_t planeNumber);
+ void crxLoadDecodeLoop(void* img, int nPlanes);
+ int crxParseImageHeader(uint8_t* cmp1TagData, int nTrack);
+ void crxConvertPlaneLineDf(void* p, int imageRow);
+ void crxLoadFinalizeLoopE3(void* p, int planeHeight);
+};
+
+} // namespace rawspeed
diff --git a/src/librawspeed/io/Buffer.h b/src/librawspeed/io/Buffer.h
index 3e0bd1f37..f130ed7ae 100644
--- a/src/librawspeed/io/Buffer.h
+++ b/src/librawspeed/io/Buffer.h
@@ -216,7 +216,7 @@ class Buffer
// WARNING: both buffers must belong to the same allocation, else this is UB!
inline bool operator<(const Buffer& lhs, const Buffer& rhs) {
- return std::pair(lhs.begin(), lhs.end()) < std::pair(rhs.begin(), rhs.end());
+ return std::make_pair(lhs.begin(), lhs.end()) < std::make_pair(rhs.begin(), rhs.end());
}
/*
diff --git a/src/librawspeed/io/ByteStream.h b/src/librawspeed/io/ByteStream.h
index 74f1d2589..5269de08d 100644
--- a/src/librawspeed/io/ByteStream.h
+++ b/src/librawspeed/io/ByteStream.h
@@ -164,6 +164,7 @@ class ByteStream : public DataBuffer
return ret;
}
+ inline uint16_t getI16() { return get(); }
inline uint16_t getU16() { return get(); }
inline int32_t getI32() { return get(); }
inline uint32_t getU32() { return get(); }
diff --git a/src/librawspeed/io/Endianness.h b/src/librawspeed/io/Endianness.h
index 065c5047c..5073b2fd5 100644
--- a/src/librawspeed/io/Endianness.h
+++ b/src/librawspeed/io/Endianness.h
@@ -67,6 +67,12 @@ inline Endianness getHostEndianness() {
#define BSWAP64(A) __builtin_bswap64(A)
#endif
+inline int8_t getByteSwapped(int8_t v) {
+ return v;
+}
+inline uint8_t getByteSwapped(uint8_t v) {
+ return v;
+}
inline int16_t getByteSwapped(int16_t v) {
return static_cast(BSWAP16(static_cast(v)));
}
diff --git a/src/librawspeed/parsers/CMakeLists.txt b/src/librawspeed/parsers/CMakeLists.txt
index 453c2dc11..5871280fc 100644
--- a/src/librawspeed/parsers/CMakeLists.txt
+++ b/src/librawspeed/parsers/CMakeLists.txt
@@ -5,6 +5,9 @@ FILE(GLOB SOURCES
"FiffParser.cpp"
"FiffParser.h"
"FiffParserException.h"
+ "IsoMParser.cpp"
+ "IsoMParser.h"
+ "IsoMParserException.h"
"RawParser.cpp"
"RawParser.h"
"RawParserException.h"
diff --git a/src/librawspeed/parsers/IsoMParser.cpp b/src/librawspeed/parsers/IsoMParser.cpp
new file mode 100644
index 000000000..dde7c3c61
--- /dev/null
+++ b/src/librawspeed/parsers/IsoMParser.cpp
@@ -0,0 +1,59 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "parsers/IsoMParser.h" // For IsoMParser
+#include "decoders/Cr3Decoder.h" // for Cr3Decoder
+#include "decoders/RawDecoder.h" // for RawDecoder
+#include "io/ByteStream.h" // for ByteStream
+#include "io/Endianness.h" // for Endianness::big
+#include "parsers/IsoMParserException.h" // for ThrowIPE
+
+namespace rawspeed {
+
+IsoMParser::IsoMParser(const Buffer& inputData) : RawParser(inputData) {}
+
+void IsoMParser::parseData() {
+ ByteStream bs(DataBuffer(mInput, Endianness::unknown));
+
+ // The 'ISO base media file format' is big-endian.
+ bs.setByteOrder(Endianness::big);
+
+ // *Everything* is the box.
+ auto box = std::make_unique(&bs);
+ // It should have consumed all of the buffer.
+ assert(bs.getRemainSize() == 0);
+
+ box->parse();
+
+ rootBox = std::move(box);
+}
+
+std::unique_ptr IsoMParser::getDecoder(const CameraMetaData* meta) {
+ if (!rootBox)
+ parseData();
+
+ if (Cr3Decoder::isAppropriateDecoder(*rootBox))
+ return std::make_unique(std::move(rootBox), mInput);
+
+ ThrowIPE("No decoder found. Sorry.");
+}
+
+} // namespace rawspeed
diff --git a/src/librawspeed/parsers/IsoMParser.h b/src/librawspeed/parsers/IsoMParser.h
new file mode 100644
index 000000000..677f858fc
--- /dev/null
+++ b/src/librawspeed/parsers/IsoMParser.h
@@ -0,0 +1,48 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "parsers/RawParser.h" // for RawParser
+#include "tiff/IsoMBox.h" // for IsoMRootBox
+#include // for unique_ptr
+
+namespace rawspeed {
+
+class Buffer;
+
+class RawDecoder;
+
+class CameraMetaData;
+
+class IsoMParser final : public RawParser {
+ std::unique_ptr rootBox;
+
+ void parseData();
+
+public:
+ explicit IsoMParser(const Buffer& input);
+
+ std::unique_ptr
+ getDecoder(const CameraMetaData* meta = nullptr) override;
+};
+
+} // namespace rawspeed
diff --git a/src/librawspeed/parsers/IsoMParserException.h b/src/librawspeed/parsers/IsoMParserException.h
new file mode 100644
index 000000000..4a7420678
--- /dev/null
+++ b/src/librawspeed/parsers/IsoMParserException.h
@@ -0,0 +1,39 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "common/RawspeedException.h" // for ThrowExceptionHelper
+#include "parsers/RawParserException.h" // for ThrowRPE, RawParserException
+#include
+
+namespace rawspeed {
+
+class IsoMParserException final : public RawParserException {
+public:
+ explicit IsoMParserException(const std::string& msg)
+ : RawParserException(msg.c_str()) {}
+ explicit IsoMParserException(const char* msg) : RawParserException(msg) {}
+};
+
+#define ThrowIPE(...) \
+ ThrowExceptionHelper(rawspeed::IsoMParserException, __VA_ARGS__)
+
+} // namespace rawspeed
diff --git a/src/librawspeed/parsers/RawParser.cpp b/src/librawspeed/parsers/RawParser.cpp
index dbc294b35..e227f64d5 100644
--- a/src/librawspeed/parsers/RawParser.cpp
+++ b/src/librawspeed/parsers/RawParser.cpp
@@ -3,6 +3,7 @@
Copyright (C) 2009-2014 Klaus Post
Copyright (C) 2017 Axel Waggershauser
+ Copyright (C) 2018 Roman Lebedev
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -31,6 +32,8 @@
#include "parsers/CiffParserException.h" // for CiffParserException
#include "parsers/FiffParser.h" // for FiffParser
#include "parsers/FiffParserException.h" // for FiffParserException
+#include "parsers/IsoMParser.h" // for IsoMParser
+#include "parsers/IsoMParserException.h" // for IsoMParserException
#include "parsers/TiffParser.h" // for TiffParser
#include "parsers/TiffParserException.h" // for TiffParserException
@@ -77,6 +80,13 @@ std::unique_ptr RawParser::getDecoder(const CameraMetaData* meta) {
} catch (const CiffParserException&) {
}
+ // ISO Media
+ try {
+ IsoMParser p(mInput);
+ return p.getDecoder(meta);
+ } catch (IsoMParserException&) {
+ }
+
// Detect camera on filesize (CHDK).
if (meta != nullptr && meta->hasChdkCamera(mInput.getSize())) {
const Camera* c = meta->getChdkCamera(mInput.getSize());
diff --git a/src/librawspeed/tiff/CMakeLists.txt b/src/librawspeed/tiff/CMakeLists.txt
index 6c2f6d417..1015fb0ba 100644
--- a/src/librawspeed/tiff/CMakeLists.txt
+++ b/src/librawspeed/tiff/CMakeLists.txt
@@ -9,6 +9,8 @@ FILE(GLOB SOURCES
"CiffIFD.cpp"
"CiffIFD.h"
"CiffTag.h"
+ "IsoMBox.cpp"
+ "IsoMBox.h"
)
target_sources(rawspeed PRIVATE
diff --git a/src/librawspeed/tiff/IsoMBox.cpp b/src/librawspeed/tiff/IsoMBox.cpp
new file mode 100644
index 000000000..0b9459743
--- /dev/null
+++ b/src/librawspeed/tiff/IsoMBox.cpp
@@ -0,0 +1,619 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "tiff/IsoMBox.h"
+#include "common/NORangesSet.h" // for NORangesSet
+#include "io/Buffer.h" // for Buffer::size_type
+#include "parsers/IsoMParserException.h" // for ThrowIPE, IsoMParserException
+#include // for find, generate_n
+#include // for assert
+#include // for memcmp
+#include // for numeric_limits
+#include // for pair
+
+namespace rawspeed {
+
+
+// The ODR-definitions
+
+const FourCharStr IsoMBoxTypes::ftyp;
+const FourCharStr IsoMBoxTypes::co64;
+const FourCharStr IsoMBoxTypes::stsz;
+const FourCharStr IsoMBoxTypes::stsc;
+const FourCharStr IsoMBoxTypes::stsd;
+const FourCharStr IsoMBoxTypes::stbl;
+const FourCharStr IsoMBoxTypes::url;
+const FourCharStr IsoMBoxTypes::dref;
+const FourCharStr IsoMBoxTypes::dinf;
+const FourCharStr IsoMBoxTypes::minf;
+const FourCharStr IsoMBoxTypes::mdia;
+const FourCharStr IsoMBoxTypes::trak;
+const FourCharStr IsoMBoxTypes::moov;
+const FourCharStr IsoMBoxTypes::mdat;
+
+const FourCharStr IsoMBoxTypes::uuid;
+
+
+// Base-level lexing/parsing.
+
+AbstractIsoMBox::AbstractIsoMBox(ByteStream* bs) {
+ const auto origPos = bs->getPosition();
+
+ // This is the size of this whole box, starting from the origPos.
+ const auto boxSize = bs->getU32();
+
+ boxType = FourCharStr(bs->getU32());
+
+ if (boxSize == 0) {
+ bs->setPosition(origPos);
+ // Rest is the whole box.
+ data = bs->getStream(bs->getRemainSize());
+ } else if (boxSize != 1) {
+ bs->setPosition(origPos);
+ assert(bs->getRemainSize() >= boxSize);
+ // The good case, this is the size of the box.
+ data = bs->getStream(boxSize);
+ } else {
+ // Meh, the ugly case :/
+ assert(boxSize == 1);
+ const auto largeSize = bs->get();
+
+ // The rawspeed::Buffer is 32-bit, so even we somehow get here with valid
+ // more-than 32-bit-sized box, we can't do anything about it.
+ // We have to handle this explicitly because else 64-bit will get truncated
+ // to 32-bit without us noticing, and nothing good will happen next.
+ if (largeSize > std::numeric_limits::max())
+ ThrowIPE("IsoM Box uses largesize which does not fit into 32-bits");
+
+ bs->setPosition(origPos);
+ assert(bs->getRemainSize() >= largeSize);
+ data = bs->getStream(static_cast(largeSize));
+ data.skipBytes(8); // skip the largeSize,
+ }
+
+ data.skipBytes(8); // already read those before in any case
+
+ if (FourCharStr({'u', 'u', 'i', 'd'}) == boxType) {
+ const auto userTypeBs = data.getBuffer(16);
+ std::copy(userTypeBs.begin(), userTypeBs.end(), userType.begin());
+ }
+}
+
+void IsoMContainer::lexBox() { boxes.emplace_back(&cData); }
+
+void IsoMContainer::lexSubBoxes() {
+ // A box is a series of boxes.
+ while (cData.getRemainSize() > 0)
+ lexBox();
+ // There is nothing else left after boxes.
+ assert(cData.getRemainSize() == 0);
+}
+
+IsoMContainer::IsoMContainer(ByteStream* bs)
+ : cData(bs->getStream(bs->getRemainSize())) {
+ lexSubBoxes();
+ // There is nothing else left after boxes.
+ assert(cData.getRemainSize() == 0);
+}
+
+const AbstractIsoMBox&
+IsoMContainer::getBox(const AbstractIsoMBox::UuidType& uuid) const {
+ for(const auto& box : boxes) {
+ if(uuid == box.userType) {
+ return box;
+ }
+ }
+ ThrowIPE("Requested box UUID not found");
+}
+
+
+
+// FileType box parsing.
+
+const std::array IsoMFileTypeBox::supportedBrands;
+IsoMFileTypeBox::operator bool() const {
+ if (std::find(supportedBrands.begin(), supportedBrands.end(), majorBrand) ==
+ supportedBrands.end())
+ ThrowIPE("Unsupported major brand: %s", majorBrand.str().c_str());
+
+ bool isComp = false;
+ for (const auto& compatibleBrand : compatibleBrands) {
+ isComp = std::find(supportedBrands.begin(), supportedBrands.end(),
+ compatibleBrand) != supportedBrands.end();
+ if (isComp)
+ break;
+ }
+ if (!isComp)
+ ThrowIPE("No intersection between compatibleBrands and supported brands");
+
+ return true; // Supported!
+}
+
+IsoMFileTypeBox::IsoMFileTypeBox(const AbstractIsoMBox& base) : BaseBox(base) {
+ majorBrand = FourCharStr(data.getU32());
+ minorVersion = data.getU32();
+ while (data.getRemainSize() > 0)
+ compatibleBrands.emplace_back(data.getU32());
+ // There is nothing else left.
+ assert(data.getRemainSize() == 0);
+
+ // Validate.
+ operator bool();
+}
+
+// SampleDescription box parsing.
+
+IsoMSampleDescriptionBox::SampleEntry::SampleEntry(ByteStream* bs)
+ : AbstractIsoMBox(bs) {
+ for (auto& c : reserved)
+ c = data.getByte();
+ dataReferenceIndex = data.getU16();
+}
+
+IsoMSampleDescriptionBox::operator bool() const {
+ if (dscs.size() != 1)
+ ThrowIPE("Unexpected entry count: %zu", dscs.size());
+
+ for (const auto& dsc : dscs) {
+ if (dsc.dataReferenceIndex != 1)
+ ThrowIPE("Unexpected data reference index: %u", dsc.dataReferenceIndex);
+ }
+
+ return true; // Supported!
+}
+
+IsoMSampleDescriptionBox::IsoMSampleDescriptionBox(const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ const auto entryCount = data.getU32();
+
+ // Can't check/reserve entryCount.
+ std::generate_n(std::back_inserter(dscs), entryCount,
+ [this]() { return SampleEntry(&data); });
+ assert(dscs.size() == entryCount);
+
+ // Validate.
+ operator bool();
+}
+
+
+
+IsoMSampleToChunkBox::operator bool() const {
+ if (dscs.size() != 1)
+ ThrowIPE("Unexpected entry count: %zu", dscs.size());
+
+ for (const auto& dsc : dscs) {
+ if (dsc.firstChunk != 1)
+ ThrowIPE("Unexpected first chunk: %u", dsc.firstChunk);
+ if (dsc.samplesPerChunk != 1)
+ ThrowIPE("Unexpected samples per chunk: %u", dsc.samplesPerChunk);
+ if (dsc.sampleDescriptionIndex != 1) {
+ ThrowIPE("Unexpected sample description index: %u",
+ dsc.sampleDescriptionIndex);
+ }
+ }
+
+ return true; // Supported!
+}
+
+IsoMSampleToChunkBox::IsoMSampleToChunkBox(const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ const auto entryCount = data.getU32();
+
+ (void)data.check(entryCount, 3 * 4);
+ dscs.reserve(entryCount);
+ std::generate_n(std::back_inserter(dscs), entryCount, [this]() {
+ Dsc d;
+ d.firstChunk = data.getU32();
+ d.samplesPerChunk = data.getU32();
+ d.sampleDescriptionIndex = data.getU32();
+ return d;
+ });
+ assert(dscs.size() == entryCount);
+
+ // Validate.
+ operator bool();
+}
+
+// SampleSize box parsing.
+
+IsoMSampleSizeBox::operator bool() const {
+ if (chunkSizes.empty())
+ ThrowIPE("No chunk sizes found");
+
+ // The actual validation of these values will happen
+ // during parsing of moov box.
+
+ return true; // Supported!
+}
+
+IsoMSampleSizeBox::IsoMSampleSizeBox(const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ const auto sampleSize = data.getU32();
+ const auto sampleCount = data.getU32();
+
+ if (sampleSize == 0) {
+ for(uint32_t i = 0; i < sampleCount; ++i) {
+ chunkSizes.emplace_back(data.getU32());
+ }
+ } else {
+ // It's the only sample size and it is stored
+ // in the sampleSize directly.
+ chunkSizes.emplace_back(sampleSize);
+ }
+
+ // Validate.
+ operator bool();
+}
+
+// ChunkLargeOffset box parsing.
+
+IsoMChunkLargeOffsetBox::operator bool() const {
+ if (chunkOffsets.empty())
+ ThrowIPE("No chunk offsets found");
+
+ // The actual validation of these values will happen
+ // during parsing of moov box.
+
+ return true; // Supported!
+}
+
+IsoMChunkLargeOffsetBox::IsoMChunkLargeOffsetBox(const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ const auto entryCount = data.getU32();
+ (void)data.check(entryCount, 8);
+
+ if (entryCount != 1)
+ ThrowIPE("Don't know how to handle co64 box with %u entries", entryCount);
+
+ chunkOffsets.reserve(entryCount);
+ std::generate_n(
+ std::back_inserter(chunkOffsets), entryCount,
+ [this]() -> Buffer::size_type {
+ const auto largeSize = data.get();
+
+ // The rawspeed::Buffer is 32-bit, so even we somehow get here with
+ // valid more-than 32-bit-sized box, we can't do anything about it. We
+ // have to handle this explicitly because else 64-bit will get truncated
+ // to 32-bit without us noticing, and nothing good will happen next.
+ if (largeSize > std::numeric_limits::max())
+ ThrowIPE("IsoM Box uses largesize which does not fit into 32-bits");
+ return static_cast(largeSize);
+ });
+ assert(chunkOffsets.size() == entryCount);
+ // Could still have some padding bytes left, but don't care.
+
+ // Validate.
+ operator bool();
+}
+
+// Sample Table box handling.
+
+void IsoMSampleTableBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMSampleDescriptionBox::BoxType == box.boxType) {
+ if (stsd)
+ ThrowIPE("duplicate stsd box found.");
+ stsd = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMSampleToChunkBox::BoxType == box.boxType) {
+ if (stsc)
+ ThrowIPE("duplicate stsc box found.");
+ stsc = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMSampleSizeBox::BoxType == box.boxType) {
+ if (stsz)
+ ThrowIPE("duplicate stsz box found.");
+ stsz = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMChunkLargeOffsetBox::BoxType == box.boxType) {
+ if (co64)
+ ThrowIPE("duplicate co64 box found.");
+ co64 = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+IsoMSampleTableBox::operator bool() const {
+ if (!stsd)
+ ThrowIPE("no stsd box found.");
+ if (!stsc)
+ ThrowIPE("no stsc box found.");
+ if (!stsz)
+ ThrowIPE("no stsz box found.");
+ if (!co64)
+ ThrowIPE("no co64 box found.");
+
+ if (stsz->chunkSizes.size() != co64->chunkOffsets.size())
+ ThrowIPE("Mismatch in chunk offset and size count.");
+ if (stsc->dscs.size() != co64->chunkOffsets.size())
+ ThrowIPE("Mismatch in stsc entry count and chunk offset count.");
+ if (stsc->dscs.size() != stsd->dscs.size())
+ ThrowIPE("Mismatch in stsc entry count and stsd entry count.");
+
+ return true; // OK!
+}
+
+// DataReference box parsing.
+
+IsoMDataReferenceBox::IsoMDataEntryUrlBox::operator bool() const {
+ if (flags != static_cast(Flags::SelfContained))
+ ThrowIPE("Unexpected flags: %u; entry is not self-contained", flags);
+
+ return true; // Supported!
+}
+
+IsoMDataReferenceBox::IsoMDataEntryUrlBox::IsoMDataEntryUrlBox(
+ const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ // Validate.
+ operator bool();
+}
+
+IsoMDataReferenceBox::operator bool() const {
+ if (entries.size() != 1)
+ ThrowIPE("Unexpected entry count: %zu", entries.size());
+
+ return true; // Supported!
+}
+
+IsoMDataReferenceBox::IsoMDataReferenceBox(const AbstractIsoMBox& base)
+ : IsoMFullBox(base) {
+ const auto entryCount = data.getU32();
+
+ for (auto entry = 1U; entry <= entryCount; entry++) {
+ auto box = AbstractIsoMBox(&data);
+ if (IsoMDataEntryUrlBox::BoxType == box.boxType) {
+ entries.emplace_back(box);
+ entries.back().parse();
+ }
+ }
+
+ // Validate.
+ operator bool();
+}
+
+// Data Information box handling.
+
+void IsoMDataInformationBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMDataReferenceBox::BoxType == box.boxType) {
+ if (dref)
+ ThrowIPE("duplicate dref box found.");
+ dref = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+IsoMDataInformationBox::operator bool() const {
+ if (!dref)
+ ThrowIPE("no dref box found.");
+
+ return true; // OK!
+}
+
+// Media Information box handling.
+
+void IsoMMediaInformationBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMDataInformationBox::BoxType == box.boxType) {
+ if (dinf)
+ ThrowIPE("duplicate dinf box found.");
+ dinf = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMSampleTableBox::BoxType == box.boxType) {
+ if (stbl)
+ ThrowIPE("duplicate stbl box found.");
+ stbl = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+IsoMMediaInformationBox::operator bool() const {
+ if (!dinf)
+ ThrowIPE("no dinf box found.");
+ if (!stbl)
+ ThrowIPE("no stbl box found.");
+
+ if (dinf->dref->entries.size() != stbl->stsd->dscs.size())
+ ThrowIPE("Mismatch in dref entry count and stsd entry count.");
+
+ return true; // OK!
+}
+
+// Media box handling.
+
+void IsoMMediaBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMMediaInformationBox::BoxType == box.boxType) {
+ if (minf)
+ ThrowIPE("duplicate minf box found.");
+ minf = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+IsoMMediaBox::operator bool() const {
+ if (!minf)
+ ThrowIPE("no minf box found.");
+
+ return true; // OK!
+}
+
+// Track box handling.
+
+void IsoMTrackBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMMediaBox::BoxType == box.boxType) {
+ if (mdia)
+ ThrowIPE("duplicate mdia box found.");
+ mdia = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+}
+
+IsoMTrackBox::operator bool() const {
+ if (!mdia)
+ ThrowIPE("no mdia box found.");
+
+ return true; // OK!
+}
+
+// Movie box handling.
+
+void IsoMMovieBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMTrackBox::BoxType == box.boxType) {
+ tracks.emplace_back(box);
+ tracks.back().parse();
+ return;
+ }
+}
+
+IsoMMovieBox::operator bool() const {
+ if (tracks.empty())
+ ThrowIPE("no track boxes found.");
+
+
+ return true; // OK!
+}
+
+// Media Data box handling.
+
+IsoMMediaDataBox::operator bool() const {
+ if (chunks.empty())
+ ThrowIPE("no chunks found.");
+
+ return true; // OK!
+}
+
+void IsoMMediaDataBox::parse(IsoMRootBox* root) {
+ assert(root);
+
+ // Visit each sample (offset+size pair) in each track.
+ auto forEachChunk = [root](auto fun) {
+ for (const auto& track : root->moov()->tracks) {
+ auto& stbl = track.mdia->minf->stbl;
+ const auto& stsz = stbl->stsz;
+ const auto& co64 = stbl->co64;
+ assert(stsz->chunkSizes.size() == co64->chunkOffsets.size());
+ const auto numChunks = stsz->chunkSizes.size();
+ stbl->chunks.reserve(numChunks);
+ for (auto chunk = 0U; chunk < numChunks; chunk++) {
+ fun(co64->chunkOffsets[chunk], stsz->chunkSizes[chunk],
+ std::back_inserter(stbl->chunks));
+ }
+ }
+ };
+
+ const unsigned numChunks = [&forEachChunk]() -> unsigned {
+ unsigned i = 0;
+ // Just count them all.
+ forEachChunk(
+ [&i](Buffer::size_type, Buffer::size_type,
+ std::back_insert_iterator>) {
+ i++;
+ });
+ return i;
+ }();
+ chunks.reserve(numChunks);
+
+ // chunk legality checks
+ NORangesSet clc;
+
+ forEachChunk(
+ [root, &clc, this](
+ Buffer::size_type offset, Buffer::size_type count,
+ std::back_insert_iterator> stblChunk) {
+ // The offset is global to the file (eww, ugh!).
+ const auto chunk = root->cData.getSubStream(offset, count);
+ // Is it actually in the mdat box?
+ if (!RangesAreNested(mData, chunk))
+ ThrowIPE("Chunk is not in the mdat box.");
+ // Does it overlap with any previous chunk?
+ if (!clc.insert(chunk))
+ ThrowIPE("Two chunks overlap.");
+ // OK!
+ chunks.emplace_back(chunk);
+ stblChunk = &(chunks.back());
+ });
+ assert(chunks.size() == numChunks);
+
+ // Validate.
+ operator bool();
+}
+
+// The handling of the root container.
+
+void IsoMRootBox::parseBox(const AbstractIsoMBox& box) {
+ if (IsoMFileTypeBox::BoxType == box.boxType) {
+ if (ftypBox)
+ ThrowIPE("duplicate ftyp box found.");
+ ftypBox = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMMovieBox::BoxType == box.boxType) {
+ if (!ftypBox)
+ ThrowIPE("no ftyp box found yet.");
+ if (moovBox)
+ ThrowIPE("duplicate moov box found.");
+ moovBox = AbstractIsoMBox::ParseBox(box);
+ return;
+ }
+ if (IsoMMediaDataBox::BoxType == box.boxType) {
+ if (!moovBox)
+ ThrowIPE("no moov box found yet.");
+ if (mdatBox)
+ ThrowIPE("duplicate mdat box found.");
+ mdatBox = AbstractIsoMBox::ParseBox(box, this);
+ return;
+ }
+}
+
+IsoMRootBox::operator bool() const {
+ if (!ftypBox)
+ ThrowIPE("ftyp box not found.");
+ if (!moovBox)
+ ThrowIPE("moov box not found.");
+ if (!mdatBox)
+ ThrowIPE("mdat box not found.");
+
+ return true; // OK!
+}
+
+const std::unique_ptr& IsoMRootBox::ftyp() const {
+ if(ftypBox)
+ return ftypBox;
+ else
+ ThrowIPE("ftyp box not available");
+}
+const std::unique_ptr& IsoMRootBox::moov() const {
+ if(moovBox)
+ return moovBox;
+ else
+ ThrowIPE("moov box not available");
+}
+const std::unique_ptr& IsoMRootBox::mdat() const {
+ if(mdatBox)
+ return mdatBox;
+ else
+ ThrowIPE("mdat box not available");
+}
+
+
+} // namespace rawspeed
diff --git a/src/librawspeed/tiff/IsoMBox.h b/src/librawspeed/tiff/IsoMBox.h
new file mode 100644
index 000000000..bcecc2c79
--- /dev/null
+++ b/src/librawspeed/tiff/IsoMBox.h
@@ -0,0 +1,412 @@
+/*
+ RawSpeed - RAW file decoder.
+
+ Copyright (C) 2018 Roman Lebedev
+ Copyright (C) 2021 Daniel Vogelbacher
+
+ This library 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 2 of the License, or (at your option) any later version.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "common/Common.h" // for uint32
+#include "io/ByteStream.h" // for ByteStream
+#include "io/Endianness.h" // for getBE
+#include "parsers/IsoMParserException.h" // for ThrowIPE
+#include // for array
+#include // for assert
+#include // for memcpy, strncpy
+#include // for initializer_list
+#include // for unique_ptr
+#include // for string
+#include // for pair
+#include // for vector
+
+namespace rawspeed {
+
+struct FourCharStr final {
+ using value_type = uint32_t;
+ static constexpr auto num_chars = sizeof(value_type) / sizeof(char);
+ static_assert(num_chars == 4, "wanted 4 chars specifically");
+
+ std::array data{};
+
+ FourCharStr() = default;
+
+ explicit constexpr FourCharStr(decltype(data) data_) : data(data_) {}
+
+ explicit FourCharStr(value_type data_) {
+ // Turn the unsigned integer into a 'string'
+ data_ = getBE(&data_);
+ std::memcpy(data.data(), &data_, num_chars);
+ }
+
+ explicit operator std::string() const {
+ return std::string(data.begin(), data.end());
+ }
+
+ std::string str() const { return static_cast(*this); }
+};
+inline bool operator==(const FourCharStr& lhs, const FourCharStr& rhs) {
+ return lhs.data == rhs.data;
+}
+inline bool operator!=(const FourCharStr& lhs, const FourCharStr& rhs) {
+ return !operator==(lhs, rhs);
+}
+
+// The base foundation of the ISO Base Media File Format.
+
+class IsoMRootBox;
+struct IsoMMediaDataBox;
+
+// The most basic box.
+struct AbstractIsoMBox {
+ using UuidType = std::array;
+
+ ByteStream data;
+
+ FourCharStr boxType;
+ UuidType userType{}; // when boxType == "uuid"
+
+ AbstractIsoMBox() = default;
+
+ explicit AbstractIsoMBox(ByteStream* bs);
+
+ template
+ static std::unique_ptr ParseBox(const AbstractIsoMBox& base,
+ IsoMRootBox* root = nullptr) {
+ auto box = std::make_unique(base);
+ box->parse(root);
+ return box;
+ }
+};
+
+struct IsoMBoxTypes final {
+ static constexpr FourCharStr ftyp = FourCharStr({'f', 't', 'y', 'p'});
+ static constexpr FourCharStr co64 = FourCharStr({'c', 'o', '6', '4'});
+ static constexpr FourCharStr stsz = FourCharStr({'s', 't', 's', 'z'});
+ static constexpr FourCharStr stsc = FourCharStr({'s', 't', 's', 'c'});
+ static constexpr FourCharStr stsd = FourCharStr({'s', 't', 's', 'd'});
+ static constexpr FourCharStr stbl = FourCharStr({'s', 't', 'b', 'l'});
+ static constexpr FourCharStr url = FourCharStr({'u', 'r', 'l', ' '});
+ static constexpr FourCharStr dref = FourCharStr({'d', 'r', 'e', 'f'});
+ static constexpr FourCharStr dinf = FourCharStr({'d', 'i', 'n', 'f'});
+ static constexpr FourCharStr minf = FourCharStr({'m', 'i', 'n', 'f'});
+ static constexpr FourCharStr mdia = FourCharStr({'m', 'd', 'i', 'a'});
+ static constexpr FourCharStr trak = FourCharStr({'t', 'r', 'a', 'k'});
+ static constexpr FourCharStr moov = FourCharStr({'m', 'o', 'o', 'v'});
+ static constexpr FourCharStr mdat = FourCharStr({'m', 'd', 'a', 't'});
+
+ static constexpr FourCharStr uuid = FourCharStr({'u', 'u', 'i', 'd'});
+};
+
+// The basic container.
+class IsoMContainer {
+ void lexBox();
+ void lexSubBoxes();
+
+protected:
+ ByteStream cData;
+
+ // These are specific for each container, and must be implemented.
+ virtual void parseBox(const AbstractIsoMBox& box) = 0;
+ virtual explicit operator bool() const = 0;
+
+ friend struct IsoMMediaDataBox; // needs access to cData
+
+public:
+ std::vector boxes;
+
+ IsoMContainer() = default;
+ virtual ~IsoMContainer() = default;
+
+ explicit IsoMContainer(ByteStream* bs);
+
+ const AbstractIsoMBox& getBox(const AbstractIsoMBox::UuidType& uuid) const;
+
+ // !!! DO NOT CALL FROM CONSTRUCTOR !!!
+ void parse(IsoMRootBox* root = nullptr) {
+ for (const auto& box : boxes)
+ parseBox(box);
+ operator bool();
+ }
+};
+
+// No further boxes shall be constructible from ByteStream!
+
+// The box that knows what it is.
+template
+struct IsoMBox : public AbstractIsoMBox {
+ using BaseBox = IsoMBox;
+
+ static constexpr const FourCharStr /* IsoMBoxTypes::* */& BoxType = type;
+
+ IsoMBox() = default;
+
+ explicit IsoMBox(const AbstractIsoMBox& base) : AbstractIsoMBox(base) {
+ assert(BoxType == boxType);
+ if (BoxType != boxType) {
+ ThrowIPE("Unexpected box type, got: '%s', expected: '%s'",
+ BoxType.str().c_str(), boxType.str().c_str());
+ }
+ }
+};
+
+template
+struct IsoMFullBox : public IsoMBox {
+ using BaseBox = IsoMBox;
+
+ uint8_t version;
+ uint32_t flags : 24;
+
+ uint8_t expectedVersion() const { return 0; }
+
+ IsoMFullBox() = default;
+ virtual ~IsoMFullBox() = default;
+
+ explicit IsoMFullBox(const AbstractIsoMBox& base) : IsoMBox(base) {
+ // Highest 8 bits - version
+ version = BaseBox::data.peekByte();
+ // The rest, low 24 bits - flags
+ flags = BaseBox::data.getU32() & ((1U << 24U) - 1U);
+
+ if (expectedVersion() != version)
+ ThrowIPE("Unexpected version of FullBox - %u", expectedVersion());
+ }
+
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+template
+class IsoMContainerBox : public IsoMBox, public IsoMContainer {
+public:
+ using BaseBox = IsoMBox;
+ using BaseContainer = IsoMContainerBox;
+
+ IsoMContainerBox() = default;
+
+ explicit IsoMContainerBox(const AbstractIsoMBox& base)
+ : IsoMBox(base), IsoMContainer(&(BaseBox::data)) {}
+};
+
+
+template
+class IsoMContainerFullBox : public IsoMFullBox, public IsoMContainer {
+public:
+ using BaseBox = IsoMFullBox;
+ using BaseContainer = IsoMContainerBox;
+
+ IsoMContainerFullBox() = default;
+
+ explicit IsoMContainerFullBox(const AbstractIsoMBox& base)
+ : IsoMFullBox(base), IsoMContainer(&(BaseBox::data)) {}
+};
+
+// The actual boxes
+
+struct IsoMFileTypeBox final : public IsoMBox {
+ static constexpr std::array supportedBrands = {
+ FourCharStr({'c', 'r', 'x', ' '})};
+
+ FourCharStr majorBrand;
+ uint32_t minorVersion;
+ std::vector compatibleBrands;
+
+ explicit IsoMFileTypeBox(const AbstractIsoMBox& base);
+
+ // Validate.
+ explicit operator bool() const;
+
+ void parse(IsoMRootBox* root = nullptr) {}
+};
+
+struct IsoMSampleDescriptionBox final : public IsoMFullBox {
+ struct SampleEntry final : public AbstractIsoMBox {
+ std::array reserved;
+ uint16_t dataReferenceIndex;
+
+ SampleEntry() = default;
+
+ explicit SampleEntry(ByteStream* bs);
+ };
+
+ std::vector dscs;
+
+ explicit IsoMSampleDescriptionBox(const AbstractIsoMBox& base);
+
+ // Validate.
+ explicit operator bool() const;
+};
+
+struct IsoMSampleToChunkBox final : public IsoMFullBox {
+ struct Dsc final {
+ uint32_t firstChunk;
+ uint32_t samplesPerChunk;
+ uint32_t sampleDescriptionIndex;
+ };
+
+ std::vector dscs;
+
+ explicit IsoMSampleToChunkBox(const AbstractIsoMBox& base);
+
+ // Validate.
+ explicit operator bool() const;
+};
+
+struct IsoMSampleSizeBox final : public IsoMFullBox {
+ std::vector chunkSizes;
+
+ explicit IsoMSampleSizeBox(const AbstractIsoMBox& base);
+
+ // Validate.
+ explicit operator bool() const;
+};
+
+struct IsoMChunkLargeOffsetBox final : public IsoMFullBox {
+ std::vector chunkOffsets;
+
+ explicit IsoMChunkLargeOffsetBox(const AbstractIsoMBox& base);
+
+ // Validate.
+ explicit operator bool() const;
+};
+
+class IsoMSampleTableBox final : public IsoMContainerBox {
+ void parseBox(const AbstractIsoMBox& box) override;
+ explicit operator bool() const override;
+
+public:
+ std::unique_ptr stsd;
+ std::unique_ptr stsc;
+ std::unique_ptr stsz;
+ std::unique_ptr co64;
+
+ // will be filed by IsoMMediaDataBox::parse()
+ std::vector chunks;
+
+ explicit IsoMSampleTableBox(const AbstractIsoMBox& base)
+ : IsoMContainerBox(base) {}
+};
+
+struct IsoMDataReferenceBox final : public IsoMFullBox {
+ struct IsoMDataEntryUrlBox final : public IsoMFullBox {
+ enum class Flags : decltype(IsoMFullBox::flags) {
+ SelfContained = 0b1,
+ };
+
+ explicit IsoMDataEntryUrlBox(const AbstractIsoMBox& base);
+
+ // Validate.
+ explicit operator bool() const;
+ };
+
+ std::vector