Coverage Report

Created: 2025-12-18 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Array.h>
8
#include <AK/Endian.h>
9
#include <AK/FixedArray.h>
10
#include <AK/LEB128.h>
11
#include <AK/MemoryStream.h>
12
#include <AK/Variant.h>
13
#include <LibCore/File.h>
14
#include <LibGfx/AntiAliasingPainter.h>
15
#include <LibGfx/ImageFormats/TinyVGLoader.h>
16
#include <LibGfx/Line.h>
17
#include <LibGfx/Painter.h>
18
#include <LibGfx/Point.h>
19
20
namespace Gfx {
21
22
using VarUInt = LEB128<u32>;
23
24
static constexpr Array<u8, 2> TVG_MAGIC { 0x72, 0x56 };
25
26
enum class ColorEncoding : u8 {
27
    RGBA8888 = 0,
28
    RGB565 = 1,
29
    RGBAF32 = 2,
30
    Custom = 3
31
};
32
33
enum class CoordinateRange : u8 {
34
    Default = 0,
35
    Reduced = 1,
36
    Enhanced = 2
37
};
38
39
enum class StyleType : u8 {
40
    FlatColored = 0,
41
    LinearGradient = 1,
42
    RadialGradinet = 2
43
};
44
45
enum class Command : u8 {
46
    EndOfDocument = 0,
47
    FillPolygon = 1,
48
    FillRectangles = 2,
49
    FillPath = 3,
50
    DrawLines = 4,
51
    DrawLineLoop = 5,
52
    DrawLineStrip = 6,
53
    DrawLinePath = 7,
54
    OutlineFillPolygon = 8,
55
    OutlineFillRectangles = 9,
56
    OutLineFillPath = 10
57
};
58
59
struct FillCommandHeader {
60
    u32 count;
61
    TinyVGDecodedImageData::Style style;
62
};
63
64
struct DrawCommandHeader {
65
    u32 count;
66
    TinyVGDecodedImageData::Style line_style;
67
    float line_width;
68
};
69
70
struct OutlineFillCommandHeader {
71
    u32 count;
72
    TinyVGDecodedImageData::Style fill_style;
73
    TinyVGDecodedImageData::Style line_style;
74
    float line_width;
75
};
76
77
enum class PathCommand : u8 {
78
    Line = 0,
79
    HorizontalLine = 1,
80
    VerticalLine = 2,
81
    CubicBezier = 3,
82
    ArcCircle = 4,
83
    ArcEllipse = 5,
84
    ClosePath = 6,
85
    QuadraticBezier = 7
86
};
87
88
struct TinyVGHeader {
89
    u8 version;
90
    u8 scale;
91
    ColorEncoding color_encoding;
92
    CoordinateRange coordinate_range;
93
    u32 width;
94
    u32 height;
95
    u32 color_count;
96
};
97
98
static ErrorOr<TinyVGHeader> decode_tinyvg_header(Stream& stream)
99
1.42k
{
100
1.42k
    TinyVGHeader header {};
101
1.42k
    Array<u8, 2> magic_bytes;
102
1.42k
    TRY(stream.read_until_filled(magic_bytes));
103
1.42k
    if (magic_bytes != TVG_MAGIC)
104
2
        return Error::from_string_literal("Invalid TinyVG: Incorrect header magic");
105
1.42k
    header.version = TRY(stream.read_value<u8>());
106
1.42k
    u8 properties = TRY(stream.read_value<u8>());
107
1.42k
    header.scale = properties & 0xF;
108
1.42k
    header.color_encoding = static_cast<ColorEncoding>((properties >> 4) & 0x3);
109
1.42k
    header.coordinate_range = static_cast<CoordinateRange>((properties >> 6) & 0x3);
110
1.42k
    switch (header.coordinate_range) {
111
197
    case CoordinateRange::Default:
112
197
        header.width = TRY(stream.read_value<LittleEndian<u16>>());
113
197
        header.height = TRY(stream.read_value<LittleEndian<u16>>());
114
197
        break;
115
1.13k
    case CoordinateRange::Reduced:
116
1.13k
        header.width = TRY(stream.read_value<u8>());
117
1.13k
        header.height = TRY(stream.read_value<u8>());
118
1.13k
        break;
119
91
    case CoordinateRange::Enhanced:
120
91
        header.width = TRY(stream.read_value<LittleEndian<u32>>());
121
91
        header.height = TRY(stream.read_value<LittleEndian<u32>>());
122
91
        break;
123
0
    default:
124
0
        return Error::from_string_literal("Invalid TinyVG: Bad coordinate range");
125
1.42k
    }
126
1.42k
    header.color_count = TRY(stream.read_value<VarUInt>());
127
1.42k
    return header;
128
1.42k
}
129
130
static ErrorOr<Vector<Color>> decode_color_table(Stream& stream, ColorEncoding encoding, u32 color_count)
131
1.42k
{
132
1.42k
    if (encoding == ColorEncoding::Custom)
133
0
        return Error::from_string_literal("Invalid TinyVG: Unsupported color encoding");
134
135
1.42k
    static constexpr size_t MAX_INITIAL_COLOR_TABLE_SIZE = 65536;
136
1.42k
    Vector<Color> color_table;
137
1.42k
    TRY(color_table.try_ensure_capacity(min(MAX_INITIAL_COLOR_TABLE_SIZE, color_count)));
138
3.53M
    auto parse_color = [&]() -> ErrorOr<Color> {
139
3.53M
        switch (encoding) {
140
3.05k
        case ColorEncoding::RGBA8888: {
141
3.05k
            Array<u8, 4> rgba;
142
3.05k
            TRY(stream.read_until_filled(rgba));
143
3.05k
            return Color(rgba[0], rgba[1], rgba[2], rgba[3]);
144
3.05k
        }
145
3.52M
        case ColorEncoding::RGB565: {
146
3.52M
            u16 color = TRY(stream.read_value<LittleEndian<u16>>());
147
3.52M
            auto red = (color >> (6 + 5)) & 0x1f;
148
3.52M
            auto green = (color >> 5) & 0x3f;
149
3.52M
            auto blue = (color >> 0) & 0x1f;
150
3.52M
            return Color((red * 255 + 15) / 31, (green * 255 + 31) / 63, (blue * 255 + 15) / 31);
151
3.52M
        }
152
2.05k
        case ColorEncoding::RGBAF32: {
153
2.05k
            auto red = TRY(stream.read_value<LittleEndian<f32>>());
154
2.03k
            auto green = TRY(stream.read_value<LittleEndian<f32>>());
155
2.02k
            auto blue = TRY(stream.read_value<LittleEndian<f32>>());
156
2.02k
            auto alpha = TRY(stream.read_value<LittleEndian<f32>>());
157
2.02k
            return Color(
158
2.02k
                clamp(red * 255.0f, 0.0f, 255.0f),
159
2.02k
                clamp(green * 255.0f, 0.0f, 255.0f),
160
2.02k
                clamp(blue * 255.0f, 0.0f, 255.0f),
161
2.02k
                clamp(alpha * 255.0f, 0.0f, 255.0f));
162
2.02k
        }
163
0
        default:
164
0
            return Error::from_string_literal("Invalid TinyVG: Bad color encoding");
165
3.53M
        }
166
3.53M
    };
167
3.53M
    while (color_count-- > 0) {
168
7.06M
        TRY(color_table.try_append(TRY(parse_color())));
169
7.06M
    }
170
1.42k
    return color_table;
171
1.42k
}
172
173
class TinyVGReader {
174
public:
175
    TinyVGReader(Stream& stream, TinyVGHeader const& header, ReadonlySpan<Color> color_table)
176
1.37k
        : m_stream(stream)
177
1.37k
        , m_scale(powf(0.5, header.scale))
178
1.37k
        , m_coordinate_range(header.coordinate_range)
179
1.37k
        , m_color_table(color_table)
180
1.37k
    {
181
1.37k
    }
182
183
    ErrorOr<float> read_unit()
184
23.2M
    {
185
23.2M
        auto read_value = [&]() -> ErrorOr<i32> {
186
23.2M
            switch (m_coordinate_range) {
187
29.5k
            case CoordinateRange::Default:
188
29.5k
                return TRY(m_stream.read_value<LittleEndian<i16>>());
189
23.2M
            case CoordinateRange::Reduced:
190
23.2M
                return TRY(m_stream.read_value<i8>());
191
5.21k
            case CoordinateRange::Enhanced:
192
5.21k
                return TRY(m_stream.read_value<LittleEndian<i32>>());
193
0
            default:
194
                // Note: Already checked while reading the header.
195
0
                VERIFY_NOT_REACHED();
196
23.2M
            }
197
23.2M
        };
198
23.2M
        return TRY(read_value()) * m_scale;
199
23.2M
    }
200
201
    ErrorOr<u32> read_var_uint()
202
260k
    {
203
260k
        return TRY(m_stream.read_value<VarUInt>());
204
260k
    }
205
206
    ErrorOr<FloatPoint> read_point()
207
3.33M
    {
208
3.33M
        return FloatPoint { TRY(read_unit()), TRY(read_unit()) };
209
3.33M
    }
210
211
    ErrorOr<TinyVGDecodedImageData::Style> read_style(StyleType type)
212
190k
    {
213
218k
        auto read_color = [&]() -> ErrorOr<Color> {
214
218k
            auto color_index = TRY(m_stream.read_value<VarUInt>());
215
218k
            if (color_index >= m_color_table.size())
216
17
                return Error::from_string_literal("Invalid color table index");
217
218
218k
            return m_color_table[color_index];
219
218k
        };
220
190k
        auto read_gradient = [&]() -> ErrorOr<NonnullRefPtr<SVGGradientPaintStyle>> {
221
28.7k
            auto point_0 = TRY(read_point());
222
28.7k
            auto point_1 = TRY(read_point());
223
28.7k
            auto color_0 = TRY(read_color());
224
28.7k
            auto color_1 = TRY(read_color());
225
            // Map TinyVG gradients to SVG gradients (since we already have those).
226
            // This is not entirely consistent with the spec, which uses gamma sRGB for gradients
227
            // (but this matches the TVG -> SVG renderings).
228
28.7k
            auto svg_gradient = TRY([&]() -> ErrorOr<NonnullRefPtr<SVGGradientPaintStyle>> {
229
28.7k
                if (type == StyleType::LinearGradient)
230
28.7k
                    return TRY(SVGLinearGradientPaintStyle::create(point_0, point_1));
231
28.7k
                auto radius = point_1.distance_from(point_0);
232
28.7k
                return TRY(SVGRadialGradientPaintStyle::create(point_0, 0, point_0, radius));
233
28.7k
            }());
234
28.7k
            TRY(svg_gradient->add_color_stop(0, color_0));
235
28.7k
            TRY(svg_gradient->add_color_stop(1, color_1));
236
28.7k
            return svg_gradient;
237
28.7k
        };
238
190k
        switch (type) {
239
161k
        case StyleType::FlatColored:
240
161k
            return TRY(read_color());
241
24.7k
        case StyleType::LinearGradient:
242
28.7k
        case StyleType::RadialGradinet:
243
190k
            return TRY(read_gradient());
244
190k
        }
245
0
        return Error::from_string_literal("Invalid TinyVG: Bad style data");
246
190k
    }
247
248
    ErrorOr<FloatRect> read_rectangle()
249
2.45M
    {
250
2.45M
        return FloatRect { TRY(read_unit()), TRY(read_unit()), TRY(read_unit()), TRY(read_unit()) };
251
2.45M
    }
252
253
    ErrorOr<FloatLine> read_line()
254
22.4k
    {
255
22.4k
        return FloatLine { TRY(read_point()), TRY(read_point()) };
256
22.4k
    }
257
258
    ErrorOr<Path> read_path(u32 segment_count)
259
10.6k
    {
260
10.6k
        Path path;
261
10.6k
        auto segment_lengths = TRY(FixedArray<u32>::create(segment_count));
262
93.8k
        for (auto& command_count : segment_lengths) {
263
93.8k
            command_count = TRY(read_var_uint()) + 1;
264
93.8k
        }
265
85.1k
        for (auto command_count : segment_lengths) {
266
85.1k
            auto start_point = TRY(read_point());
267
85.1k
            path.move_to(start_point);
268
2.83M
            for (u32 i = 0; i < command_count; i++) {
269
2.75M
                u8 command_tag = TRY(m_stream.read_value<u8>());
270
2.75M
                auto path_command = static_cast<PathCommand>(command_tag & 0x7);
271
2.75M
                bool has_line_width = (command_tag >> 4) & 0b1;
272
2.75M
                if (has_line_width) {
273
                    // FIXME: TinyVG allows changing the line width within a path.
274
                    // This is not supported in LibGfx, so we currently ignore this.
275
24.6k
                    (void)TRY(read_unit());
276
24.6k
                }
277
2.75M
                switch (path_command) {
278
98.7k
                case PathCommand::Line:
279
98.7k
                    path.line_to(TRY(read_point()));
280
98.7k
                    break;
281
217k
                case PathCommand::HorizontalLine:
282
217k
                    path.line_to({ TRY(read_unit()), path.last_point().y() });
283
217k
                    break;
284
39.2k
                case PathCommand::VerticalLine:
285
39.2k
                    path.line_to({ path.last_point().x(), TRY(read_unit()) });
286
39.2k
                    break;
287
62.8k
                case PathCommand::CubicBezier: {
288
62.8k
                    auto control_0 = TRY(read_point());
289
62.8k
                    auto control_1 = TRY(read_point());
290
62.8k
                    auto point_1 = TRY(read_point());
291
62.8k
                    path.cubic_bezier_curve_to(control_0, control_1, point_1);
292
62.8k
                    break;
293
62.8k
                }
294
53.4k
                case PathCommand::ArcCircle: {
295
53.4k
                    u8 flags = TRY(m_stream.read_value<u8>());
296
53.4k
                    bool large_arc = (flags >> 0) & 0b1;
297
53.4k
                    bool sweep = (flags >> 1) & 0b1;
298
53.4k
                    auto radius = TRY(read_unit());
299
53.4k
                    auto target = TRY(read_point());
300
53.3k
                    path.arc_to(target, radius, large_arc, !sweep);
301
53.3k
                    break;
302
53.4k
                }
303
2.09M
                case PathCommand::ArcEllipse: {
304
2.09M
                    u8 flags = TRY(m_stream.read_value<u8>());
305
2.09M
                    bool large_arc = (flags >> 0) & 0b1;
306
2.09M
                    bool sweep = (flags >> 1) & 0b1;
307
2.09M
                    auto radius_x = TRY(read_unit());
308
2.09M
                    auto radius_y = TRY(read_unit());
309
2.09M
                    auto rotation = TRY(read_unit());
310
2.09M
                    auto target = TRY(read_point());
311
2.09M
                    path.elliptical_arc_to(target, { radius_x, radius_y }, rotation, large_arc, !sweep);
312
2.09M
                    break;
313
2.09M
                }
314
168k
                case PathCommand::ClosePath: {
315
168k
                    path.close();
316
168k
                    break;
317
2.09M
                }
318
20.4k
                case PathCommand::QuadraticBezier: {
319
20.4k
                    auto control = TRY(read_point());
320
20.4k
                    auto point_1 = TRY(read_point());
321
20.4k
                    path.quadratic_bezier_curve_to(control, point_1);
322
20.4k
                    break;
323
20.4k
                }
324
0
                default:
325
0
                    return Error::from_string_literal("Invalid TinyVG: Bad path command");
326
2.75M
                }
327
2.75M
            }
328
85.1k
        }
329
10.4k
        return path;
330
10.6k
    }
331
332
    ErrorOr<FillCommandHeader> read_fill_command_header(StyleType style_type)
333
68.7k
    {
334
68.7k
        return FillCommandHeader { TRY(read_var_uint()) + 1, TRY(read_style(style_type)) };
335
68.7k
    }
336
337
    ErrorOr<DrawCommandHeader> read_draw_command_header(StyleType style_type)
338
97.5k
    {
339
97.5k
        return DrawCommandHeader { TRY(read_var_uint()) + 1, TRY(read_style(style_type)), TRY(read_unit()) };
340
97.5k
    }
341
342
    ErrorOr<OutlineFillCommandHeader> read_outline_fill_command_header(StyleType style_type)
343
12.0k
    {
344
12.0k
        u8 header = TRY(m_stream.read_value<u8>());
345
12.0k
        u8 count = (header & 0x3f) + 1;
346
12.0k
        auto stroke_type = static_cast<StyleType>((header >> 6) & 0x3);
347
12.0k
        return OutlineFillCommandHeader { count, TRY(read_style(style_type)), TRY(read_style(stroke_type)), TRY(read_unit()) };
348
11.9k
    }
349
350
private:
351
    Stream& m_stream;
352
    float m_scale {};
353
    CoordinateRange m_coordinate_range;
354
    ReadonlySpan<Color> m_color_table;
355
};
356
357
ErrorOr<NonnullRefPtr<TinyVGDecodedImageData>> TinyVGDecodedImageData::decode(Stream& stream)
358
0
{
359
0
    return decode(stream, TRY(decode_tinyvg_header(stream)));
360
0
}
361
362
ErrorOr<NonnullRefPtr<TinyVGDecodedImageData>> TinyVGDecodedImageData::decode(Stream& stream, TinyVGHeader const& header)
363
1.42k
{
364
1.42k
    if (header.version != 1)
365
0
        return Error::from_string_literal("Invalid TinyVG: Unsupported version");
366
367
1.42k
    auto color_table = TRY(decode_color_table(stream, header.color_encoding, header.color_count));
368
1.37k
    TinyVGReader reader { stream, header, color_table.span() };
369
370
2.45M
    auto rectangle_to_path = [](FloatRect const& rect) -> Path {
371
2.45M
        Path path;
372
2.45M
        path.move_to({ rect.x(), rect.y() });
373
2.45M
        path.line_to({ rect.x() + rect.width(), rect.y() });
374
2.45M
        path.line_to({ rect.x() + rect.width(), rect.y() + rect.height() });
375
2.45M
        path.line_to({ rect.x(), rect.y() + rect.height() });
376
2.45M
        path.close();
377
2.45M
        return path;
378
2.45M
    };
379
380
1.37k
    Vector<DrawCommand> draw_commands;
381
1.37k
    bool at_end = false;
382
179k
    while (!at_end) {
383
179k
        u8 command_info = TRY(stream.read_value<u8>());
384
179k
        auto command = static_cast<Command>(command_info & 0x3f);
385
179k
        auto style_type = static_cast<StyleType>((command_info >> 6) & 0x3);
386
387
179k
        switch (command) {
388
796
        case Command::EndOfDocument:
389
796
            at_end = true;
390
796
            break;
391
40.8k
        case Command::FillPolygon: {
392
40.8k
            auto header = TRY(reader.read_fill_command_header(style_type));
393
40.8k
            Path polygon;
394
40.8k
            polygon.move_to(TRY(reader.read_point()));
395
85.3k
            for (u32 i = 0; i < header.count - 1; i++)
396
44.5k
                polygon.line_to(TRY(reader.read_point()));
397
40.8k
            TRY(draw_commands.try_append(DrawCommand { move(polygon), move(header.style) }));
398
40.7k
            break;
399
40.7k
        }
400
21.0k
        case Command::FillRectangles: {
401
21.0k
            auto header = TRY(reader.read_fill_command_header(style_type));
402
2.39M
            for (u32 i = 0; i < header.count; i++) {
403
4.73M
                TRY(draw_commands.try_append(DrawCommand {
404
4.73M
                    rectangle_to_path(TRY(reader.read_rectangle())), header.style }));
405
4.73M
            }
406
21.0k
            break;
407
21.0k
        }
408
20.9k
        case Command::FillPath: {
409
6.87k
            auto header = TRY(reader.read_fill_command_header(style_type));
410
6.86k
            auto path = TRY(reader.read_path(header.count));
411
6.76k
            TRY(draw_commands.try_append(DrawCommand { move(path), move(header.style) }));
412
6.76k
            break;
413
6.76k
        }
414
3.38k
        case Command::DrawLines: {
415
3.38k
            auto header = TRY(reader.read_draw_command_header(style_type));
416
3.38k
            Path path;
417
25.8k
            for (u32 i = 0; i < header.count; i++) {
418
22.4k
                auto line = TRY(reader.read_line());
419
22.4k
                path.move_to(line.a());
420
22.4k
                path.line_to(line.b());
421
22.4k
            }
422
3.38k
            TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
423
3.36k
            break;
424
3.36k
        }
425
34.8k
        case Command::DrawLineStrip:
426
92.4k
        case Command::DrawLineLoop: {
427
92.4k
            auto header = TRY(reader.read_draw_command_header(style_type));
428
92.4k
            Path path;
429
92.4k
            path.move_to(TRY(reader.read_point()));
430
585k
            for (u32 i = 0; i < header.count - 1; i++)
431
492k
                path.line_to(TRY(reader.read_point()));
432
92.3k
            if (command == Command::DrawLineLoop)
433
57.5k
                path.close();
434
92.3k
            TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
435
92.3k
            break;
436
92.3k
        }
437
1.72k
        case Command::DrawLinePath: {
438
1.72k
            auto header = TRY(reader.read_draw_command_header(style_type));
439
1.72k
            auto path = TRY(reader.read_path(header.count));
440
1.63k
            TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
441
1.63k
            break;
442
1.63k
        }
443
1.27k
        case Command::OutlineFillPolygon: {
444
1.27k
            auto header = TRY(reader.read_outline_fill_command_header(style_type));
445
1.27k
            Path polygon;
446
1.27k
            polygon.move_to(TRY(reader.read_point()));
447
6.33k
            for (u32 i = 0; i < header.count - 1; i++)
448
5.07k
                polygon.line_to(TRY(reader.read_point()));
449
1.26k
            polygon.close();
450
1.25k
            TRY(draw_commands.try_append(DrawCommand { move(polygon), move(header.fill_style), move(header.line_style), header.line_width }));
451
1.25k
            break;
452
1.25k
        }
453
8.65k
        case Command::OutlineFillRectangles: {
454
8.65k
            auto header = TRY(reader.read_outline_fill_command_header(style_type));
455
98.0k
            for (u32 i = 0; i < header.count; i++) {
456
178k
                TRY(draw_commands.try_append(DrawCommand {
457
178k
                    rectangle_to_path(TRY(reader.read_rectangle())), header.fill_style, header.line_style, header.line_width }));
458
178k
            }
459
8.64k
            break;
460
8.64k
        }
461
8.61k
        case Command::OutLineFillPath: {
462
2.07k
            auto header = TRY(reader.read_outline_fill_command_header(style_type));
463
2.06k
            auto path = TRY(reader.read_path(header.count));
464
2.03k
            TRY(draw_commands.try_append(DrawCommand { move(path), move(header.fill_style), move(header.line_style), header.line_width }));
465
2.03k
            break;
466
2.03k
        }
467
41
        default:
468
41
            return Error::from_string_literal("Invalid TinyVG: Bad command");
469
179k
        }
470
179k
    }
471
472
796
    return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) TinyVGDecodedImageData({ header.width, header.height }, move(draw_commands))));
