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:
Matthew Olsson 2021-05-27 14:03:29 -07:00 committed by Ali Mohammad Pur
parent 7b4e36bf88
commit 006f5498de
Notes: sideshowbarker 2024-07-18 12:21:03 +09:00
3 changed files with 205 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -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 };
};
}

View File

@ -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();
}