mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
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.
This commit is contained in:
parent
7b4e36bf88
commit
006f5498de
Notes:
sideshowbarker
2024-07-18 12:21:03 +09:00
Author: https://github.com/mattco98 Commit: https://github.com/SerenityOS/serenity/commit/006f5498dec Pull-request: https://github.com/SerenityOS/serenity/pull/7675 Reviewed-by: https://github.com/FireFox317 Reviewed-by: https://github.com/alimpfard
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user