/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 | | } |