473
796
}
474
475
void TinyVGDecodedImageData::draw_transformed(Painter& painter, AffineTransform transform) const
476
792
{
477
    // FIXME: Correctly handle non-uniform scales.
478
792
    auto scale = max(transform.x_scale(), transform.y_scale());
479
792
    AntiAliasingPainter aa_painter { painter };
480
677k
    for (auto const& command : draw_commands()) {
481
677k
        auto draw_path = command.path.copy_transformed(transform);
482
677k
        if (command.fill.has_value()) {
483
617k
            auto fill_path = draw_path;
484
617k
            fill_path.close_all_subpaths();
485
617k
            command.fill->visit(
486
617k
                [&](Color color) { aa_painter.fill_path(fill_path, color, WindingRule::EvenOdd); },
487
617k
                [&](NonnullRefPtr<SVGGradientPaintStyle> style) {
488
574k
                    const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
489
574k
                    aa_painter.fill_path(fill_path, style, 1.0f, WindingRule::EvenOdd);
490
574k
                });
491
617k
        }
492
677k
        if (command.stroke.has_value()) {
493
72.3k
            command.stroke->visit(
494
72.3k
                [&](Color color) { aa_painter.stroke_path(draw_path, color, { command.stroke_width * scale }); },
495
72.3k
                [&](NonnullRefPtr<SVGGradientPaintStyle> style) {
496
7.73k
                    const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
497
7.73k
                    aa_painter.stroke_path(draw_path, style, { command.stroke_width * scale });
498
7.73k
                });
499
72.3k
        }
500
677k
    }
501
792
}
502
503
struct TinyVGLoadingContext {
504
    FixedMemoryStream stream;
505
    TinyVGHeader header {};
506
    RefPtr<TinyVGDecodedImageData> decoded_image {};
507
    RefPtr<Bitmap> bitmap {};
508
    enum class State {
509
        NotDecoded = 0,
510
        HeaderDecoded,
511
        ImageDecoded,
512
        Error,
513
    };
514
    State state { State::NotDecoded };
515
};
516
517
static ErrorOr<void> decode_header_and_update_context(TinyVGLoadingContext& context)
518
1.42k
{
519
1.42k
    VERIFY(context.state == TinyVGLoadingContext::State::NotDecoded);
520
1.42k
    context.header = TRY(decode_tinyvg_header(context.stream));
521
1.42k
    context.state = TinyVGLoadingContext::State::HeaderDecoded;
522
1.42k
    return {};
523
1.42k
}
524
525
static ErrorOr<void> decode_image_data_and_update_context(TinyVGLoadingContext& context)
526
1.42k
{
527
1.42k
    VERIFY(context.state == TinyVGLoadingContext::State::HeaderDecoded);
528
1.42k
    auto image_data_or_error = TinyVGDecodedImageData::decode(context.stream, context.header);
529
1.42k
    if (image_data_or_error.is_error()) {
530
630
        context.state = TinyVGLoadingContext::State::Error;
531
630
        return image_data_or_error.release_error();
532
630
    }
533
796
    context.state = TinyVGLoadingContext::State::ImageDecoded;
534
796
    context.decoded_image = image_data_or_error.release_value();
535
796
    return {};
536
1.42k
}
537
538
static ErrorOr<void> ensure_fully_decoded(TinyVGLoadingContext& context)
539
1.42k
{
540
1.42k
    if (context.state == TinyVGLoadingContext::State::Error)
541
0
        return Error::from_string_literal("TinyVGImageDecoderPlugin: Decoding failed!");
542
1.42k
    if (context.state == TinyVGLoadingContext::State::HeaderDecoded)
543
1.42k
        TRY(decode_image_data_and_update_context(context));
544
1.42k
    VERIFY(context.state == TinyVGLoadingContext::State::ImageDecoded);
545
796
    return {};
546
796
}
547
548
TinyVGImageDecoderPlugin::TinyVGImageDecoderPlugin(ReadonlyBytes bytes)
549
1.42k
    : m_context { make<TinyVGLoadingContext>(FixedMemoryStream { bytes }) }
