diff --git a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn index f94019cdce4..9bf9cc4d861 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn @@ -87,6 +87,7 @@ shared_library("LibGfx") { "ImageFormats/PNGWriter.cpp", "ImageFormats/PPMLoader.cpp", "ImageFormats/PortableFormatWriter.cpp", + "ImageFormats/QMArithmeticDecoder.cpp", "ImageFormats/QOILoader.cpp", "ImageFormats/QOIWriter.cpp", "ImageFormats/TGALoader.cpp", diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 459b53e72fd..fd05303534b 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index 54abc6c2746..52fe5b40aa0 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -60,6 +60,7 @@ set(SOURCES ImageFormats/PortableFormatWriter.cpp ImageFormats/PAMLoader.cpp ImageFormats/PPMLoader.cpp + ImageFormats/QMArithmeticDecoder.cpp ImageFormats/QOILoader.cpp ImageFormats/QOIWriter.cpp ImageFormats/TGALoader.cpp diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp index ea83d9d6c55..da9a1f6bd3c 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include // Spec: ITU-T_T_88__08_2018.pdf in the zip file here: @@ -27,203 +28,6 @@ namespace Gfx { -// Table E.1 – Qe values and probability estimation process -// See also E.1.2 Coding conventions and approximations -// and E.2.5 Probability estimation. -struct QeEntry { - u16 qe; // Sub-interval for the less probable symbol. - u16 nmps; // Next index if the more probable symbol is decoded - u16 nlps; // Next index if the less probable symbol is decoded - u16 switch_flag; // See second-to-last paragraph in E.1.2. -}; -constexpr auto qe_table = to_array({ - { 0x5601, 1, 1, 1 }, - { 0x3401, 2, 6, 0 }, - { 0x1801, 3, 9, 0 }, - { 0x0AC1, 4, 12, 0 }, - { 0x0521, 5, 29, 0 }, - { 0x0221, 38, 33, 0 }, - { 0x5601, 7, 6, 1 }, - { 0x5401, 8, 14, 0 }, - { 0x4801, 9, 14, 0 }, - { 0x3801, 10, 14, 0 }, - { 0x3001, 11, 17, 0 }, - { 0x2401, 12, 18, 0 }, - { 0x1C01, 13, 20, 0 }, - { 0x1601, 29, 21, 0 }, - { 0x5601, 15, 14, 1 }, - { 0x5401, 16, 14, 0 }, - { 0x5101, 17, 15, 0 }, - { 0x4801, 18, 16, 0 }, - { 0x3801, 19, 17, 0 }, - { 0x3401, 20, 18, 0 }, - { 0x3001, 21, 19, 0 }, - { 0x2801, 22, 19, 0 }, - { 0x2401, 23, 20, 0 }, - { 0x2201, 24, 21, 0 }, - { 0x1C01, 25, 22, 0 }, - { 0x1801, 26, 23, 0 }, - { 0x1601, 27, 24, 0 }, - { 0x1401, 28, 25, 0 }, - { 0x1201, 29, 26, 0 }, - { 0x1101, 30, 27, 0 }, - { 0x0AC1, 31, 28, 0 }, - { 0x09C1, 32, 29, 0 }, - { 0x08A1, 33, 30, 0 }, - { 0x0521, 34, 31, 0 }, - { 0x0441, 35, 32, 0 }, - { 0x02A1, 36, 33, 0 }, - { 0x0221, 37, 34, 0 }, - { 0x0141, 38, 35, 0 }, - { 0x0111, 39, 36, 0 }, - { 0x0085, 40, 37, 0 }, - { 0x0049, 41, 38, 0 }, - { 0x0025, 42, 39, 0 }, - { 0x0015, 43, 40, 0 }, - { 0x0009, 44, 41, 0 }, - { 0x0005, 45, 42, 0 }, - { 0x0001, 45, 43, 0 }, - { 0x5601, 46, 46, 0 }, -}); - -ErrorOr QMArithmeticDecoder::initialize(ReadonlyBytes data) -{ - QMArithmeticDecoder decoder { data }; - decoder.INITDEC(); - return decoder; -} - -bool QMArithmeticDecoder::get_next_bit(Context& context) -{ - CX = &context; - // Useful for comparing to Table H.1 – Encoder and decoder trace data. - // dbg("I={} MPS={} A={:#x} C={:#x} CT={} B={:#x}", I(CX), MPS(CX), A, C, CT, B()); - u8 D = DECODE(); - // dbgln(" -> D={}", D); - return D; -} - -u16 QMArithmeticDecoder::Qe(u16 index) { return qe_table[index].qe; } -u8 QMArithmeticDecoder::NMPS(u16 index) { return qe_table[index].nmps; } -u8 QMArithmeticDecoder::NLPS(u16 index) { return qe_table[index].nlps; } -u8 QMArithmeticDecoder::SWITCH(u16 index) { return qe_table[index].switch_flag; } - -u8 QMArithmeticDecoder::B(size_t offset) const -{ - // E.2.10 Minimization of the compressed data - // "the convention is used in the decoder that when a marker code is encountered, - // 1-bits (without bit stuffing) are supplied to the decoder until the coding interval is complete." - if (BP + offset >= m_data.size()) - return 0xFF; - return m_data[BP + offset]; -} - -void QMArithmeticDecoder::INITDEC() -{ - // E.3.5 Initialization of the decoder (INITDEC) - // Figure G.1 – Initialization of the software conventions decoder - - // "BP, the pointer to the compressed data, is initialized to BPST (pointing to the first compressed byte)." - auto const BPST = 0; - BP = BPST; - C = (B() ^ 0xFF) << 16; - - BYTEIN(); - - C = C << 7; - CT = CT - 7; - A = 0x8000; -} - -u8 QMArithmeticDecoder::DECODE() -{ - // E.3.2 Decoding a decision (DECODE) - // Figure G.2 – Decoding an MPS or an LPS in the software-conventions decoder - u8 D; - A = A - Qe(I(CX)); - if (C < ((u32)A << 16)) { // `(C_high < A)` in spec - if ((A & 0x8000) == 0) { - D = MPS_EXCHANGE(); - RENORMD(); - } else { - D = MPS(CX); - } - } else { - C = C - ((u32)A << 16); // `C_high = C_high - A` in spec - D = LPS_EXCHANGE(); - RENORMD(); - } - return D; -} - -u8 QMArithmeticDecoder::MPS_EXCHANGE() -{ - // Figure E.16 – Decoder MPS path conditional exchange procedure - u8 D; - if (A < Qe(I(CX))) { - D = 1 - MPS(CX); - if (SWITCH(I(CX)) == 1) { - MPS(CX) = 1 - MPS(CX); - } - I(CX) = NLPS(I(CX)); - } else { - D = MPS(CX); - I(CX) = NMPS(I(CX)); - } - return D; -} - -u8 QMArithmeticDecoder::LPS_EXCHANGE() -{ - // Figure E.17 – Decoder LPS path conditional exchange procedure - u8 D; - if (A < Qe(I(CX))) { - A = Qe(I(CX)); - D = MPS(CX); - I(CX) = NMPS(I(CX)); - } else { - A = Qe(I(CX)); - D = 1 - MPS(CX); - if (SWITCH(I(CX)) == 1) { - MPS(CX) = 1 - MPS(CX); - } - I(CX) = NLPS(I(CX)); - } - return D; -} - -void QMArithmeticDecoder::RENORMD() -{ - // E.3.3 Renormalization in the decoder (RENORMD) - // Figure E.18 – Decoder renormalization procedure - do { - if (CT == 0) - BYTEIN(); - A = A << 1; - C = C << 1; - CT = CT - 1; - } while ((A & 0x8000) == 0); -} - -void QMArithmeticDecoder::BYTEIN() -{ - // E.3.4 Compressed data input (BYTEIN) - // Figure G.3 – Inserting a new byte into the C register in the software-conventions decoder - if (B() == 0xFF) { - if (B(1) > 0x8F) { - CT = 8; - } else { - BP = BP + 1; - C = C + 0xFE00 - (B() << 9); - CT = 7; - } - } else { - BP = BP + 1; - C = C + 0xFF00 - (B() << 8); - CT = 8; - } -} - namespace JBIG2 { // Annex A, Arithmetic integer decoding procedure diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h index 3142a966b0b..1841ed8be6d 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h @@ -14,62 +14,6 @@ namespace Gfx { struct JBIG2LoadingContext; -// E.3 Arithmetic decoding procedure, but with the changes described in -// Annex G Arithmetic decoding procedure (software conventions). -// Exposed for testing. -class QMArithmeticDecoder { -public: - struct Context { - u8 I { 0 }; // Index I stored for context CX (E.2.4) - u8 is_mps { 0 }; // "More probable symbol" (E.1.1). 0 or 1. - }; - - static ErrorOr initialize(ReadonlyBytes data); - - bool get_next_bit(Context& context); - -private: - QMArithmeticDecoder(ReadonlyBytes data) - : m_data(data) - { - } - - ReadonlyBytes m_data; - - // The code below uses names from the spec, so that the algorithms look exactly like the flowcharts in the spec. - - // Abbreviations: - // "CX": "Context" (E.1) - // "D": "Decision" (as in "encoder input" / "decoder output") (E.1) - // "I(CX)": "Index I stored for context CX" (E.2.4) - // "MPS": "More probable symbol" (E.1.1) - // "LPS": "Less probable symbol" (E.1.1) - - void INITDEC(); - u8 DECODE(); // Returns a single decoded bit. - u8 MPS_EXCHANGE(); - u8 LPS_EXCHANGE(); - void RENORMD(); - void BYTEIN(); - - u8 B(size_t offset = 0) const; // Byte pointed to by BP. - size_t BP { 0 }; // Pointer into compressed data. - - // E.3.1 Decoder code register conventions - u32 C { 0 }; // Consists of u16 C_high, C_low. - u16 A { 0 }; // Current value of the fraction. Fixed precision; 0x8000 is equivalent to 0.75. - - u8 CT { 0 }; // Count of the number of bits in C. - - Context* CX { nullptr }; - static u8& I(Context* cx) { return cx->I; } - static u8& MPS(Context* cx) { return cx->is_mps; } - static u16 Qe(u16); - static u8 NMPS(u16); - static u8 NLPS(u16); - static u8 SWITCH(u16); -}; - class JBIG2ImageDecoderPlugin : public ImageDecoderPlugin { public: static bool sniff(ReadonlyBytes); diff --git a/Userland/Libraries/LibGfx/ImageFormats/QMArithmeticDecoder.cpp b/Userland/Libraries/LibGfx/ImageFormats/QMArithmeticDecoder.cpp new file mode 100644 index 00000000000..c8e927afc2f --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/QMArithmeticDecoder.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024, Nico Weber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Gfx { + +// Table E.1 – Qe values and probability estimation process +// See also E.1.2 Coding conventions and approximations +// and E.2.5 Probability estimation. +struct QeEntry { + u16 qe; // Sub-interval for the less probable symbol. + u16 nmps; // Next index if the more probable symbol is decoded + u16 nlps; // Next index if the less probable symbol is decoded + u16 switch_flag; // See second-to-last paragraph in E.1.2. +}; +constexpr auto qe_table = to_array({ + { 0x5601, 1, 1, 1 }, + { 0x3401, 2, 6, 0 }, + { 0x1801, 3, 9, 0 }, + { 0x0AC1, 4, 12, 0 }, + { 0x0521, 5, 29, 0 }, + { 0x0221, 38, 33, 0 }, + { 0x5601, 7, 6, 1 }, + { 0x5401, 8, 14, 0 }, + { 0x4801, 9, 14, 0 }, + { 0x3801, 10, 14, 0 }, + { 0x3001, 11, 17, 0 }, + { 0x2401, 12, 18, 0 }, + { 0x1C01, 13, 20, 0 }, + { 0x1601, 29, 21, 0 }, + { 0x5601, 15, 14, 1 }, + { 0x5401, 16, 14, 0 }, + { 0x5101, 17, 15, 0 }, + { 0x4801, 18, 16, 0 }, + { 0x3801, 19, 17, 0 }, + { 0x3401, 20, 18, 0 }, + { 0x3001, 21, 19, 0 }, + { 0x2801, 22, 19, 0 }, + { 0x2401, 23, 20, 0 }, + { 0x2201, 24, 21, 0 }, + { 0x1C01, 25, 22, 0 }, + { 0x1801, 26, 23, 0 }, + { 0x1601, 27, 24, 0 }, + { 0x1401, 28, 25, 0 }, + { 0x1201, 29, 26, 0 }, + { 0x1101, 30, 27, 0 }, + { 0x0AC1, 31, 28, 0 }, + { 0x09C1, 32, 29, 0 }, + { 0x08A1, 33, 30, 0 }, + { 0x0521, 34, 31, 0 }, + { 0x0441, 35, 32, 0 }, + { 0x02A1, 36, 33, 0 }, + { 0x0221, 37, 34, 0 }, + { 0x0141, 38, 35, 0 }, + { 0x0111, 39, 36, 0 }, + { 0x0085, 40, 37, 0 }, + { 0x0049, 41, 38, 0 }, + { 0x0025, 42, 39, 0 }, + { 0x0015, 43, 40, 0 }, + { 0x0009, 44, 41, 0 }, + { 0x0005, 45, 42, 0 }, + { 0x0001, 45, 43, 0 }, + { 0x5601, 46, 46, 0 }, +}); + +ErrorOr QMArithmeticDecoder::initialize(ReadonlyBytes data) +{ + QMArithmeticDecoder decoder { data }; + decoder.INITDEC(); + return decoder; +} + +bool QMArithmeticDecoder::get_next_bit(Context& context) +{ + CX = &context; + // Useful for comparing to Table H.1 – Encoder and decoder trace data. + // dbg("I={} MPS={} A={:#x} C={:#x} CT={} B={:#x}", I(CX), MPS(CX), A, C, CT, B()); + u8 D = DECODE(); + // dbgln(" -> D={}", D); + return D; +} + +u16 QMArithmeticDecoder::Qe(u16 index) { return qe_table[index].qe; } +u8 QMArithmeticDecoder::NMPS(u16 index) { return qe_table[index].nmps; } +u8 QMArithmeticDecoder::NLPS(u16 index) { return qe_table[index].nlps; } +u8 QMArithmeticDecoder::SWITCH(u16 index) { return qe_table[index].switch_flag; } + +u8 QMArithmeticDecoder::B(size_t offset) const +{ + // E.2.10 Minimization of the compressed data + // "the convention is used in the decoder that when a marker code is encountered, + // 1-bits (without bit stuffing) are supplied to the decoder until the coding interval is complete." + if (BP + offset >= m_data.size()) + return 0xFF; + return m_data[BP + offset]; +} + +void QMArithmeticDecoder::INITDEC() +{ + // E.3.5 Initialization of the decoder (INITDEC) + // Figure G.1 – Initialization of the software conventions decoder + + // "BP, the pointer to the compressed data, is initialized to BPST (pointing to the first compressed byte)." + auto const BPST = 0; + BP = BPST; + C = (B() ^ 0xFF) << 16; + + BYTEIN(); + + C = C << 7; + CT = CT - 7; + A = 0x8000; +} + +u8 QMArithmeticDecoder::DECODE() +{ + // E.3.2 Decoding a decision (DECODE) + // Figure G.2 – Decoding an MPS or an LPS in the software-conventions decoder + u8 D; + A = A - Qe(I(CX)); + if (C < ((u32)A << 16)) { // `(C_high < A)` in spec + if ((A & 0x8000) == 0) { + D = MPS_EXCHANGE(); + RENORMD(); + } else { + D = MPS(CX); + } + } else { + C = C - ((u32)A << 16); // `C_high = C_high - A` in spec + D = LPS_EXCHANGE(); + RENORMD(); + } + return D; +} + +u8 QMArithmeticDecoder::MPS_EXCHANGE() +{ + // Figure E.16 – Decoder MPS path conditional exchange procedure + u8 D; + if (A < Qe(I(CX))) { + D = 1 - MPS(CX); + if (SWITCH(I(CX)) == 1) { + MPS(CX) = 1 - MPS(CX); + } + I(CX) = NLPS(I(CX)); + } else { + D = MPS(CX); + I(CX) = NMPS(I(CX)); + } + return D; +} + +u8 QMArithmeticDecoder::LPS_EXCHANGE() +{ + // Figure E.17 – Decoder LPS path conditional exchange procedure + u8 D; + if (A < Qe(I(CX))) { + A = Qe(I(CX)); + D = MPS(CX); + I(CX) = NMPS(I(CX)); + } else { + A = Qe(I(CX)); + D = 1 - MPS(CX); + if (SWITCH(I(CX)) == 1) { + MPS(CX) = 1 - MPS(CX); + } + I(CX) = NLPS(I(CX)); + } + return D; +} + +void QMArithmeticDecoder::RENORMD() +{ + // E.3.3 Renormalization in the decoder (RENORMD) + // Figure E.18 – Decoder renormalization procedure + do { + if (CT == 0) + BYTEIN(); + A = A << 1; + C = C << 1; + CT = CT - 1; + } while ((A & 0x8000) == 0); +} + +void QMArithmeticDecoder::BYTEIN() +{ + // E.3.4 Compressed data input (BYTEIN) + // Figure G.3 – Inserting a new byte into the C register in the software-conventions decoder + if (B() == 0xFF) { + if (B(1) > 0x8F) { + CT = 8; + } else { + BP = BP + 1; + C = C + 0xFE00 - (B() << 9); + CT = 7; + } + } else { + BP = BP + 1; + C = C + 0xFF00 - (B() << 8); + CT = 8; + } +} + +} diff --git a/Userland/Libraries/LibGfx/ImageFormats/QMArithmeticDecoder.h b/Userland/Libraries/LibGfx/ImageFormats/QMArithmeticDecoder.h new file mode 100644 index 00000000000..0111fe84522 --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/QMArithmeticDecoder.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, Nico Weber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Gfx { + +// E.3 Arithmetic decoding procedure, but with the changes described in +// Annex G Arithmetic decoding procedure (software conventions). +// Exposed for testing. +class QMArithmeticDecoder { +public: + struct Context { + u8 I { 0 }; // Index I stored for context CX (E.2.4) + u8 is_mps { 0 }; // "More probable symbol" (E.1.1). 0 or 1. + }; + + static ErrorOr initialize(ReadonlyBytes data); + + bool get_next_bit(Context& context); + +private: + QMArithmeticDecoder(ReadonlyBytes data) + : m_data(data) + { + } + + ReadonlyBytes m_data; + + // The code below uses names from the spec, so that the algorithms look exactly like the flowcharts in the spec. + + // Abbreviations: + // "CX": "Context" (E.1) + // "D": "Decision" (as in "encoder input" / "decoder output") (E.1) + // "I(CX)": "Index I stored for context CX" (E.2.4) + // "MPS": "More probable symbol" (E.1.1) + // "LPS": "Less probable symbol" (E.1.1) + + void INITDEC(); + u8 DECODE(); // Returns a single decoded bit. + u8 MPS_EXCHANGE(); + u8 LPS_EXCHANGE(); + void RENORMD(); + void BYTEIN(); + + u8 B(size_t offset = 0) const; // Byte pointed to by BP. + size_t BP { 0 }; // Pointer into compressed data. + + // E.3.1 Decoder code register conventions + u32 C { 0 }; // Consists of u16 C_high, C_low. + u16 A { 0 }; // Current value of the fraction. Fixed precision; 0x8000 is equivalent to 0.75. + + u8 CT { 0 }; // Count of the number of bits in C. + + Context* CX { nullptr }; + static u8& I(Context* cx) { return cx->I; } + static u8& MPS(Context* cx) { return cx->is_mps; } + static u16 Qe(u16); + static u8 NMPS(u16); + static u8 NLPS(u16); + static u8 SWITCH(u16); +}; + +}