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