Blender V5.0
grease_pencil_io_import_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_main.hh"
7#include "BLI_assert.h"
8#include "BLI_bounds.hh"
9#include "BLI_color.hh"
10#include "BLI_math_color.h"
12#include "BLI_math_matrix.hh"
13#include "BLI_math_rotation.h"
14#include "BLI_math_vector.hh"
15#include "BLI_offset_indices.hh"
16#include "BLI_path_utils.hh"
17#include "BLI_string.h"
18
19#include "BKE_curves.hh"
20#include "BKE_grease_pencil.hh"
21#include "BKE_report.hh"
22
24#include "DNA_space_types.h"
26
27#include "ED_grease_pencil.hh"
28
30
31#include "nanosvg.h"
32
33#include <fmt/core.h>
34#include <fmt/format.h>
35
39
43
45
47 public:
49
50 bool read(StringRefNull filepath);
51};
52
53static std::string get_layer_id(const NSVGshape &shape, const int prefix)
54{
55 return (shape.id_parent[0] == '\0') ? fmt::format("Layer_{:03d}", prefix) :
56 fmt::format("{:s}", shape.id_parent);
57}
58
59/* Unpack internal NanoSVG color. */
61{
62 const uchar4 rgb_u = {uint8_t(((pack) >> 0) & 0xFF),
63 uint8_t(((pack) >> 8) & 0xFF),
64 uint8_t(((pack) >> 16) & 0xFF),
65 uint8_t(((pack) >> 24) & 0xFF)};
66 const float4 rgb_f = {float(rgb_u[0]) / 255.0f,
67 float(rgb_u[1]) / 255.0f,
68 float(rgb_u[2]) / 255.0f,
69 float(rgb_u[3]) / 255.0f};
70
73 return color;
74}
75
76/* Simple approximation of a gradient by a single color. */
77static ColorGeometry4f average_gradient_color(const NSVGgradient &svg_gradient)
78{
79 const Span<NSVGgradientStop> stops = {svg_gradient.stops, svg_gradient.nstops};
80
81 float4 avg_color = float4(0, 0, 0, 0);
82 if (stops.is_empty()) {
83 return ColorGeometry4f(avg_color);
84 }
85
86 for (const int i : stops.index_range()) {
87 avg_color += float4(unpack_nano_color(stops[i].color));
88 }
89 avg_color /= stops.size();
90
91 return ColorGeometry4f(avg_color);
92}
93
94/* TODO Gradients are not yet supported (will output magenta placeholder color).
95 * This is because gradients for fill materials in particular can only be defined by materials.
96 * Since each path can have a unique gradient it potentially requires a material per curve.
97 * Stroke gradients could be baked into vertex colors. */
98static ColorGeometry4f convert_svg_color(const NSVGpaint &svg_paint)
99{
100 switch (NSVGpaintType(svg_paint.type)) {
101 case NSVG_PAINT_UNDEF:
102 return ColorGeometry4f(1, 0, 1, 1);
103 case NSVG_PAINT_NONE:
104 return ColorGeometry4f(0, 0, 0, 1);
105 case NSVG_PAINT_COLOR:
106 return unpack_nano_color(svg_paint.color);
107 case NSVG_PAINT_LINEAR_GRADIENT:
108 return average_gradient_color(*svg_paint.gradient);
109 case NSVG_PAINT_RADIAL_GRADIENT:
110 return average_gradient_color(*svg_paint.gradient);
111
112 default:
114 return ColorGeometry4f(0, 0, 0, 0);
115 }
116}
117
118/* Make room for curves and points from the SVG shape.
119 * Returns the index range of newly added curves. */
120static IndexRange extend_curves_geometry(bke::CurvesGeometry &curves, const NSVGshape &shape)
121{
122 const int old_curves_num = curves.curves_num();
123 const int old_points_num = curves.points_num();
124 const Span<int> old_offsets = curves.offsets();
125
126 /* Count curves and points. */
127 Vector<int> new_curve_offsets;
128 for (NSVGpath *path = shape.paths; path; path = path->next) {
129 if (path->npts == 0) {
130 continue;
131 }
132 BLI_assert(path->npts >= 1 && path->npts == int(path->npts / 3) * 3 + 1);
133 /* nanosvg converts everything to bezier curves, points come in triplets. Round up to the
134 * next full integer, since there is one point without handles (3*n+1 points in total). */
135 const int point_num = (path->npts + 2) / 3;
136 new_curve_offsets.append(point_num);
137 }
138 if (new_curve_offsets.is_empty()) {
139 return {};
140 }
141 new_curve_offsets.append(0);
143 new_curve_offsets, old_points_num);
144
145 const IndexRange new_curves_range = {old_curves_num, new_points_by_curve.size()};
146 const int curves_num = new_curves_range.one_after_last();
147 const int points_num = new_points_by_curve.total_size() + old_points_num;
148
149 Array<int> new_offsets(curves_num + 1);
150 if (old_curves_num > 0) {
151 new_offsets.as_mutable_span().slice(0, old_curves_num).copy_from(old_offsets.drop_back(1));
152 }
153 new_offsets.as_mutable_span()
154 .slice(old_curves_num, new_curve_offsets.size())
155 .copy_from(new_curve_offsets);
156
157 curves.resize(points_num, curves_num);
158 curves.offsets_for_write().copy_from(new_offsets);
159
160 curves.tag_topology_changed();
161
162 return new_curves_range;
163}
164
166 const NSVGshape &shape,
167 const IndexRange curves_range,
168 const float4x4 &transform,
169 const int material_index)
170{
171 /* Path width is twice the radius. */
172 const float path_width_scale = 0.5f * math::average(math::to_scale(transform));
173 const OffsetIndices points_by_curve = curves.points_by_curve();
174
175 /* nanosvg converts everything to Bezier curves. */
176 curves.curve_types_for_write().slice(curves_range).fill(CURVE_TYPE_BEZIER);
177 curves.update_curve_types();
178
181 "material_index", bke::AttrDomain::Curve);
182 MutableSpan<bool> cyclic = curves.cyclic_for_write();
183
185 "fill_color", bke::AttrDomain::Curve);
186 bke::SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
187 "fill_opacity", bke::AttrDomain::Curve);
188
189 MutableSpan<float3> positions = curves.positions_for_write();
190 MutableSpan<float3> handle_positions_left = curves.handle_positions_left_for_write();
191 MutableSpan<float3> handle_positions_right = curves.handle_positions_right_for_write();
192 MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
193 MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
195 "radius", bke::AttrDomain::Point);
197 attributes.lookup_or_add_for_write_span<ColorGeometry4f>("vertex_color",
199 bke::SpanAttributeWriter<float> point_opacities = attributes.lookup_or_add_for_write_span<float>(
200 "opacity", bke::AttrDomain::Point);
201
202 materials.span.slice(curves_range).fill(material_index);
203 const ColorGeometry4f shape_color = convert_svg_color(shape.fill);
204 if (fill_colors) {
205 fill_colors.span.slice(curves_range).fill(shape_color);
206 }
207 if (fill_opacities) {
208 fill_opacities.span.slice(curves_range).fill(shape_color.a);
209 }
210
211 int curve_index = curves_range.start();
212 for (NSVGpath *path = shape.paths; path; path = path->next) {
213 if (path->npts == 0) {
214 continue;
215 }
216
217 cyclic[curve_index] = bool(path->closed);
218
219 /* 2D vectors in triplets: [control point, left handle, right handle]. */
220 const Span<float2> svg_path_data = Span<float>(path->pts, 2 * path->npts).cast<float2>();
221
222 const IndexRange points = points_by_curve[curve_index];
223 for (const int i : points.index_range()) {
224 const int point_index = points[i];
225 const float2 pos_center = svg_path_data[i * 3];
226 const float2 pos_handle_left = (i > 0) ? svg_path_data[i * 3 - 1] : pos_center;
227 const float2 pos_handle_right = (i < points.size() - 1) ? svg_path_data[i * 3 + 1] :
228 pos_center;
229 positions[point_index] = math::transform_point(transform, float3(pos_center, 0.0f));
230 handle_positions_left[point_index] = math::transform_point(transform,
231 float3(pos_handle_left, 0.0f));
232 handle_positions_right[point_index] = math::transform_point(transform,
233 float3(pos_handle_right, 0.0f));
234 handle_types_left[point_index] = BEZIER_HANDLE_FREE;
235 handle_types_right[point_index] = BEZIER_HANDLE_FREE;
236
237 radii.span[point_index] = shape.strokeWidth * path_width_scale;
238
239 const ColorGeometry4f point_color = convert_svg_color(shape.stroke);
240 if (vertex_colors) {
241 vertex_colors.span[point_index] = point_color;
242 }
243 if (point_opacities) {
244 point_opacities.span[point_index] = point_color.a;
245 }
246 }
247
248 ++curve_index;
249 }
250
251 materials.finish();
252 fill_colors.finish();
253 fill_opacities.finish();
254 radii.finish();
255 vertex_colors.finish();
256 point_opacities.finish();
257 curves.tag_positions_changed();
258 curves.tag_radii_changed();
259}
260
262{
263 const std::optional<Bounds<float3>> bounds = [&]() {
264 std::optional<Bounds<float3>> bounds;
265 for (GreasePencilDrawingBase *drawing_base : grease_pencil.drawings()) {
266 if (drawing_base->type != GP_DRAWING) {
267 continue;
268 }
269 Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
271 }
272 return bounds;
273 }();
274 if (!bounds) {
275 return;
276 }
277 const float3 offset = -bounds->center();
278
279 for (GreasePencilDrawingBase *drawing_base : grease_pencil.drawings()) {
280 if (drawing_base->type != GP_DRAWING) {
281 continue;
282 }
283 Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
284 drawing.strokes_for_write().translate(offset);
285 drawing.tag_positions_changed();
286 }
287}
288
290{
291 /* Fixed SVG unit for scaling. */
292 constexpr const char *svg_units = "mm";
293 constexpr float svg_dpi = 96.0f;
294
295 char abs_filepath[FILE_MAX];
296 STRNCPY(abs_filepath, filepath.c_str());
298
299 NSVGimage *svg_data = nullptr;
300 svg_data = nsvgParseFromFile(abs_filepath, svg_units, svg_dpi);
301 if (svg_data == nullptr) {
302 BKE_report(context_.reports, RPT_ERROR, "Could not open SVG");
303 return false;
304 }
305
306 /* Create grease pencil object. */
307 char filename[FILE_MAX];
308 BLI_path_split_file_part(abs_filepath, filename, ARRAY_SIZE(filename));
309 object_ = create_object(filename);
310 if (object_ == nullptr) {
311 BKE_report(context_.reports, RPT_ERROR, "Unable to create new object");
312 nsvgDelete(svg_data);
313 return false;
314 }
315 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object_->data);
316
317 const float scene_unit_scale = (context_.scene->unit.system != USER_UNIT_NONE &&
318 params_.use_scene_unit) ?
319 context_.scene->unit.scale_length :
320 1.0f;
321 /* Overall scale for SVG coordinates in millimeters. */
322 const float svg_scale = 0.001f * scene_unit_scale * params_.scale;
323 /* Grease pencil is rotated 90 degrees in X axis by default. */
326
327 /* True if any shape has a color gradient, which are not fully supported. */
328 bool has_color_gradient = false;
329
330 /* Loop all shapes. */
331 std::string prv_id = "*";
332 int prefix = 0;
333 for (NSVGshape *shape = svg_data->shapes; shape; shape = shape->next) {
334 std::string layer_id = get_layer_id(*shape, prefix);
335 if (prv_id != layer_id) {
336 prefix++;
337 layer_id = get_layer_id(*shape, prefix);
338 prv_id = layer_id;
339 }
340
341 /* Check if the layer exist and create if needed. */
342 Layer &layer = [&]() -> Layer & {
343 TreeNode *layer_node = grease_pencil.find_node_by_name(layer_id);
344 if (layer_node && layer_node->is_layer()) {
345 return layer_node->as_layer();
346 }
347
348 Layer &layer = grease_pencil.add_layer(layer_id);
350 return layer;
351 }();
352
353 /* Check frame. */
354 Drawing *drawing = grease_pencil.get_drawing_at(layer, params_.frame_number);
355 if (drawing == nullptr) {
356 drawing = grease_pencil.insert_frame(layer, params_.frame_number);
357 if (!drawing) {
358 continue;
359 }
360 }
361
362 /* Create materials. */
363 const bool is_fill = bool(shape->fill.type);
364 const bool is_stroke = bool(shape->stroke.type) || !is_fill;
365 const StringRefNull mat_name = (is_stroke ? (is_fill ? "Both" : "Stroke") : "Fill");
366 const int material_index = create_material(mat_name, is_stroke, is_fill);
367
368 if (ELEM(shape->fill.type, NSVG_PAINT_LINEAR_GRADIENT, NSVG_PAINT_RADIAL_GRADIENT)) {
369 has_color_gradient = true;
370 }
371
372 bke::CurvesGeometry &curves = drawing->strokes_for_write();
373 const IndexRange new_curves_range = extend_curves_geometry(curves, *shape);
374 if (new_curves_range.is_empty()) {
375 continue;
376 }
377
378 shape_attributes_to_curves(curves, *shape, new_curves_range, transform, material_index);
379 drawing->strokes_for_write() = std::move(curves);
380 }
381
382 /* Free SVG memory. */
383 nsvgDelete(svg_data);
384
385 /* Calculate bounding box and move all points to new origin center. */
386 if (params_.recenter_bounds) {
388 }
389
390 if (has_color_gradient) {
391 BKE_report(context_.reports,
393 "SVG has gradients, Grease Pencil color will be approximated");
394 }
395
396 return true;
397}
398
399bool import_svg(const IOContext &context, const ImportParams &params, StringRefNull filepath)
400{
401 SVGImporter importer(context, params);
402 return importer.read(filepath);
403}
404
405} // namespace blender::io::grease_pencil
Low-level operations for curves.
Low-level operations for grease pencil.
const char * BKE_main_blendfile_path_from_global()
Definition main.cc:892
@ RPT_ERROR
Definition BKE_report.hh:39
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE void srgb_to_linearrgb_v4(float linear[4], const float srgb[4])
#define DEG2RAD(_deg)
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1
#define FILE_MAX
void void void BLI_path_split_file_part(const char *filepath, char *file, size_t file_maxncpy) ATTR_NONNULL(1
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
unsigned int uint
#define ARRAY_SIZE(arr)
#define ELEM(...)
@ BEZIER_HANDLE_FREE
@ GP_LAYER_TREE_NODE_USE_LIGHTS
@ USER_UNIT_NONE
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
ChannelStorageType a
constexpr int64_t one_after_last() const
constexpr bool is_empty() const
constexpr int64_t start() const
Span< NewT > constexpr cast() const
Definition BLI_span.hh:418
constexpr Span drop_back(int64_t n) const
Definition BLI_span.hh:182
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
constexpr const char * c_str() const
int64_t size() const
void append(const T &value)
bool is_empty() const
MutableSpan< float3 > positions_for_write()
void translate(const float3 &translation)
OffsetIndices< int > points_by_curve() const
MutableSpan< int8_t > handle_types_right_for_write()
MutableSpan< int8_t > curve_types_for_write()
MutableSpan< float3 > handle_positions_left_for_write()
MutableAttributeAccessor attributes_for_write()
MutableSpan< float3 > handle_positions_right_for_write()
void resize(int points_num, int curves_num)
MutableSpan< int > offsets_for_write()
std::optional< Bounds< float3 > > bounds_min_max(bool use_radius=true) const
MutableSpan< bool > cyclic_for_write()
MutableSpan< int8_t > handle_types_left_for_write()
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
bke::CurvesGeometry & strokes_for_write()
const bke::CurvesGeometry & strokes() const
GreasePencilImporter(const IOContext &context, const ImportParams &params)
int32_t create_material(StringRefNull name, bool stroke, bool fill)
GreasePencilImporter(const IOContext &context, const ImportParams &params)
nullptr float
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
Bounds< T > merge(const Bounds< T > &a, const Bounds< T > &b)
Definition BLI_bounds.hh:26
static void shape_attributes_to_curves(bke::CurvesGeometry &curves, const NSVGshape &shape, const IndexRange curves_range, const float4x4 &transform, const int material_index)
static void shift_to_bounds_center(GreasePencil &grease_pencil)
static ColorGeometry4f average_gradient_color(const NSVGgradient &svg_gradient)
bool import_svg(const IOContext &context, const ImportParams &params, StringRefNull filepath)
static ColorGeometry4f unpack_nano_color(const uint pack)
static ColorGeometry4f convert_svg_color(const NSVGpaint &svg_paint)
static IndexRange extend_curves_geometry(bke::CurvesGeometry &curves, const NSVGshape &shape)
static std::string get_layer_id(const NSVGshape &shape, const int prefix)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
EulerXYZBase< float > EulerXYZ
T average(const VecBase< T, Size > &a)
VecBase< T, 3 > to_scale(const MatBase< T, NumCol, NumRow > &mat)
MatT from_rotation(const RotationT &rotation)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
MatBase< float, 4, 4 > float4x4
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 4 > float4
VecBase< float, 2 > float2
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
VecBase< float, 3 > float3
float wrap(float value, float max, float min)
Definition node_math.h:103
i
Definition text_draw.cc:230