Ver Fonte

LibPDF: Add support for the CalRGB ColorSpace

This isn't tested all that well, as the PDF I am testing with only uses
it for black (which is trivial). It can be tested further when LibPDF
is able to process more complex PDFs that actually use this color space
non-trivially.
Matthew Olsson há 4 anos atrás
pai
commit
006f5498de

+ 170 - 0
Userland/Libraries/LibPDF/ColorSpace.cpp

@@ -53,4 +53,174 @@ Color DeviceCMYKColorSpace::color(const Vector<Value>& arguments) const
     return Color::from_cmyk(c, m, y, k);
 }
 
+RefPtr<CalRGBColorSpace> CalRGBColorSpace::create(RefPtr<Document> document, Vector<Value>&& parameters)
+{
+    if (parameters.size() != 1)
+        return {};
+
+    auto param = parameters[0];
+    if (!param.is_object() || !param.as_object()->is_dict())
+        return {};
+
+    auto dict = object_cast<DictObject>(param.as_object());
+    if (!dict->contains(CommonNames::WhitePoint))
+        return {};
+
+    auto white_point_array = dict->get_array(document, CommonNames::WhitePoint);
+    if (white_point_array->size() != 3)
+        return {};
+
+    auto color_space = adopt_ref(*new CalRGBColorSpace());
+
+    color_space->m_whitepoint[0] = white_point_array->at(0).to_float();
+    color_space->m_whitepoint[1] = white_point_array->at(1).to_float();
+    color_space->m_whitepoint[2] = white_point_array->at(2).to_float();
+
+    if (color_space->m_whitepoint[1] != 1.0f)
+        return {};
+
+    if (dict->contains(CommonNames::BlackPoint)) {
+        auto black_point_array = dict->get_array(document, CommonNames::BlackPoint);
+        if (black_point_array->size() == 3) {
+            color_space->m_blackpoint[0] = black_point_array->at(0).to_float();
+            color_space->m_blackpoint[1] = black_point_array->at(1).to_float();
+            color_space->m_blackpoint[2] = black_point_array->at(2).to_float();
+        }
+    }
+
+    if (dict->contains(CommonNames::Gamma)) {
+        auto gamma_array = dict->get_array(document, CommonNames::Gamma);
+        if (gamma_array->size() == 3) {
+            color_space->m_gamma[0] = gamma_array->at(0).to_float();
+            color_space->m_gamma[1] = gamma_array->at(1).to_float();
+            color_space->m_gamma[2] = gamma_array->at(2).to_float();
+        }
+    }
+
+    if (dict->contains(CommonNames::Matrix)) {
+        auto matrix_array = dict->get_array(document, CommonNames::Matrix);
+        if (matrix_array->size() == 3) {
+            color_space->m_matrix[0] = matrix_array->at(0).to_float();
+            color_space->m_matrix[1] = matrix_array->at(1).to_float();
+            color_space->m_matrix[2] = matrix_array->at(2).to_float();
+            color_space->m_matrix[3] = matrix_array->at(3).to_float();
+            color_space->m_matrix[4] = matrix_array->at(4).to_float();
+            color_space->m_matrix[5] = matrix_array->at(5).to_float();
+            color_space->m_matrix[6] = matrix_array->at(6).to_float();
+            color_space->m_matrix[7] = matrix_array->at(7).to_float();
+            color_space->m_matrix[8] = matrix_array->at(8).to_float();
+        }
+    }
+
+    return color_space;
+}
+
+constexpr Array<float, 3> matrix_multiply(Array<float, 9> a, Array<float, 3> b)
+{
+    return Array<float, 3> {
+        a[0] * b[0] + a[1] * b[1] + a[2] * b[2],
+        a[3] * b[0] + a[4] * b[1] + a[5] * b[2],
+        a[6] * b[0] + a[7] * b[1] + a[8] * b[2]
+    };
+}
+
+// Converts to a flat XYZ space with white point = (1, 1, 1)
+// Step 2 of https://www.adobe.com/content/dam/acom/en/devnet/photoshop/sdk/AdobeBPC.pdf
+constexpr Array<float, 3> flatten_and_normalize_whitepoint(Array<float, 3> whitepoint, Array<float, 3> xyz)
+{
+    VERIFY(whitepoint[1] == 1.0f);
+
+    return {
+        (1.0f / whitepoint[0]) * xyz[0],
+        xyz[1],
+        (1.0f / whitepoint[2]) * xyz[2],
+    };
+}
+
+constexpr float decode_l(float input)
+{
+    constexpr float decode_l_scaling_constant = 0.00110705646f; // (((8 + 16) / 116) ^ 3) / 8
+
+    if (input < 0.0f)
+        return -decode_l(-input);
+    if (input >= 0.0f && input <= 8.0f)
+        return input * decode_l_scaling_constant;
+    return powf(((input + 16.0f) / 116.0f), 3.0f);
+}
+
+constexpr Array<float, 3> scale_black_point(Array<float, 3> blackpoint, Array<float, 3> xyz)
+{
+    auto y_dst = decode_l(0); // DestinationBlackPoint is just [0, 0, 0]
+    auto y_src = decode_l(blackpoint[0]);
+    auto scale = (1 - y_dst) / (1 - y_src);
+    auto offset = 1 - scale;
+
+    return {
+        xyz[0] * scale + offset,
+        xyz[1] * scale + offset,
+        xyz[2] * scale + offset,
+    };
+}
+
+// https://en.wikipedia.org/wiki/Illuminant_D65
+constexpr Array<float, 3> convert_to_d65(Array<float, 3> whitepoint, Array<float, 3> xyz)
+{
+    constexpr float d65x = 0.95047f;
+    constexpr float d65y = 1.0f;
+    constexpr float d65z = 1.08883f;
+
+    return {
+        (xyz[0] * d65x) / whitepoint[0],
+        (xyz[1] * d65y) / whitepoint[1],
+        (xyz[2] * d65z) / whitepoint[2],
+    };
+}
+
+// https://en.wikipedia.org/wiki/SRGB
+constexpr Array<float, 3> convert_to_srgb(Array<float, 3> xyz)
+{
+    // See the sRGB D65 [M]^-1 matrix in the following page
+    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+    constexpr Array<float, 9> conversion_matrix = {
+        3.2404542,
+        -1.5371385,
+        -0.4985314,
+        -0.969266,
+        1.8760108,
+        0.0415560,
+        0.0556434,
+        -0.2040259,
+        1.0572252,
+    };
+
+    return matrix_multiply(conversion_matrix, xyz);
+}
+
+Color CalRGBColorSpace::color(const Vector<Value>& arguments) const
+{
+    VERIFY(arguments.size() == 3);
+    auto a = clamp(arguments[0].to_float(), 0.0f, 1.0f);
+    auto b = clamp(arguments[1].to_float(), 0.0f, 1.0f);
+    auto c = clamp(arguments[2].to_float(), 0.0f, 1.0f);
+
+    auto agr = powf(a, m_gamma[0]);
+    auto bgg = powf(b, m_gamma[1]);
+    auto cgb = powf(c, m_gamma[2]);
+
+    auto x = m_matrix[0] * agr + m_matrix[3] * bgg + m_matrix[6] * cgb;
+    auto y = m_matrix[1] * agr + m_matrix[4] * bgg + m_matrix[7] * cgb;
+    auto z = m_matrix[2] * agr + m_matrix[5] * bgg + m_matrix[8] * cgb;
+
+    auto flattened_xyz = flatten_and_normalize_whitepoint(m_whitepoint, { x, y, z });
+    auto scaled_black_point_xyz = scale_black_point(m_blackpoint, flattened_xyz);
+    auto d65_normalized = convert_to_d65(m_whitepoint, scaled_black_point_xyz);
+    auto srgb = convert_to_srgb(d65_normalized);
+
+    auto red = static_cast<u8>(srgb[0] * 255.0f);
+    auto green = static_cast<u8>(srgb[1] * 255.0f);
+    auto blue = static_cast<u8>(srgb[2] * 255.0f);
+
+    return Color(red, green, blue);
+}
+
 }