550
1.42k
{
551
1.42k
}
552
553
1.42k
TinyVGImageDecoderPlugin::~TinyVGImageDecoderPlugin() = default;
554
555
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> TinyVGImageDecoderPlugin::create(ReadonlyBytes bytes)
556
1.42k
{
557
1.42k
    auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TinyVGImageDecoderPlugin(bytes)));
558
1.42k
    TRY(decode_header_and_update_context(*plugin->m_context));
559
1.42k
    return plugin;
560
1.42k
}
561
562
bool TinyVGImageDecoderPlugin::sniff(ReadonlyBytes bytes)
563
0
{
564
0
    FixedMemoryStream stream { { bytes.data(), bytes.size() } };
565
0
    return !decode_tinyvg_header(stream).is_error();
566
0
}
567
568
IntSize TinyVGImageDecoderPlugin::size()
569
0
{
570
0
    return { m_context->header.width, m_context->header.height };
571
0
}
572
573
ErrorOr<ImageFrameDescriptor> TinyVGImageDecoderPlugin::frame(size_t, Optional<IntSize> ideal_size)
574
1.42k
{
575
1.42k
    TRY(ensure_fully_decoded(*m_context));
576
796
    auto target_size = ideal_size.value_or(m_context->decoded_image->size());
577
796
    if (!m_context->bitmap || m_context->bitmap->size() != target_size)
578
796
        m_context->bitmap = TRY(m_context->decoded_image->bitmap(target_size));
579
796
    return ImageFrameDescriptor { m_context->bitmap };
580
796
}
581
582
ErrorOr<VectorImageFrameDescriptor> TinyVGImageDecoderPlugin::vector_frame(size_t)
583
0
{
584
0
    TRY(ensure_fully_decoded(*m_context));
585
0
    return VectorImageFrameDescriptor { m_context->decoded_image, 0 };
586
0
}
587
588
}