From fa00d94e824deef727b0550fac6fab00fe96d9fc Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 23 Jan 2023 13:55:57 -0500 Subject: [PATCH] LibGfx+icc: Add ICCProfile support for parametricCurveType and print it With this, we can parse all types required in v4 "Three-component matrix-based Input profiles". --- Userland/Libraries/LibGfx/ICCProfile.cpp | 54 ++++++++++++++ Userland/Libraries/LibGfx/ICCProfile.h | 93 +++++++++++++++++++++++- Userland/Utilities/icc.cpp | 27 +++++++ 3 files changed, 172 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICCProfile.cpp b/Userland/Libraries/LibGfx/ICCProfile.cpp index 84d11f254e6..0b96ef625d3 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.cpp +++ b/Userland/Libraries/LibGfx/ICCProfile.cpp @@ -635,6 +635,58 @@ ErrorOr> MultiLocalizedUnicodeTagDat return adopt_ref(*new MultiLocalizedUnicodeTagData(offset, size, move(records))); } +unsigned ParametricCurveTagData::parameter_count(FunctionType function_type) +{ + switch (function_type) { + case FunctionType::Type0: + return 1; + case FunctionType::Type1: + return 3; + case FunctionType::Type2: + return 4; + case FunctionType::Type3: + return 5; + case FunctionType::Type4: + return 7; + } + VERIFY_NOT_REACHED(); +} + +ErrorOr> ParametricCurveTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) +{ + // ICC v4, 10.18 parametricCurveType + VERIFY(tag_type(bytes) == Type); + TRY(check_reserved(bytes)); + + // "The parametricCurveType describes a one-dimensional curve by specifying one of a predefined set of functions + // using the parameters." + + if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16)) + return Error::from_string_literal("ICC::Profile: parametricCurveType has not enough data"); + + u16 raw_function_type = *bit_cast const*>(bytes.data() + 8); + u16 reserved = *bit_cast const*>(bytes.data() + 10); + if (reserved != 0) + return Error::from_string_literal("ICC::Profile: parametricCurveType reserved u16 after function type not 0"); + + if (raw_function_type > 4) + return Error::from_string_literal("ICC::Profile: parametricCurveType unknown function type"); + + FunctionType function_type = (FunctionType)raw_function_type; + unsigned count = parameter_count(function_type); + + if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16) + count * sizeof(s15Fixed16Number)) + return Error::from_string_literal("ICC::Profile: parametricCurveType has not enough data for parameters"); + + BigEndian const* raw_parameters = bit_cast const*>(bytes.data() + 12); + Array parameters; + parameters.fill(0); + for (unsigned i = 0; i < count; ++i) + parameters[i] = S15Fixed16::create_raw(raw_parameters[i]); + + return adopt_ref(*new ParametricCurveTagData(offset, size, function_type, move(parameters))); +} + ErrorOr> S15Fixed16ArrayTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) { // ICC v4, 10.22 s15Fixed16ArrayType @@ -897,6 +949,8 @@ ErrorOr> Profile::read_tag(ReadonlyBytes bytes, Detail::T return CurveTagData::from_bytes(tag_bytes, entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element); case MultiLocalizedUnicodeTagData::Type: return MultiLocalizedUnicodeTagData::from_bytes(tag_bytes, entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element); + case ParametricCurveTagData::Type: + return ParametricCurveTagData::from_bytes(tag_bytes, entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element); case S15Fixed16ArrayTagData::Type: return S15Fixed16ArrayTagData::from_bytes(tag_bytes, entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element); case TextDescriptionTagData::Type: diff --git a/Userland/Libraries/LibGfx/ICCProfile.h b/Userland/Libraries/LibGfx/ICCProfile.h index 6243dab5db0..c56c0f3d3b5 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.h +++ b/Userland/Libraries/LibGfx/ICCProfile.h @@ -224,6 +224,8 @@ private: u64 m_bits = 0; }; +using S15Fixed16 = FixedPoint<16, i32>; + struct XYZ { double x { 0 }; double y { 0 }; @@ -313,13 +315,100 @@ private: Vector m_records; }; +// ICC v4, 10.18 parametricCurveType +class ParametricCurveTagData : public TagData { +public: + // Table 68 — parametricCurveType function type encoding + enum class FunctionType { + // Y = X**g + Type0, + + // Y = (a*X + b)**g if X >= -b/a + // = 0 else + Type1, + CIE_122_1966 = Type1, + + // Y = (a*X + b)**g + c if X >= -b/a + // = c else + Type2, + IEC_61966_1 = Type2, + + // Y = (a*X + b)**g if X >= d + // = c*X else + Type3, + IEC_61966_2_1 = Type3, + sRGB = Type3, + + // Y = (a*X + b)**g + e if X >= d + // = c*X + f else + Type4, + }; + + // "The domain and range of each function shall be [0,0 1,0]. Any function value outside the range shall be clipped + // to the range of the function." + // "NOTE 1 The parameters selected for a parametric curve can result in complex or undefined values for the input range + // used. This can occur, for example, if d < -b/a. In such cases the behaviour of the curve is undefined." + + static constexpr TagTypeSignature Type { 0x70617261 }; // 'para' + + static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); + + ParametricCurveTagData(u32 offset, u32 size, FunctionType function_type, Array parameters) + : TagData(offset, size, Type) + , m_function_type(function_type) + , m_parameters(move(parameters)) + { + } + + FunctionType function_type() const { return m_function_type; } + + static unsigned parameter_count(FunctionType); + + S15Fixed16 g() const { return m_parameters[0]; } + S15Fixed16 a() const + { + VERIFY(function_type() >= FunctionType::Type1); + return m_parameters[1]; + } + S15Fixed16 b() const + { + VERIFY(function_type() >= FunctionType::Type1); + return m_parameters[2]; + } + S15Fixed16 c() const + { + VERIFY(function_type() >= FunctionType::Type2); + return m_parameters[3]; + } + S15Fixed16 d() const + { + VERIFY(function_type() >= FunctionType::Type3); + return m_parameters[4]; + } + S15Fixed16 e() const + { + VERIFY(function_type() >= FunctionType::Type4); + return m_parameters[5]; + } + S15Fixed16 f() const + { + VERIFY(function_type() >= FunctionType::Type4); + return m_parameters[6]; + } + +private: + FunctionType m_function_type; + + // Contains, in this order, g a b c d e f. + // Not all FunctionTypes use all parameters. + Array m_parameters; +}; + // ICC v4, 10.22 s15Fixed16ArrayType class S15Fixed16ArrayTagData : public TagData { public: static constexpr TagTypeSignature Type { 0x73663332 }; // 'sf32' - using S15Fixed16 = FixedPoint<16, i32>; - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); S15Fixed16ArrayTagData(u32 offset, u32 size, Vector values) diff --git a/Userland/Utilities/icc.cpp b/Userland/Utilities/icc.cpp index a989a275c8e..2b6b189322f 100644 --- a/Userland/Utilities/icc.cpp +++ b/Userland/Utilities/icc.cpp @@ -112,6 +112,33 @@ ErrorOr serenity_main(Main::Arguments arguments) record.iso_3166_1_country_code >> 8, record.iso_3166_1_country_code & 0xff, record.text); } + } else if (tag_data->type() == Gfx::ICC::ParametricCurveTagData::Type) { + auto& parametric_curve = static_cast(*tag_data); + switch (parametric_curve.function_type()) { + case Gfx::ICC::ParametricCurveTagData::FunctionType::Type0: + outln(" Y = X**{}", parametric_curve.g()); + break; + case Gfx::ICC::ParametricCurveTagData::FunctionType::Type1: + outln(" Y = ({}*X + {})**{} if X >= -{}/{}", + parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.b(), parametric_curve.a()); + outln(" Y = 0 else"); + break; + case Gfx::ICC::ParametricCurveTagData::FunctionType::Type2: + outln(" Y = ({}*X + {})**{} + {} if X >= -{}/{}", + parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.c(), parametric_curve.b(), parametric_curve.a()); + outln(" Y = {} else", parametric_curve.c()); + break; + case Gfx::ICC::ParametricCurveTagData::FunctionType::Type3: + outln(" Y = ({}*X + {})**{} if X >= {}", + parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.d()); + outln(" Y = {}*X else", parametric_curve.c()); + break; + case Gfx::ICC::ParametricCurveTagData::FunctionType::Type4: + outln(" Y = ({}*X + {})**{} + {} if X >= {}", + parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.e(), parametric_curve.d()); + outln(" Y = {}*X + {} else", parametric_curve.c(), parametric_curve.f()); + break; + } } else if (tag_data->type() == Gfx::ICC::S15Fixed16ArrayTagData::Type) { // This tag can contain arbitrarily many fixed-point numbers, but in practice it's // exclusively used for the 'chad' tag, where it always contains 9 values that