+ 16 - 0
Userland/Libraries/LibPDF/ColorSpace.h

@@ -69,4 +69,20 @@ private:
     DeviceCMYKColorSpace() = default;
 };
 
+class CalRGBColorSpace final : public ColorSpace {
+public:
+    static RefPtr<CalRGBColorSpace> create(RefPtr<Document>, Vector<Value>&& parameters);
+    virtual ~CalRGBColorSpace() override = default;
+
+    virtual Color color(const Vector<Value>& arguments) const override;
+
+private:
+    CalRGBColorSpace() = default;
+
+    Array<float, 3> m_whitepoint { 0, 0, 0 };
+    Array<float, 3> m_blackpoint { 0, 0, 0 };
+    Array<float, 3> m_gamma { 1, 1, 1 };
+    Array<float, 9> m_matrix { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
+};
+
 }

+ 19 - 0
Userland/Libraries/LibPDF/Renderer.cpp

@@ -517,6 +517,25 @@ RefPtr<ColorSpace> Renderer::get_color_space(const Value& value)
         return DeviceRGBColorSpace::the();
     if (name == CommonNames::DeviceCMYK)
         return DeviceCMYKColorSpace::the();
+    if (name == CommonNames::Pattern)
+        TODO();
+
+    // The color space is a complex color space with parameters that resides in
+    // the resource dictionary
+    auto color_space_resource_dict = m_page.resources->get_dict(m_document, CommonNames::ColorSpace);
+    if (!color_space_resource_dict->contains(name))
+        TODO();
+
+    auto color_space_array = color_space_resource_dict->get_array(m_document, name);
+    name = color_space_array->get_name_at(m_document, 0)->name();
+
+    Vector<Value> parameters;
+    parameters.ensure_capacity(color_space_array->size() - 1);
+    for (size_t i = 1; i < color_space_array->size(); i++)
+        parameters.unchecked_append(color_space_array->at(i));
+
+    if (name == CommonNames::CalRGB)
+        return CalRGBColorSpace::create(m_document, move(parameters));
 
     TODO();
 }