Blender V4.3
grease_pencil_io_export_svg.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BKE_attribute.hh"
6#include "BKE_curves.hh"
7#include "BKE_material.h"
8#include "BLI_color.hh"
9#include "BLI_math_matrix.hh"
10#include "BLI_offset_indices.hh"
11#include "BLI_string.h"
12#include "BLI_task.hh"
13#include "BLI_vector.hh"
14#include "BLI_virtual_array.hh"
15
16#include "BKE_grease_pencil.hh"
17
18#include "DNA_material_types.h"
19#include "DNA_object_types.h"
20
22#include "DNA_view3d_types.h"
23
25
26#include "ED_grease_pencil.hh"
27#include "ED_view3d.hh"
28
30
31#include <fmt/core.h>
32#include <fmt/format.h>
33#include <numeric>
34#include <optional>
35#include <pugixml.hpp>
36
37#ifdef WIN32
38# include "utfconv.hh"
39#endif
40
46
47constexpr const char *svg_exporter_name = "SVG Export for Grease Pencil";
48constexpr const char *svg_exporter_version = "v2.0";
49
50static std::string rgb_to_hexstr(const float color[3])
51{
52 uint8_t r = color[0] * 255.0f;
53 uint8_t g = color[1] * 255.0f;
54 uint8_t b = color[2] * 255.0f;
55 return fmt::format("#{:02X}{:02X}{:02X}", r, g, b);
56}
57
58static void write_stroke_color_attribute(pugi::xml_node node,
59 const ColorGeometry4f &stroke_color,
60 const float stroke_opacity,
61 const bool round_cap)
62{
63 ColorGeometry4f color;
64 linearrgb_to_srgb_v3_v3(color, stroke_color);
65 std::string stroke_hex = rgb_to_hexstr(color);
66
67 node.append_attribute("stroke").set_value(stroke_hex.c_str());
68 node.append_attribute("stroke-opacity").set_value(stroke_color.a * stroke_opacity);
69
70 node.append_attribute("fill").set_value("none");
71 node.append_attribute("stroke-linecap").set_value(round_cap ? "round" : "square");
72}
73
74static void write_fill_color_attribute(pugi::xml_node node,
75 const ColorGeometry4f &fill_color,
76 const float layer_opacity)
77{
78 ColorGeometry4f color;
79 linearrgb_to_srgb_v3_v3(color, fill_color);
80 std::string stroke_hex = rgb_to_hexstr(color);
81
82 node.append_attribute("fill").set_value(stroke_hex.c_str());
83 node.append_attribute("stroke").set_value("none");
84 node.append_attribute("fill-opacity").set_value(fill_color.a * layer_opacity);
85}
86
87static void write_rect(pugi::xml_node node,
88 const float x,
89 const float y,
90 const float width,
91 const float height,
92 const float thickness,
93 const std::string &hexcolor)
94{
95 pugi::xml_node rect_node = node.append_child("rect");
96 rect_node.append_attribute("x").set_value(x);
97 rect_node.append_attribute("y").set_value(y);
98 rect_node.append_attribute("width").set_value(width);
99 rect_node.append_attribute("height").set_value(height);
100 rect_node.append_attribute("fill").set_value("none");
101 if (thickness > 0.0f) {
102 rect_node.append_attribute("stroke").set_value(hexcolor.c_str());
103 rect_node.append_attribute("stroke-width").set_value(thickness);
104 }
105}
106
108 public:
110
111 pugi::xml_document main_doc_;
112
113 bool export_scene(Scene &scene, StringRefNull filepath);
114 void export_grease_pencil_objects(pugi::xml_node node, int frame_number);
115 void export_grease_pencil_layer(pugi::xml_node node,
116 const Object &object,
117 const bke::greasepencil::Layer &layer,
118 const bke::greasepencil::Drawing &drawing);
119
121 pugi::xml_node write_main_node();
122 pugi::xml_node write_polygon(pugi::xml_node node,
123 const float4x4 &transform,
124 Span<float3> positions);
125 pugi::xml_node write_polyline(pugi::xml_node node,
126 const float4x4 &transform,
127 Span<float3> positions,
128 bool cyclic,
129 std::optional<float> width);
130 pugi::xml_node write_path(pugi::xml_node node,
131 const float4x4 &transform,
132 Span<float3> positions,
133 bool cyclic);
134
135 bool write_to_file(StringRefNull filepath);
136};
137
139{
140 const int frame_number = scene.r.cfra;
141
142 this->prepare_render_params(scene, frame_number);
143
144 this->write_document_header();
145 pugi::xml_node main_node = this->write_main_node();
146 this->export_grease_pencil_objects(main_node, frame_number);
147
148 return this->write_to_file(filepath);
149}
150
151void SVGExporter::export_grease_pencil_objects(pugi::xml_node node, const int frame_number)
152{
154
155 const bool is_clipping = camera_persmat_ && params_.use_clip_camera;
156
158
159 for (const ObjectInfo &info : objects) {
160 const Object *ob = info.object;
161
162 /* Camera clipping. */
163 if (is_clipping) {
164 pugi::xml_node clip_node = node.append_child("clipPath");
165 clip_node.append_attribute("id").set_value(
166 ("clip-path" + std::to_string(frame_number)).c_str());
167
168 write_rect(clip_node, 0, 0, render_rect_.size().x, render_rect_.size().y, 0.0f, "#000000");
169 }
170
171 pugi::xml_node frame_node = node.append_child("g");
172 std::string frametxt = "blender_frame_" + std::to_string(frame_number);
173 frame_node.append_attribute("id").set_value(frametxt.c_str());
174
175 /* Clip area. */
176 if (is_clipping) {
177 frame_node.append_attribute("clip-path")
178 .set_value(("url(#clip-path" + std::to_string(frame_number) + ")").c_str());
179 }
180
181 pugi::xml_node ob_node = frame_node.append_child("g");
182
183 char obtxt[96];
184 SNPRINTF(obtxt, "blender_object_%s", ob->id.name + 2);
185 ob_node.append_attribute("id").set_value(obtxt);
186
187 /* Use evaluated version to get strokes with modifiers. */
188 Object *ob_eval = DEG_get_evaluated_object(context_.depsgraph, const_cast<Object *>(ob));
189 BLI_assert(ob_eval->type == OB_GREASE_PENCIL);
190 const GreasePencil *grease_pencil_eval = static_cast<const GreasePencil *>(ob_eval->data);
191
192 for (const bke::greasepencil::Layer *layer : grease_pencil_eval->layers()) {
193 if (!layer->is_visible()) {
194 continue;
195 }
196 const Drawing *drawing = grease_pencil_eval->get_drawing_at(*layer, frame_number);
197 if (drawing == nullptr) {
198 continue;
199 }
200
201 /* Layer node. */
202 const std::string txt = "Layer: " + layer->name();
203 ob_node.append_child(pugi::node_comment).set_value(txt.c_str());
204
205 pugi::xml_node layer_node = ob_node.append_child("g");
206 layer_node.append_attribute("id").set_value(layer->name().c_str());
207
208 export_grease_pencil_layer(layer_node, *ob_eval, *layer, *drawing);
209 }
210 }
211}
212
213void SVGExporter::export_grease_pencil_layer(pugi::xml_node layer_node,
214 const Object &object,
215 const bke::greasepencil::Layer &layer,
216 const bke::greasepencil::Drawing &drawing)
217{
219
220 const float4x4 layer_to_world = layer.to_world_space(object);
221
222 auto write_stroke = [&](const Span<float3> positions,
223 const bool cyclic,
224 const ColorGeometry4f &color,
225 const float opacity,
226 const std::optional<float> width,
227 const bool round_cap,
228 const bool is_outline) {
229 if (is_outline) {
230 pugi::xml_node element_node = write_path(layer_node, layer_to_world, positions, cyclic);
231 write_fill_color_attribute(element_node, color, opacity);
232 }
233 else {
234 /* Fill is always exported as polygon because the stroke of the fill is done
235 * in a different SVG command. */
236 pugi::xml_node element_node = write_polyline(
237 layer_node, layer_to_world, positions, cyclic, width);
238
239 if (width) {
240 write_stroke_color_attribute(element_node, color, opacity, round_cap);
241 }
242 else {
243 write_fill_color_attribute(element_node, color, opacity);
244 }
245 }
246 };
247
248 foreach_stroke_in_layer(object, layer, drawing, write_stroke);
249}
250
252{
253 /* Add a custom document declaration node. */
254 pugi::xml_node decl = main_doc_.prepend_child(pugi::node_declaration);
255 decl.append_attribute("version") = "1.0";
256 decl.append_attribute("encoding") = "UTF-8";
257
258 pugi::xml_node comment = main_doc_.append_child(pugi::node_comment);
259 std::string txt = std::string(" Generator: Blender, ") + svg_exporter_name + " - " +
261 comment.set_value(txt.c_str());
262
263 pugi::xml_node doctype = main_doc_.append_child(pugi::node_doctype);
264 doctype.set_value(
265 "svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
266 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"");
267}
268
270{
271 pugi::xml_node main_node = main_doc_.append_child("svg");
272 main_node.append_attribute("version").set_value("1.0");
273 main_node.append_attribute("x").set_value("0px");
274 main_node.append_attribute("y").set_value("0px");
275 main_node.append_attribute("xmlns").set_value("http://www.w3.org/2000/svg");
276
277 std::string width = std::to_string(render_rect_.size().x);
278 std::string height = std::to_string(render_rect_.size().y);
279
280 main_node.append_attribute("width").set_value((width + "px").c_str());
281 main_node.append_attribute("height").set_value((height + "px").c_str());
282 std::string viewbox = "0 0 " + width + " " + height;
283 main_node.append_attribute("viewBox").set_value(viewbox.c_str());
284
285 return main_node;
286}
287
288pugi::xml_node SVGExporter::write_polygon(pugi::xml_node node,
289 const float4x4 &transform,
290 const Span<float3> positions)
291{
292 pugi::xml_node element_node = node.append_child("polygon");
293
294 std::string txt;
295 for (const int i : positions.index_range()) {
296 if (i > 0) {
297 txt.append(" ");
298 }
299 /* SVG has inverted Y axis. */
300 const float2 screen_co = this->project_to_screen(transform, positions[i]);
301 txt.append(std::to_string(screen_co.x) + "," +
302 std::to_string(render_rect_.size().y - screen_co.y));
303 }
304
305 element_node.append_attribute("points").set_value(txt.c_str());
306
307 return element_node;
308}
309
310pugi::xml_node SVGExporter::write_polyline(pugi::xml_node node,
311 const float4x4 &transform,
312 const Span<float3> positions,
313 const bool cyclic,
314 const std::optional<float> width)
315{
316 pugi::xml_node element_node = node.append_child(cyclic ? "polygon" : "polyline");
317
318 if (width) {
319 element_node.append_attribute("stroke-width").set_value(*width);
320 }
321
322 std::string txt;
323 for (const int i : positions.index_range()) {
324 if (i > 0) {
325 txt.append(" ");
326 }
327 /* SVG has inverted Y axis. */
328 const float2 screen_co = this->project_to_screen(transform, positions[i]);
329 txt.append(std::to_string(screen_co.x) + "," +
330 std::to_string(render_rect_.size().y - screen_co.y));
331 }
332
333 element_node.append_attribute("points").set_value(txt.c_str());
334
335 return element_node;
336}
337
338pugi::xml_node SVGExporter::write_path(pugi::xml_node node,
339 const float4x4 &transform,
340 const Span<float3> positions,
341 const bool cyclic)
342{
343 pugi::xml_node element_node = node.append_child("path");
344
345 std::string txt = "M";
346 for (const int i : positions.index_range()) {
347 if (i > 0) {
348 txt.append("L");
349 }
350 const float2 screen_co = this->project_to_screen(transform, positions[i]);
351 /* SVG has inverted Y axis. */
352 txt.append(std::to_string(screen_co.x) + "," +
353 std::to_string(render_rect_.size().y - screen_co.y));
354 }
355 /* Close patch (cyclic). */
356 if (cyclic) {
357 txt.append("z");
358 }
359
360 element_node.append_attribute("d").set_value(txt.c_str());
361
362 return element_node;
363}
364
366{
367 bool result = true;
368 /* Support unicode character paths on Windows. */
369#ifdef WIN32
370 wchar_t *filepath_16 = alloc_utf16_from_8(filepath.c_str(), 0);
371 std::wstring wstr(filepath_16);
372 result = main_doc_.save_file(wstr.c_str());
373 free(filepath_16);
374#else
375 result = main_doc_.save_file(filepath.c_str());
376#endif
377
378 return result;
379}
380
381bool export_svg(const IOContext &context,
382 const ExportParams &params,
383 Scene &scene,
384 StringRefNull filepath)
385{
386 SVGExporter exporter(context, params);
387 return exporter.export_scene(scene, filepath);
388}
389
390} // namespace blender::io::grease_pencil
Low-level operations for curves.
Low-level operations for grease pencil.
General operations, lookup, etc. for materials.
#define BLI_assert(a)
Definition BLI_assert.h:50
void BLI_kdtree_nd_ free(KDTree *tree)
void linearrgb_to_srgb_v3_v3(float srgb[3], const float linear[3])
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:597
Object * DEG_get_evaluated_object(const Depsgraph *depsgraph, Object *object)
Object is a sort of wrapper for general info.
@ OB_GREASE_PENCIL
ChannelStorageType a
Definition BLI_color.hh:88
constexpr const char * c_str() const
GreasePencilExporter(const IOContext &context, const ExportParams &params)
void prepare_render_params(Scene &scene, int frame_number)
void foreach_stroke_in_layer(const Object &object, const bke::greasepencil::Layer &layer, const bke::greasepencil::Drawing &drawing, WriteStrokeFn stroke_fn)
float2 project_to_screen(const float4x4 &transform, const float3 &position) const
pugi::xml_node write_polygon(pugi::xml_node node, const float4x4 &transform, Span< float3 > positions)
void export_grease_pencil_layer(pugi::xml_node node, const Object &object, const bke::greasepencil::Layer &layer, const bke::greasepencil::Drawing &drawing)
void export_grease_pencil_objects(pugi::xml_node node, int frame_number)
bool export_scene(Scene &scene, StringRefNull filepath)
pugi::xml_node write_path(pugi::xml_node node, const float4x4 &transform, Span< float3 > positions, bool cyclic)
pugi::xml_node write_polyline(pugi::xml_node node, const float4x4 &transform, Span< float3 > positions, bool cyclic, std::optional< float > width)
local_group_size(16, 16) .push_constant(Type b
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static std::string rgb_to_hexstr(const float color[3])
static void write_fill_color_attribute(pugi::xml_node node, const ColorGeometry4f &fill_color, const float layer_opacity)
bool export_svg(const IOContext &context, const ExportParams &params, Scene &scene, StringRefNull filepath)
static void write_rect(pugi::xml_node node, const float x, const float y, const float width, const float height, const float thickness, const std::string &hexcolor)
static void write_stroke_color_attribute(pugi::xml_node node, const ColorGeometry4f &stroke_color, const float stroke_opacity, const bool round_cap)
constexpr const char * svg_exporter_version
unsigned char uint8_t
Definition stdint.h:78
char name[66]
Definition DNA_ID.h:425
void set_value(const SocketType &input, const Node &other, const SocketType &other_input)
wchar_t * alloc_utf16_from_8(const char *in8, size_t add)
Definition utfconv.cc:292