mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-07 12:38:38 -10:00
1906 lines
94 KiB
C++
1906 lines
94 KiB
C++
/*
|
||
* Copyright (c) 2025, Nico Weber <[email protected]>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <AK/BitStream.h>
|
||
#include <AK/Enumerate.h>
|
||
#include <AK/GenericShorthands.h>
|
||
#include <LibGfx/EdgeFlagPathRasterizer.h>
|
||
#include <LibGfx/Painter.h>
|
||
#include <LibGfx/Vector2.h>
|
||
#include <LibPDF/ColorSpace.h>
|
||
#include <LibPDF/CommonNames.h>
|
||
#include <LibPDF/Document.h>
|
||
#include <LibPDF/Shading.h>
|
||
|
||
namespace PDF {
|
||
|
||
namespace {
|
||
|
||
// TABLE 4.28 Entries common to all shading dictionaries
|
||
struct CommonEntries {
|
||
// "(Required) The color space in which color values are expressed. This may be
|
||
// any device, CIE-based, or special color space except a Pattern space."
|
||
AK::NonnullRefPtr<ColorSpaceWithFloatArgs> color_space;
|
||
|
||
// "(Optional) An array of color components appropriate to the color space,
|
||
// specifying a single background color value. If present, this color is used, be-
|
||
// fore any painting operation involving the shading, to fill those portions of the
|
||
// area to be painted that lie outside the bounds of the shading object
|
||
// Note: The background color is applied only when the shading is used as part of
|
||
// a shading pattern, not when it is painted directly with the sh operator."
|
||
Optional<Vector<float>> background {};
|
||
|
||
// "(Optional) An array of four numbers giving the left, bottom, right, and top
|
||
// coordinates, respectively, of the shading’s bounding box. The coordinates are
|
||
// interpreted in the shading’s target coordinate space. If present, this bounding
|
||
// box is applied as a temporary clipping boundary when the shading is painted,
|
||
// in addition to the current clipping path and any other clipping boundaries in
|
||
// effect at that time."
|
||
Optional<Gfx::FloatRect> b_box {};
|
||
|
||
// "(Optional) A flag indicating whether to filter the shading function to prevent
|
||
// aliasing artifacts. [...] Anti-aliasing
|
||
// may not be implemented on some output devices, in which case this flag is
|
||
// ignored. Default value: false."
|
||
// We currently ignore this.
|
||
bool anti_alias { false };
|
||
|
||
PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const
|
||
{
|
||
if (!background.has_value())
|
||
return Optional<Gfx::Color> {};
|
||
return TRY(color_space->color(background.value()));
|
||
}
|
||
};
|
||
|
||
PDFErrorOr<CommonEntries> read_common_entries(Document* document, DictObject const& shading_dict, Renderer& renderer)
|
||
{
|
||
// "(Required) The color space in which color values are expressed. This may be
|
||
// any device, CIE-based, or special color space except a Pattern space. See
|
||
// “Color Space: Special Considerations” on page 306 for further information."
|
||
auto color_space_object = TRY(shading_dict.get_object(document, CommonNames::ColorSpace));
|
||
auto color_space = TRY(ColorSpace::create(document, move(color_space_object), renderer));
|
||
if (color_space->family() == ColorSpaceFamily::Pattern)
|
||
return Error::malformed_error("Shading color space must not be pattern");
|
||
|
||
CommonEntries common_entries { .color_space = verify_cast<ColorSpaceWithFloatArgs>(*color_space) };
|
||
|
||
if (shading_dict.contains(CommonNames::Background)) {
|
||
auto background_array = TRY(shading_dict.get_array(document, CommonNames::Background));
|
||
Vector<float> background;
|
||
for (auto const& value : background_array->elements())
|
||
background.append(value.to_float());
|
||
common_entries.background = move(background);
|
||
}
|
||
|
||
if (shading_dict.contains(CommonNames::BBox)) {
|
||
auto bbox_array = TRY(shading_dict.get_array(document, CommonNames::BBox));
|
||
if (bbox_array->size() != 4)
|
||
return Error::malformed_error("BBox must have 4 elements");
|
||
auto bbox = Gfx::FloatRect::from_two_points(
|
||
{ bbox_array->at(0).to_float(), bbox_array->at(1).to_float() },
|
||
{ bbox_array->at(2).to_float(), bbox_array->at(3).to_float() });
|
||
common_entries.b_box = bbox;
|
||
}
|
||
|
||
if (shading_dict.contains(CommonNames::AntiAlias))
|
||
common_entries.anti_alias = TRY(document->resolve(shading_dict.get_value(CommonNames::AntiAlias))).get<bool>();
|
||
|
||
return common_entries;
|
||
}
|
||
|
||
using ShadingFunctionsType = Variant<Empty, NonnullRefPtr<Function>, Vector<NonnullRefPtr<Function>>>;
|
||
using NonemptyShadingFunctionsType = Variant<NonnullRefPtr<Function>, Vector<NonnullRefPtr<Function>>>;
|
||
|
||
static PDFErrorOr<NonemptyShadingFunctionsType> read_shading_functions(Document* document, NonnullRefPtr<DictObject> shading_dict, NonnullRefPtr<ColorSpace> color_space, ReadonlySpan<float> function_input)
|
||
{
|
||
if (color_space->family() == ColorSpaceFamily::Indexed)
|
||
return Error::malformed_error("Function cannot be used with Indexed color space");
|
||
|
||
auto function_object = TRY(shading_dict->get_object(document, CommonNames::Function));
|
||
if (function_object->is<ArrayObject>()) {
|
||
auto function_array = function_object->cast<ArrayObject>();
|
||
Vector<NonnullRefPtr<Function>> functions_vector;
|
||
if (function_array->size() != static_cast<size_t>(color_space->number_of_components()))
|
||
return Error::malformed_error("Function array must have as many elements as color space has components");
|
||
for (size_t i = 0; i < function_array->size(); ++i) {
|
||
auto function = TRY(Function::create(document, TRY(document->resolve_to<Object>(function_array->at(i)))));
|
||
if (TRY(function->evaluate(function_input)).size() != 1)
|
||
return Error::malformed_error("Function must have 1 output component");
|
||
TRY(functions_vector.try_append(move(function)));
|
||
}
|
||
return functions_vector;
|
||
}
|
||
auto function = TRY(Function::create(document, function_object));
|
||
if (TRY(function->evaluate(function_input)).size() != static_cast<size_t>(color_space->number_of_components()))
|
||
return Error::malformed_error("Function must have as many output components as color space");
|
||
return function;
|
||
}
|
||
|
||
static PDFErrorOr<NonemptyShadingFunctionsType> read_shading_functions(Document* document, NonnullRefPtr<DictObject> shading_dict, NonnullRefPtr<ColorSpace> color_space, float function_input)
|
||
{
|
||
return read_shading_functions(document, shading_dict, color_space, Array { function_input });
|
||
}
|
||
|
||
class FunctionBasedShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<FunctionBasedShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
FunctionBasedShading(CommonEntries common_entries, Gfx::FloatRect domain, Gfx::AffineTransform matrix, NonemptyShadingFunctionsType functions)
|
||
: m_common_entries(move(common_entries))
|
||
, m_domain(domain)
|
||
, m_matrix(matrix)
|
||
, m_functions(move(functions))
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
Gfx::FloatRect m_domain;
|
||
Gfx::AffineTransform m_matrix;
|
||
NonemptyShadingFunctionsType m_functions;
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<FunctionBasedShading>> FunctionBasedShading::create(Document* document, NonnullRefPtr<DictObject> shading_dict, CommonEntries common_entries)
|
||
{
|
||
// TABLE 4.29 Additional entries specific to a type 1 shading dictionary
|
||
|
||
// "(Optional) An array of four numbers [ xmin xmax ymin ymax ] specifying the
|
||
// rectangular domain of coordinates over which the color function(s) are defined.
|
||
// Default value: [ 0.0 1.0 0.0 1.0 ]."
|
||
Gfx::FloatRect domain { 0.0f, 0.0f, 1.0f, 1.0f };
|
||
if (shading_dict->contains(CommonNames::Domain)) {
|
||
auto domain_array = TRY(shading_dict->get_array(document, CommonNames::Domain));
|
||
if (domain_array->size() != 4)
|
||
return Error::malformed_error("Domain must have 4 elements");
|
||
float xmin = domain_array->at(0).to_float();
|
||
float xmax = domain_array->at(1).to_float();
|
||
float ymin = domain_array->at(2).to_float();
|
||
float ymax = domain_array->at(3).to_float();
|
||
domain = Gfx::FloatRect::from_two_points({ xmin, ymin }, { xmax, ymax });
|
||
}
|
||
|
||
// "(Optional) An array of six numbers specifying a transformation matrix mapping
|
||
// the coordinate space specified by the Domain entry into the shading’s target co-
|
||
// ordinate space. For example, to map the domain rectangle [ 0.0 1.0 0.0 1.0 ] to a
|
||
// 1-inch square with lower-left corner at coordinates (100, 100) in default user
|
||
// space, the Matrix value would be [ 72 0 0 72 100 100 ]. Default value: the iden-
|
||
// tity matrix [ 1 0 0 1 0 0 ]."
|
||
Gfx::AffineTransform matrix;
|
||
if (shading_dict->contains(CommonNames::Matrix)) {
|
||
auto matrix_array = TRY(shading_dict->get_array(document, CommonNames::Matrix));
|
||
if (matrix_array->size() != 6)
|
||
return Error::malformed_error("Matrix must have 6 elements");
|
||
matrix = Gfx::AffineTransform {
|
||
matrix_array->at(0).to_float(),
|
||
matrix_array->at(1).to_float(),
|
||
matrix_array->at(2).to_float(),
|
||
matrix_array->at(3).to_float(),
|
||
matrix_array->at(4).to_float(),
|
||
matrix_array->at(5).to_float(),
|
||
};
|
||
}
|
||
|
||
// "(Required) A 2-in, n-out function or an array of n 2-in, 1-out functions (where n
|
||
// is the number of color components in the shading dictionary’s color space). Each
|
||
// function’s domain must be a superset of that of the shading dictionary. If the val-
|
||
// ue returned by the function for a given color component is out of range, it is ad-
|
||
// justed to the nearest valid value."
|
||
auto functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, Array { domain.x(), domain.y() }));
|
||
|
||
return adopt_ref(*new FunctionBasedShading(move(common_entries), domain, matrix, move(functions)));
|
||
}
|
||
|
||
PDFErrorOr<void> FunctionBasedShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
auto maybe_inverse_ctm = ctm.inverse();
|
||
if (!maybe_inverse_ctm.has_value())
|
||
return {};
|
||
auto inverse_ctm = maybe_inverse_ctm.value();
|
||
|
||
auto& bitmap = painter.target();
|
||
|
||
auto scale = painter.scale();
|
||
auto clip_rect = painter.clip_rect() * scale;
|
||
|
||
Vector<float, 4> color_components;
|
||
color_components.resize(m_common_entries.color_space->number_of_components());
|
||
|
||
auto maybe_to_domain = m_matrix.inverse();
|
||
if (!maybe_to_domain.has_value())
|
||
return Error::malformed_error("Matrix is not invertible");
|
||
auto to_domain = maybe_to_domain.value();
|
||
|
||
for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) {
|
||
for (int x = clip_rect.left(); x < clip_rect.right(); ++x) {
|
||
Gfx::FloatPoint shading_point = inverse_ctm.map(Gfx::FloatPoint { x, y } / scale);
|
||
auto domain_point = to_domain.map(shading_point);
|
||
if (!m_domain.contains(domain_point))
|
||
continue;
|
||
|
||
TRY(m_functions.visit(
|
||
[&](Function const& function) -> PDFErrorOr<void> {
|
||
auto result = TRY(function.evaluate(to_array({ domain_point.x(), domain_point.y() })));
|
||
result.copy_to(color_components);
|
||
return {};
|
||
},
|
||
[&](Vector<NonnullRefPtr<Function>> const& functions) -> PDFErrorOr<void> {
|
||
for (size_t i = 0; i < functions.size(); ++i) {
|
||
auto result = TRY(functions[i]->evaluate(to_array({ domain_point.x(), domain_point.y() })));
|
||
color_components[i] = result[0];
|
||
}
|
||
return {};
|
||
}));
|
||
|
||
auto color = TRY(m_common_entries.color_space->color(color_components));
|
||
bitmap.scanline(y)[x] = color.value();
|
||
}
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
class AxialShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<AxialShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
AxialShading(CommonEntries common_entries, Gfx::FloatPoint start, Gfx::FloatPoint end, float t0, float t1, NonemptyShadingFunctionsType functions, bool extend_start, bool extend_end)
|
||
: m_common_entries(move(common_entries))
|
||
, m_start(start)
|
||
, m_end(end)
|
||
, m_t0(t0)
|
||
, m_t1(t1)
|
||
, m_functions(move(functions))
|
||
, m_extend_start(extend_start)
|
||
, m_extend_end(extend_end)
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
Gfx::FloatPoint m_start;
|
||
Gfx::FloatPoint m_end;
|
||
float m_t0 { 0.0f };
|
||
float m_t1 { 1.0f };
|
||
NonemptyShadingFunctionsType m_functions;
|
||
bool m_extend_start { false };
|
||
bool m_extend_end { false };
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<AxialShading>> AxialShading::create(Document* document, NonnullRefPtr<DictObject> shading_dict, CommonEntries common_entries)
|
||
{
|
||
// TABLE 4.30 Additional entries specific to a type 2 shading dictionary
|
||
// "(Required) An array of four numbers [ x0 y0 x1 y1 ] specifying the starting and
|
||
// ending coordinates of the axis, expressed in the shading’s target coordinate
|
||
// space."
|
||
auto coords = TRY(shading_dict->get_array(document, CommonNames::Coords));
|
||
if (coords->size() != 4)
|
||
return Error::malformed_error("Coords must have 4 elements");
|
||
Gfx::FloatPoint start { coords->at(0).to_float(), coords->at(1).to_float() };
|
||
Gfx::FloatPoint end { coords->at(2).to_float(), coords->at(3).to_float() };
|
||
|
||
// "(Optional) An array of two numbers [ t0 t1 ] specifying the limiting values of a
|
||
// parametric variable t. The variable is considered to vary linearly between these
|
||
// two values as the color gradient varies between the starting and ending points of
|
||
// the axis. The variable t becomes the input argument to the color function(s). De-
|
||
// fault value: [ 0.0 1.0 ]."
|
||
float t0 = 0.0f;
|
||
float t1 = 1.0f;
|
||
if (shading_dict->contains(CommonNames::Domain)) {
|
||
auto domain_array = TRY(shading_dict->get_array(document, CommonNames::Domain));
|
||
if (domain_array->size() != 2)
|
||
return Error::malformed_error("Domain must have 2 elements");
|
||
t0 = domain_array->at(0).to_float();
|
||
t1 = domain_array->at(1).to_float();
|
||
}
|
||
|
||
// "(Required) A 1-in, n-out function or an array of n 1-in, 1-out functions (where n
|
||
// is the number of color components in the shading dictionary’s color space). The
|
||
// function(s) are called with values of the parametric variable t in the domain de-
|
||
// fined by the Domain entry. Each function’s domain must be a superset of that of
|
||
// the shading dictionary. If the value returned by the function for a given color
|
||
// component is out of range, it is adjusted to the nearest valid value."
|
||
auto functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, t0));
|
||
|
||
// "(Optional) An array of two boolean values specifying whether to extend the
|
||
// shading beyond the starting and ending points of the axis, respectively. Default
|
||
// value: [ false false ]."
|
||
bool extend_start = false;
|
||
bool extend_end = false;
|
||
if (shading_dict->contains(CommonNames::Extend)) {
|
||
auto extend_array = TRY(shading_dict->get_array(document, CommonNames::Extend));
|
||
if (extend_array->size() != 2)
|
||
return Error::malformed_error("Extend must have 2 elements");
|
||
extend_start = extend_array->at(0).get<bool>();
|
||
extend_end = extend_array->at(1).get<bool>();
|
||
}
|
||
|
||
return adopt_ref(*new AxialShading(move(common_entries), start, end, t0, t1, move(functions), extend_start, extend_end));
|
||
}
|
||
|
||
PDFErrorOr<void> AxialShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
auto maybe_inverse_ctm = ctm.inverse();
|
||
if (!maybe_inverse_ctm.has_value())
|
||
return {};
|
||
auto inverse_ctm = maybe_inverse_ctm.value();
|
||
|
||
auto& bitmap = painter.target();
|
||
|
||
auto scale = painter.scale();
|
||
auto clip_rect = painter.clip_rect() * scale;
|
||
|
||
Vector<float, 4> color_components;
|
||
color_components.resize(m_common_entries.color_space->number_of_components());
|
||
|
||
for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) {
|
||
for (int x = clip_rect.left(); x < clip_rect.right(); ++x) {
|
||
Gfx::FloatPoint pdf = inverse_ctm.map(Gfx::FloatPoint { x, y } / scale);
|
||
|
||
// FIXME: Normalize m_end to have unit length from m_start.
|
||
Gfx::FloatVector2 to_point { pdf.x() - m_start.x(), pdf.y() - m_start.y() };
|
||
Gfx::FloatVector2 to_end { m_end.x() - m_start.x(), m_end.y() - m_start.y() };
|
||
float x_prime = to_point.dot(to_end) / to_end.dot(to_end);
|
||
|
||
float t;
|
||
if (0 <= x_prime && x_prime <= 1)
|
||
t = m_t0 + (m_t1 - m_t0) * x_prime;
|
||
else if (x_prime < 0) {
|
||
if (!m_extend_start)
|
||
continue;
|
||
t = m_t0;
|
||
} else {
|
||
if (!m_extend_end)
|
||
continue;
|
||
t = m_t1;
|
||
}
|
||
|
||
TRY(m_functions.visit(
|
||
[&](Function const& function) -> PDFErrorOr<void> {
|
||
auto result = TRY(function.evaluate(to_array({ t })));
|
||
result.copy_to(color_components);
|
||
return {};
|
||
},
|
||
[&](Vector<NonnullRefPtr<Function>> const& functions) -> PDFErrorOr<void> {
|
||
for (size_t i = 0; i < functions.size(); ++i) {
|
||
auto result = TRY(functions[i]->evaluate(to_array({ t })));
|
||
color_components[i] = result[0];
|
||
}
|
||
return {};
|
||
}));
|
||
|
||
auto color = TRY(m_common_entries.color_space->color(color_components));
|
||
bitmap.scanline(y)[x] = color.value();
|
||
}
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
class RadialShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<RadialShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
RadialShading(CommonEntries common_entries, Gfx::FloatPoint start, float start_radius, Gfx::FloatPoint end, float end_radius, float t0, float t1, NonemptyShadingFunctionsType functions, bool extend_start, bool extend_end)
|
||
: m_common_entries(move(common_entries))
|
||
, m_start(start)
|
||
, m_start_radius(start_radius)
|
||
, m_end(end)
|
||
, m_end_radius(end_radius)
|
||
, m_t0(t0)
|
||
, m_t1(t1)
|
||
, m_functions(move(functions))
|
||
, m_extend_start(extend_start)
|
||
, m_extend_end(extend_end)
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
Gfx::FloatPoint m_start;
|
||
float m_start_radius { 0.0f };
|
||
Gfx::FloatPoint m_end;
|
||
float m_end_radius { 0.0f };
|
||
float m_t0 { 0.0f };
|
||
float m_t1 { 1.0f };
|
||
NonemptyShadingFunctionsType m_functions;
|
||
bool m_extend_start { false };
|
||
bool m_extend_end { false };
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<RadialShading>> RadialShading::create(Document* document, NonnullRefPtr<DictObject> shading_dict, CommonEntries common_entries)
|
||
{
|
||
// TABLE 4.31 Additional entries specific to a type 3 shading dictionary
|
||
// "(Required) An array of six numbers [ x0 y0 r0 x1 y1 r1 ] specifying the centers and
|
||
// radii of the starting and ending circles, expressed in the shading’s target coor-
|
||
// dinate space. The radii r0 and r1 must both be greater than or equal to 0. If one
|
||
// radius is 0, the corresponding circle is treated as a point; if both are 0, nothing is
|
||
// painted."
|
||
auto coords = TRY(shading_dict->get_array(document, CommonNames::Coords));
|
||
if (coords->size() != 6)
|
||
return Error::malformed_error("Coords must have 6 elements");
|
||
Gfx::FloatPoint start { coords->at(0).to_float(), coords->at(1).to_float() };
|
||
float start_radius = coords->at(2).to_float();
|
||
Gfx::FloatPoint end { coords->at(3).to_float(), coords->at(4).to_float() };
|
||
float end_radius = coords->at(5).to_float();
|
||
|
||
// "(Optional) An array of two numbers [ t0 t1 ] specifying the limiting values of a
|
||
// parametric variable t. The variable is considered to vary linearly between these
|
||
// two values as the color gradient varies between the starting and ending circles.
|
||
// The variable t becomes the input argument to the color function(s). Default
|
||
// value: [ 0.0 1.0 ]."
|
||
float t0 = 0.0f;
|
||
float t1 = 1.0f;
|
||
if (shading_dict->contains(CommonNames::Domain)) {
|
||
auto domain_array = TRY(shading_dict->get_array(document, CommonNames::Domain));
|
||
if (domain_array->size() != 2)
|
||
return Error::malformed_error("Domain must have 2 elements");
|
||
t0 = domain_array->at(0).to_float();
|
||
t1 = domain_array->at(1).to_float();
|
||
}
|
||
|
||
// "(Required) A 1-in, n-out function or an array of n 1-in, 1-out functions (where n
|
||
// is the number of color components in the shading dictionary’s color space). The
|
||
// function(s) are called with values of the parametric variable t in the domain de-
|
||
// fined by the shading dictionary’s Domain entry. Each function’s domain must be
|
||
// a superset of that of the shading dictionary. If the value returned by the function
|
||
// for a given color component is out of range, it is adjusted to the nearest valid val-
|
||
// ue."
|
||
auto functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, t0));
|
||
|
||
// "(Optional) An array of two boolean values specifying whether to extend the
|
||
// shading beyond the starting and ending circles, respectively. Default value:
|
||
// [ false false ]."
|
||
bool extend_start = false;
|
||
bool extend_end = false;
|
||
if (shading_dict->contains(CommonNames::Extend)) {
|
||
auto extend_array = TRY(shading_dict->get_array(document, CommonNames::Extend));
|
||
if (extend_array->size() != 2)
|
||
return Error::malformed_error("Extend must have 2 elements");
|
||
extend_start = extend_array->at(0).get<bool>();
|
||
extend_end = extend_array->at(1).get<bool>();
|
||
}
|
||
|
||
return adopt_ref(*new RadialShading(move(common_entries), start, start_radius, end, end_radius, t0, t1, move(functions), extend_start, extend_end));
|
||
}
|
||
|
||
PDFErrorOr<void> RadialShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
auto maybe_inverse_ctm = ctm.inverse();
|
||
if (!maybe_inverse_ctm.has_value())
|
||
return {};
|
||
auto inverse_ctm = maybe_inverse_ctm.value();
|
||
|
||
auto& bitmap = painter.target();
|
||
|
||
auto scale = painter.scale();
|
||
auto clip_rect = painter.clip_rect() * scale;
|
||
|
||
Vector<float, 4> color_components;
|
||
color_components.resize(m_common_entries.color_space->number_of_components());
|
||
|
||
// FIXME: Use smaller box if the circles are nested and the outer circle is not extended.
|
||
|
||
for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) {
|
||
for (int x = clip_rect.left(); x < clip_rect.right(); ++x) {
|
||
Gfx::FloatPoint point = inverse_ctm.map(Gfx::FloatPoint { x, y } / scale);
|
||
|
||
// The spec explains how to get a point given s. We want to solve the inverse problem:
|
||
// The current pixel is at p. We want to find the s where (c(s) - p)^2 = r(s)^2 (eq 1).
|
||
// Per spec, the circle depending on s has its center at
|
||
//
|
||
// c(s) = c0 + s * (c1 - c0)
|
||
//
|
||
// and a radius of
|
||
//
|
||
// r(s) = r0 + s * (r1 - r0)
|
||
//
|
||
// Putting that into (eq 1):
|
||
//
|
||
// (c0 + s * (c1 - c0) - p)^2 = (r0 + s * (r1 - r0))^2
|
||
//
|
||
// Rearranging terms, we get a quadratic equation in s:
|
||
//
|
||
// A * s^2 + B * s + C = 0
|
||
//
|
||
// with:
|
||
//
|
||
// A = (c1 - c0)^2 - (r1 - r0)^2
|
||
// B = -2 * ((c1 - c0) * (p - c0) + (r1 - r0) * r0)
|
||
// C = (c0 - p)^2 - r0^2
|
||
//
|
||
// When both circles touch in one point, A = 0 and we get a linear equation instead.
|
||
|
||
// FIXME: Normalize m_end to have unit length from m_start.
|
||
Gfx::FloatVector2 to_point { point.x() - m_start.x(), point.y() - m_start.y() };
|
||
Gfx::FloatVector2 to_end { m_end.x() - m_start.x(), m_end.y() - m_start.y() };
|
||
float dr = m_end_radius - m_start_radius;
|
||
|
||
float A = to_end.dot(to_end) - dr * dr;
|
||
float B = -2 * (to_end.dot(to_point) + dr * m_start_radius);
|
||
float C = to_point.dot(to_point) - m_start_radius * m_start_radius;
|
||
float s_0;
|
||
float s_1;
|
||
if (A != 0) {
|
||
float discriminant = B * B - 4 * A * C;
|
||
if (discriminant < 0)
|
||
continue;
|
||
|
||
s_0 = (-B + sqrt(discriminant)) / (2 * A);
|
||
s_1 = (-B - sqrt(discriminant)) / (2 * A);
|
||
if (A < 0)
|
||
swap(s_0, s_1);
|
||
} else {
|
||
// Linear case: B * s + C = 0
|
||
s_0 = -C / B;
|
||
s_1 = s_0;
|
||
}
|
||
|
||
float s;
|
||
if (to_end.length() < max(m_start_radius, m_end_radius) - min(m_start_radius, m_end_radius)) {
|
||
// One circle is inside the other one.
|
||
// One of s_0, s_1 will be 0..1 in the main gradient part, and the other one will be negative in the whole circle.
|
||
s = m_start_radius < m_end_radius ? s_0 : s_1;
|
||
if (s < 0) {
|
||
if (!m_extend_start)
|
||
continue;
|
||
s = 0;
|
||
} else if (s > 1) {
|
||
if (!m_extend_end)
|
||
continue;
|
||
s = 1;
|
||
}
|
||
} else {
|
||
// Two disjoint or overlapping circles. Assuming the start circle is to the left of the end circle,
|
||
// s_0 is the value of s when the left side of the circle touches the current point, while s_1 is the value
|
||
// of s when the right side of the circle touches the current point. The forward formulation in the spec
|
||
// says we're drawing the circles in increasing order of s, so the s_0 value is the one that draws on
|
||
// top for the points drawn by both edges.
|
||
// s_0 is in [0..1] in the start circle up to outside of the end circle (where it's > 1).
|
||
// s_1 is in [0..1] in the end circle up to outside of the start circle (where it's < 0).
|
||
s = s_0 >= 0 && s_0 <= 1 ? s_0 : s_1;
|
||
|
||
if (m_extend_start) {
|
||
if (m_start_radius <= m_end_radius && s < -m_start_radius / dr)
|
||
continue;
|
||
if (s < 0)
|
||
s = 0;
|
||
} else {
|
||
if (s < 0 && !(m_extend_end && s_0 > 0))
|
||
continue;
|
||
}
|
||
|
||
if (m_extend_end) {
|
||
if (m_start_radius > m_end_radius && s > -m_start_radius / dr)
|
||
continue;
|
||
if (s_0 > 1)
|
||
s = 1;
|
||
} else {
|
||
if (s > 1)
|
||
continue;
|
||
}
|
||
}
|
||
|
||
float t = m_t0 + s * (m_t1 - m_t0);
|
||
|
||
TRY(m_functions.visit(
|
||
[&](Function const& function) -> PDFErrorOr<void> {
|
||
auto result = TRY(function.evaluate(to_array({ t })));
|
||
result.copy_to(color_components);
|
||
return {};
|
||
},
|
||
[&](Vector<NonnullRefPtr<Function>> const& functions) -> PDFErrorOr<void> {
|
||
for (size_t i = 0; i < functions.size(); ++i) {
|
||
auto result = TRY(functions[i]->evaluate(to_array({ t })));
|
||
color_components[i] = result[0];
|
||
}
|
||
return {};
|
||
}));
|
||
|
||
auto color = TRY(m_common_entries.color_space->color(color_components));
|
||
bitmap.scanline(y)[x] = color.value();
|
||
}
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
using GouraudColor = Vector<float, 4>;
|
||
|
||
struct GouraudBounds {
|
||
GouraudColor min;
|
||
GouraudColor max;
|
||
};
|
||
|
||
static GouraudBounds bounds_from_decode_array(ReadonlySpan<float> decode_array)
|
||
{
|
||
VERIFY(decode_array.size() % 2 == 0);
|
||
size_t number_of_components = decode_array.size() / 2;
|
||
GouraudBounds bounds;
|
||
bounds.min.resize(number_of_components);
|
||
bounds.max.resize(number_of_components);
|
||
for (size_t i = 0; i < number_of_components; ++i) {
|
||
bounds.min[i] = decode_array[i * 2];
|
||
bounds.max[i] = decode_array[i * 2 + 1];
|
||
}
|
||
return bounds;
|
||
}
|
||
|
||
class GouraudPaintStyle final : public Gfx::PaintStyle {
|
||
public:
|
||
static NonnullRefPtr<GouraudPaintStyle> create(NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 3> points, Array<GouraudColor, 3> colors, GouraudBounds bounds)
|
||
{
|
||
return adopt_ref(*new GouraudPaintStyle(move(color_space), move(functions), move(points), move(colors), move(bounds)));
|
||
}
|
||
|
||
// We can't override sample_color() because it doesn't receive a useful origin.
|
||
// Instead, override `paint()` and pass the origin to similar function.
|
||
// FIXME: Try changing the signature of sample_color() to receive the actual origin.
|
||
virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override
|
||
{
|
||
paint([this, physical_bounding_box](Gfx::IntPoint point) { return sample_color_in_bbox(physical_bounding_box.location() + point); });
|
||
}
|
||
|
||
private:
|
||
GouraudPaintStyle(NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 3> points, Array<GouraudColor, 3> colors, GouraudBounds bounds)
|
||
: m_functions(move(functions))
|
||
, m_color_space(move(color_space))
|
||
, m_points(move(points))
|
||
, m_colors(move(colors))
|
||
, m_bounds(move(bounds))
|
||
{
|
||
}
|
||
|
||
Gfx::Color sample_color_in_bbox(Gfx::IntPoint) const;
|
||
|
||
ShadingFunctionsType m_functions;
|
||
NonnullRefPtr<ColorSpaceWithFloatArgs> m_color_space;
|
||
Array<Gfx::FloatPoint, 3> m_points;
|
||
Array<GouraudColor, 3> m_colors;
|
||
GouraudBounds m_bounds;
|
||
};
|
||
|
||
Gfx::Color GouraudPaintStyle::sample_color_in_bbox(Gfx::IntPoint point_in_bbox) const
|
||
{
|
||
auto signed_area = [](Gfx::FloatPoint a, Gfx::FloatPoint b, Gfx::FloatPoint c) {
|
||
return (a.x() - c.x()) * (b.y() - c.y()) - (b.x() - c.x()) * (a.y() - c.y());
|
||
};
|
||
|
||
auto point = Gfx::FloatPoint { point_in_bbox };
|
||
|
||
float area = signed_area(m_points[0], m_points[1], m_points[2]);
|
||
VERIFY(area != 0);
|
||
float alpha = signed_area(point, m_points[1], m_points[2]) / area;
|
||
float beta = signed_area(m_points[0], point, m_points[2]) / area;
|
||
float gamma = signed_area(m_points[0], m_points[1], point) / area;
|
||
|
||
GouraudColor color;
|
||
color.resize(m_color_space->number_of_components());
|
||
|
||
m_functions.visit(
|
||
[&](Empty) {
|
||
for (int i = 0; i < m_color_space->number_of_components(); ++i)
|
||
color[i] = clamp(alpha * m_colors[0][i] + beta * m_colors[1][i] + gamma * m_colors[2][i], m_bounds.min[i], m_bounds.max[i]);
|
||
},
|
||
[&](Function const& function) {
|
||
float input = clamp(alpha * m_colors[0][0] + beta * m_colors[1][0] + gamma * m_colors[2][0], m_bounds.min[0], m_bounds.max[0]);
|
||
auto result = MUST(function.evaluate(to_array({ input })));
|
||
result.copy_to(color);
|
||
},
|
||
[&](Vector<NonnullRefPtr<Function>> const& functions) {
|
||
float input = clamp(alpha * m_colors[0][0] + beta * m_colors[1][0] + gamma * m_colors[2][0], m_bounds.min[0], m_bounds.max[0]);
|
||
for (size_t i = 0; i < functions.size(); ++i) {
|
||
auto result = MUST(functions[i]->evaluate(to_array({ input })));
|
||
color[i] = result[0];
|
||
}
|
||
});
|
||
|
||
return MUST(m_color_space->color(color));
|
||
}
|
||
|
||
void draw_gouraud_triangle(Gfx::Painter& painter, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 3> points, Array<GouraudColor, 3> colors, GouraudBounds const& bounds)
|
||
{
|
||
static_assert(points.size() == 3);
|
||
static_assert(colors.size() == 3);
|
||
|
||
Gfx::Path triangle_path;
|
||
triangle_path.move_to(points[0]);
|
||
triangle_path.line_to(points[1]);
|
||
triangle_path.line_to(points[2]);
|
||
triangle_path.close();
|
||
|
||
auto paint_style = GouraudPaintStyle::create(move(color_space), move(functions), move(points), move(colors), bounds);
|
||
|
||
// To hide triangle edges. (Setting this to <Gfx::SampleAA> is useful for debugging; it makes triangle edges visible.)
|
||
painter.fill_path<Gfx::SampleNoAA>(triangle_path, paint_style);
|
||
}
|
||
|
||
struct Triangle {
|
||
u32 a;
|
||
u32 b;
|
||
u32 c;
|
||
};
|
||
|
||
PDFErrorOr<void> draw_gouraud_triangles(Gfx::Painter& painter, Gfx::AffineTransform const& ctm, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType const& functions, Vector<Triangle> const& triangles, Vector<float> const& vertex_data, GouraudBounds bounds)
|
||
{
|
||
size_t const number_of_components = !functions.has<Empty>() ? 1 : color_space->number_of_components();
|
||
bool is_indexed = color_space->family() == ColorSpaceFamily::Indexed;
|
||
RefPtr<IndexedColorSpace> indexed_color_space;
|
||
if (is_indexed) {
|
||
indexed_color_space = static_ptr_cast<IndexedColorSpace>(color_space);
|
||
color_space = indexed_color_space->base_color_space();
|
||
bounds = bounds_from_decode_array(color_space->default_decode());
|
||
}
|
||
|
||
int const n = 2 + number_of_components;
|
||
for (auto const& triangle : triangles) {
|
||
// FIXME: early-out for triangles completely outside clip
|
||
auto a = Gfx::FloatPoint { vertex_data[triangle.a * n], vertex_data[triangle.a * n + 1] };
|
||
auto b = Gfx::FloatPoint { vertex_data[triangle.b * n], vertex_data[triangle.b * n + 1] };
|
||
auto c = Gfx::FloatPoint { vertex_data[triangle.c * n], vertex_data[triangle.c * n + 1] };
|
||
|
||
a = ctm.map(a);
|
||
b = ctm.map(b);
|
||
c = ctm.map(c);
|
||
|
||
Array<GouraudColor, 3> colors;
|
||
for (auto [i, triangle_index] : enumerate(to_array<u32>({ triangle.a, triangle.b, triangle.c }))) {
|
||
GouraudColor color;
|
||
if (is_indexed) {
|
||
// "If ColorSpace is an Indexed color space, all color values specified in the shading
|
||
// are immediately converted to the base color space. [...] Interpolation never occurs
|
||
// in an Indexed color space, which is quantized and therefore inappropriate for calculations
|
||
// that assume a continuous range of colors."
|
||
color.extend(TRY(indexed_color_space->base_components(vertex_data[triangle_index * n + 2])));
|
||
} else {
|
||
color.resize(number_of_components);
|
||
for (size_t j = 0; j < number_of_components; ++j)
|
||
color[j] = vertex_data[triangle_index * n + 2 + j];
|
||
}
|
||
colors[i] = color;
|
||
}
|
||
draw_gouraud_triangle(painter, color_space, functions, { a, b, c }, move(colors), move(bounds));
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
static void draw_gouraud_quad(Gfx::Painter& painter, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 4> points, Array<GouraudColor, 4> colors, GouraudBounds const& bounds)
|
||
{
|
||
// FIXME: https://gpuopen.com/learn/bilinear-interpolation-quadrilateral-barycentric-coordinates/ / https://jcgt.org/published/0011/03/04/paper.pdf instead.
|
||
draw_gouraud_triangle(painter, color_space, functions, { points[0], points[1], points[3] }, { colors[0], colors[1], colors[3] }, bounds);
|
||
draw_gouraud_triangle(painter, color_space, functions, { points[0], points[2], points[3] }, { colors[0], colors[2], colors[3] }, bounds);
|
||
}
|
||
|
||
struct GouraudBezierPatch {
|
||
Array<Gfx::FloatPoint, 16> points {};
|
||
Array<GouraudColor, 4> colors {};
|
||
};
|
||
|
||
static void draw_gouraud_bezier_patch(Gfx::Painter& painter, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, GouraudBezierPatch const& patch, GouraudBounds const& bounds, int depth = 0)
|
||
{
|
||
auto const& points = patch.points;
|
||
auto const& colors = patch.colors;
|
||
|
||
// FIXME: This is very naive. Instead, compute error from linear patch and adaptively subdivide based on that error.
|
||
// Figure out a way to deal with T-junctions.
|
||
if (depth == 5) {
|
||
draw_gouraud_quad(painter, color_space, functions, { points[0], points[3], points[12], points[15] }, colors, bounds);
|
||
return;
|
||
}
|
||
|
||
auto lerp = [](GouraudColor a, GouraudColor b, float t) {
|
||
GouraudColor c;
|
||
c.resize(a.size());
|
||
for (size_t i = 0; i < a.size(); ++i)
|
||
c[i] = mix(a[i], b[i], t);
|
||
return c;
|
||
};
|
||
|
||
GouraudBezierPatch new_patch;
|
||
auto& new_points = new_patch.points;
|
||
auto& new_colors = new_patch.colors;
|
||
|
||
// FIXME: Use separable De Casteljau's to do fewer additions and multiplications.
|
||
|
||
// Lower left.
|
||
// clang-format off
|
||
new_points[0] = points[0];
|
||
new_points[1] = (points[0] + points[1]) / 2.0f;
|
||
new_points[2] = (points[0] + points[1] * 2 + points[2]) / 4.0f;
|
||
new_points[3] = (points[0] + points[1] * 3 + points[2] * 3 + points[3]) / 8.0f;
|
||
|
||
new_points[4] = (points[0] + points[4]) / 2.0f;
|
||
new_points[5] = (points[0] + points[4] + points[1] + points[5]) / 4.0f;
|
||
new_points[6] = (points[0] + points[4] + (points[1] + points[5]) * 2 + points[2] + points[6]) / 8.0f;
|
||
new_points[7] = (points[0] + points[4] + (points[1] + points[5]) * 3 + (points[2] + points[6]) * 3 + points[3] + points[7]) / 16.0f;
|
||
|
||
new_points[8] = (points[0] + points[4] * 2 + points[8]) / 4.0f;
|
||
new_points[9] = (points[0] + points[4] * 2 + points[8] + points[1] + points[5] * 2 + points[9]) / 8.0f;
|
||
new_points[10] = (points[0] + points[4] * 2 + points[8] + (points[1] + points[5] * 2 + points[9]) * 2 + points[2] + points[6] * 2 + points[10]) / 16.0f;
|
||
new_points[11] = (points[0] + points[4] * 2 + points[8] + (points[1] + points[5] * 2 + points[9]) * 3 + (points[2] + points[6] * 2 + points[10]) * 3 + points[3] + points[7] * 2 + points[11]) / 32.0f;
|
||
|
||
new_points[12] = (points[0] + points[4] * 3 + points[8] * 3 + points[12]) / 8.0f;
|
||
new_points[13] = (points[0] + points[4] * 3 + points[8] * 3 + points[12] + points[1] + points[5] * 3 + points[9] * 3 + points[13]) / 16.0f;
|
||
new_points[14] = (points[0] + points[4] * 3 + points[8] * 3 + points[12] + (points[1] + points[5] * 3 + points[9] * 3 + points[13]) * 2 + points[2] + points[6] * 3 + points[10] * 3 + points[14]) / 32.0f;
|
||
new_points[15] = (points[0] + points[4] * 3 + points[8] * 3 + points[12] + (points[1] + points[5] * 3 + points[9] * 3 + points[13]) * 3 + (points[2] + points[6] * 3 + points[10] * 3 + points[14]) * 3 + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 64.0f;
|
||
// clang-format on
|
||
|
||
new_colors[0] = colors[0];
|
||
new_colors[1] = lerp(colors[0], colors[1], 0.5f);
|
||
new_colors[2] = lerp(colors[0], colors[2], 0.5f);
|
||
new_colors[3] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
|
||
|
||
draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);
|
||
|
||
// Lower right.
|
||
// clang-format off
|
||
new_points[0] = (points[0] + points[1] * 3 + points[2] * 3 + points[3]) / 8.0f;
|
||
new_points[1] = (points[1] + points[2] * 2 + points[3]) / 4.0f;
|
||
new_points[2] = (points[2] + points[3]) / 2.0f;
|
||
new_points[3] = points[3];
|
||
|
||
new_points[4] = (points[0] + points[4] + (points[1] + points[5]) * 3 + (points[2] + points[6]) * 3 + points[3] + points[7]) / 16.0f;
|
||
new_points[5] = (points[1] + points[5] + (points[2] + points[6]) * 2 + points[3] + points[7]) / 8.0f;
|
||
new_points[6] = (points[2] + points[6] + points[3] + points[7]) / 4.0f;
|
||
new_points[7] = (points[3] + points[7]) / 2.0f;
|
||
|
||
new_points[8] = (points[0] + points[4] * 2 + points[ 8] + (points[1] + points[5] * 2 + points[ 9]) * 3 + (points[2] + points[6] * 2 + points[10]) * 3 + points[3] + points[7] * 2 + points[11]) / 32.0f;
|
||
new_points[9] = (points[1] + points[5] * 2 + points[ 9] + (points[2] + points[6] * 2 + points[10]) * 2 + points[3] + points[7] * 2 + points[11]) / 16.0f;
|
||
new_points[10] = (points[2] + points[6] * 2 + points[10] + points[3] + points[7] * 2 + points[11]) / 8.0f;
|
||
new_points[11] = (points[3] + points[7] * 2 + points[11]) / 4.0f;
|
||
|
||
new_points[12] = (points[0] + points[4] * 3 + points[ 8] * 3 + points[12] + (points[1] + points[5] * 3 + points[9] * 3 + points[13]) * 3 + (points[2] + points[6] * 3 + points[10] * 3 + points[14]) * 3 + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 64.0f;
|
||
new_points[13] = (points[1] + points[5] * 3 + points[ 9] * 3 + points[13] + (points[2] + points[6] * 3 + points[10] * 3 + points[14]) * 2 + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 32.0f;
|
||
new_points[14] = (points[2] + points[6] * 3 + points[10] * 3 + points[14] + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 16.0f;
|
||
new_points[15] = (points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 8.0f;
|
||
// clang-format on
|
||
|
||
new_colors[0] = lerp(colors[0], colors[1], 0.5f);
|
||
new_colors[1] = colors[1];
|
||
new_colors[2] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
|
||
new_colors[3] = lerp(colors[1], colors[3], 0.5f);
|
||
|
||
draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);
|
||
|
||
// Upper left.
|
||
// clang-format off
|
||
new_points[12] = points[12];
|
||
new_points[13] = (points[12] + points[13]) / 2.0f;
|
||
new_points[14] = (points[12] + points[13] * 2 + points[14]) / 4.0f;
|
||
new_points[15] = (points[12] + points[13] * 3 + points[14] * 3 + points[15]) / 8.0f;
|
||
|
||
new_points[8] = (points[12] + points[8]) / 2.0f;
|
||
new_points[9] = (points[12] + points[8] + points[13] + points[9]) / 4.0f;
|
||
new_points[10] = (points[12] + points[8] + (points[13] + points[9]) * 2 + points[14] + points[10]) / 8.0f;
|
||
new_points[11] = (points[12] + points[8] + (points[13] + points[9]) * 3 + (points[14] + points[10]) * 3 + points[15] + points[11]) / 16.0f;
|
||
|
||
new_points[4] = (points[12] + points[8] * 2 + points[4]) / 4.0f;
|
||
new_points[5] = (points[12] + points[8] * 2 + points[4] + points[13] + points[9] * 2 + points[5]) / 8.0f;
|
||
new_points[6] = (points[12] + points[8] * 2 + points[4] + (points[13] + points[9] * 2 + points[5]) * 2 + points[14] + points[10] * 2 + points[6]) / 16.0f;
|
||
new_points[7] = (points[12] + points[8] * 2 + points[4] + (points[13] + points[9] * 2 + points[5]) * 3 + (points[14] + points[10] * 2 + points[6]) * 3 + points[15] + points[11] * 2 + points[7]) / 32.0f;
|
||
|
||
new_points[0] = (points[12] + points[8] * 3 + points[4] * 3 + points[0]) / 8.0f;
|
||
new_points[1] = (points[12] + points[8] * 3 + points[4] * 3 + points[0] + points[13] + points[9] * 3 + points[5] * 3 + points[1]) / 16.0f;
|
||
new_points[2] = (points[12] + points[8] * 3 + points[4] * 3 + points[0] + (points[13] + points[9] * 3 + points[5] * 3 + points[1]) * 2 + points[14] + points[10] * 3 + points[6] * 3 + points[2]) / 32.0f;
|
||
new_points[3] = (points[12] + points[8] * 3 + points[4] * 3 + points[0] + (points[13] + points[9] * 3 + points[5] * 3 + points[1]) * 3 + (points[14] + points[10] * 3 + points[6] * 3 + points[2]) * 3 + points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 64.0f;
|
||
// clang-format on
|
||
|
||
new_colors[0] = lerp(colors[0], colors[2], 0.5f);
|
||
new_colors[1] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
|
||
new_colors[2] = colors[2];
|
||
new_colors[3] = lerp(colors[2], colors[3], 0.5f);
|
||
|
||
draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);
|
||
|
||
// Upper right.
|
||
// clang-format off
|
||
new_points[12] = (points[12] + points[13] * 3 + points[14] * 3 + points[15]) / 8.0f;
|
||
new_points[13] = (points[13] + points[14] * 2 + points[15]) / 4.0f;
|
||
new_points[14] = (points[14] + points[15]) / 2.0f;
|
||
new_points[15] = points[15];
|
||
|
||
new_points[8] = (points[12] + points[ 8] + (points[13] + points[ 9]) * 3 + (points[14] + points[10]) * 3 + points[15] + points[11]) / 16.0f;
|
||
new_points[9] = (points[13] + points[ 9] + (points[14] + points[10]) * 2 + points[15] + points[11]) / 8.0f;
|
||
new_points[10] = (points[14] + points[10] + points[15] + points[11]) / 4.0f;
|
||
new_points[11] = (points[15] + points[11]) / 2.0f;
|
||
|
||
new_points[4] = (points[12] + points[ 8] * 2 + points[4] + (points[13] + points[ 9] * 2 + points[5]) * 3 + (points[14] + points[10] * 2 + points[6]) * 3 + points[15] + points[11] * 2 + points[7]) / 32.0f;
|
||
new_points[5] = (points[13] + points[ 9] * 2 + points[5] + (points[14] + points[10] * 2 + points[6]) * 2 + points[15] + points[11] * 2 + points[7]) / 16.0f;
|
||
new_points[6] = (points[14] + points[10] * 2 + points[6] + points[15] + points[11] * 2 + points[7]) / 8.0f;
|
||
new_points[7] = (points[15] + points[11] * 2 + points[7]) / 4.0f;
|
||
|
||
new_points[0] = (points[12] + points[ 8] * 3 + points[4] * 3 + points[0] + (points[13] + points[ 9] * 3 + points[5] * 3 + points[1]) * 3 + (points[14] + points[10] * 3 + points[6] * 3 + points[2]) * 3 + points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 64.0f;
|
||
new_points[1] = (points[13] + points[ 9] * 3 + points[5] * 3 + points[1] + (points[14] + points[10] * 3 + points[6] * 3 + points[2]) * 2 + points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 32.0f;
|
||
new_points[2] = (points[14] + points[10] * 3 + points[6] * 3 + points[2] + points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 16.0f;
|
||
new_points[3] = (points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 8.0f;
|
||
// clang-format on
|
||
|
||
new_colors[0] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
|
||
new_colors[1] = lerp(colors[1], colors[3], 0.5f);
|
||
new_colors[2] = lerp(colors[2], colors[3], 0.5f);
|
||
new_colors[3] = colors[3];
|
||
|
||
draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);
|
||
}
|
||
|
||
class FreeFormGouraudShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<FreeFormGouraudShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
FreeFormGouraudShading(CommonEntries common_entries, Vector<float> vertex_data, Vector<Triangle> triangles, ShadingFunctionsType functions, GouraudBounds bounds)
|
||
: m_common_entries(move(common_entries))
|
||
, m_vertex_data(move(vertex_data))
|
||
, m_triangles(move(triangles))
|
||
, m_functions(move(functions))
|
||
, m_bounds(move(bounds))
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
|
||
// Interleaved x, y, c0, c1, c2, ...
|
||
Vector<float> m_vertex_data;
|
||
Vector<Triangle> m_triangles;
|
||
ShadingFunctionsType m_functions;
|
||
GouraudBounds m_bounds;
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<FreeFormGouraudShading>> FreeFormGouraudShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
|
||
{
|
||
auto shading_dict = shading_stream->dict();
|
||
|
||
// TABLE 4.32 Additional entries specific to a type 4 shading dictionary
|
||
// "(Required) The number of bits used to represent each vertex coordinate.
|
||
// Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
|
||
int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
|
||
if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
|
||
return Error::malformed_error("BitsPerCoordinate invalid");
|
||
|
||
// "(Required) The number of bits used to represent each color component.
|
||
// Valid values are 1, 2, 4, 8, 12, and 16."
|
||
int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
|
||
if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
|
||
return Error::malformed_error("BitsPerComponent invalid");
|
||
|
||
// "(Required) The number of bits used to represent the edge flag for each ver-
|
||
// tex (see below). Valid values of BitsPerFlag are 2, 4, and 8, but only the
|
||
// least significant 2 bits in each flag value are used. Valid values for the edge
|
||
// flag are 0, 1, and 2."
|
||
int bits_per_flag = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerFlag))).to_int();
|
||
if (!first_is_one_of(bits_per_flag, 2, 4, 8))
|
||
return Error::malformed_error("BitsPerFlag invalid");
|
||
|
||
// "(Required) An array of numbers specifying how to map vertex coordinates
|
||
// and color components into the appropriate ranges of values. The decoding
|
||
// method is similar to that used in image dictionaries (see “Decode Arrays”
|
||
// on page 344). The ranges are specified as follows:
|
||
//
|
||
// [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
|
||
//
|
||
// Note that only one pair of c values should be specified if a Function entry
|
||
// is present."
|
||
auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
|
||
size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
|
||
if (decode_array->size() != 4 + 2 * number_of_components)
|
||
return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
|
||
Vector<float> decode;
|
||
decode.resize(decode_array->size());
|
||
for (size_t i = 0; i < decode_array->size(); ++i)
|
||
decode[i] = decode_array->at(i).to_float();
|
||
|
||
// "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
|
||
// (where n is the number of color components in the shading dictionary’s
|
||
// color space). If this entry is present, the color data for each vertex must be
|
||
// specified by a single parametric variable rather than by n separate color
|
||
// components. The designated function(s) are called with each interpolated
|
||
// value of the parametric variable to determine the actual color at each
|
||
// point. Each input value is forced into the range interval specified for the
|
||
// corresponding color component in the shading dictionary’s Decode array.
|
||
// Each function’s domain must be a superset of that interval. If the value re-
|
||
// turned by the function for a given color component is out of range, it is
|
||
// adjusted to the nearest valid value.
|
||
// This entry may not be used with an Indexed color space."
|
||
ShadingFunctionsType functions;
|
||
if (shading_dict->contains(CommonNames::Function))
|
||
functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));
|
||
|
||
// See "Type 4 Shadings (Free-Form Gouraud-Shaded Triangle Meshes)" in the PDF 1.7 spec for a description of the stream contents.
|
||
auto stream = FixedMemoryStream { shading_stream->bytes() };
|
||
BigEndianInputBitStream bitstream { MaybeOwned { stream } };
|
||
|
||
Vector<u8> flags;
|
||
Vector<float> vertex_data;
|
||
while (!bitstream.is_eof()) {
|
||
u8 flag = TRY(bitstream.read_bits<u8>(bits_per_flag));
|
||
if (flag > 2)
|
||
return Error::malformed_error("Invalid edge flag");
|
||
TRY(flags.try_append(flag));
|
||
|
||
u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
TRY(vertex_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
TRY(vertex_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
for (size_t i = 0; i < number_of_components; ++i) {
|
||
u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
|
||
TRY(vertex_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
|
||
}
|
||
bitstream.align_to_byte_boundary();
|
||
}
|
||
|
||
Vector<Triangle> triangles;
|
||
for (u32 i = 0; i < flags.size(); ++i) {
|
||
if (flags[i] == 0) {
|
||
if (i + 2 >= flags.size())
|
||
return Error::malformed_error("Invalid triangle");
|
||
triangles.append({ i, i + 1, i + 2 });
|
||
i += 2;
|
||
} else if (flags[i] == 1) {
|
||
if (triangles.is_empty())
|
||
return Error::malformed_error("Invalid triangle strip");
|
||
triangles.append({ triangles.last().b, triangles.last().c, i });
|
||
} else if (flags[i] == 2) {
|
||
if (triangles.is_empty())
|
||
return Error::malformed_error("Invalid triangle fan");
|
||
triangles.append({ triangles.last().a, triangles.last().c, i });
|
||
}
|
||
}
|
||
|
||
GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
|
||
return adopt_ref(*new FreeFormGouraudShading(move(common_entries), move(vertex_data), move(triangles), move(functions), move(bounds)));
|
||
}
|
||
|
||
PDFErrorOr<void> FreeFormGouraudShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
return draw_gouraud_triangles(painter, ctm, m_common_entries.color_space, m_functions, m_triangles, m_vertex_data, m_bounds);
|
||
}
|
||
|
||
class LatticeFormGouraudShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<LatticeFormGouraudShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
LatticeFormGouraudShading(CommonEntries common_entries, Vector<float> vertex_data, Vector<Triangle> triangles, ShadingFunctionsType functions, GouraudBounds bounds)
|
||
: m_common_entries(move(common_entries))
|
||
, m_vertex_data(move(vertex_data))
|
||
, m_triangles(move(triangles))
|
||
, m_functions(move(functions))
|
||
, m_bounds(move(bounds))
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
|
||
// Interleaved x, y, c0, c1, c2, ...
|
||
Vector<float> m_vertex_data;
|
||
Vector<Triangle> m_triangles;
|
||
ShadingFunctionsType m_functions;
|
||
GouraudBounds m_bounds;
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<LatticeFormGouraudShading>> LatticeFormGouraudShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
|
||
{
|
||
auto shading_dict = shading_stream->dict();
|
||
|
||
// TABLE 4.33 Additional entries specific to a type 5 shading dictionary
|
||
// "(Required) The number of bits used to represent each vertex coordinate.
|
||
// Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
|
||
int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
|
||
if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
|
||
return Error::malformed_error("BitsPerCoordinate invalid");
|
||
|
||
// "(Required) The number of bits used to represent each color component.
|
||
// Valid values are 1, 2, 4, 8, 12, and 16."
|
||
int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
|
||
if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
|
||
return Error::malformed_error("BitsPerComponent invalid");
|
||
|
||
// "(Required) The number of vertices in each row of the lattice; the value
|
||
// must be greater than or equal to 2. The number of rows need not be
|
||
// specified."
|
||
int vertices_per_row = TRY(document->resolve(shading_dict->get_value(CommonNames::VerticesPerRow))).to_int();
|
||
if (vertices_per_row < 2)
|
||
return Error::malformed_error("VerticesPerRow invalid");
|
||
|
||
// "(Required) An array of numbers specifying how to map vertex coordinates
|
||
// and color components into the appropriate ranges of values. The decoding
|
||
// method is similar to that used in image dictionaries (see “Decode Arrays”
|
||
// on page 344). The ranges are specified as follows:
|
||
//
|
||
// [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
|
||
//
|
||
// Note that only one pair of c values should be specified if a Function entry
|
||
// is present."
|
||
auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
|
||
size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
|
||
if (decode_array->size() != 4 + 2 * number_of_components)
|
||
return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
|
||
Vector<float> decode;
|
||
decode.resize(decode_array->size());
|
||
for (size_t i = 0; i < decode_array->size(); ++i)
|
||
decode[i] = decode_array->at(i).to_float();
|
||
|
||
// "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
|
||
// (where n is the number of color components in the shading dictionary’s
|
||
// color space). If this entry is present, the color data for each vertex must be
|
||
// specified by a single parametric variable rather than by n separate color
|
||
// components. The designated function(s) are called with each interpolated
|
||
// value of the parametric variable to determine the actual color at each
|
||
// point. Each input value is forced into the range interval specified for the
|
||
// corresponding color component in the shading dictionary’s Decode array.
|
||
// Each function’s domain must be a superset of that interval. If the value re-
|
||
// turned by the function for a given color component is out of range, it is
|
||
// adjusted to the nearest valid value.
|
||
// This entry may not be used with an Indexed color space."
|
||
ShadingFunctionsType functions;
|
||
if (shading_dict->contains(CommonNames::Function))
|
||
functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));
|
||
|
||
// See "Type 5 Shadings (Lattice-Form Gouraud-Shaded Triangle Meshes)" in the PDF 1.7 spec for a description of the stream contents.
|
||
auto stream = FixedMemoryStream { shading_stream->bytes() };
|
||
BigEndianInputBitStream bitstream { MaybeOwned { stream } };
|
||
|
||
Vector<float> vertex_data;
|
||
while (!bitstream.is_eof()) {
|
||
u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
TRY(vertex_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
TRY(vertex_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
for (size_t i = 0; i < number_of_components; ++i) {
|
||
u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
|
||
TRY(vertex_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
|
||
}
|
||
bitstream.align_to_byte_boundary();
|
||
}
|
||
|
||
size_t number_of_vertices = vertex_data.size() / (2 + number_of_components);
|
||
if (number_of_vertices % vertices_per_row != 0)
|
||
return Error::malformed_error("Number of vertices must be a multiple of vertices per row");
|
||
size_t number_of_rows = number_of_vertices / vertices_per_row;
|
||
if (number_of_rows < 2)
|
||
return Error::malformed_error("Number of rows must be at least 2");
|
||
|
||
Vector<Triangle> triangles;
|
||
for (u32 i = 0; i <= number_of_rows - 2; ++i) {
|
||
for (u32 j = 0; j <= static_cast<u32>(vertices_per_row - 2); ++j) {
|
||
triangles.append({ i * vertices_per_row + j, i * vertices_per_row + j + 1, (i + 1) * vertices_per_row + j });
|
||
triangles.append({ i * vertices_per_row + j + 1, (i + 1) * vertices_per_row + j, (i + 1) * vertices_per_row + j + 1 });
|
||
}
|
||
}
|
||
|
||
GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
|
||
return adopt_ref(*new LatticeFormGouraudShading(move(common_entries), move(vertex_data), move(triangles), move(functions), move(bounds)));
|
||
}
|
||
|
||
PDFErrorOr<void> LatticeFormGouraudShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
return draw_gouraud_triangles(painter, ctm, m_common_entries.color_space, m_functions, m_triangles, m_vertex_data, m_bounds);
|
||
}
|
||
|
||
class CoonsPatchShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<CoonsPatchShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
// Indexes into m_patch_data.
|
||
struct CoonsPatch {
|
||
u32 control_points[12];
|
||
u32 colors[4];
|
||
};
|
||
|
||
CoonsPatchShading(CommonEntries common_entries, Vector<float> patch_data, Vector<CoonsPatch> patches, ShadingFunctionsType functions, GouraudBounds bounds)
|
||
: m_common_entries(move(common_entries))
|
||
, m_patch_data(move(patch_data))
|
||
, m_patches(move(patches))
|
||
, m_functions(move(functions))
|
||
, m_bounds(move(bounds))
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
|
||
// Interleaved x0, y0, x1, y1, ..., x11, y11, c0, c1, c2, c3, ...
|
||
// (For flags 1-3, only 8 coordinates and 2 colors.)
|
||
Vector<float> m_patch_data;
|
||
Vector<CoonsPatch> m_patches;
|
||
ShadingFunctionsType m_functions;
|
||
GouraudBounds m_bounds;
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<CoonsPatchShading>> CoonsPatchShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
|
||
{
|
||
auto shading_dict = shading_stream->dict();
|
||
|
||
// TABLE 4.34 Additional entries specific to a type 6 shading dictionary
|
||
// "(Required) The number of bits used to represent each geometric coordi-
|
||
// nate. Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
|
||
int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
|
||
if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
|
||
return Error::malformed_error("BitsPerCoordinate invalid");
|
||
|
||
// "(Required) The number of bits used to represent each color component.
|
||
// Valid values are 1, 2, 4, 8, 12, and 16."
|
||
int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
|
||
if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
|
||
return Error::malformed_error("BitsPerComponent invalid");
|
||
|
||
// "(Required) The number of bits used to represent the edge flag for each
|
||
// patch (see below). Valid values of BitsPerFlag are 2, 4, and 8, but only the
|
||
// least significant 2 bits in each flag value are used. Valid values for the edge
|
||
// flag are 0, 1, 2, and 3."
|
||
int bits_per_flag = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerFlag))).to_int();
|
||
if (!first_is_one_of(bits_per_flag, 2, 4, 8))
|
||
return Error::malformed_error("BitsPerFlag invalid");
|
||
|
||
// "(Required) An array of numbers specifying how to map vertex coordinates
|
||
// and color components into the appropriate ranges of values. The decoding
|
||
// method is similar to that used in image dictionaries (see “Decode Arrays”
|
||
// on page 344). The ranges are specified as follows:
|
||
//
|
||
// [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
|
||
//
|
||
// Note that only one pair of c values should be specified if a Function entry
|
||
// is present."
|
||
auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
|
||
size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
|
||
if (decode_array->size() != 4 + 2 * number_of_components)
|
||
return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
|
||
Vector<float> decode;
|
||
decode.resize(decode_array->size());
|
||
for (size_t i = 0; i < decode_array->size(); ++i)
|
||
decode[i] = decode_array->at(i).to_float();
|
||
|
||
// "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
|
||
// (where n is the number of color components in the shading dictionary’s
|
||
// color space). If this entry is present, the color data for each vertex must be
|
||
// specified by a single parametric variable rather than by n separate color
|
||
// components. The designated function(s) are called with each interpolated
|
||
// value of the parametric variable to determine the actual color at each
|
||
// point. Each input value is forced into the range interval specified for the
|
||
// corresponding color component in the shading dictionary’s Decode array.
|
||
// Each function’s domain must be a superset of that interval. If the value re-
|
||
// turned by the function for a given color component is out of range, it is
|
||
// adjusted to the nearest valid value.
|
||
// This entry may not be used with an Indexed color space."
|
||
ShadingFunctionsType functions;
|
||
if (shading_dict->contains(CommonNames::Function))
|
||
functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));
|
||
|
||
// See "Type 6 Shadings (Coons Patch Meshes)" in the PDF 1.7 spec for a description of the stream contents.
|
||
auto stream = FixedMemoryStream { shading_stream->bytes() };
|
||
BigEndianInputBitStream bitstream { MaybeOwned { stream } };
|
||
|
||
Vector<float> patch_data;
|
||
Vector<CoonsPatch> patches;
|
||
|
||
auto read_point = [&]() -> ErrorOr<void> {
|
||
u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
TRY(patch_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
TRY(patch_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
return {};
|
||
};
|
||
|
||
auto read_points = [&](u32 n) -> ErrorOr<void> {
|
||
for (u32 i = 0; i < n; ++i)
|
||
TRY(read_point());
|
||
return {};
|
||
};
|
||
|
||
auto read_color = [&]() -> ErrorOr<void> {
|
||
for (size_t i = 0; i < number_of_components; ++i) {
|
||
u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
|
||
TRY(patch_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
|
||
}
|
||
return {};
|
||
};
|
||
|
||
auto read_colors = [&](u32 n) -> ErrorOr<void> {
|
||
for (u32 i = 0; i < n; ++i)
|
||
TRY(read_color());
|
||
return {};
|
||
};
|
||
|
||
while (!bitstream.is_eof()) {
|
||
u8 flag = TRY(bitstream.read_bits<u8>(bits_per_flag));
|
||
|
||
int n = patch_data.size();
|
||
CoonsPatch patch;
|
||
|
||
// "TABLE 4.35 Data values in a Coons patch mesh"
|
||
switch (flag) {
|
||
case 0:
|
||
// "x1 y1 x2 y2 x3 y3 x4 y4 x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c1 c2 c3 c4
|
||
// New patch; no implicit values"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 4 + number_of_components));
|
||
TRY(read_points(12));
|
||
TRY(read_colors(4));
|
||
for (int i = 0; i < 12; ++i)
|
||
patch.control_points[i] = n + 2 * i;
|
||
for (int i = 0; i < 4; ++i)
|
||
patch.colors[i] = n + 24 + number_of_components * i;
|
||
break;
|
||
case 1:
|
||
if (patches.is_empty())
|
||
return Error::malformed_error("Edge flag 1 without preceding patch");
|
||
// "x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c3 c4
|
||
// Implicit values:
|
||
// (x1, y1) = (x4, y4) previous
|
||
// (x2, y2) = (x5, y5) previous
|
||
// (x3, y3) = (x6, y6) previous
|
||
// (x4, y4) = (x7, y7) previous
|
||
// c1 = c2 previous
|
||
// c2 = c3 previous"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 8 * 2 + 2 + number_of_components));
|
||
TRY(read_points(8));
|
||
TRY(read_colors(2));
|
||
patch.control_points[0] = patches.last().control_points[3];
|
||
patch.control_points[1] = patches.last().control_points[4];
|
||
patch.control_points[2] = patches.last().control_points[5];
|
||
patch.control_points[3] = patches.last().control_points[6];
|
||
for (int i = 0; i < 8; ++i)
|
||
patch.control_points[i + 4] = n + 2 * i;
|
||
patch.colors[0] = patches.last().colors[1];
|
||
patch.colors[1] = patches.last().colors[2];
|
||
for (int i = 0; i < 2; ++i)
|
||
patch.colors[i + 2] = n + 16 + number_of_components * i;
|
||
break;
|
||
case 2:
|
||
if (patches.is_empty())
|
||
return Error::malformed_error("Edge flag 2 without preceding patch");
|
||
// "x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c3 c4
|
||
// Implicit values:
|
||
// (x1, y1) = (x7, y7) previous
|
||
// (x2, y2) = (x8, y8) previous
|
||
// (x3, y3) = (x9, y9) previous
|
||
// (x4, y4) = (x10, y10) previous
|
||
// c1 = c3 previous
|
||
// c2 = c4 previous"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 8 * 2 + 2 + number_of_components));
|
||
TRY(read_points(8));
|
||
TRY(read_colors(2));
|
||
patch.control_points[0] = patches.last().control_points[6];
|
||
patch.control_points[1] = patches.last().control_points[7];
|
||
patch.control_points[2] = patches.last().control_points[8];
|
||
patch.control_points[3] = patches.last().control_points[9];
|
||
for (int i = 0; i < 8; ++i)
|
||
patch.control_points[i + 4] = n + 2 * i;
|
||
patch.colors[0] = patches.last().colors[2];
|
||
patch.colors[1] = patches.last().colors[3];
|
||
for (int i = 0; i < 2; ++i)
|
||
patch.colors[i + 2] = n + 16 + number_of_components * i;
|
||
break;
|
||
case 3:
|
||
if (patches.is_empty())
|
||
return Error::malformed_error("Edge flag 3 without preceding patch");
|
||
// "x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c3 c4
|
||
// Implicit values:
|
||
// (x1, y1) = (x10, y10) previous
|
||
// (x2, y2) = (x11, y11) previous
|
||
// (x3, y3) = (x12, y12) previous
|
||
// (x4, y4) = (x1, y1) previous
|
||
// c1 = c4 previous
|
||
// c2 = c1 previous"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 8 * 2 + 2 + number_of_components));
|
||
TRY(read_points(8));
|
||
TRY(read_colors(2));
|
||
patch.control_points[0] = patches.last().control_points[9];
|
||
patch.control_points[1] = patches.last().control_points[10];
|
||
patch.control_points[2] = patches.last().control_points[11];
|
||
patch.control_points[3] = patches.last().control_points[0];
|
||
for (int i = 0; i < 8; ++i)
|
||
patch.control_points[i + 4] = n + 2 * i;
|
||
patch.colors[0] = patches.last().colors[3];
|
||
patch.colors[1] = patches.last().colors[0];
|
||
for (int i = 0; i < 2; ++i)
|
||
patch.colors[i + 2] = n + 16 + number_of_components * i;
|
||
break;
|
||
default:
|
||
return Error::malformed_error("Invalid edge flag");
|
||
}
|
||
|
||
TRY(patches.try_append(patch));
|
||
bitstream.align_to_byte_boundary();
|
||
}
|
||
|
||
GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
|
||
return adopt_ref(*new CoonsPatchShading(move(common_entries), move(patch_data), move(patches), move(functions), move(bounds)));
|
||
}
|
||
|
||
PDFErrorOr<void> CoonsPatchShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
NonnullRefPtr<ColorSpaceWithFloatArgs> color_space = m_common_entries.color_space;
|
||
size_t const number_of_components = !m_functions.has<Empty>() ? 1 : color_space->number_of_components();
|
||
|
||
bool is_indexed = color_space->family() == ColorSpaceFamily::Indexed;
|
||
RefPtr<IndexedColorSpace> indexed_color_space;
|
||
auto bounds = m_bounds;
|
||
if (is_indexed) {
|
||
indexed_color_space = static_ptr_cast<IndexedColorSpace>(color_space);
|
||
color_space = indexed_color_space->base_color_space();
|
||
bounds = bounds_from_decode_array(color_space->default_decode());
|
||
}
|
||
|
||
for (auto& patch : m_patches) {
|
||
GouraudBezierPatch bezier_patch;
|
||
auto& control_points = bezier_patch.points;
|
||
|
||
for (size_t i = 0; i < 4; ++i)
|
||
control_points[i] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[i]], m_patch_data[patch.control_points[i] + 1] });
|
||
|
||
for (size_t i = 0; i < 3; ++i)
|
||
control_points[7 + i * 4] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[4 + i]], m_patch_data[patch.control_points[4 + i] + 1] });
|
||
for (size_t i = 0; i < 3; ++i)
|
||
control_points[14 - i] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[7 + i]], m_patch_data[patch.control_points[7 + i] + 1] });
|
||
|
||
control_points[8] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[10]], m_patch_data[patch.control_points[10] + 1] });
|
||
control_points[4] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[11]], m_patch_data[patch.control_points[11] + 1] });
|
||
|
||
// "The Coons patch (type 6) is actually a special case of the tensor-product patch
|
||
// (type 7) in which the four internal control points (p11 , p12 , p21 , p22 ) are implicitly
|
||
// defined by the boundary curves. The values of the internal control points are giv-
|
||
// en by these equations:"
|
||
auto p = [&](int c, int r) -> Gfx::FloatPoint& { return bezier_patch.points[c + 4 * r]; };
|
||
|
||
p(1, 1) = (1.0f / 9.0f)
|
||
* (-4.0f * p(0, 0)
|
||
+ 6.0f * (p(0, 1) + p(1, 0))
|
||
- 2.0f * (p(0, 3) + p(3, 0))
|
||
+ 3.0f * (p(3, 1) + p(1, 3))
|
||
- 1.0f * p(3, 3));
|
||
|
||
p(1, 2) = (1.0f / 9.0f)
|
||
* (-4.0f * p(0, 3)
|
||
+ 6.0f * (p(0, 2) + p(1, 3))
|
||
- 2.0f * (p(0, 0) + p(3, 3))
|
||
+ 3.0f * (p(3, 2) + p(1, 0))
|
||
- 1.0f * p(3, 0));
|
||
|
||
p(2, 1) = (1.0f / 9.0f)
|
||
* (-4.0f * p(3, 0)
|
||
+ 6.0f * (p(3, 1) + p(2, 0))
|
||
- 2.0f * (p(3, 3) + p(0, 0))
|
||
+ 3.0f * (p(0, 1) + p(2, 3))
|
||
- 1.0f * p(0, 3));
|
||
|
||
p(2, 2) = (1.0f / 9.0f)
|
||
* (-4.0f * p(3, 3)
|
||
+ 6.0f * (p(3, 2) + p(2, 3))
|
||
- 2.0f * (p(3, 0) + p(0, 3))
|
||
+ 3.0f * (p(0, 2) + p(2, 0))
|
||
- 1.0f * p(0, 0));
|
||
|
||
for (size_t i = 0; i < 4; ++i) {
|
||
GouraudColor color;
|
||
|
||
if (is_indexed) {
|
||
// "If ColorSpace is an Indexed color space, all color values specified in the shading
|
||
// are immediately converted to the base color space. [...] Interpolation never occurs
|
||
// in an Indexed color space, which is quantized and therefore inappropriate for calculations
|
||
// that assume a continuous range of colors."
|
||
color.extend(TRY(indexed_color_space->base_components(m_patch_data[patch.colors[i]])));
|
||
} else {
|
||
color.resize(number_of_components);
|
||
for (size_t j = 0; j < number_of_components; ++j)
|
||
color[j] = m_patch_data[patch.colors[i] + j];
|
||
}
|
||
|
||
bezier_patch.colors[i] = color;
|
||
}
|
||
|
||
swap(bezier_patch.colors[2], bezier_patch.colors[3]); // Coons order goes counter-clockwise, bezier patch in scanline order.
|
||
draw_gouraud_bezier_patch(painter, color_space, m_functions, bezier_patch, bounds);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
class TensorProductPatchShading final : public Shading {
|
||
public:
|
||
static PDFErrorOr<NonnullRefPtr<TensorProductPatchShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);
|
||
|
||
virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
|
||
virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
|
||
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;
|
||
|
||
private:
|
||
// Indexes into m_patch_data.
|
||
struct TensorProductPatch {
|
||
// Pij (col i, row j) is at index:
|
||
// p03 p13 p23 p33 12 13 14 15
|
||
// p02 p12 p22 p32 <=> 8 9 10 11
|
||
// p01 p11 p21 p31 4 5 6 7
|
||
// p00 p10 p20 p30 0 1 2 3
|
||
u32 control_points[16];
|
||
|
||
// cij (col i, row j) is at index:
|
||
// c03 c33 2 3
|
||
// c00 c30 <=> 0 1
|
||
u32 colors[4];
|
||
};
|
||
|
||
TensorProductPatchShading(CommonEntries common_entries, Vector<float> patch_data, Vector<TensorProductPatch> patches, ShadingFunctionsType functions, GouraudBounds bounds)
|
||
: m_common_entries(move(common_entries))
|
||
, m_patch_data(move(patch_data))
|
||
, m_patches(move(patches))
|
||
, m_functions(move(functions))
|
||
, m_bounds(move(bounds))
|
||
{
|
||
}
|
||
|
||
CommonEntries m_common_entries;
|
||
|
||
// Interleaved x0, y0, x1, y1, ..., x15, y15, c0, c1, c2, c3, ...
|
||
// (For flags 1-3, only 12 coordinates and 2 colors.)
|
||
Vector<float> m_patch_data;
|
||
Vector<TensorProductPatch> m_patches;
|
||
ShadingFunctionsType m_functions;
|
||
GouraudBounds m_bounds;
|
||
};
|
||
|
||
PDFErrorOr<NonnullRefPtr<TensorProductPatchShading>> TensorProductPatchShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
|
||
{
|
||
auto shading_dict = shading_stream->dict();
|
||
|
||
// "Type 7 shadings (tensor-product patch meshes) are identical to type 6, except that
|
||
// they are based on a bicubic tensor-product patch defined by 16 control points in-
|
||
// stead of the 12 control points that define a Coons patch. The shading dictionaries
|
||
// representing the two patch types differ only in the value of the ShadingType entry
|
||
// and in the number of control points specified for each patch in the data stream."
|
||
|
||
// FIXME: Extract some common code once we have implemented painting and can make sure
|
||
// that refactoring doesn't break things.
|
||
|
||
// TABLE 4.34 Additional entries specific to a type 6 shading dictionary
|
||
// "(Required) The number of bits used to represent each geometric coordi-
|
||
// nate. Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
|
||
int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
|
||
if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
|
||
return Error::malformed_error("BitsPerCoordinate invalid");
|
||
|
||
// "(Required) The number of bits used to represent each color component.
|
||
// Valid values are 1, 2, 4, 8, 12, and 16."
|
||
int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
|
||
if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
|
||
return Error::malformed_error("BitsPerComponent invalid");
|
||
|
||
// "(Required) The number of bits used to represent the edge flag for each
|
||
// patch (see below). Valid values of BitsPerFlag are 2, 4, and 8, but only the
|
||
// least significant 2 bits in each flag value are used. Valid values for the edge
|
||
// flag are 0, 1, 2, and 3."
|
||
int bits_per_flag = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerFlag))).to_int();
|
||
if (!first_is_one_of(bits_per_flag, 2, 4, 8))
|
||
return Error::malformed_error("BitsPerFlag invalid");
|
||
|
||
// "(Required) An array of numbers specifying how to map vertex coordinates
|
||
// and color components into the appropriate ranges of values. The decoding
|
||
// method is similar to that used in image dictionaries (see “Decode Arrays”
|
||
// on page 344). The ranges are specified as follows:
|
||
//
|
||
// [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
|
||
//
|
||
// Note that only one pair of c values should be specified if a Function entry
|
||
// is present."
|
||
auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
|
||
size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
|
||
if (decode_array->size() != 4 + 2 * number_of_components)
|
||
return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
|
||
Vector<float> decode;
|
||
decode.resize(decode_array->size());
|
||
for (size_t i = 0; i < decode_array->size(); ++i)
|
||
decode[i] = decode_array->at(i).to_float();
|
||
|
||
// "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
|
||
// (where n is the number of color components in the shading dictionary’s
|
||
// color space). If this entry is present, the color data for each vertex must be
|
||
// specified by a single parametric variable rather than by n separate color
|
||
// components. The designated function(s) are called with each interpolated
|
||
// value of the parametric variable to determine the actual color at each
|
||
// point. Each input value is forced into the range interval specified for the
|
||
// corresponding color component in the shading dictionary’s Decode array.
|
||
// Each function’s domain must be a superset of that interval. If the value re-
|
||
// turned by the function for a given color component is out of range, it is
|
||
// adjusted to the nearest valid value.
|
||
// This entry may not be used with an Indexed color space."
|
||
ShadingFunctionsType functions;
|
||
if (shading_dict->contains(CommonNames::Function))
|
||
functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));
|
||
|
||
// See "Type 6 Shadings (Coons Patch Meshes)" in the PDF 1.7 spec for a description of the stream contents.
|
||
auto stream = FixedMemoryStream { shading_stream->bytes() };
|
||
BigEndianInputBitStream bitstream { MaybeOwned { stream } };
|
||
|
||
Vector<float> patch_data;
|
||
Vector<TensorProductPatch> patches;
|
||
|
||
auto read_point = [&]() -> ErrorOr<void> {
|
||
u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
|
||
TRY(patch_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
TRY(patch_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
|
||
return {};
|
||
};
|
||
|
||
auto read_points = [&](u32 n) -> ErrorOr<void> {
|
||
for (u32 i = 0; i < n; ++i)
|
||
TRY(read_point());
|
||
return {};
|
||
};
|
||
|
||
auto read_color = [&]() -> ErrorOr<void> {
|
||
for (size_t i = 0; i < number_of_components; ++i) {
|
||
u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
|
||
TRY(patch_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
|
||
}
|
||
return {};
|
||
};
|
||
|
||
auto read_colors = [&](u32 n) -> ErrorOr<void> {
|
||
for (u32 i = 0; i < n; ++i)
|
||
TRY(read_color());
|
||
return {};
|
||
};
|
||
|
||
// "The coordinates of the control points in a tensor-product patch are actually spec-
|
||
// ified in the shading’s data stream in the following order:
|
||
// 4 5 6 7
|
||
// 3 14 15 8
|
||
// 2 13 16 9
|
||
// 1 12 11 10"
|
||
// We need to invert this to map data stream index to control point index.
|
||
u32 const patch_index[] = {
|
||
// clang-format off
|
||
0, 4, 8, 12,
|
||
13, 14, 15,
|
||
11, 7, 3,
|
||
2, 1,
|
||
5, 9, 10, 6,
|
||
// clang-format on
|
||
};
|
||
u32 const color_index[] = { 0, 2, 3, 1 };
|
||
|
||
// "The 16 control points can be arranged in a
|
||
// 4-by-4 array indexed by row and column, as follows (see Figure 4.24):
|
||
// p03 p13 p23 p33
|
||
// p02 p12 p22 p32
|
||
// p01 p11 p21 p31
|
||
// p00 p10 p20 p30"
|
||
|
||
while (!bitstream.is_eof()) {
|
||
u8 flag = TRY(bitstream.read_bits<u8>(bits_per_flag));
|
||
|
||
int n = patch_data.size();
|
||
TensorProductPatch patch;
|
||
|
||
// "TABLE 4.36 Data values in a tensor-product patch mesh"
|
||
switch (flag) {
|
||
case 0:
|
||
// "x00 y00 x01 y01 x02 y02 x03 y03 x13 y13 x23 y23 x33 y33 x32 y32
|
||
// x31 y31 x30 y30 x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
|
||
// c00 c03 c33 c30
|
||
// New patch; no implicit values"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 16 * 2 + 4 + number_of_components));
|
||
TRY(read_points(16));
|
||
TRY(read_colors(4));
|
||
for (int i = 0; i < 16; ++i)
|
||
patch.control_points[patch_index[i]] = n + 2 * i;
|
||
for (int i = 0; i < 4; ++i)
|
||
patch.colors[color_index[i]] = n + 32 + number_of_components * i;
|
||
break;
|
||
case 1:
|
||
if (patches.is_empty())
|
||
return Error::malformed_error("Edge flag 1 without preceding patch");
|
||
// "x13 y13 x23 y23 x33 y33 x32 y32 x31 y31 x30 y30
|
||
// x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
|
||
// c33 c30
|
||
// Implicit values:
|
||
// (x00, y00) = (x03, y03) previous
|
||
// (x01, y01) = (x13, y13) previous
|
||
// (x02, y02) = (x23, y23) previous
|
||
// (x03, y03) = (x33, y33) previous
|
||
// c00 = c03 previous
|
||
// c03 = c33 previous"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 2 + number_of_components));
|
||
TRY(read_points(12));
|
||
TRY(read_colors(2));
|
||
patch.control_points[patch_index[0]] = patches.last().control_points[12];
|
||
patch.control_points[patch_index[1]] = patches.last().control_points[13];
|
||
patch.control_points[patch_index[2]] = patches.last().control_points[14];
|
||
patch.control_points[patch_index[3]] = patches.last().control_points[15];
|
||
for (int i = 0; i < 12; ++i)
|
||
patch.control_points[patch_index[i + 4]] = n + 2 * i;
|
||
patch.colors[color_index[0]] = patches.last().colors[2];
|
||
patch.colors[color_index[1]] = patches.last().colors[3];
|
||
for (int i = 0; i < 2; ++i)
|
||
patch.colors[color_index[i + 2]] = n + 24 + number_of_components * i;
|
||
break;
|
||
case 2:
|
||
if (patches.is_empty())
|
||
return Error::malformed_error("Edge flag 2 without preceding patch");
|
||
// "x13 y13 x23 y23 x33 y33 x32 y32 x31 y31 x30 y30
|
||
// x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
|
||
// c33 c30
|
||
// Implicit values:
|
||
// (x00, y00) = (x33, y33) previous
|
||
// (x01, y01) = (x32, y32) previous
|
||
// (x02, y02) = (x31, y31) previous
|
||
// (x03, y03) = (x30, y30) previous
|
||
// c00 = c33 previous
|
||
// c03 = c30 previous"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 2 + number_of_components));
|
||
TRY(read_points(12));
|
||
TRY(read_colors(2));
|
||
patch.control_points[patch_index[0]] = patches.last().control_points[15];
|
||
patch.control_points[patch_index[1]] = patches.last().control_points[11];
|
||
patch.control_points[patch_index[2]] = patches.last().control_points[7];
|
||
patch.control_points[patch_index[3]] = patches.last().control_points[3];
|
||
for (int i = 0; i < 12; ++i)
|
||
patch.control_points[patch_index[i + 4]] = n + 2 * i;
|
||
patch.colors[color_index[0]] = patches.last().colors[3];
|
||
patch.colors[color_index[1]] = patches.last().colors[1];
|
||
for (int i = 0; i < 2; ++i)
|
||
patch.colors[color_index[i + 2]] = n + 24 + number_of_components * i;
|
||
break;
|
||
case 3:
|
||
if (patches.is_empty())
|
||
return Error::malformed_error("Edge flag 3 without preceding patch");
|
||
// "x13 y13 x23 y23 x33 y33 x32 y32 x31 y31 x30 y30
|
||
// x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
|
||
// c33 c30
|
||
// Implicit values:
|
||
// (x00, y00) = (x30, y30) previous
|
||
// (x01, y01) = (x20, y20) previous
|
||
// (x02, y02) = (x10, y10) previous
|
||
// (x03, y03) = (x00, y00) previous
|
||
// c00 = c30 previous
|
||
// c03 = c00 previous"
|
||
TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 2 + number_of_components));
|
||
TRY(read_points(12));
|
||
TRY(read_colors(2));
|
||
patch.control_points[patch_index[0]] = patches.last().control_points[3];
|
||
patch.control_points[patch_index[1]] = patches.last().control_points[2];
|
||
patch.control_points[patch_index[2]] = patches.last().control_points[1];
|
||
patch.control_points[patch_index[3]] = patches.last().control_points[0];
|
||
for (int i = 0; i < 12; ++i)
|
||
patch.control_points[patch_index[i + 4]] = n + 2 * i;
|
||
patch.colors[color_index[0]] = patches.last().colors[1];
|
||
patch.colors[color_index[1]] = patches.last().colors[0];
|
||
for (int i = 0; i < 2; ++i)
|
||
patch.colors[color_index[i + 2]] = n + 24 + number_of_components * i;
|
||
break;
|
||
default:
|
||
return Error::malformed_error("Invalid edge flag");
|
||
}
|
||
|
||
TRY(patches.try_append(patch));
|
||
bitstream.align_to_byte_boundary();
|
||
}
|
||
|
||
GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
|
||
return adopt_ref(*new TensorProductPatchShading(move(common_entries), move(patch_data), move(patches), move(functions), move(bounds)));
|
||
}
|
||
|
||
PDFErrorOr<void> TensorProductPatchShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
|
||
{
|
||
NonnullRefPtr<ColorSpaceWithFloatArgs> color_space = m_common_entries.color_space;
|
||
size_t const number_of_components = !m_functions.has<Empty>() ? 1 : color_space->number_of_components();
|
||
bool is_indexed = color_space->family() == ColorSpaceFamily::Indexed;
|
||
RefPtr<IndexedColorSpace> indexed_color_space;
|
||
auto bounds = m_bounds;
|
||
if (is_indexed) {
|
||
indexed_color_space = static_ptr_cast<IndexedColorSpace>(color_space);
|
||
color_space = indexed_color_space->base_color_space();
|
||
bounds = bounds_from_decode_array(color_space->default_decode());
|
||
}
|
||
|
||
for (auto& patch : m_patches) {
|
||
GouraudBezierPatch bezier_patch;
|
||
|
||
for (size_t i = 0; i < 16; ++i)
|
||
bezier_patch.points[i] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[i]], m_patch_data[patch.control_points[i] + 1] });
|
||
|
||
for (size_t i = 0; i < 4; ++i) {
|
||
GouraudColor color;
|
||
|
||
if (is_indexed) {
|
||
// "If ColorSpace is an Indexed color space, all color values specified in the shading
|
||
// are immediately converted to the base color space. [...] Interpolation never occurs
|
||
// in an Indexed color space, which is quantized and therefore inappropriate for calculations
|
||
// that assume a continuous range of colors."
|
||
color.extend(TRY(indexed_color_space->base_components(m_patch_data[patch.colors[i]])));
|
||
} else {
|
||
color.resize(number_of_components);
|
||
for (size_t j = 0; j < number_of_components; ++j)
|
||
color[j] = m_patch_data[patch.colors[i] + j];
|
||
}
|
||
|
||
bezier_patch.colors[i] = color;
|
||
}
|
||
|
||
draw_gouraud_bezier_patch(painter, color_space, m_functions, bezier_patch, bounds);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
}
|
||
|
||
PDFErrorOr<NonnullRefPtr<Shading>> Shading::create(Document* document, NonnullRefPtr<Object> shading_dict_or_stream, Renderer& renderer)
|
||
{
|
||
// "Shading types 4 to 7 are defined by a stream containing descriptive data charac-
|
||
// terizing the shading’s gradient fill. In these cases, the shading dictionary is also a
|
||
// stream dictionary and can contain any of the standard entries common to all
|
||
// streams"
|
||
auto shading_dict = TRY([&]() -> PDFErrorOr<NonnullRefPtr<DictObject>> {
|
||
if (shading_dict_or_stream->is<DictObject>())
|
||
return shading_dict_or_stream->cast<DictObject>();
|
||
if (shading_dict_or_stream->is<StreamObject>())
|
||
return shading_dict_or_stream->cast<StreamObject>()->dict();
|
||
return Error::malformed_error("Shading must be a dictionary or stream");
|
||
}());
|
||
|
||
int shading_type = TRY(document->resolve(shading_dict->get_value(CommonNames::ShadingType))).to_int();
|
||
auto common_entries = TRY(read_common_entries(document, *shading_dict, renderer));
|
||
|
||
switch (shading_type) {
|
||
case 1:
|
||
if (!shading_dict_or_stream->is<DictObject>())
|
||
return Error::malformed_error("Function-based shading dictionary has wrong type");
|
||
return FunctionBasedShading::create(document, shading_dict, move(common_entries));
|
||
case 2:
|
||
if (!shading_dict_or_stream->is<DictObject>())
|
||
return Error::malformed_error("Axial shading dictionary has wrong type");
|
||
return AxialShading::create(document, shading_dict, move(common_entries));
|
||
case 3:
|
||
if (!shading_dict_or_stream->is<DictObject>())
|
||
return Error::malformed_error("Radial shading dictionary has wrong type");
|
||
return RadialShading::create(document, shading_dict, move(common_entries));
|
||
case 4:
|
||
if (!shading_dict_or_stream->is<StreamObject>())
|
||
return Error::malformed_error("Free-form Gouraud-shaded triangle mesh stream has wrong type");
|
||
return FreeFormGouraudShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
|
||
case 5:
|
||
if (!shading_dict_or_stream->is<StreamObject>())
|
||
return Error::malformed_error("Lattice-form Gouraud-shaded triangle mesh stream has wrong type");
|
||
return LatticeFormGouraudShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
|
||
case 6:
|
||
if (!shading_dict_or_stream->is<StreamObject>())
|
||
return Error::malformed_error("Coons patch mesh stream has wrong type");
|
||
return CoonsPatchShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
|
||
case 7:
|
||
if (!shading_dict_or_stream->is<StreamObject>())
|
||
return Error::malformed_error("Tensor-product patch mesh stream has wrong type");
|
||
return TensorProductPatchShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
|
||
}
|
||
dbgln("Shading type {}", shading_type);
|
||
return Error::malformed_error("Invalid shading type");
|
||
}
|
||
|
||
}
|