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 entries; + + explicit IsoMDataReferenceBox(const AbstractIsoMBox& base); + + // Validate. + explicit operator bool() const; +}; + +class IsoMDataInformationBox final + : public IsoMContainerBox { + void parseBox(const AbstractIsoMBox& box) override; + explicit operator bool() const override; + +public: + std::unique_ptr dref; + + explicit IsoMDataInformationBox(const AbstractIsoMBox& base) + : IsoMContainerBox(base) {} +}; + +class IsoMMediaInformationBox final + : public IsoMContainerBox { + void parseBox(const AbstractIsoMBox& box) override; + explicit operator bool() const override; + +public: + std::unique_ptr dinf; + std::unique_ptr stbl; + + explicit IsoMMediaInformationBox(const AbstractIsoMBox& base) + : IsoMContainerBox(base) {} +}; + +class IsoMMediaBox final : public IsoMContainerBox { + void parseBox(const AbstractIsoMBox& box) override; + explicit operator bool() const override; + +public: + std::unique_ptr minf; + + explicit IsoMMediaBox(const AbstractIsoMBox& base) : IsoMContainerBox(base) {} +}; + +class IsoMTrackBox final : public IsoMContainerBox { + void parseBox(const AbstractIsoMBox& box) override; + explicit operator bool() const override; + +public: + std::unique_ptr mdia; + + explicit IsoMTrackBox(const AbstractIsoMBox& base) : IsoMContainerBox(base) {} +}; + +class IsoMMovieBox final : public IsoMContainerBox { + void parseBox(const AbstractIsoMBox& box) override; + explicit operator bool() const override; + +public: + std::vector tracks; + + explicit IsoMMovieBox(const AbstractIsoMBox& base) : IsoMContainerBox(base) {} +}; + +struct IsoMMediaDataBox final : public IsoMBox { + explicit IsoMMediaDataBox(const AbstractIsoMBox& base) + : BaseBox(base), + mData(BaseBox::data.getStream(BaseBox::data.getRemainSize())) {} + + ByteStream mData; + + // The actual slicing of mData. Derived from SampleTable box. + std::vector chunks; + + // Validate. + explicit operator bool() const; + + void parse(IsoMRootBox* root); +}; + +// The root box. It's just a container, and can only be created from ByteStream. + +class IsoMRootBox final : public IsoMContainer { + void parseBox(const AbstractIsoMBox& box) override; + explicit operator bool() const override; + +public: + std::unique_ptr ftypBox; + std::unique_ptr moovBox; + std::unique_ptr mdatBox; + + const std::unique_ptr& ftyp() const; + const std::unique_ptr& moov() const; + const std::unique_ptr& mdat() const; + + explicit IsoMRootBox(ByteStream* bs) : IsoMContainer(bs) {} +}; + +} // namespace rawspeed diff --git a/test/librawspeed/test/ExceptionsTest.cpp b/test/librawspeed/test/ExceptionsTest.cpp index 64f3fe0e8..7b8068dbe 100644 --- a/test/librawspeed/test/ExceptionsTest.cpp +++ b/test/librawspeed/test/ExceptionsTest.cpp @@ -1,7 +1,7 @@ /* RawSpeed - RAW file decoder. - Copyright (C) 2016-2017 Roman Lebedev + Copyright (C) 2016-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 @@ -25,10 +25,11 @@ #include "metadata/CameraMetadataException.h" // for CameraMetadataExceptio... #include "parsers/CiffParserException.h" // for CiffParserException (p... #include "parsers/FiffParserException.h" // for FiffParserException (p... -#include "parsers/RawParserException.h" // for RawParserException (pt... -#include "parsers/TiffParserException.h" // for TiffParserException (p... -#include // for exception -#include // for HasSubstr, ASSERT_THAT +#include "parsers/IsoMParserException.h" // for ThrowIPE, IsoMParserEx... +#include "parsers/RawParserException.h" // for ThrowRPE, RawParserEx... +#include "parsers/TiffParserException.h" // for ThrowTPE, TiffParserEx... +#include // IWYU pragma: keep +#include // for MakePredicateFormatter... #include // for Message, TestPartResult #include // for unique_ptr #include // for runtime_error @@ -38,6 +39,7 @@ using rawspeed::CiffParserException; using rawspeed::FiffParserException; using rawspeed::FileIOException; using rawspeed::IOException; +using rawspeed::IsoMParserException; using rawspeed::RawDecoderException; using rawspeed::RawParserException; using rawspeed::RawspeedException; @@ -91,13 +93,16 @@ template <> void* MetaHelper(const char* str) { ThrowFPE(FMT, str); } +template <> void* MetaHelper(const char* str) { + ThrowIPE(FMT, str); +} + template class ExceptionsTest : public testing::Test {}; -using Classes = - testing::Types; +using Classes = testing::Types< + RawspeedException, CameraMetadataException, CiffParserException, + FileIOException, IOException, RawDecoderException, TiffParserException, + FiffParserException, IsoMParserException, RawParserException>; TYPED_TEST_CASE(ExceptionsTest, Classes);