2021-05-27 14:01:37 -07:00
|
|
|
/*
|
2022-03-05 17:30:55 -07:00
|
|
|
* Copyright (c) 2021-2022, Matthew Olsson <mattco@serenityos.org>
|
2021-05-27 14:01:37 -07:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2023-07-18 18:35:49 -07:00
|
|
|
#include <LibGfx/ICC/WellKnownProfiles.h>
|
2021-05-27 14:01:37 -07:00
|
|
|
#include <LibPDF/ColorSpace.h>
|
|
|
|
|
#include <LibPDF/CommonNames.h>
|
2022-03-24 10:08:08 -07:00
|
|
|
#include <LibPDF/Document.h>
|
2021-09-17 02:28:52 +02:00
|
|
|
#include <LibPDF/ObjectDerivatives.h>
|
2021-05-27 14:01:37 -07:00
|
|
|
|
|
|
|
|
namespace PDF {
|
|
|
|
|
|
2023-07-18 18:35:49 -07:00
|
|
|
RefPtr<Gfx::ICC::Profile> ICCBasedColorSpace::s_srgb_profile;
|
|
|
|
|
|
2023-10-20 11:11:03 -04:00
|
|
|
#define ENUMERATE(name, may_be_specified_directly) \
|
|
|
|
|
ColorSpaceFamily ColorSpaceFamily::name { #name, may_be_specified_directly };
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
ENUMERATE_COLOR_SPACE_FAMILIES(ENUMERATE);
|
|
|
|
|
#undef ENUMERATE
|
|
|
|
|
|
2023-01-08 19:23:00 -05:00
|
|
|
PDFErrorOr<ColorSpaceFamily> ColorSpaceFamily::get(DeprecatedFlyString const& family_name)
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
{
|
2023-10-20 11:11:03 -04:00
|
|
|
#define ENUMERATE(f_name, may_be_specified_directly) \
|
|
|
|
|
if (family_name == f_name.name()) { \
|
|
|
|
|
return ColorSpaceFamily::f_name; \
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
}
|
|
|
|
|
ENUMERATE_COLOR_SPACE_FAMILIES(ENUMERATE)
|
|
|
|
|
#undef ENUMERATE
|
2023-10-25 23:45:56 +01:00
|
|
|
dbgln_if(PDF_DEBUG, "Unknown ColorSpace family: {}", family_name);
|
|
|
|
|
return Error(Error::Type::MalformedPDF, "Unknown ColorSpace family"_string);
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
}
|
|
|
|
|
|
2023-01-08 19:23:00 -05:00
|
|
|
PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString const& name)
|
2022-03-24 10:08:08 -07:00
|
|
|
{
|
|
|
|
|
// Simple color spaces with no parameters, which can be specified directly
|
|
|
|
|
if (name == CommonNames::DeviceGray)
|
|
|
|
|
return DeviceGrayColorSpace::the();
|
|
|
|
|
if (name == CommonNames::DeviceRGB)
|
|
|
|
|
return DeviceRGBColorSpace::the();
|
|
|
|
|
if (name == CommonNames::DeviceCMYK)
|
|
|
|
|
return DeviceCMYKColorSpace::the();
|
2023-10-20 11:12:38 -04:00
|
|
|
if (name == CommonNames::Pattern)
|
|
|
|
|
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
2022-03-24 10:08:08 -07:00
|
|
|
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(Document* document, NonnullRefPtr<ArrayObject> color_space_array)
|
|
|
|
|
{
|
2022-03-24 10:08:08 -07:00
|
|
|
auto color_space_name = TRY(color_space_array->get_name_at(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 (color_space_name == CommonNames::CalRGB)
|
|
|
|
|
return TRY(CalRGBColorSpace::create(document, move(parameters)));
|
|
|
|
|
|
2023-10-24 10:26:20 -07:00
|
|
|
if (color_space_name == CommonNames::DeviceN)
|
|
|
|
|
return TRY(DeviceNColorSpace::create(document, move(parameters)));
|
|
|
|
|
|
2022-03-24 10:08:43 -07:00
|
|
|
if (color_space_name == CommonNames::ICCBased)
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
return TRY(ICCBasedColorSpace::create(document, move(parameters)));
|
2022-03-24 10:08:43 -07:00
|
|
|
|
2023-07-23 22:44:52 -04:00
|
|
|
if (color_space_name == CommonNames::Indexed)
|
|
|
|
|
return Error::rendering_unsupported_error("Indexed color spaces not yet implemented");
|
|
|
|
|
|
2023-10-24 11:13:04 -07:00
|
|
|
if (color_space_name == CommonNames::Lab)
|
|
|
|
|
return TRY(LabColorSpace::create(document, move(parameters)));
|
|
|
|
|
|
2023-07-23 22:44:52 -04:00
|
|
|
if (color_space_name == CommonNames::Pattern)
|
|
|
|
|
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
|
|
|
|
|
|
2023-07-23 16:55:10 -04:00
|
|
|
if (color_space_name == CommonNames::Separation)
|
|
|
|
|
return TRY(SeparationColorSpace::create(document, move(parameters)));
|
|
|
|
|
|
2022-03-24 10:08:08 -07:00
|
|
|
dbgln("Unknown color space: {}", color_space_name);
|
2023-07-23 10:44:52 -04:00
|
|
|
return Error::rendering_unsupported_error("unknown color space");
|
2022-03-24 10:08:08 -07:00
|
|
|
}
|
|
|
|
|
|
2022-03-05 17:52:00 -07:00
|
|
|
NonnullRefPtr<DeviceGrayColorSpace> DeviceGrayColorSpace::the()
|
2021-05-27 14:01:37 -07:00
|
|
|
{
|
|
|
|
|
static auto instance = adopt_ref(*new DeviceGrayColorSpace());
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-20 11:55:38 -04:00
|
|
|
PDFErrorOr<Color> DeviceGrayColorSpace::color(ReadonlySpan<Value> arguments) const
|
2021-05-27 14:01:37 -07:00
|
|
|
{
|
|
|
|
|
VERIFY(arguments.size() == 1);
|
|
|
|
|
auto gray = static_cast<u8>(arguments[0].to_float() * 255.0f);
|
|
|
|
|
return Color(gray, gray, gray);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 01:43:31 +08:00
|
|
|
Vector<float> DeviceGrayColorSpace::default_decode() const
|
|
|
|
|
{
|
|
|
|
|
return { 0.0f, 1.0f };
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-05 17:52:00 -07:00
|
|
|
NonnullRefPtr<DeviceRGBColorSpace> DeviceRGBColorSpace::the()
|
2021-05-27 14:01:37 -07:00
|
|
|
{
|
|
|
|
|
static auto instance = adopt_ref(*new DeviceRGBColorSpace());
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-20 11:55:38 -04:00
|
|
|
PDFErrorOr<Color> DeviceRGBColorSpace::color(ReadonlySpan<Value> arguments) const
|
2021-05-27 14:01:37 -07:00
|
|
|
{
|
|
|
|
|
VERIFY(arguments.size() == 3);
|
|
|
|
|
auto r = static_cast<u8>(arguments[0].to_float() * 255.0f);
|
|
|
|
|
auto g = static_cast<u8>(arguments[1].to_float() * 255.0f);
|
|
|
|
|
auto b = static_cast<u8>(arguments[2].to_float() * 255.0f);
|
|
|
|
|
return Color(r, g, b);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 01:43:31 +08:00
|
|
|
Vector<float> DeviceRGBColorSpace::default_decode() const
|
|
|
|
|
{
|
|
|
|
|
return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-05 17:52:00 -07:00
|
|
|
NonnullRefPtr<DeviceCMYKColorSpace> DeviceCMYKColorSpace::the()
|
2021-05-27 14:01:37 -07:00
|
|
|
{
|
|
|
|
|
static auto instance = adopt_ref(*new DeviceCMYKColorSpace());
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-20 11:55:38 -04:00
|
|
|
PDFErrorOr<Color> DeviceCMYKColorSpace::color(ReadonlySpan<Value> arguments) const
|
2021-05-27 14:01:37 -07:00
|
|
|
{
|
|
|
|
|
VERIFY(arguments.size() == 4);
|
|
|
|
|
auto c = arguments[0].to_float();
|
|
|
|
|
auto m = arguments[1].to_float();
|
|
|
|
|
auto y = arguments[2].to_float();
|
|
|
|
|
auto k = arguments[3].to_float();
|
|
|
|
|
return Color::from_cmyk(c, m, y, k);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 01:43:31 +08:00
|
|
|
Vector<float> DeviceCMYKColorSpace::default_decode() const
|
|
|
|
|
{
|
|
|
|
|
return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-24 10:26:20 -07:00
|
|
|
PDFErrorOr<NonnullRefPtr<DeviceNColorSpace>> DeviceNColorSpace::create(Document*, Vector<Value>&& parameters)
|
|
|
|
|
{
|
|
|
|
|
// "[ /DeviceN names alternateSpace tintTransform ]
|
|
|
|
|
// or
|
|
|
|
|
// [ /DeviceN names alternateSpace tintTransform attributes ]"
|
|
|
|
|
if (parameters.size() != 4 && parameters.size() != 5)
|
|
|
|
|
return Error { Error::Type::MalformedPDF, "DevicN color space expects 4 or 5 parameters" };
|
|
|
|
|
|
|
|
|
|
// "The names parameter is an array of name objects specifying the individual color components.
|
|
|
|
|
// The length of the array determines the number of components in the DeviceN color space"
|
|
|
|
|
auto names = parameters[0].get<NonnullRefPtr<Object>>()->cast<ArrayObject>();
|
|
|
|
|
|
|
|
|
|
// "The alternateSpace parameter is an array or name object that can be any device or CIE-based color space
|
|
|
|
|
// but not another special color space (Pattern, Indexed, Separation, or DeviceN)."
|
|
|
|
|
|
|
|
|
|
// FIXME: Implement.
|
|
|
|
|
|
|
|
|
|
return adopt_ref(*new DeviceNColorSpace(names->size()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PDFErrorOr<Color> DeviceNColorSpace::color(ReadonlySpan<Value>) const
|
|
|
|
|
{
|
|
|
|
|
return Error::rendering_unsupported_error("DeviceN color spaces not yet implemented");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DeviceNColorSpace::number_of_components() const
|
|
|
|
|
{
|
|
|
|
|
return m_number_of_components;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<float> DeviceNColorSpace::default_decode() const
|
|
|
|
|
{
|
|
|
|
|
Vector<float> decoding_ranges;
|
|
|
|
|
for (u8 i = 0; i < number_of_components(); i++) {
|
|
|
|
|
decoding_ranges.append(0.0);
|
|
|
|
|
decoding_ranges.append(1.0);
|
|
|
|
|
}
|
|
|
|
|
return decoding_ranges;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeviceNColorSpace::DeviceNColorSpace(size_t number_of_components)
|
|
|
|
|
: m_number_of_components(number_of_components)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-24 10:08:08 -07:00
|
|
|
PDFErrorOr<NonnullRefPtr<CalRGBColorSpace>> CalRGBColorSpace::create(Document* document, Vector<Value>&& parameters)
|
2021-05-27 14:03:29 -07:00
|
|
|
{
|
|
|
|
|
if (parameters.size() != 1)
|
2022-03-05 17:52:00 -07:00
|
|
|
return Error { Error::Type::MalformedPDF, "RGB color space expects one parameter" };
|
2021-05-27 14:03:29 -07:00
|
|
|
|
|
|
|
|
auto param = parameters[0];
|
2022-03-05 17:30:55 -07:00
|
|
|
if (!param.has<NonnullRefPtr<Object>>() || !param.get<NonnullRefPtr<Object>>()->is<DictObject>())
|
2022-03-05 17:52:00 -07:00
|
|
|
return Error { Error::Type::MalformedPDF, "RGB color space expects a dict parameter" };
|
2021-05-27 14:03:29 -07:00
|
|
|
|
2022-03-05 17:30:55 -07:00
|
|
|
auto dict = param.get<NonnullRefPtr<Object>>()->cast<DictObject>();
|
2021-05-27 14:03:29 -07:00
|
|
|
if (!dict->contains(CommonNames::WhitePoint))
|
2022-03-05 17:52:00 -07:00
|
|
|
return Error { Error::Type::MalformedPDF, "RGB color space expects a Whitepoint key" };
|
2021-05-27 14:03:29 -07:00
|
|
|
|
2022-03-05 17:52:00 -07:00
|
|
|
auto white_point_array = TRY(dict->get_array(document, CommonNames::WhitePoint));
|
2021-05-27 14:03:29 -07:00
|
|
|
if (white_point_array->size() != 3)
|
2022-03-05 17:52:00 -07:00
|
|
|
return Error { Error::Type::MalformedPDF, "RGB color space expects 3 Whitepoint parameters" };
|
2021-05-27 14:03:29 -07:00
|
|
|
|
|
|
|
|
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)
|
2022-03-05 17:52:00 -07:00
|
|
|
return Error { Error::Type::MalformedPDF, "RGB color space expects 2nd Whitepoint to be 1.0" };
|
2021-05-27 14:03:29 -07:00
|
|
|
|
|
|
|
|
if (dict->contains(CommonNames::BlackPoint)) {
|
2022-03-05 17:52:00 -07:00
|
|
|
auto black_point_array = TRY(dict->get_array(document, CommonNames::BlackPoint));
|
2021-05-27 14:03:29 -07:00
|
|
|
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)) {
|
2022-03-05 17:52:00 -07:00
|
|
|
auto gamma_array = TRY(dict->get_array(document, CommonNames::Gamma));
|
2021-05-27 14:03:29 -07:00
|
|
|
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)) {
|
2022-03-05 17:52:00 -07:00
|
|
|
auto matrix_array = TRY(dict->get_array(document, CommonNames::Matrix));
|
2021-05-27 14:03:29 -07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-20 11:55:38 -04:00
|
|
|
PDFErrorOr<Color> CalRGBColorSpace::color(ReadonlySpan<Value> arguments) const
|
2021-05-27 14:03:29 -07:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 01:43:31 +08:00
|
|
|
Vector<float> CalRGBColorSpace::default_decode() const
|
|
|
|
|
{
|
|
|
|
|
return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
|
|
|
|
|
}
|
|
|
|
|
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
PDFErrorOr<NonnullRefPtr<ColorSpace>> ICCBasedColorSpace::create(Document* document, Vector<Value>&& parameters)
|
2022-03-24 10:08:43 -07:00
|
|
|
{
|
|
|
|
|
if (parameters.is_empty())
|
|
|
|
|
return Error { Error::Type::MalformedPDF, "ICCBased color space expected one parameter" };
|
|
|
|
|
|
|
|
|
|
auto param = TRY(document->resolve(parameters[0]));
|
|
|
|
|
if (!param.has<NonnullRefPtr<Object>>() || !param.get<NonnullRefPtr<Object>>()->is<StreamObject>())
|
|
|
|
|
return Error { Error::Type::MalformedPDF, "ICCBased color space expects a stream parameter" };
|
|
|
|
|
|
2023-07-18 18:35:49 -07:00
|
|
|
auto stream = param.get<NonnullRefPtr<Object>>()->cast<StreamObject>();
|
|
|
|
|
auto dict = stream->dict();
|
|
|
|
|
|
|
|
|
|
auto maybe_profile = Gfx::ICC::Profile::try_load_from_externally_owned_memory(stream->bytes());
|
|
|
|
|
if (!maybe_profile.is_error())
|
|
|
|
|
return adopt_ref(*new ICCBasedColorSpace(maybe_profile.release_value()));
|
|
|
|
|
|
|
|
|
|
if (dict->contains(CommonNames::Alternate)) {
|
|
|
|
|
auto alternate_color_space_object = MUST(dict->get_object(document, CommonNames::Alternate));
|
|
|
|
|
if (alternate_color_space_object->is<NameObject>())
|
|
|
|
|
return ColorSpace::create(alternate_color_space_object->cast<NameObject>()->name());
|
2022-10-27 20:06:02 +02:00
|
|
|
|
2023-07-18 18:35:49 -07:00
|
|
|
return Error { Error::Type::Internal, "Alternate color spaces in array format are not supported" };
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 12:40:24 +08:00
|
|
|
}
|
|
|
|
|
|
2023-07-18 18:35:49 -07:00
|
|
|
return Error { Error::Type::MalformedPDF, "Failed to load ICC color space with malformed profile and no alternate" };
|
2022-03-24 10:08:43 -07:00
|
|
|
}
|
|
|
|
|
|
2023-07-18 18:35:49 -07:00
|
|
|
ICCBasedColorSpace::ICCBasedColorSpace(NonnullRefPtr<Gfx::ICC::Profile> profile)
|
|
|
|
|
: m_profile(profile)
|
2022-03-24 10:08:43 -07:00
|
|
|
{
|
2023-07-18 18:35:49 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-20 11:55:38 -04:00
|
|
|
PDFErrorOr<Color> ICCBasedColorSpace::color(ReadonlySpan<Value> arguments) const
|
2023-07-18 18:35:49 -07:00
|
|
|
{
|
|
|
|
|
if (!s_srgb_profile)
|
2023-07-18 18:35:51 -07:00
|
|
|
s_srgb_profile = TRY(Gfx::ICC::sRGB());
|
2023-07-18 18:35:49 -07:00
|
|
|
|
|
|
|
|
Vector<u8> bytes;
|
|
|
|
|
for (auto const& arg : arguments) {
|
|
|
|
|
VERIFY(arg.has_number());
|
|
|
|
|
bytes.append(static_cast<u8>(arg.to_float() * 255.0f));
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-18 18:35:51 -07:00
|
|
|
auto pcs = TRY(m_profile->to_pcs(bytes));
|
2023-07-18 18:35:49 -07:00
|
|
|
Array<u8, 3> output;
|
2023-07-18 18:35:51 -07:00
|
|
|
TRY(s_srgb_profile->from_pcs(pcs, output.span()));
|
2023-07-18 18:35:49 -07:00
|
|
|
|
|
|
|
|
return Color(output[0], output[1], output[2]);
|
2022-03-24 10:08:43 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-19 21:54:34 -04:00
|
|
|
int ICCBasedColorSpace::number_of_components() const
|
|
|
|
|
{
|
|
|
|
|
return Gfx::ICC::number_of_components_in_color_space(m_profile->data_color_space());
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 01:43:31 +08:00
|
|
|
Vector<float> ICCBasedColorSpace::default_decode() const
|
|
|
|
|
{
|
2023-07-18 18:35:49 -07:00
|
|
|
auto color_space = m_profile->data_color_space();
|
|
|
|
|
switch (color_space) {
|
|
|
|
|
case Gfx::ICC::ColorSpace::Gray:
|
|
|
|
|
return { 0.0, 1.0 };
|
|
|
|
|
case Gfx::ICC::ColorSpace::RGB:
|
|
|
|
|
return { 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 };
|
|
|
|
|
case Gfx::ICC::ColorSpace::CMYK:
|
|
|
|
|
return { 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 };
|
|
|
|
|
default:
|
|
|
|
|
warnln("PDF: Unknown default_decode params for color space {}", Gfx::ICC::data_color_space_name(color_space));
|
|
|
|
|
Vector<float> decoding_ranges;
|
|
|
|
|
for (u8 i = 0; i < Gfx::ICC::number_of_components_in_color_space(color_space); i++) {
|
|
|
|
|
decoding_ranges.append(0.0);
|
|
|
|
|
decoding_ranges.append(1.0);
|
|
|
|
|
}
|
|
|
|
|
return decoding_ranges;
|
|
|
|
|
}
|
2022-11-25 01:43:31 +08:00
|
|
|
}
|
|
|
|
|
|
2023-10-24 11:13:04 -07:00
|
|
|
PDFErrorOr<NonnullRefPtr<LabColorSpace>> LabColorSpace::create(Document*, Vector<Value>&& parameters)
|
|
|
|
|
{
|
|
|
|
|
if (parameters.size() != 1)
|
|
|
|
|
return Error { Error::Type::MalformedPDF, "Lab color space expects one parameter" };
|
|
|
|
|
|
|
|
|
|
auto color_space = adopt_ref(*new LabColorSpace());
|
|
|
|
|
// FIXME: Implement.
|
|
|
|
|
return color_space;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PDFErrorOr<Color> LabColorSpace::color(ReadonlySpan<Value>) const
|
|
|
|
|
{
|
|
|
|
|
return Error::rendering_unsupported_error("Lab color spaces not yet implemented");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<float> LabColorSpace::default_decode() const
|
|
|
|
|
{
|
|
|
|
|
return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-23 16:55:10 -04:00
|
|
|
PDFErrorOr<NonnullRefPtr<SeparationColorSpace>> SeparationColorSpace::create(Document*, Vector<Value>&&)
|
|
|
|
|
{
|
|
|
|
|
auto color_space = adopt_ref(*new SeparationColorSpace());
|
|
|
|
|
// FIXME: Implement.
|
|
|
|
|
return color_space;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-20 11:55:38 -04:00
|
|
|
PDFErrorOr<Color> SeparationColorSpace::color(ReadonlySpan<Value>) const
|
2023-07-23 16:55:10 -04:00
|
|
|
{
|
|
|
|
|
return Error::rendering_unsupported_error("Separation color spaces not yet implemented");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<float> SeparationColorSpace::default_decode() const
|
|
|
|
|
{
|
2023-10-24 10:39:58 -07:00
|
|
|
return { 0.0f, 1.0f };
|
2023-07-23 16:55:10 -04:00
|
|
|
}
|
|
|
|
|
|
2021-05-27 14:01:37 -07:00
|
|
|
}
|