Skip to content

Commit

Permalink
LibPDF: Implement painting of axial shadings
Browse files Browse the repository at this point in the history
Or, as they're called elsewhere, linear gradients.

Since this uses PDF function objects and PDF color spaces, it can't use
the existing gradient code in LibGfx.

The way shadings are drawn with the `sh` operator is that one sets a
clip path that's to be filled with the shading and the calls the `sh`
operator.

However, we only support clip rects at the moment, not yet arbitrary
clip paths. So this leads to rectangles with linear gradients
appearing, which sometimes can overlap other page elements. This will
improve once we implement support for arbitrary clip paths.

This is also still missing support for the optional additional /BBox
shading dict entry, which further constrains the clip rect.
  • Loading branch information
nico committed Feb 18, 2025
1 parent 9d5e219 commit ea92eb7
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 5 deletions.
8 changes: 7 additions & 1 deletion Userland/Libraries/LibPDF/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,10 @@ RENDERER_HANDLER(set_painting_color_and_space_to_cmyk)

RENDERER_HANDLER(shade)
{
auto inverse_ctm = state().ctm.inverse();
if (!inverse_ctm.has_value())
return {};

VERIFY(args.size() == 1);
auto shading_name = MUST(m_document->resolve_to<NameObject>(args[0]))->name();
auto resources = extra_resources.value_or(m_page.resources);
Expand All @@ -743,7 +747,9 @@ RENDERER_HANDLER(shade)

auto shading_dict_or_stream = TRY(shading_resource_dict->get_object(m_document, shading_name));
auto shading = TRY(Shading::create(m_document, shading_dict_or_stream, *this));
return shading->draw();

ClipRAII clip_raii { *this };
return shading->draw(m_painter, inverse_ctm.value());
}

RENDERER_HANDLER(inline_image_begin)
Expand Down
59 changes: 56 additions & 3 deletions Userland/Libraries/LibPDF/Shading.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibGfx/Painter.h>
#include <LibGfx/Vector2.h>
#include <LibPDF/ColorSpace.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Document.h>
Expand Down Expand Up @@ -82,7 +84,7 @@ class AxialShading final : public Shading {
public:
static PDFErrorOr<NonnullRefPtr<AxialShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);

virtual PDFErrorOr<void> draw() override;
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
using FunctionsType = Variant<NonnullRefPtr<Function>, Vector<NonnullRefPtr<Function>>>;
Expand Down Expand Up @@ -179,9 +181,60 @@ PDFErrorOr<NonnullRefPtr<AxialShading>> AxialShading::create(Document* document,
return adopt_ref(*new AxialShading(move(common_entries), start, end, t0, t1, move(functions), extend_start, extend_end));
}

PDFErrorOr<void> AxialShading::draw()
PDFErrorOr<void> AxialShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& inverse_ctm)
{
return Error::rendering_unsupported_error("Cannot draw axial shading yet");
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: Do something with m_common_entries.b_box if it's set.

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->style(color_components));
bitmap.scanline(y)[x] = color.get<Gfx::Color>().value();
}
}

return {};
}

}
Expand Down
3 changes: 2 additions & 1 deletion Userland/Libraries/LibPDF/Shading.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma once

#include <AK/RefCounted.h>
#include <LibGfx/Forward.h>
#include <LibPDF/Value.h>

namespace PDF {
Expand All @@ -19,7 +20,7 @@ class Shading : public RefCounted<Shading> {

virtual ~Shading() = default;

virtual PDFErrorOr<void> draw() = 0;
virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) = 0;
};

}

0 comments on commit ea92eb7

Please sign in to comment.