TinyVGLoader.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*
  2. * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Array.h>
  7. #include <AK/Endian.h>
  8. #include <AK/FixedArray.h>
  9. #include <AK/LEB128.h>
  10. #include <AK/MemoryStream.h>
  11. #include <AK/Variant.h>
  12. #include <LibCore/File.h>
  13. #include <LibGfx/AntiAliasingPainter.h>
  14. #include <LibGfx/ImageFormats/TinyVGLoader.h>
  15. #include <LibGfx/Line.h>
  16. #include <LibGfx/Painter.h>
  17. #include <LibGfx/Point.h>
  18. namespace Gfx {
  19. using VarUInt = LEB128<u32>;
  20. static constexpr Array<u8, 2> TVG_MAGIC { 0x72, 0x56 };
  21. enum class ColorEncoding : u8 {
  22. RGBA8888 = 0,
  23. RGB565 = 1,
  24. RGBAF32 = 2,
  25. Custom = 3
  26. };
  27. enum class CoordinateRange : u8 {
  28. Default = 0,
  29. Reduced = 1,
  30. Enhanced = 2
  31. };
  32. enum class StyleType : u8 {
  33. FlatColored = 0,
  34. LinearGradient = 1,
  35. RadialGradinet = 2
  36. };
  37. enum class Command : u8 {
  38. EndOfDocument = 0,
  39. FillPolygon = 1,
  40. FillRectangles = 2,
  41. FillPath = 3,
  42. DrawLines = 4,
  43. DrawLineLoop = 5,
  44. DrawLineStrip = 6,
  45. DrawLinePath = 7,
  46. OutlineFillPolygon = 8,
  47. OutlineFillRectangles = 9,
  48. OutLineFillPath = 10
  49. };
  50. struct FillCommandHeader {
  51. u32 count;
  52. TinyVGDecodedImageData::Style style;
  53. };
  54. struct DrawCommandHeader {
  55. u32 count;
  56. TinyVGDecodedImageData::Style line_style;
  57. float line_width;
  58. };
  59. struct OutlineFillCommandHeader {
  60. u32 count;
  61. TinyVGDecodedImageData::Style fill_style;
  62. TinyVGDecodedImageData::Style line_style;
  63. float line_width;
  64. };
  65. enum class PathCommand : u8 {
  66. Line = 0,
  67. HorizontalLine = 1,
  68. VerticalLine = 2,
  69. CubicBezier = 3,
  70. ArcCircle = 4,
  71. ArcEllipse = 5,
  72. ClosePath = 6,
  73. QuadraticBezier = 7
  74. };
  75. struct TinyVGHeader {
  76. u8 version;
  77. u8 scale;
  78. ColorEncoding color_encoding;
  79. CoordinateRange coordinate_range;
  80. u32 width;
  81. u32 height;
  82. u32 color_count;
  83. };
  84. static ErrorOr<TinyVGHeader> decode_tinyvg_header(Stream& stream)
  85. {
  86. TinyVGHeader header {};
  87. Array<u8, 2> magic_bytes;
  88. TRY(stream.read_until_filled(magic_bytes));
  89. if (magic_bytes != TVG_MAGIC)
  90. return Error::from_string_literal("Invalid TinyVG: Incorrect header magic");
  91. header.version = TRY(stream.read_value<u8>());
  92. u8 properties = TRY(stream.read_value<u8>());
  93. header.scale = properties & 0xF;
  94. header.color_encoding = static_cast<ColorEncoding>((properties >> 4) & 0x3);
  95. header.coordinate_range = static_cast<CoordinateRange>((properties >> 6) & 0x3);
  96. switch (header.coordinate_range) {
  97. case CoordinateRange::Default:
  98. header.width = TRY(stream.read_value<LittleEndian<u16>>());
  99. header.height = TRY(stream.read_value<LittleEndian<u16>>());
  100. break;
  101. case CoordinateRange::Reduced:
  102. header.width = TRY(stream.read_value<u8>());
  103. header.height = TRY(stream.read_value<u8>());
  104. break;
  105. case CoordinateRange::Enhanced:
  106. header.width = TRY(stream.read_value<LittleEndian<u32>>());
  107. header.height = TRY(stream.read_value<LittleEndian<u32>>());
  108. break;
  109. default:
  110. return Error::from_string_literal("Invalid TinyVG: Bad coordinate range");
  111. }
  112. header.color_count = TRY(stream.read_value<VarUInt>());
  113. return header;
  114. }
  115. static ErrorOr<FixedArray<Color>> decode_color_table(Stream& stream, ColorEncoding encoding, u32 color_count)
  116. {
  117. if (encoding == ColorEncoding::Custom)
  118. return Error::from_string_literal("Invalid TinyVG: Unsupported color encoding");
  119. auto color_table = TRY(FixedArray<Color>::create(color_count));
  120. auto parse_color = [&]() -> ErrorOr<Color> {
  121. switch (encoding) {
  122. case ColorEncoding::RGBA8888: {
  123. Array<u8, 4> rgba;
  124. TRY(stream.read_until_filled(rgba));
  125. return Color(rgba[0], rgba[1], rgba[2], rgba[3]);
  126. }
  127. case ColorEncoding::RGB565: {
  128. u16 color = TRY(stream.read_value<LittleEndian<u16>>());
  129. auto red = (color >> (6 + 5)) & 0x1f;
  130. auto green = (color >> 5) & 0x3f;
  131. auto blue = (color >> 0) & 0x1f;
  132. return Color((red * 255 + 15) / 31, (green * 255 + 31), (blue * 255 + 15) / 31);
  133. }
  134. case ColorEncoding::RGBAF32: {
  135. auto red = TRY(stream.read_value<LittleEndian<f32>>());
  136. auto green = TRY(stream.read_value<LittleEndian<f32>>());
  137. auto blue = TRY(stream.read_value<LittleEndian<f32>>());
  138. auto alpha = TRY(stream.read_value<LittleEndian<f32>>());
  139. return Color(red * 255, green * 255, blue * 255, alpha * 255);
  140. }
  141. default:
  142. return Error::from_string_literal("Invalid TinyVG: Bad color encoding");
  143. }
  144. };
  145. for (auto& color : color_table) {
  146. color = TRY(parse_color());
  147. }
  148. return color_table;
  149. }
  150. class TinyVGReader {
  151. public:
  152. TinyVGReader(Stream& stream, TinyVGHeader const& header, ReadonlySpan<Color> color_table)
  153. : m_stream(stream)
  154. , m_scale(powf(0.5, header.scale))
  155. , m_coordinate_range(header.coordinate_range)
  156. , m_color_table(color_table)
  157. {
  158. }
  159. ErrorOr<float> read_unit()
  160. {
  161. auto read_value = [&]() -> ErrorOr<i32> {
  162. switch (m_coordinate_range) {
  163. case CoordinateRange::Default:
  164. return TRY(m_stream.read_value<LittleEndian<i16>>());
  165. case CoordinateRange::Reduced:
  166. return TRY(m_stream.read_value<i8>());
  167. case CoordinateRange::Enhanced:
  168. return TRY(m_stream.read_value<LittleEndian<i32>>());
  169. default:
  170. // Note: Already checked while reading the header.
  171. VERIFY_NOT_REACHED();
  172. }
  173. };
  174. return TRY(read_value()) * m_scale;
  175. }
  176. ErrorOr<u32> read_var_uint()
  177. {
  178. return TRY(m_stream.read_value<VarUInt>());
  179. }
  180. ErrorOr<FloatPoint> read_point()
  181. {
  182. return FloatPoint { TRY(read_unit()), TRY(read_unit()) };
  183. }
  184. ErrorOr<TinyVGDecodedImageData::Style> read_style(StyleType type)
  185. {
  186. auto read_color = [&]() -> ErrorOr<Color> {
  187. auto color_index = TRY(m_stream.read_value<VarUInt>());
  188. return m_color_table[color_index];
  189. };
  190. switch (type) {
  191. case StyleType::FlatColored: {
  192. return TRY(read_color());
  193. }
  194. case StyleType::LinearGradient:
  195. case StyleType::RadialGradinet: {
  196. // TODO: Make PaintStyle (for these ultra basic gradients)
  197. [[maybe_unused]] auto point_0 = TRY(read_point());
  198. [[maybe_unused]] auto point_1 = TRY(read_point());
  199. [[maybe_unused]] auto color_0 = TRY(read_color());
  200. [[maybe_unused]] auto color_1 = TRY(read_color());
  201. return Color(Color::Black);
  202. }
  203. }
  204. return Error::from_string_literal("Invalid TinyVG: Bad style data");
  205. }
  206. ErrorOr<FloatRect> read_rectangle()
  207. {
  208. return FloatRect { TRY(read_unit()), TRY(read_unit()), TRY(read_unit()), TRY(read_unit()) };
  209. }
  210. ErrorOr<FloatLine> read_line()
  211. {
  212. return FloatLine { TRY(read_point()), TRY(read_point()) };
  213. }
  214. ErrorOr<Path> read_path(u32 segment_count)
  215. {
  216. Path path;
  217. auto segment_lengths = TRY(FixedArray<u32>::create(segment_count));
  218. for (auto& command_count : segment_lengths) {
  219. command_count = TRY(read_var_uint()) + 1;
  220. }
  221. for (auto command_count : segment_lengths) {
  222. auto start_point = TRY(read_point());
  223. path.move_to(start_point);
  224. for (u32 i = 0; i < command_count; i++) {
  225. u8 command_tag = TRY(m_stream.read_value<u8>());
  226. auto path_command = static_cast<PathCommand>(command_tag & 0x7);
  227. switch (path_command) {
  228. case PathCommand::Line:
  229. path.line_to(TRY(read_point()));
  230. break;
  231. case PathCommand::HorizontalLine:
  232. path.line_to({ TRY(read_unit()), path.segments().last()->point().y() });
  233. break;
  234. case PathCommand::VerticalLine:
  235. path.line_to({ path.segments().last()->point().x(), TRY(read_unit()) });
  236. break;
  237. case PathCommand::CubicBezier: {
  238. auto control_0 = TRY(read_point());
  239. auto control_1 = TRY(read_point());
  240. auto point_1 = TRY(read_point());
  241. path.cubic_bezier_curve_to(control_0, control_1, point_1);
  242. break;
  243. }
  244. case PathCommand::ArcCircle: {
  245. u8 flags = TRY(m_stream.read_value<u8>());
  246. bool large_arc = (flags >> 0) & 0b1;
  247. bool sweep = (flags >> 1) & 0b1;
  248. auto radius = TRY(read_unit());
  249. auto target = TRY(read_point());
  250. path.arc_to(target, radius, large_arc, !sweep);
  251. break;
  252. }
  253. case PathCommand::ArcEllipse: {
  254. u8 flags = TRY(m_stream.read_value<u8>());
  255. bool large_arc = (flags >> 0) & 0b1;
  256. bool sweep = (flags >> 1) & 0b1;
  257. auto radius_x = TRY(read_unit());
  258. auto radius_y = TRY(read_unit());
  259. auto rotation = TRY(read_unit());
  260. auto target = TRY(read_point());
  261. path.elliptical_arc_to(target, { radius_x, radius_y }, rotation, large_arc, !sweep);
  262. break;
  263. }
  264. case PathCommand::ClosePath: {
  265. path.close();
  266. break;
  267. }
  268. case PathCommand::QuadraticBezier: {
  269. auto control = TRY(read_point());
  270. auto point_1 = TRY(read_point());
  271. path.quadratic_bezier_curve_to(control, point_1);
  272. break;
  273. }
  274. default:
  275. return Error::from_string_literal("Invalid TinyVG: Bad path command");
  276. }
  277. }
  278. }
  279. return path;
  280. }
  281. ErrorOr<FillCommandHeader> read_fill_command_header(StyleType style_type)
  282. {
  283. return FillCommandHeader { TRY(read_var_uint()) + 1, TRY(read_style(style_type)) };
  284. }
  285. ErrorOr<DrawCommandHeader> read_draw_command_header(StyleType style_type)
  286. {
  287. return DrawCommandHeader { TRY(read_var_uint()) + 1, TRY(read_style(style_type)), TRY(read_unit()) };
  288. }
  289. ErrorOr<OutlineFillCommandHeader> read_outline_fill_command_header(StyleType style_type)
  290. {
  291. u8 header = TRY(m_stream.read_value<u8>());
  292. u8 count = (header & 0x3f) + 1;
  293. auto stroke_type = static_cast<StyleType>((header >> 6) & 0x3);
  294. return OutlineFillCommandHeader { count, TRY(read_style(style_type)), TRY(read_style(stroke_type)), TRY(read_unit()) };
  295. }
  296. private:
  297. Stream& m_stream;
  298. float m_scale {};
  299. CoordinateRange m_coordinate_range;
  300. ReadonlySpan<Color> m_color_table;
  301. };
  302. ErrorOr<TinyVGDecodedImageData> TinyVGDecodedImageData::decode(Stream& stream)
  303. {
  304. auto header = TRY(decode_tinyvg_header(stream));
  305. if (header.version != 1)
  306. return Error::from_string_literal("Invalid TinyVG: Unsupported version");
  307. auto color_table = TRY(decode_color_table(stream, header.color_encoding, header.color_count));
  308. TinyVGReader reader { stream, header, color_table.span() };
  309. auto rectangle_to_path = [](FloatRect const& rect) -> Path {
  310. Path path;
  311. path.move_to({ rect.x(), rect.y() });
  312. path.line_to({ rect.x() + rect.width(), rect.y() });
  313. path.line_to({ rect.x() + rect.width(), rect.y() + rect.height() });
  314. path.line_to({ rect.x(), rect.y() + rect.height() });
  315. path.close();
  316. return path;
  317. };
  318. Vector<DrawCommand> draw_commands;
  319. bool at_end = false;
  320. while (!at_end) {
  321. u8 command_info = TRY(stream.read_value<u8>());
  322. auto command = static_cast<Command>(command_info & 0x3f);
  323. auto style_type = static_cast<StyleType>((command_info >> 6) & 0x3);
  324. switch (command) {
  325. case Command::EndOfDocument:
  326. at_end = true;
  327. break;
  328. case Command::FillPolygon: {
  329. auto header = TRY(reader.read_fill_command_header(style_type));
  330. Path polygon;
  331. polygon.move_to(TRY(reader.read_point()));
  332. for (u32 i = 0; i < header.count - 1; i++)
  333. polygon.line_to(TRY(reader.read_point()));
  334. TRY(draw_commands.try_append(DrawCommand { move(polygon), move(header.style) }));
  335. break;
  336. }
  337. case Command::FillRectangles: {
  338. auto header = TRY(reader.read_fill_command_header(style_type));
  339. for (u32 i = 0; i < header.count; i++) {
  340. TRY(draw_commands.try_append(DrawCommand {
  341. rectangle_to_path(TRY(reader.read_rectangle())), move(header.style) }));
  342. }
  343. break;
  344. }
  345. case Command::FillPath: {
  346. auto header = TRY(reader.read_fill_command_header(style_type));
  347. auto path = TRY(reader.read_path(header.count));
  348. TRY(draw_commands.try_append(DrawCommand { move(path), move(header.style) }));
  349. break;
  350. }
  351. case Command::DrawLines: {
  352. auto header = TRY(reader.read_draw_command_header(style_type));
  353. Path path;
  354. for (u32 i = 0; i < header.count; i++) {
  355. auto line = TRY(reader.read_line());
  356. path.move_to(line.a());
  357. path.line_to(line.b());
  358. }
  359. TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
  360. break;
  361. }
  362. case Command::DrawLineStrip:
  363. case Command::DrawLineLoop: {
  364. auto header = TRY(reader.read_draw_command_header(style_type));
  365. Path path;
  366. path.move_to(TRY(reader.read_point()));
  367. for (u32 i = 0; i < header.count - 1; i++)
  368. path.line_to(TRY(reader.read_point()));
  369. if (command == Command::DrawLineLoop)
  370. path.close();
  371. TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
  372. break;
  373. }
  374. case Command::DrawLinePath: {
  375. auto header = TRY(reader.read_draw_command_header(style_type));
  376. auto path = TRY(reader.read_path(header.count));
  377. TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
  378. break;
  379. }
  380. case Command::OutlineFillPolygon: {
  381. auto header = TRY(reader.read_outline_fill_command_header(style_type));
  382. Path polygon;
  383. polygon.move_to(TRY(reader.read_point()));
  384. for (u32 i = 0; i < header.count - 1; i++)
  385. polygon.line_to(TRY(reader.read_point()));
  386. TRY(draw_commands.try_append(DrawCommand { move(polygon), move(header.fill_style), move(header.line_style), header.line_width }));
  387. break;
  388. }
  389. case Command::OutlineFillRectangles: {
  390. auto header = TRY(reader.read_outline_fill_command_header(style_type));
  391. for (u32 i = 0; i < header.count - 1; i++) {
  392. TRY(draw_commands.try_append(DrawCommand {
  393. rectangle_to_path(TRY(reader.read_rectangle())), move(header.fill_style), move(header.line_style), header.line_width }));
  394. }
  395. break;
  396. }
  397. case Command::OutLineFillPath: {
  398. auto header = TRY(reader.read_outline_fill_command_header(style_type));
  399. auto path = TRY(reader.read_path(header.count));
  400. TRY(draw_commands.try_append(DrawCommand { move(path), move(header.fill_style), move(header.line_style), header.line_width }));
  401. break;
  402. }
  403. default:
  404. return Error::from_string_literal("Invalid TinyVG: Bad command");
  405. }
  406. }
  407. return TinyVGDecodedImageData { { header.width, header.height }, move(draw_commands) };
  408. }
  409. ErrorOr<RefPtr<Gfx::Bitmap>> TinyVGDecodedImageData::bitmap(IntSize size) const
  410. {
  411. auto scale_x = float(size.width()) / m_size.width();
  412. auto scale_y = float(size.height()) / m_size.height();
  413. auto transform = Gfx::AffineTransform {}.scale(scale_x, scale_y);
  414. auto bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::BGRA8888, size));
  415. Painter base_painter { *bitmap };
  416. AntiAliasingPainter painter { base_painter };
  417. for (auto const& command : draw_commands()) {
  418. auto draw_path = command.path.copy_transformed(transform);
  419. if (command.fill.has_value()) {
  420. auto fill_path = draw_path;
  421. fill_path.close_all_subpaths();
  422. command.fill->visit(
  423. [&](Color color) { painter.fill_path(fill_path, color, Painter::WindingRule::EvenOdd); },
  424. [&](NonnullRefPtr<SVGGradientPaintStyle> style) {
  425. const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
  426. painter.fill_path(fill_path, style, 1.0f, Painter::WindingRule::EvenOdd);
  427. });
  428. }
  429. if (command.stroke.has_value()) {
  430. // FIXME: A more correct way to non-uniformly scale strokes would be:
  431. // 1. Scale the path uniformly by the largest of scale_x/y
  432. // 2. Convert that to a fill with .stroke_to_fill()
  433. // 3.
  434. // If scale_x > scale_y
  435. // Scale by: (1, scale_y/scale_x)
  436. // else
  437. // Scale by: (scale_x/scale_y, 1)
  438. auto stroke_scale = max(scale_x, scale_y);
  439. command.stroke->visit(
  440. [&](Color color) { painter.stroke_path(draw_path, color, command.stroke_width * stroke_scale); },
  441. [&](NonnullRefPtr<SVGGradientPaintStyle> style) {
  442. const_cast<SVGGradientPaintStyle&>(*style).set_gradient_transform(transform);
  443. painter.stroke_path(draw_path, style, command.stroke_width * stroke_scale);
  444. });
  445. }
  446. }
  447. return bitmap;
  448. }
  449. TinyVGImageDecoderPlugin::TinyVGImageDecoderPlugin(ReadonlyBytes bytes)
  450. : m_context { bytes }
  451. {
  452. }
  453. ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> TinyVGImageDecoderPlugin::create(ReadonlyBytes bytes)
  454. {
  455. return adopt_nonnull_own_or_enomem(new (nothrow) TinyVGImageDecoderPlugin(bytes));
  456. }
  457. bool TinyVGImageDecoderPlugin::sniff(ReadonlyBytes bytes)
  458. {
  459. FixedMemoryStream stream { { bytes.data(), bytes.size() } };
  460. return !decode_tinyvg_header(stream).is_error();
  461. }
  462. IntSize TinyVGImageDecoderPlugin::size()
  463. {
  464. if (m_context.decoded_image)
  465. return m_context.decoded_image->size();
  466. return {};
  467. }
  468. ErrorOr<void> TinyVGImageDecoderPlugin::initialize()
  469. {
  470. FixedMemoryStream stream { { m_context.data.data(), m_context.data.size() } };
  471. m_context.decoded_image = make<TinyVGDecodedImageData>(TRY(TinyVGDecodedImageData::decode(stream)));
  472. return {};
  473. }
  474. ErrorOr<ImageFrameDescriptor> TinyVGImageDecoderPlugin::frame(size_t, Optional<IntSize> ideal_size)
  475. {
  476. auto target_size = ideal_size.value_or(m_context.decoded_image->size());
  477. if (!m_context.bitmap || m_context.bitmap->size() != target_size)
  478. m_context.bitmap = TRY(m_context.decoded_image->bitmap(target_size));
  479. return ImageFrameDescriptor { m_context.bitmap };
  480. }
  481. }