mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 19:57:45 +00:00
LibGfx: Allow requesting vector graphic frames from ImageDecoder
Only supported for plugins that set is_vector() to true. This returns the frames as Gfx::VectorGraphics, which allows painting/rasterizing them with arbitrary transforms and scales.
This commit is contained in:
parent
8784568a36
commit
753cf04be8
3 changed files with 39 additions and 30 deletions
|
@ -12,6 +12,7 @@
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
#include <LibGfx/Bitmap.h>
|
#include <LibGfx/Bitmap.h>
|
||||||
#include <LibGfx/Size.h>
|
#include <LibGfx/Size.h>
|
||||||
|
#include <LibGfx/VectorGraphic.h>
|
||||||
|
|
||||||
namespace Gfx {
|
namespace Gfx {
|
||||||
|
|
||||||
|
@ -25,6 +26,11 @@ struct ImageFrameDescriptor {
|
||||||
int duration { 0 };
|
int duration { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VectorImageFrameDescriptor {
|
||||||
|
RefPtr<VectorGraphic> image;
|
||||||
|
int duration { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
class ImageDecoderPlugin {
|
class ImageDecoderPlugin {
|
||||||
public:
|
public:
|
||||||
virtual ~ImageDecoderPlugin() = default;
|
virtual ~ImageDecoderPlugin() = default;
|
||||||
|
@ -34,12 +40,16 @@ public:
|
||||||
virtual ErrorOr<void> initialize() { return {}; }
|
virtual ErrorOr<void> initialize() { return {}; }
|
||||||
|
|
||||||
virtual bool is_animated() = 0;
|
virtual bool is_animated() = 0;
|
||||||
|
|
||||||
virtual size_t loop_count() = 0;
|
virtual size_t loop_count() = 0;
|
||||||
virtual size_t frame_count() = 0;
|
virtual size_t frame_count() = 0;
|
||||||
virtual size_t first_animated_frame_index() = 0;
|
virtual size_t first_animated_frame_index() = 0;
|
||||||
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) = 0;
|
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) = 0;
|
||||||
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() = 0;
|
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() = 0;
|
||||||
|
|
||||||
|
virtual bool is_vector() { return false; }
|
||||||
|
virtual ErrorOr<VectorImageFrameDescriptor> vector_frame(size_t) { VERIFY_NOT_REACHED(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ImageDecoderPlugin() = default;
|
ImageDecoderPlugin() = default;
|
||||||
};
|
};
|
||||||
|
@ -59,6 +69,9 @@ public:
|
||||||
ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) const { return m_plugin->frame(index, ideal_size); }
|
ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) const { return m_plugin->frame(index, ideal_size); }
|
||||||
ErrorOr<Optional<ReadonlyBytes>> icc_data() const { return m_plugin->icc_data(); }
|
ErrorOr<Optional<ReadonlyBytes>> icc_data() const { return m_plugin->icc_data(); }
|
||||||
|
|
||||||
|
bool is_vector() { return m_plugin->is_vector(); }
|
||||||
|
ErrorOr<VectorImageFrameDescriptor> vector_frame(size_t index) { return m_plugin->vector_frame(index); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ImageDecoder(NonnullOwnPtr<ImageDecoderPlugin>);
|
explicit ImageDecoder(NonnullOwnPtr<ImageDecoderPlugin>);
|
||||||
|
|
||||||
|
|
|
@ -327,7 +327,7 @@ private:
|
||||||
ReadonlySpan<Color> m_color_table;
|
ReadonlySpan<Color> m_color_table;
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorOr<TinyVGDecodedImageData> TinyVGDecodedImageData::decode(Stream& stream)
|
ErrorOr<NonnullRefPtr<TinyVGDecodedImageData>> TinyVGDecodedImageData::decode(Stream& stream)
|
||||||
{
|
{
|
||||||
auto header = TRY(decode_tinyvg_header(stream));
|
auto header = TRY(decode_tinyvg_header(stream));
|
||||||
if (header.version != 1)
|
if (header.version != 1)
|
||||||
|
@ -437,48 +437,35 @@ ErrorOr<TinyVGDecodedImageData> TinyVGDecodedImageData::decode(Stream& stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TinyVGDecodedImageData { { header.width, header.height }, move(draw_commands) };
|
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) TinyVGDecodedImageData({ header.width, header.height }, move(draw_commands))));
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<RefPtr<Gfx::Bitmap>> TinyVGDecodedImageData::bitmap(IntSize size) const
|
void TinyVGDecodedImageData::draw_transformed(Painter& painter, AffineTransform transform) const
|
||||||
{
|
{
|
||||||
auto scale_x = float(size.width()) / m_size.width();
|
// FIXME: Correctly handle non-uniform scales.
|
||||||
auto scale_y = float(size.height()) / m_size.height();
|
auto scale = max(transform.x_scale(), transform.y_scale());
|
||||||
auto transform = Gfx::AffineTransform {}.scale(scale_x, scale_y);
|
AntiAliasingPainter aa_painter { painter };
|
||||||
auto bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::BGRA8888, size));
|
|
||||||
Painter base_painter { *bitmap };
|
|
||||||
AntiAliasingPainter painter { base_painter };
|
|
||||||
for (auto const& command : draw_commands()) {
|
for (auto const& command : draw_commands()) {
|
||||||
auto draw_path = command.path.copy_transformed(transform);
|
auto draw_path = command.path.copy_transformed(transform);
|
||||||
if (command.fill.has_value()) {
|
if (command.fill.has_value()) {
|
||||||
auto fill_path = draw_path;
|
auto fill_path = draw_path;
|
||||||
fill_path.close_all_subpaths();
|
fill_path.close_all_subpaths();
|
||||||
command.fill->visit(
|
command.fill->visit(
|
||||||
[&](Color color) { painter.fill_path(fill_path, color, Painter::WindingRule::EvenOdd); },
|
[&](Color color) { aa_painter.fill_path(fill_path, color, Painter::WindingRule::EvenOdd); },
|
||||||
[&](NonnullRefPtr<SVGGradientPaintStyle> style) {
|
[&](NonnullRefPtr<SVGGradientPaintStyle> style) {
|
||||||
const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
|
const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
|
||||||
painter.fill_path(fill_path, style, 1.0f, Painter::WindingRule::EvenOdd);
|
aa_painter.fill_path(fill_path, style, 1.0f, Painter::WindingRule::EvenOdd);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (command.stroke.has_value()) {
|
if (command.stroke.has_value()) {
|
||||||
// FIXME: A more correct way to non-uniformly scale strokes would be:
|
|
||||||
// 1. Scale the path uniformly by the largest of scale_x/y
|
|
||||||
// 2. Convert that to a fill with .stroke_to_fill()
|
|
||||||
// 3.
|
|
||||||
// If scale_x > scale_y
|
|
||||||
// Scale by: (1, scale_y/scale_x)
|
|
||||||
// else
|
|
||||||
// Scale by: (scale_x/scale_y, 1)
|
|
||||||
auto stroke_scale = max(scale_x, scale_y);
|
|
||||||
command.stroke->visit(
|
command.stroke->visit(
|
||||||
[&](Color color) { painter.stroke_path(draw_path, color, command.stroke_width * stroke_scale); },
|
[&](Color color) { aa_painter.stroke_path(draw_path, color, command.stroke_width * scale); },
|
||||||
[&](NonnullRefPtr<SVGGradientPaintStyle> style) {
|
[&](NonnullRefPtr<SVGGradientPaintStyle> style) {
|
||||||
const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
|
const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
|
||||||
painter.stroke_path(draw_path, style, command.stroke_width * stroke_scale);
|
aa_painter.stroke_path(draw_path, style, command.stroke_width * scale);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bitmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TinyVGImageDecoderPlugin::TinyVGImageDecoderPlugin(ReadonlyBytes bytes)
|
TinyVGImageDecoderPlugin::TinyVGImageDecoderPlugin(ReadonlyBytes bytes)
|
||||||
|
@ -507,7 +494,7 @@ IntSize TinyVGImageDecoderPlugin::size()
|
||||||
ErrorOr<void> TinyVGImageDecoderPlugin::initialize()
|
ErrorOr<void> TinyVGImageDecoderPlugin::initialize()
|
||||||
{
|
{
|
||||||
FixedMemoryStream stream { { m_context.data.data(), m_context.data.size() } };
|
FixedMemoryStream stream { { m_context.data.data(), m_context.data.size() } };
|
||||||
m_context.decoded_image = make<TinyVGDecodedImageData>(TRY(TinyVGDecodedImageData::decode(stream)));
|
m_context.decoded_image = TRY(TinyVGDecodedImageData::decode(stream));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,4 +506,9 @@ ErrorOr<ImageFrameDescriptor> TinyVGImageDecoderPlugin::frame(size_t, Optional<I
|
||||||
return ImageFrameDescriptor { m_context.bitmap };
|
return ImageFrameDescriptor { m_context.bitmap };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorOr<VectorImageFrameDescriptor> TinyVGImageDecoderPlugin::vector_frame(size_t)
|
||||||
|
{
|
||||||
|
return VectorImageFrameDescriptor { m_context.decoded_image, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
||||||
#include <LibGfx/PaintStyle.h>
|
#include <LibGfx/PaintStyle.h>
|
||||||
#include <LibGfx/Path.h>
|
#include <LibGfx/Path.h>
|
||||||
|
#include <LibGfx/VectorGraphic.h>
|
||||||
|
|
||||||
namespace Gfx {
|
namespace Gfx {
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ namespace Gfx {
|
||||||
// Decoder from the "Tiny Vector Graphics" format (v1.0).
|
// Decoder from the "Tiny Vector Graphics" format (v1.0).
|
||||||
// https://tinyvg.tech/download/specification.pdf
|
// https://tinyvg.tech/download/specification.pdf
|
||||||
|
|
||||||
class TinyVGDecodedImageData {
|
class TinyVGDecodedImageData final : public VectorGraphic {
|
||||||
public:
|
public:
|
||||||
using Style = Variant<Color, NonnullRefPtr<SVGGradientPaintStyle>>;
|
using Style = Variant<Color, NonnullRefPtr<SVGGradientPaintStyle>>;
|
||||||
|
|
||||||
|
@ -44,19 +45,19 @@ public:
|
||||||
float stroke_width { 0.0f };
|
float stroke_width { 0.0f };
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorOr<RefPtr<Gfx::Bitmap>> bitmap(IntSize size) const;
|
virtual IntSize intrinsic_size() const override
|
||||||
|
|
||||||
IntSize size() const
|
|
||||||
{
|
{
|
||||||
return m_size;
|
return m_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void draw_transformed(Painter&, AffineTransform) const override;
|
||||||
|
|
||||||
ReadonlySpan<DrawCommand> draw_commands() const
|
ReadonlySpan<DrawCommand> draw_commands() const
|
||||||
{
|
{
|
||||||
return m_draw_commands;
|
return m_draw_commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ErrorOr<TinyVGDecodedImageData> decode(Stream& stream);
|
static ErrorOr<NonnullRefPtr<TinyVGDecodedImageData>> decode(Stream& stream);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TinyVGDecodedImageData(IntSize size, Vector<DrawCommand> draw_commands)
|
TinyVGDecodedImageData(IntSize size, Vector<DrawCommand> draw_commands)
|
||||||
|
@ -71,7 +72,7 @@ private:
|
||||||
|
|
||||||
struct TinyVGLoadingContext {
|
struct TinyVGLoadingContext {
|
||||||
ReadonlyBytes data;
|
ReadonlyBytes data;
|
||||||
OwnPtr<TinyVGDecodedImageData> decoded_image {};
|
RefPtr<TinyVGDecodedImageData> decoded_image {};
|
||||||
RefPtr<Bitmap> bitmap {};
|
RefPtr<Bitmap> bitmap {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,6 +90,9 @@ public:
|
||||||
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
|
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
|
||||||
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override { return OptionalNone {}; }
|
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override { return OptionalNone {}; }
|
||||||
|
|
||||||
|
virtual bool is_vector() override { return true; }
|
||||||
|
virtual ErrorOr<VectorImageFrameDescriptor> vector_frame(size_t index) override;
|
||||||
|
|
||||||
virtual ~TinyVGImageDecoderPlugin() override = default;
|
virtual ~TinyVGImageDecoderPlugin() override = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue