Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2021-03-25 00:46:39 +0300
committerHans Goudey <h.goudey@me.com>2021-03-25 00:46:39 +0300
commit4ea3d249301003e5d823bddce2962c81274556d8 (patch)
treef11e69df04d82d6e7477c38447d425205bf1f755
parent2495c0c53929d0480cfb6ae81502d79636a656b8 (diff)
parent9ad3d1d36b64f336fabdd9b3d9f02b13740068b0 (diff)
Merge branch 'master' into temp-geometry-nodes-processor-prototype
-rw-r--r--intern/clog/clog.c1
-rw-r--r--intern/cycles/render/alembic.cpp77
-rw-r--r--intern/cycles/render/alembic.h69
-rw-r--r--intern/cycles/render/geometry.cpp10
-rw-r--r--intern/cycles/render/geometry.h2
-rw-r--r--intern/cycles/render/object.cpp15
-rw-r--r--intern/cycles/render/object.h1
-rw-r--r--intern/guardedalloc/MEM_guardedalloc.h2
-rw-r--r--intern/guardedalloc/intern/leak_detector.cc1
-rw-r--r--intern/guardedalloc/intern/mallocn_guarded_impl.c1
-rw-r--r--intern/guardedalloc/intern/mallocn_lockfree_impl.c1
-rw-r--r--release/scripts/startup/bl_ui/space_topbar.py9
-rw-r--r--source/blender/blenkernel/BKE_blender_version.h2
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h16
-rw-r--r--source/blender/blenkernel/intern/appdir.c4
-rw-r--r--source/blender/blenkernel/intern/armature.c4
-rw-r--r--source/blender/blenkernel/intern/colortools.c4
-rw-r--r--source/blender/blenkernel/intern/fluid.c2
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.c553
-rw-r--r--source/blender/blenkernel/intern/keyconfig.c1
-rw-r--r--source/blender/blenkernel/intern/main.c2
-rw-r--r--source/blender/blenkernel/intern/mesh_wrapper.c14
-rw-r--r--source/blender/blenkernel/intern/object.c4
-rw-r--r--source/blender/blenkernel/intern/paint.c2
-rw-r--r--source/blender/blenkernel/intern/text_suggestions.c1
-rw-r--r--source/blender/blenkernel/intern/undo_system.c1
-rw-r--r--source/blender/blenkernel/intern/workspace.c1
-rw-r--r--source/blender/blenlib/intern/BLI_args.c1
-rw-r--r--source/blender/blenlib/intern/BLI_dynstr.c1
-rw-r--r--source/blender/blenlib/intern/edgehash.c1
-rw-r--r--source/blender/blenlib/intern/string.c1
-rw-r--r--source/blender/blenlib/intern/string_search.cc6
-rw-r--r--source/blender/blenloader/intern/versioning_290.c27
-rw-r--r--source/blender/compositor/intern/COM_CPUDevice.h2
-rw-r--r--source/blender/compositor/intern/COM_Device.h15
-rw-r--r--source/blender/compositor/intern/COM_ExecutionGroup.cc13
-rw-r--r--source/blender/compositor/intern/COM_ExecutionGroup.h2
-rw-r--r--source/blender/compositor/intern/COM_ExecutionSystem.cc110
-rw-r--r--source/blender/compositor/intern/COM_ExecutionSystem.h6
-rw-r--r--source/blender/compositor/intern/COM_MemoryProxy.h10
-rw-r--r--source/blender/compositor/intern/COM_OpenCLDevice.cc6
-rw-r--r--source/blender/compositor/intern/COM_OpenCLDevice.h17
-rw-r--r--source/blender/compositor/intern/COM_WorkScheduler.cc74
-rw-r--r--source/blender/editors/curve/editcurve_select.c4
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c15
-rw-r--r--source/blender/editors/include/ED_gpencil.h1
-rw-r--r--source/blender/editors/interface/interface.c33
-rw-r--r--source/blender/editors/interface/interface_eyedropper.c2
-rw-r--r--source/blender/editors/interface/interface_handlers.c2
-rw-r--r--source/blender/editors/interface/interface_intern.h6
-rw-r--r--source/blender/editors/interface/interface_query.c7
-rw-r--r--source/blender/editors/interface/interface_style.c1
-rw-r--r--source/blender/editors/interface/interface_template_search_menu.c1
-rw-r--r--source/blender/editors/io/CMakeLists.txt15
-rw-r--r--source/blender/editors/io/io_gpencil.h45
-rw-r--r--source/blender/editors/io/io_gpencil_export.c430
-rw-r--r--source/blender/editors/io/io_gpencil_import.c195
-rw-r--r--source/blender/editors/io/io_gpencil_utils.c64
-rw-r--r--source/blender/editors/io/io_ops.c11
-rw-r--r--source/blender/editors/object/object_add.c36
-rw-r--r--source/blender/editors/space_file/filelist.c2
-rw-r--r--source/blender/editors/space_node/drawnode.c4
-rw-r--r--source/blender/editors/space_node/node_add.c15
-rw-r--r--source/blender/editors/space_outliner/tree/tree_display_view_layer.cc8
-rw-r--r--source/blender/editors/util/ed_util.c2
-rw-r--r--source/blender/editors/util/select_utils.c4
-rw-r--r--source/blender/editors/uvedit/uvedit_rip.c4
-rw-r--r--source/blender/editors/uvedit/uvedit_select.c2
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c19
-rw-r--r--source/blender/gpu/intern/gpu_material_library.c1
-rw-r--r--source/blender/gpu/intern/gpu_node_graph.c1
-rw-r--r--source/blender/imbuf/intern/thumbs.c4
-rw-r--r--source/blender/imbuf/intern/thumbs_blend.c1
-rw-r--r--source/blender/io/CMakeLists.txt2
-rw-r--r--source/blender/io/collada/Materials.cpp4
-rw-r--r--source/blender/io/gpencil/CMakeLists.txt99
-rw-r--r--source/blender/io/gpencil/gpencil_io.h92
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_base.cc386
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_base.h116
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_capi.cc204
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_base.h38
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc311
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_pdf.h67
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_svg.cc464
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_svg.h89
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_base.cc83
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_base.h41
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_svg.cc253
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_svg.h56
-rw-r--r--source/blender/io/gpencil/nanosvg/nanosvg.h3313
-rw-r--r--source/blender/makesdna/DNA_ID.h74
-rw-r--r--source/blender/makesrna/intern/rna_object.c1
-rw-r--r--source/blender/modifiers/intern/MOD_subsurf.c1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc4
-rw-r--r--source/blender/python/bmesh/bmesh_py_types.c2
-rw-r--r--source/blender/python/bmesh/bmesh_py_types_customdata.c4
-rw-r--r--source/blender/python/generic/imbuf_py_api.c2
-rw-r--r--source/blender/python/gpu/gpu_py_matrix.c4
-rw-r--r--source/blender/python/gpu/gpu_py_vertex_buffer.c3
-rw-r--r--source/blender/python/intern/CMakeLists.txt8
-rw-r--r--source/blender/python/intern/bpy_app_build_options.c14
-rw-r--r--source/blender/python/intern/bpy_rna.c6
-rw-r--r--source/blender/python/mathutils/mathutils_Matrix.c5
-rw-r--r--source/blender/sequencer/intern/sequencer.c2
-rw-r--r--source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c8
-rw-r--r--source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c2
-rw-r--r--source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c2
-rw-r--r--source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c2
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.c6
-rw-r--r--source/blender/windowmanager/intern/wm_files.c2
-rw-r--r--source/blender/windowmanager/intern/wm_files_link.c2
-rw-r--r--source/blender/windowmanager/intern/wm_menu_type.c2
-rw-r--r--source/blender/windowmanager/intern/wm_operators.c6
-rw-r--r--source/blender/windowmanager/intern/wm_uilist_type.c2
-rw-r--r--source/blender/windowmanager/intern/wm_window.c2
118 files changed, 7448 insertions, 375 deletions
diff --git a/intern/clog/clog.c b/intern/clog/clog.c
index 391b71d77de..01d1c0a1770 100644
--- a/intern/clog/clog.c
+++ b/intern/clog/clog.c
@@ -21,6 +21,7 @@
#include <assert.h>
#include <stdarg.h>
#include <stdint.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/intern/cycles/render/alembic.cpp b/intern/cycles/render/alembic.cpp
index e4f0690c401..1336f81896a 100644
--- a/intern/cycles/render/alembic.cpp
+++ b/intern/cycles/render/alembic.cpp
@@ -396,6 +396,10 @@ static void add_uvs(AlembicProcedural *proc,
ccl::set<chrono_t> times = get_relevant_sample_times(proc, time_sampling, uvs.getNumSamples());
+ /* Keys used to determine if the UVs do actually change over time. */
+ ArraySample::Key previous_indices_key;
+ ArraySample::Key previous_values_key;
+
foreach (chrono_t time, times) {
if (progress.get_cancel()) {
return;
@@ -422,21 +426,32 @@ static void add_uvs(AlembicProcedural *proc,
float2 *data_float2 = reinterpret_cast<float2 *>(data.data());
- const unsigned int *indices = uvsample.getIndices()->get();
- const V2f *values = uvsample.getVals()->get();
+ const ArraySample::Key indices_key = uvsample.getIndices()->getKey();
+ const ArraySample::Key values_key = uvsample.getVals()->getKey();
- for (const int3 &loop : *triangles_loops) {
- unsigned int v0 = indices[loop.x];
- unsigned int v1 = indices[loop.y];
- unsigned int v2 = indices[loop.z];
+ if (indices_key == previous_indices_key && values_key == previous_values_key) {
+ attr.data.reuse_data_for_last_time(time);
+ }
+ else {
+ const unsigned int *indices = uvsample.getIndices()->get();
+ const V2f *values = uvsample.getVals()->get();
+
+ for (const int3 &loop : *triangles_loops) {
+ unsigned int v0 = indices[loop.x];
+ unsigned int v1 = indices[loop.y];
+ unsigned int v2 = indices[loop.z];
+
+ data_float2[0] = make_float2(values[v0][0], values[v0][1]);
+ data_float2[1] = make_float2(values[v1][0], values[v1][1]);
+ data_float2[2] = make_float2(values[v2][0], values[v2][1]);
+ data_float2 += 3;
+ }
- data_float2[0] = make_float2(values[v0][0], values[v0][1]);
- data_float2[1] = make_float2(values[v1][0], values[v1][1]);
- data_float2[2] = make_float2(values[v2][0], values[v2][1]);
- data_float2 += 3;
+ attr.data.add_data(data, time);
}
- attr.data.add_data(data, time);
+ previous_indices_key = indices_key;
+ previous_values_key = values_key;
}
}
@@ -736,6 +751,11 @@ void AlembicObject::load_all_data(AlembicProcedural *proc,
ccl::set<chrono_t> times = get_relevant_sample_times(
proc, *time_sampling, schema.getNumSamples());
+ /* Key used to determine if the triangles change over time, if the key is the same as the
+ * last one, we can avoid creating a new entry in the cache and simply point to the last
+ * frame. */
+ ArraySample::Key previous_key;
+
/* read topology */
foreach (chrono_t time, times) {
if (progress.get_cancel()) {
@@ -747,22 +767,27 @@ void AlembicObject::load_all_data(AlembicProcedural *proc,
add_positions(sample.getPositions(), time, cached_data);
- /* Only copy triangles for other frames if the topology is changing over time as well.
- *
- * TODO(@kevindietrich): even for dynamic simulations, this is a waste of memory and
- * processing time if only the positions are changing in a subsequence of frames but we
- * cannot optimize in this current system if the attributes are changing over time as well,
- * as we need valid data for each time point. This can be solved by using reference counting
- * on the ccl::array and simply share the array across frames. */
+ /* Only copy triangles for other frames if the topology is changing over time as well. */
if (schema.getTopologyVariance() != kHomogenousTopology || cached_data.triangles.size() == 0) {
- /* start by reading the face sets (per face shader), as we directly split polygons to
- * triangles
- */
- array<int> polygon_to_shader;
- read_face_sets(schema, polygon_to_shader, iss);
-
- add_triangles(
- sample.getFaceCounts(), sample.getFaceIndices(), time, cached_data, polygon_to_shader);
+ const ArraySample::Key key = sample.getFaceIndices()->getKey();
+
+ if (key == previous_key) {
+ cached_data.triangles.reuse_data_for_last_time(time);
+ cached_data.triangles_loops.reuse_data_for_last_time(time);
+ cached_data.shader.reuse_data_for_last_time(time);
+ }
+ else {
+ /* start by reading the face sets (per face shader), as we directly split polygons to
+ * triangles
+ */
+ array<int> polygon_to_shader;
+ read_face_sets(schema, polygon_to_shader, iss);
+
+ add_triangles(
+ sample.getFaceCounts(), sample.getFaceIndices(), time, cached_data, polygon_to_shader);
+ }
+
+ previous_key = key;
}
if (normals.valid()) {
diff --git a/intern/cycles/render/alembic.h b/intern/cycles/render/alembic.h
index 0203475571e..d0c5856a353 100644
--- a/intern/cycles/render/alembic.h
+++ b/intern/cycles/render/alembic.h
@@ -128,12 +128,25 @@ template<typename T> class CacheLookupResult {
* The data is supposed to be stored in chronological order, and is looked up using the current
* animation time in seconds using the TimeSampling from the Alembic property. */
template<typename T> class DataStore {
- struct DataTimePair {
+ /* Holds information to map a cache entry for a given time to an index into the data array. */
+ struct TimeIndexPair {
+ /* Frame time for this entry. */
double time = 0;
- T data{};
+ /* Frame time for the data pointed to by `index`. */
+ double source_time = 0;
+ /* Index into the data array. */
+ size_t index = 0;
};
- vector<DataTimePair> data{};
+ /* This is the actual data that is stored. We deduplicate data across frames to avoid storing
+ * values if they have not changed yet (e.g. the triangles for a building before fracturing, or a
+ * fluid simulation before a break or splash) */
+ vector<T> data{};
+
+ /* This is used to map they entry for a given time to an index into the data array, multiple
+ * frames can point to the same index. */
+ vector<TimeIndexPair> index_data_map{};
+
Alembic::AbcCoreAbstract::TimeSampling time_sampling{};
double last_loaded_time = std::numeric_limits<double>::max();
@@ -157,17 +170,21 @@ template<typename T> class DataStore {
return CacheLookupResult<T>::no_data_found_for_time();
}
- std::pair<size_t, Alembic::Abc::chrono_t> index_pair;
- index_pair = time_sampling.getNearIndex(time, data.size());
- DataTimePair &data_pair = data[index_pair.first];
+ const TimeIndexPair &index = get_index_for_time(time);
+
+ if (index.index == -1ul) {
+ return CacheLookupResult<T>::no_data_found_for_time();
+ }
- if (last_loaded_time == data_pair.time) {
+ if (last_loaded_time == index.time || last_loaded_time == index.source_time) {
return CacheLookupResult<T>::already_loaded();
}
- last_loaded_time = data_pair.time;
+ last_loaded_time = index.source_time;
- return CacheLookupResult<T>::new_data(&data_pair.data);
+ assert(index.index < data.size());
+
+ return CacheLookupResult<T>::new_data(&data[index.index]);
}
/* get the data for the specified time, but do not check if the data was already loaded for this
@@ -178,22 +195,34 @@ template<typename T> class DataStore {
return CacheLookupResult<T>::no_data_found_for_time();
}
- std::pair<size_t, Alembic::Abc::chrono_t> index_pair;
- index_pair = time_sampling.getNearIndex(time, data.size());
- DataTimePair &data_pair = data[index_pair.first];
- return CacheLookupResult<T>::new_data(&data_pair.data);
+ const TimeIndexPair &index = get_index_for_time(time);
+
+ if (index.index == -1ul) {
+ return CacheLookupResult<T>::no_data_found_for_time();
+ }
+
+ assert(index.index < data.size());
+
+ return CacheLookupResult<T>::new_data(&data[index.index]);
}
void add_data(T &data_, double time)
{
+ index_data_map.push_back({time, time, data.size()});
+
if constexpr (is_array<T>::value) {
data.emplace_back();
- data.back().data.steal_data(data_);
- data.back().time = time;
+ data.back().steal_data(data_);
return;
}
- data.push_back({time, data_});
+ data.push_back(data_);
+ }
+
+ void reuse_data_for_last_time(double time)
+ {
+ const TimeIndexPair &data_index = index_data_map.back();
+ index_data_map.push_back({time, data_index.source_time, data_index.index});
}
bool is_constant() const
@@ -232,6 +261,14 @@ template<typename T> class DataStore {
T value = result.get_data();
node->set(*socket, value);
}
+
+ private:
+ const TimeIndexPair &get_index_for_time(double time) const
+ {
+ std::pair<size_t, Alembic::Abc::chrono_t> index_pair;
+ index_pair = time_sampling.getNearIndex(time, index_data_map.size());
+ return index_data_map[index_pair.first];
+ }
};
/* Actual cache for the stored data.
diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp
index e9a70e4b3fd..124a41db21e 100644
--- a/intern/cycles/render/geometry.cpp
+++ b/intern/cycles/render/geometry.cpp
@@ -1917,9 +1917,12 @@ void GeometryManager::device_update(Device *device,
}
}
- /* update the bvh even when there is no geometry so the kernel bvh data is still valid,
- * especially when removing all of the objects during interactive renders */
- bool need_update_scene_bvh = (scene->bvh == nullptr);
+ /* Update the BVH even when there is no geometry so the kernel's BVH data is still valid,
+ * especially when removing all of the objects during interactive renders.
+ * Also update the BVH if the transformations change, we cannot rely on tagging the Geometry
+ * as modified in this case, as we may accumulate displacement if the vertices do not also
+ * change. */
+ bool need_update_scene_bvh = (scene->bvh == nullptr || (update_flags & TRANSFORM_MODIFIED) != 0);
{
scoped_callback_timer timer([scene](double time) {
if (scene->update_stats) {
@@ -1961,7 +1964,6 @@ void GeometryManager::device_update(Device *device,
scene->update_stats->geometry.times.add_entry({"device_update (compute bounds)", time});
}
});
- vector<Object *> volume_objects;
foreach (Object *object, scene->objects) {
object->compute_bounds(motion_blur);
}
diff --git a/intern/cycles/render/geometry.h b/intern/cycles/render/geometry.h
index fe30f3a807c..abdd851a089 100644
--- a/intern/cycles/render/geometry.h
+++ b/intern/cycles/render/geometry.h
@@ -189,6 +189,8 @@ class GeometryManager {
GEOMETRY_ADDED = MESH_ADDED | HAIR_ADDED,
GEOMETRY_REMOVED = MESH_REMOVED | HAIR_REMOVED,
+ TRANSFORM_MODIFIED = (1 << 10),
+
/* tag everything in the manager for an update */
UPDATE_ALL = ~0u,
diff --git a/intern/cycles/render/object.cpp b/intern/cycles/render/object.cpp
index e71d7d4a3eb..52f63685aeb 100644
--- a/intern/cycles/render/object.cpp
+++ b/intern/cycles/render/object.cpp
@@ -221,16 +221,7 @@ void Object::tag_update(Scene *scene)
if (geometry) {
if (tfm_is_modified()) {
- /* tag the geometry as modified so the BVH is updated, but do not tag everything as modified
- */
- if (geometry->is_mesh() || geometry->is_volume()) {
- Mesh *mesh = static_cast<Mesh *>(geometry);
- mesh->tag_verts_modified();
- }
- else if (geometry->is_hair()) {
- Hair *hair = static_cast<Hair *>(geometry);
- hair->tag_curve_keys_modified();
- }
+ flag |= ObjectManager::TRANSFORM_MODIFIED;
}
foreach (Node *node, geometry->get_used_shaders()) {
@@ -923,6 +914,10 @@ void ObjectManager::tag_update(Scene *scene, uint32_t flag)
geometry_flag |= (GeometryManager::GEOMETRY_ADDED | GeometryManager::GEOMETRY_REMOVED);
}
+ if ((flag & TRANSFORM_MODIFIED) != 0) {
+ geometry_flag |= GeometryManager::TRANSFORM_MODIFIED;
+ }
+
scene->geometry_manager->tag_update(scene, geometry_flag);
}
diff --git a/intern/cycles/render/object.h b/intern/cycles/render/object.h
index cf1b9ca510a..23682270fd1 100644
--- a/intern/cycles/render/object.h
+++ b/intern/cycles/render/object.h
@@ -133,6 +133,7 @@ class ObjectManager {
OBJECT_REMOVED = (1 << 4),
OBJECT_MODIFIED = (1 << 5),
HOLDOUT_MODIFIED = (1 << 6),
+ TRANSFORM_MODIFIED = (1 << 7),
/* tag everything in the manager for an update */
UPDATE_ALL = ~0u,
diff --git a/intern/guardedalloc/MEM_guardedalloc.h b/intern/guardedalloc/MEM_guardedalloc.h
index df5593ba484..3d51c04f929 100644
--- a/intern/guardedalloc/MEM_guardedalloc.h
+++ b/intern/guardedalloc/MEM_guardedalloc.h
@@ -49,8 +49,6 @@
#ifndef __MEM_GUARDEDALLOC_H__
#define __MEM_GUARDEDALLOC_H__
-#include <stdio.h> /* needed for FILE* */
-
/* Needed for uintptr_t and attributes, exception, don't use BLI anywhere else in `MEM_*` */
#include "../../source/blender/blenlib/BLI_compiler_attrs.h"
#include "../../source/blender/blenlib/BLI_sys_types.h"
diff --git a/intern/guardedalloc/intern/leak_detector.cc b/intern/guardedalloc/intern/leak_detector.cc
index fb8d4e72cac..03d54f2e776 100644
--- a/intern/guardedalloc/intern/leak_detector.cc
+++ b/intern/guardedalloc/intern/leak_detector.cc
@@ -18,6 +18,7 @@
* \ingroup MEM
*/
+#include <cstdio> /* Needed for `printf` on WIN32/APPLE. */
#include <cstdlib>
#include "MEM_guardedalloc.h"
diff --git a/intern/guardedalloc/intern/mallocn_guarded_impl.c b/intern/guardedalloc/intern/mallocn_guarded_impl.c
index 6a0d104d47d..a7c3dc0951e 100644
--- a/intern/guardedalloc/intern/mallocn_guarded_impl.c
+++ b/intern/guardedalloc/intern/mallocn_guarded_impl.c
@@ -25,6 +25,7 @@
#include <stdarg.h>
#include <stddef.h> /* offsetof */
+#include <stdio.h> /* printf */
#include <stdlib.h>
#include <string.h> /* memcpy */
#include <sys/types.h>
diff --git a/intern/guardedalloc/intern/mallocn_lockfree_impl.c b/intern/guardedalloc/intern/mallocn_lockfree_impl.c
index 8f5c9cf85a9..a843086a1f1 100644
--- a/intern/guardedalloc/intern/mallocn_lockfree_impl.c
+++ b/intern/guardedalloc/intern/mallocn_lockfree_impl.c
@@ -21,6 +21,7 @@
*/
#include <stdarg.h>
+#include <stdio.h> /* printf */
#include <stdlib.h>
#include <string.h> /* memcpy */
#include <sys/types.h>
diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index 7219922c379..adab0b0c88a 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -469,6 +469,8 @@ class TOPBAR_MT_file_import(Menu):
if bpy.app.build_options.alembic:
self.layout.operator("wm.alembic_import", text="Alembic (.abc)")
+ self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")
+
class TOPBAR_MT_file_export(Menu):
bl_idname = "TOPBAR_MT_file_export"
@@ -485,6 +487,13 @@ class TOPBAR_MT_file_export(Menu):
self.layout.operator(
"wm.usd_export", text="Universal Scene Description (.usd, .usdc, .usda)")
+ # Pugixml lib dependency
+ if bpy.app.build_options.pugixml:
+ self.layout.operator("wm.gpencil_export_svg", text="Grease Pencil as SVG")
+ # Haru lib dependency
+ if bpy.app.build_options.haru:
+ self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF")
+
class TOPBAR_MT_file_external_data(Menu):
bl_label = "External Data"
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h
index eb5f910f555..c9585430ae2 100644
--- a/source/blender/blenkernel/BKE_blender_version.h
+++ b/source/blender/blenkernel/BKE_blender_version.h
@@ -39,7 +39,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
-#define BLENDER_FILE_SUBVERSION 13
+#define BLENDER_FILE_SUBVERSION 14
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file
diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index 89a794f2df3..a9bd0a524c4 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -27,10 +27,10 @@
extern "C" {
#endif
-struct BoundBox;
struct Depsgraph;
struct Main;
struct Object;
+struct RegionView3D;
struct Scene;
struct bGPDcurve;
struct bGPDframe;
@@ -173,6 +173,20 @@ void BKE_gpencil_stroke_uniform_subdivide(struct bGPdata *gpd,
const uint32_t target_number,
const bool select);
+void BKE_gpencil_stroke_to_view_space(struct RegionView3D *rv3d,
+ struct bGPDstroke *gps,
+ const float diff_mat[4][4]);
+void BKE_gpencil_stroke_from_view_space(struct RegionView3D *rv3d,
+ struct bGPDstroke *gps,
+ const float diff_mat[4][4]);
+struct bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
+ struct bGPdata *gpd,
+ const struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ const int subdivisions,
+ const float diff_mat[4][4]);
+float BKE_gpencil_stroke_average_pressure_get(struct bGPDstroke *gps);
+bool BKE_gpencil_stroke_is_pressure_constant(struct bGPDstroke *gps);
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/intern/appdir.c b/source/blender/blenkernel/intern/appdir.c
index ff799d9a495..1075a46e72b 100644
--- a/source/blender/blenkernel/intern/appdir.c
+++ b/source/blender/blenkernel/intern/appdir.c
@@ -664,7 +664,7 @@ bool BKE_appdir_folder_id_ex(const int folder_id,
return false;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
break;
}
@@ -719,7 +719,7 @@ const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *su
get_path_user_ex(path, sizeof(path), "scripts", subfolder, version, check_is_dir);
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
break;
}
diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c
index 92759221f79..a1ebec1d756 100644
--- a/source/blender/blenkernel/intern/armature.c
+++ b/source/blender/blenkernel/intern/armature.c
@@ -1691,7 +1691,7 @@ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag,
break;
default:
- BLI_assert(false);
+ BLI_assert_unreachable();
}
}
/* If removing parent pose rotation: */
@@ -1723,7 +1723,7 @@ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag,
break;
default:
- BLI_assert(false);
+ BLI_assert_unreachable();
}
}
diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c
index 44d9bd6b2d2..f30fcc54b23 100644
--- a/source/blender/blenkernel/intern/colortools.c
+++ b/source/blender/blenkernel/intern/colortools.c
@@ -1374,7 +1374,7 @@ void BKE_histogram_update_sample_line(Histogram *hist,
rgba[3] = 1.0f;
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
}
hist->data_luma[i] = IMB_colormanagement_get_luminance(rgba);
@@ -1476,7 +1476,7 @@ static void scopes_update_cb(void *__restrict userdata,
rgba[3] = 1.0f;
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
else {
diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c
index 4c85fda4a37..851d8aae378 100644
--- a/source/blender/blenkernel/intern/fluid.c
+++ b/source/blender/blenkernel/intern/fluid.c
@@ -936,7 +936,7 @@ static void update_velocities(FluidEffectorSettings *fes,
}
else {
/* Should never reach this block. */
- BLI_assert(false);
+ BLI_assert_unreachable();
}
}
else {
diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c
index 8c4882854d1..5d8dd99b3ae 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.c
+++ b/source/blender/blenkernel/intern/gpencil_geom.c
@@ -46,9 +46,11 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
#include "BLT_translation.h"
+#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
@@ -3460,4 +3462,555 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
+/**
+ * Stroke to view space
+ * Transforms a stroke to view space. This allows for manipulations in 2D but also easy conversion
+ * back to 3D.
+ * Note: also takes care of parent space transform
+ */
+void BKE_gpencil_stroke_to_view_space(RegionView3D *rv3d,
+ bGPDstroke *gps,
+ const float diff_mat[4][4])
+{
+ for (int i = 0; i < gps->totpoints; i++) {
+ bGPDspoint *pt = &gps->points[i];
+ /* Point to parent space. */
+ mul_v3_m4v3(&pt->x, diff_mat, &pt->x);
+ /* point to view space */
+ mul_m4_v3(rv3d->viewmat, &pt->x);
+ }
+}
+
+/**
+ * Stroke from view space
+ * Transforms a stroke from view space back to world space. Inverse of
+ * BKE_gpencil_stroke_to_view_space
+ * Note: also takes care of parent space transform
+ */
+void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d,
+ bGPDstroke *gps,
+ const float diff_mat[4][4])
+{
+ float inverse_diff_mat[4][4];
+ invert_m4_m4(inverse_diff_mat, diff_mat);
+
+ for (int i = 0; i < gps->totpoints; i++) {
+ bGPDspoint *pt = &gps->points[i];
+ mul_v3_m4v3(&pt->x, rv3d->viewinv, &pt->x);
+ mul_m4_v3(inverse_diff_mat, &pt->x);
+ }
+}
+
+/* ----------------------------------------------------------------------------- */
+/* Stroke to perimeter */
+
+typedef struct tPerimeterPoint {
+ struct tPerimeterPoint *next, *prev;
+ float x, y, z;
+} tPerimeterPoint;
+
+static tPerimeterPoint *new_perimeter_point(const float pt[3])
+{
+ tPerimeterPoint *new_pt = MEM_callocN(sizeof(tPerimeterPoint), __func__);
+ copy_v3_v3(&new_pt->x, pt);
+ return new_pt;
+}
+
+static int generate_arc_from_point_to_point(ListBase *list,
+ tPerimeterPoint *from,
+ tPerimeterPoint *to,
+ float center_pt[3],
+ int subdivisions,
+ bool clockwise)
+{
+ float vec_from[2];
+ float vec_to[2];
+ sub_v2_v2v2(vec_from, &from->x, center_pt);
+ sub_v2_v2v2(vec_to, &to->x, center_pt);
+ if (is_zero_v2(vec_from) || is_zero_v2(vec_to)) {
+ return 0;
+ }
+
+ float dot = dot_v2v2(vec_from, vec_to);
+ float det = cross_v2v2(vec_from, vec_to);
+ float angle = clockwise ? M_PI - atan2f(-det, -dot) : atan2f(-det, -dot) + M_PI;
+
+ /* Number of points is 2^(n+1) + 1 on half a circle (n=subdivisions)
+ * so we multiply by (angle / pi) to get the right amount of
+ * points to insert. */
+ int num_points = (int)(((1 << (subdivisions + 1)) - 1) * (angle / M_PI));
+ if (num_points > 0) {
+ float angle_incr = angle / (float)num_points;
+
+ float vec_p[3];
+ float vec_t[3];
+ float tmp_angle;
+ tPerimeterPoint *last_point;
+ if (clockwise) {
+ last_point = to;
+ copy_v2_v2(vec_t, vec_to);
+ }
+ else {
+ last_point = from;
+ copy_v2_v2(vec_t, vec_from);
+ }
+
+ for (int i = 0; i < num_points - 1; i++) {
+ tmp_angle = (i + 1) * angle_incr;
+
+ rotate_v2_v2fl(vec_p, vec_t, tmp_angle);
+ add_v2_v2(vec_p, center_pt);
+ vec_p[2] = center_pt[2];
+
+ tPerimeterPoint *new_point = new_perimeter_point(vec_p);
+ if (clockwise) {
+ BLI_insertlinkbefore(list, last_point, new_point);
+ }
+ else {
+ BLI_insertlinkafter(list, last_point, new_point);
+ }
+
+ last_point = new_point;
+ }
+
+ return num_points - 1;
+ }
+
+ return 0;
+}
+
+static int generate_semi_circle_from_point_to_point(ListBase *list,
+ tPerimeterPoint *from,
+ tPerimeterPoint *to,
+ int subdivisions)
+{
+ int num_points = (1 << (subdivisions + 1)) + 1;
+ float center_pt[3];
+ interp_v3_v3v3(center_pt, &from->x, &to->x, 0.5f);
+
+ float vec_center[2];
+ sub_v2_v2v2(vec_center, &from->x, center_pt);
+ if (is_zero_v2(vec_center)) {
+ return 0;
+ }
+
+ float vec_p[3];
+ float angle_incr = M_PI / ((float)num_points - 1);
+
+ tPerimeterPoint *last_point = from;
+ for (int i = 1; i < num_points; i++) {
+ float angle = i * angle_incr;
+
+ /* Rotate vector around point to get perimeter points. */
+ rotate_v2_v2fl(vec_p, vec_center, angle);
+ add_v2_v2(vec_p, center_pt);
+ vec_p[2] = center_pt[2];
+
+ tPerimeterPoint *new_point = new_perimeter_point(vec_p);
+ BLI_insertlinkafter(list, last_point, new_point);
+
+ last_point = new_point;
+ }
+
+ return num_points - 1;
+}
+
+static int generate_perimeter_cap(const float point[4],
+ const float other_point[4],
+ float radius,
+ ListBase *list,
+ int subdivisions,
+ short cap_type)
+{
+ float cap_vec[2];
+ sub_v2_v2v2(cap_vec, other_point, point);
+ normalize_v2(cap_vec);
+
+ float cap_nvec[2];
+ if (is_zero_v2(cap_vec)) {
+ cap_nvec[0] = 0;
+ cap_nvec[1] = radius;
+ }
+ else {
+ cap_nvec[0] = -cap_vec[1];
+ cap_nvec[1] = cap_vec[0];
+ mul_v2_fl(cap_nvec, radius);
+ }
+ float cap_nvec_inv[2];
+ negate_v2_v2(cap_nvec_inv, cap_nvec);
+
+ float vec_perimeter[3];
+ copy_v3_v3(vec_perimeter, point);
+ add_v2_v2(vec_perimeter, cap_nvec);
+
+ float vec_perimeter_inv[3];
+ copy_v3_v3(vec_perimeter_inv, point);
+ add_v2_v2(vec_perimeter_inv, cap_nvec_inv);
+
+ tPerimeterPoint *p_pt = new_perimeter_point(vec_perimeter);
+ tPerimeterPoint *p_pt_inv = new_perimeter_point(vec_perimeter_inv);
+
+ BLI_addtail(list, p_pt);
+ BLI_addtail(list, p_pt_inv);
+
+ int num_points = 0;
+ if (cap_type == GP_STROKE_CAP_ROUND) {
+ num_points += generate_semi_circle_from_point_to_point(list, p_pt, p_pt_inv, subdivisions);
+ }
+
+ return num_points + 2;
+}
+
+/**
+ * Calculate the perimeter (outline) of a stroke as list of tPerimeterPoint.
+ * \param subdivisions: Number of subdivions for the start and end caps
+ * \return: list of tPerimeterPoint
+ */
+static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd,
+ const bGPDlayer *gpl,
+ const bGPDstroke *gps,
+ int subdivisions,
+ int *r_num_perimeter_points)
+{
+ /* sanity check */
+ if (gps->totpoints < 1) {
+ return NULL;
+ }
+
+ float defaultpixsize = 1000.0f / gpd->pixfactor;
+ float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f;
+
+ ListBase *perimeter_right_side = MEM_callocN(sizeof(ListBase), __func__);
+ ListBase *perimeter_left_side = MEM_callocN(sizeof(ListBase), __func__);
+ int num_perimeter_points = 0;
+
+ bGPDspoint *first = &gps->points[0];
+ bGPDspoint *last = &gps->points[gps->totpoints - 1];
+
+ float first_radius = stroke_radius * first->pressure;
+ float last_radius = stroke_radius * last->pressure;
+
+ bGPDspoint *first_next;
+ bGPDspoint *last_prev;
+ if (gps->totpoints > 1) {
+ first_next = &gps->points[1];
+ last_prev = &gps->points[gps->totpoints - 2];
+ }
+ else {
+ first_next = first;
+ last_prev = last;
+ }
+
+ float first_pt[3];
+ float last_pt[3];
+ float first_next_pt[3];
+ float last_prev_pt[3];
+ copy_v3_v3(first_pt, &first->x);
+ copy_v3_v3(last_pt, &last->x);
+ copy_v3_v3(first_next_pt, &first_next->x);
+ copy_v3_v3(last_prev_pt, &last_prev->x);
+
+ /* edgecase if single point */
+ if (gps->totpoints == 1) {
+ first_next_pt[0] += 1.0f;
+ last_prev_pt[0] -= 1.0f;
+ }
+
+ /* generate points for start cap */
+ num_perimeter_points += generate_perimeter_cap(
+ first_pt, first_next_pt, first_radius, perimeter_right_side, subdivisions, gps->caps[0]);
+
+ /* generate perimeter points */
+ float curr_pt[3], next_pt[3], prev_pt[3];
+ float vec_next[2], vec_prev[2];
+ float nvec_next[2], nvec_prev[2];
+ float nvec_next_pt[3], nvec_prev_pt[3];
+ float vec_tangent[2];
+
+ float vec_miter_left[2], vec_miter_right[2];
+ float miter_left_pt[3], miter_right_pt[3];
+
+ for (int i = 1; i < gps->totpoints - 1; i++) {
+ bGPDspoint *curr = &gps->points[i];
+ bGPDspoint *prev = &gps->points[i - 1];
+ bGPDspoint *next = &gps->points[i + 1];
+ float radius = stroke_radius * curr->pressure;
+
+ copy_v3_v3(curr_pt, &curr->x);
+ copy_v3_v3(next_pt, &next->x);
+ copy_v3_v3(prev_pt, &prev->x);
+
+ sub_v2_v2v2(vec_prev, curr_pt, prev_pt);
+ sub_v2_v2v2(vec_next, next_pt, curr_pt);
+ float prev_length = len_v2(vec_prev);
+ float next_length = len_v2(vec_next);
+
+ if (normalize_v2(vec_prev) == 0.0f) {
+ vec_prev[0] = 1.0f;
+ vec_prev[1] = 0.0f;
+ }
+ if (normalize_v2(vec_next) == 0.0f) {
+ vec_next[0] = 1.0f;
+ vec_next[1] = 0.0f;
+ }
+
+ nvec_prev[0] = -vec_prev[1];
+ nvec_prev[1] = vec_prev[0];
+
+ nvec_next[0] = -vec_next[1];
+ nvec_next[1] = vec_next[0];
+
+ add_v2_v2v2(vec_tangent, vec_prev, vec_next);
+ if (normalize_v2(vec_tangent) == 0.0f) {
+ copy_v2_v2(vec_tangent, nvec_prev);
+ }
+
+ vec_miter_left[0] = -vec_tangent[1];
+ vec_miter_left[1] = vec_tangent[0];
+
+ /* calculate miter length */
+ float an1 = dot_v2v2(vec_miter_left, nvec_prev);
+ if (an1 == 0.0f) {
+ an1 = 1.0f;
+ }
+ float miter_length = radius / an1;
+ if (miter_length <= 0.0f) {
+ miter_length = 0.01f;
+ }
+
+ normalize_v2_length(vec_miter_left, miter_length);
+
+ copy_v2_v2(vec_miter_right, vec_miter_left);
+ negate_v2(vec_miter_right);
+
+ float angle = dot_v2v2(vec_next, nvec_prev);
+ /* add two points if angle is close to beeing straight */
+ if (fabsf(angle) < 0.0001f) {
+ normalize_v2_length(nvec_prev, radius);
+ normalize_v2_length(nvec_next, radius);
+
+ copy_v3_v3(nvec_prev_pt, curr_pt);
+ add_v2_v2(nvec_prev_pt, nvec_prev);
+
+ copy_v3_v3(nvec_next_pt, curr_pt);
+ negate_v2(nvec_next);
+ add_v2_v2(nvec_next_pt, nvec_next);
+
+ tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
+ tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
+
+ BLI_addtail(perimeter_left_side, normal_prev);
+ BLI_addtail(perimeter_right_side, normal_next);
+ num_perimeter_points += 2;
+ }
+ else {
+ /* bend to the left */
+ if (angle < 0.0f) {
+ normalize_v2_length(nvec_prev, radius);
+ normalize_v2_length(nvec_next, radius);
+
+ copy_v3_v3(nvec_prev_pt, curr_pt);
+ add_v2_v2(nvec_prev_pt, nvec_prev);
+
+ copy_v3_v3(nvec_next_pt, curr_pt);
+ add_v2_v2(nvec_next_pt, nvec_next);
+
+ tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
+ tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
+
+ BLI_addtail(perimeter_left_side, normal_prev);
+ BLI_addtail(perimeter_left_side, normal_next);
+ num_perimeter_points += 2;
+
+ num_perimeter_points += generate_arc_from_point_to_point(
+ perimeter_left_side, normal_prev, normal_next, curr_pt, subdivisions, true);
+
+ if (miter_length < prev_length && miter_length < next_length) {
+ copy_v3_v3(miter_right_pt, curr_pt);
+ add_v2_v2(miter_right_pt, vec_miter_right);
+ }
+ else {
+ copy_v3_v3(miter_right_pt, curr_pt);
+ negate_v2(nvec_next);
+ add_v2_v2(miter_right_pt, nvec_next);
+ }
+
+ tPerimeterPoint *miter_right = new_perimeter_point(miter_right_pt);
+ BLI_addtail(perimeter_right_side, miter_right);
+ num_perimeter_points++;
+ }
+ /* bend to the right */
+ else {
+ normalize_v2_length(nvec_prev, -radius);
+ normalize_v2_length(nvec_next, -radius);
+
+ copy_v3_v3(nvec_prev_pt, curr_pt);
+ add_v2_v2(nvec_prev_pt, nvec_prev);
+
+ copy_v3_v3(nvec_next_pt, curr_pt);
+ add_v2_v2(nvec_next_pt, nvec_next);
+
+ tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
+ tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
+
+ BLI_addtail(perimeter_right_side, normal_prev);
+ BLI_addtail(perimeter_right_side, normal_next);
+ num_perimeter_points += 2;
+
+ num_perimeter_points += generate_arc_from_point_to_point(
+ perimeter_right_side, normal_prev, normal_next, curr_pt, subdivisions, false);
+
+ if (miter_length < prev_length && miter_length < next_length) {
+ copy_v3_v3(miter_left_pt, curr_pt);
+ add_v2_v2(miter_left_pt, vec_miter_left);
+ }
+ else {
+ copy_v3_v3(miter_left_pt, curr_pt);
+ negate_v2(nvec_prev);
+ add_v2_v2(miter_left_pt, nvec_prev);
+ }
+
+ tPerimeterPoint *miter_left = new_perimeter_point(miter_left_pt);
+ BLI_addtail(perimeter_left_side, miter_left);
+ num_perimeter_points++;
+ }
+ }
+ }
+
+ /* generate points for end cap */
+ num_perimeter_points += generate_perimeter_cap(
+ last_pt, last_prev_pt, last_radius, perimeter_right_side, subdivisions, gps->caps[1]);
+
+ /* merge both sides to one list */
+ BLI_listbase_reverse(perimeter_right_side);
+ BLI_movelisttolist(perimeter_left_side,
+ perimeter_right_side); // perimeter_left_side contains entire list
+ ListBase *perimeter_list = perimeter_left_side;
+
+ /* close by creating a point close to the first (make a small gap) */
+ float close_pt[3];
+ tPerimeterPoint *close_first = (tPerimeterPoint *)perimeter_list->first;
+ tPerimeterPoint *close_last = (tPerimeterPoint *)perimeter_list->last;
+ interp_v3_v3v3(close_pt, &close_last->x, &close_first->x, 0.99f);
+
+ if (compare_v3v3(close_pt, &close_first->x, FLT_EPSILON) == false) {
+ tPerimeterPoint *close_p_pt = new_perimeter_point(close_pt);
+ BLI_addtail(perimeter_list, close_p_pt);
+ num_perimeter_points++;
+ }
+
+ /* free temp data */
+ BLI_freelistN(perimeter_right_side);
+ MEM_freeN(perimeter_right_side);
+
+ *r_num_perimeter_points = num_perimeter_points;
+ return perimeter_list;
+}
+
+/**
+ * Calculates the perimeter of a stroke projected from the view and
+ * returns it as a new stroke.
+ * \param subdivisions: Number of subdivions for the start and end caps
+ * \return: bGPDstroke pointer to stroke perimeter
+ */
+bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
+ bGPdata *gpd,
+ const bGPDlayer *gpl,
+ bGPDstroke *gps,
+ const int subdivisions,
+ const float diff_mat[4][4])
+{
+ if (gps->totpoints == 0) {
+ return NULL;
+ }
+ bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, true, false);
+ const bool cyclic = ((gps_temp->flag & GP_STROKE_CYCLIC) != 0);
+
+ /* If Cyclic, add a new point. */
+ if (cyclic && (gps_temp->totpoints > 1)) {
+ gps_temp->totpoints++;
+ gps_temp->points = MEM_recallocN(gps_temp->points,
+ sizeof(*gps_temp->points) * gps_temp->totpoints);
+ bGPDspoint *pt_src = &gps_temp->points[0];
+ bGPDspoint *pt_dst = &gps_temp->points[gps_temp->totpoints - 1];
+ copy_v3_v3(&pt_dst->x, &pt_src->x);
+ pt_dst->pressure = pt_src->pressure;
+ pt_dst->strength = pt_src->strength;
+ pt_dst->uv_fac = 1.0f;
+ pt_dst->uv_rot = 0;
+ }
+
+ BKE_gpencil_stroke_to_view_space(rv3d, gps_temp, diff_mat);
+ int num_perimeter_points = 0;
+ ListBase *perimeter_points = gpencil_stroke_perimeter_ex(
+ gpd, gpl, gps_temp, subdivisions, &num_perimeter_points);
+
+ if (num_perimeter_points == 0) {
+ return NULL;
+ }
+
+ /* Create new stroke. */
+ bGPDstroke *perimeter_stroke = BKE_gpencil_stroke_new(gps_temp->mat_nr, num_perimeter_points, 1);
+
+ int i = 0;
+ LISTBASE_FOREACH_INDEX (tPerimeterPoint *, curr, perimeter_points, i) {
+ bGPDspoint *pt = &perimeter_stroke->points[i];
+
+ copy_v3_v3(&pt->x, &curr->x);
+ pt->pressure = 0.0f;
+ pt->strength = 1.0f;
+
+ pt->flag |= GP_SPOINT_SELECT;
+ }
+
+ BKE_gpencil_stroke_from_view_space(rv3d, perimeter_stroke, diff_mat);
+
+ /* Free temp data. */
+ BLI_freelistN(perimeter_points);
+ MEM_freeN(perimeter_points);
+
+ /* Triangles cache needs to be recalculated. */
+ BKE_gpencil_stroke_geometry_update(gpd, perimeter_stroke);
+
+ perimeter_stroke->flag |= GP_STROKE_SELECT | GP_STROKE_CYCLIC;
+
+ BKE_gpencil_free_stroke(gps_temp);
+
+ return perimeter_stroke;
+}
+
+/** Get average pressure. */
+float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps)
+{
+
+ if (gps->totpoints == 1) {
+ return gps->points[0].pressure;
+ }
+
+ float tot = 0.0f;
+ for (int i = 0; i < gps->totpoints; i++) {
+ const bGPDspoint *pt = &gps->points[i];
+ tot += pt->pressure;
+ }
+
+ return tot / (float)gps->totpoints;
+}
+
+/** Check if the thickness of the stroke is constant. */
+bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps)
+{
+ if (gps->totpoints == 1) {
+ return true;
+ }
+
+ const float first_pressure = gps->points[0].pressure;
+ for (int i = 0; i < gps->totpoints; i++) {
+ const bGPDspoint *pt = &gps->points[i];
+ if (pt->pressure != first_pressure) {
+ return false;
+ }
+ }
+
+ return true;
+}
/** \} */
diff --git a/source/blender/blenkernel/intern/keyconfig.c b/source/blender/blenkernel/intern/keyconfig.c
index ada5fc5b6aa..552760c9b34 100644
--- a/source/blender/blenkernel/intern/keyconfig.c
+++ b/source/blender/blenkernel/intern/keyconfig.c
@@ -19,6 +19,7 @@
*/
#include <stddef.h>
+#include <stdio.h>
#include <stdlib.h>
#include "RNA_types.h"
diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c
index d1f34ad8ce9..dc678f248c9 100644
--- a/source/blender/blenkernel/intern/main.c
+++ b/source/blender/blenkernel/intern/main.c
@@ -182,7 +182,7 @@ void BKE_main_free(Main *mainvar)
BKE_id_free_ex(mainvar, id, free_flag, false);
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
break;
}
#endif
diff --git a/source/blender/blenkernel/intern/mesh_wrapper.c b/source/blender/blenkernel/intern/mesh_wrapper.c
index acd272ac305..5df9f7816e3 100644
--- a/source/blender/blenkernel/intern/mesh_wrapper.c
+++ b/source/blender/blenkernel/intern/mesh_wrapper.c
@@ -148,7 +148,7 @@ bool BKE_mesh_wrapper_minmax(const Mesh *me, float min[3], float max[3])
case ME_WRAPPER_TYPE_MDATA:
return BKE_mesh_minmax(me, min, max);
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return false;
}
@@ -189,7 +189,7 @@ void BKE_mesh_wrapper_vert_coords_copy(const Mesh *me,
return;
}
}
- BLI_assert(0);
+ BLI_assert_unreachable();
}
void BKE_mesh_wrapper_vert_coords_copy_with_mat4(const Mesh *me,
@@ -226,7 +226,7 @@ void BKE_mesh_wrapper_vert_coords_copy_with_mat4(const Mesh *me,
return;
}
}
- BLI_assert(0);
+ BLI_assert_unreachable();
}
/** \} */
@@ -243,7 +243,7 @@ int BKE_mesh_wrapper_vert_len(const Mesh *me)
case ME_WRAPPER_TYPE_MDATA:
return me->totvert;
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return -1;
}
@@ -255,7 +255,7 @@ int BKE_mesh_wrapper_edge_len(const Mesh *me)
case ME_WRAPPER_TYPE_MDATA:
return me->totedge;
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return -1;
}
@@ -267,7 +267,7 @@ int BKE_mesh_wrapper_loop_len(const Mesh *me)
case ME_WRAPPER_TYPE_MDATA:
return me->totloop;
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return -1;
}
@@ -279,7 +279,7 @@ int BKE_mesh_wrapper_poly_len(const Mesh *me)
case ME_WRAPPER_TYPE_MDATA:
return me->totpoly;
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return -1;
}
diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c
index 00e99f193a2..b07c4b22c39 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.c
@@ -1858,7 +1858,7 @@ bool BKE_object_data_is_in_editmode(const ID *id)
case ID_AR:
return ((const bArmature *)id)->edbo != NULL;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
return false;
}
}
@@ -1905,7 +1905,7 @@ char *BKE_object_data_editmode_flush_ptr_get(struct ID *id)
return &arm->needs_flush_to_id;
}
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
return NULL;
}
return NULL;
diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c
index 2e81b61ad8c..3494630e1fa 100644
--- a/source/blender/blenkernel/intern/paint.c
+++ b/source/blender/blenkernel/intern/paint.c
@@ -656,7 +656,7 @@ void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint)
paint->runtime.ob_mode = OB_MODE_WEIGHT_GPENCIL;
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
diff --git a/source/blender/blenkernel/intern/text_suggestions.c b/source/blender/blenkernel/intern/text_suggestions.c
index 6df1aff722b..d717b88e8ca 100644
--- a/source/blender/blenkernel/intern/text_suggestions.c
+++ b/source/blender/blenkernel/intern/text_suggestions.c
@@ -22,6 +22,7 @@
*/
#include <ctype.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c
index 52f0fe3f5a2..377802f1af7 100644
--- a/source/blender/blenkernel/intern/undo_system.c
+++ b/source/blender/blenkernel/intern/undo_system.c
@@ -20,6 +20,7 @@
* Used by ED_undo.h, internal implementation.
*/
+#include <stdio.h>
#include <string.h>
#include "CLG_log.h"
diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c
index 481d190952f..be67b2370e3 100644
--- a/source/blender/blenkernel/intern/workspace.c
+++ b/source/blender/blenkernel/intern/workspace.c
@@ -18,6 +18,7 @@
* \ingroup bke
*/
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/source/blender/blenlib/intern/BLI_args.c b/source/blender/blenlib/intern/BLI_args.c
index 07ab4f407f2..70a51982925 100644
--- a/source/blender/blenlib/intern/BLI_args.c
+++ b/source/blender/blenlib/intern/BLI_args.c
@@ -23,6 +23,7 @@
*/
#include <ctype.h> /* for tolower */
+#include <stdio.h>
#include <string.h>
#include "MEM_guardedalloc.h"
diff --git a/source/blender/blenlib/intern/BLI_dynstr.c b/source/blender/blenlib/intern/BLI_dynstr.c
index 3e80f791fa5..7b25fecfa45 100644
--- a/source/blender/blenlib/intern/BLI_dynstr.c
+++ b/source/blender/blenlib/intern/BLI_dynstr.c
@@ -22,6 +22,7 @@
* \ingroup bli
*/
+#include <stdio.h>
#include <stdlib.h> /* malloc */
#include <string.h>
diff --git a/source/blender/blenlib/intern/edgehash.c b/source/blender/blenlib/intern/edgehash.c
index 05ee02ad869..b8bf535a3b4 100644
--- a/source/blender/blenlib/intern/edgehash.c
+++ b/source/blender/blenlib/intern/edgehash.c
@@ -24,6 +24,7 @@
*/
#include <limits.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c
index 3bfedd6f586..ccc11af9f2b 100644
--- a/source/blender/blenlib/intern/string.c
+++ b/source/blender/blenlib/intern/string.c
@@ -25,6 +25,7 @@
#include <inttypes.h>
#include <math.h>
#include <stdarg.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/source/blender/blenlib/intern/string_search.cc b/source/blender/blenlib/intern/string_search.cc
index 44baff1f5e3..25a13674932 100644
--- a/source/blender/blenlib/intern/string_search.cc
+++ b/source/blender/blenlib/intern/string_search.cc
@@ -432,9 +432,11 @@ int BLI_string_search_query(StringSearch *search, const char *query, void ***r_d
{
using namespace blender;
+ const StringRef query_str = query;
+
LinearAllocator<> allocator;
Vector<StringRef, 64> query_words;
- string_search::extract_normalized_words(query, allocator, query_words);
+ string_search::extract_normalized_words(query_str, allocator, query_words);
/* Compute score of every result. */
MultiValueMap<int, int> result_indices_by_score;
@@ -457,7 +459,7 @@ int BLI_string_search_query(StringSearch *search, const char *query, void ***r_d
Vector<int> sorted_result_indices;
for (const int score : found_scores) {
MutableSpan<int> indices = result_indices_by_score.lookup(score);
- if (score == found_scores[0]) {
+ if (score == found_scores[0] && !query_str.is_empty()) {
/* Sort items with best score by length. Shorter items are more likely the ones you are
* looking for. This also ensures that exact matches will be at the top, even if the query is
* a substring of another item. */
diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c
index 00f4c49fda9..31e4b659c2f 100644
--- a/source/blender/blenloader/intern/versioning_290.c
+++ b/source/blender/blenloader/intern/versioning_290.c
@@ -52,6 +52,7 @@
#include "BKE_animsys.h"
#include "BKE_armature.h"
+#include "BKE_attribute.h"
#include "BKE_collection.h"
#include "BKE_colortools.h"
#include "BKE_cryptomatte.h"
@@ -1905,6 +1906,25 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
+ if (!MAIN_VERSION_ATLEAST(bmain, 293, 14)) {
+ if (!DNA_struct_elem_find(fd->filesdna, "Light", "float", "diff_fac")) {
+ LISTBASE_FOREACH (Light *, light, &bmain->lights) {
+ light->diff_fac = 1.0f;
+ light->volume_fac = 1.0f;
+ }
+ }
+
+ LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
+ if (ntree->type == NTREE_GEOMETRY) {
+ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
+ if (node->type == GEO_NODE_ATTRIBUTE_FILL) {
+ node->custom2 = ATTR_DOMAIN_AUTO;
+ }
+ }
+ }
+ }
+ }
+
/**
* Versioning code until next subversion bump goes here.
*
@@ -1916,12 +1936,5 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
*/
{
/* Keep this block, even when empty. */
-
- if (!DNA_struct_elem_find(fd->filesdna, "Light", "float", "diff_fac")) {
- LISTBASE_FOREACH (Light *, light, &bmain->lights) {
- light->diff_fac = 1.0f;
- light->volume_fac = 1.0f;
- }
- }
}
}
diff --git a/source/blender/compositor/intern/COM_CPUDevice.h b/source/blender/compositor/intern/COM_CPUDevice.h
index 962380d7bc8..6df1f41419d 100644
--- a/source/blender/compositor/intern/COM_CPUDevice.h
+++ b/source/blender/compositor/intern/COM_CPUDevice.h
@@ -33,7 +33,7 @@ class CPUDevice : public Device {
* \brief execute a WorkPackage
* \param work: the WorkPackage to execute
*/
- void execute(WorkPackage *work);
+ void execute(WorkPackage *work) override;
int thread_id()
{
diff --git a/source/blender/compositor/intern/COM_Device.h b/source/blender/compositor/intern/COM_Device.h
index 0b0f0f5c1c6..0a456760045 100644
--- a/source/blender/compositor/intern/COM_Device.h
+++ b/source/blender/compositor/intern/COM_Device.h
@@ -37,21 +37,6 @@ class Device {
}
/**
- * \brief initialize the device
- */
- virtual bool initialize()
- {
- return true;
- }
-
- /**
- * \brief deinitialize the device
- */
- virtual void deinitialize()
- {
- }
-
- /**
* \brief execute a WorkPackage
* \param work: the WorkPackage to execute
*/
diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.cc b/source/blender/compositor/intern/COM_ExecutionGroup.cc
index 3cedc5da663..f500327b7a7 100644
--- a/source/blender/compositor/intern/COM_ExecutionGroup.cc
+++ b/source/blender/compositor/intern/COM_ExecutionGroup.cc
@@ -65,7 +65,7 @@ ExecutionGroup::ExecutionGroup()
this->m_executionStartTime = 0;
}
-CompositorPriority ExecutionGroup::getRenderPriotrity()
+CompositorPriority ExecutionGroup::getRenderPriority()
{
return this->getOutputOperation()->getRenderPriority();
}
@@ -130,9 +130,7 @@ void ExecutionGroup::initExecution()
if (this->m_chunks_len != 0) {
m_chunk_execution_states.resize(this->m_chunks_len);
- for (int index = 0; index < this->m_chunks_len; index++) {
- m_chunk_execution_states[index] = eChunkExecutionState::NOT_SCHEDULED;
- }
+ m_chunk_execution_states.fill(eChunkExecutionState::NOT_SCHEDULED);
}
unsigned int max_offset = 0;
@@ -185,7 +183,6 @@ void ExecutionGroup::determineNumberOfChunks()
blender::Array<unsigned int> ExecutionGroup::determine_chunk_execution_order() const
{
- int index;
blender::Array<unsigned int> chunk_order(m_chunks_len);
for (int chunk_index = 0; chunk_index < this->m_chunks_len; chunk_index++) {
chunk_order[chunk_index] = chunk_index;
@@ -205,7 +202,7 @@ blender::Array<unsigned int> ExecutionGroup::determine_chunk_execution_order() c
const int border_width = BLI_rcti_size_x(&this->m_viewerBorder);
const int border_height = BLI_rcti_size_y(&this->m_viewerBorder);
-
+ int index;
switch (order_type) {
case ChunkOrdering::Random: {
static blender::RandomNumberGenerator rng;
@@ -303,7 +300,6 @@ void ExecutionGroup::execute(ExecutionSystem *graph)
this->m_chunks_finished = 0;
this->m_bTree = bTree;
- unsigned int index;
blender::Array<unsigned int> chunk_order = determine_chunk_execution_order();
@@ -320,7 +316,8 @@ void ExecutionGroup::execute(ExecutionSystem *graph)
finished = true;
int numberEvaluated = 0;
- for (index = startIndex; index < this->m_chunks_len && numberEvaluated < maxNumberEvaluated;
+ for (int index = startIndex;
+ index < this->m_chunks_len && numberEvaluated < maxNumberEvaluated;
index++) {
chunk_index = chunk_order[index];
int yChunk = chunk_index / this->m_x_chunks_len;
diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.h b/source/blender/compositor/intern/COM_ExecutionGroup.h
index 6fd93d2d93c..13ff06cd5d1 100644
--- a/source/blender/compositor/intern/COM_ExecutionGroup.h
+++ b/source/blender/compositor/intern/COM_ExecutionGroup.h
@@ -424,7 +424,7 @@ class ExecutionGroup {
* \brief get the Render priority of this ExecutionGroup
* \see ExecutionSystem.execute
*/
- CompositorPriority getRenderPriotrity();
+ CompositorPriority getRenderPriority();
/**
* \brief set border for viewer operation
diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.cc b/source/blender/compositor/intern/COM_ExecutionSystem.cc
index 993aedf7715..df97b8079b2 100644
--- a/source/blender/compositor/intern/COM_ExecutionSystem.cc
+++ b/source/blender/compositor/intern/COM_ExecutionSystem.cc
@@ -71,7 +71,6 @@ ExecutionSystem::ExecutionSystem(RenderData *rd,
builder.convertToOperations(this);
}
- unsigned int index;
unsigned int resolution[2];
rctf *viewer_border = &editingtree->viewer_border;
@@ -81,10 +80,9 @@ ExecutionSystem::ExecutionSystem(RenderData *rd,
editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Determining resolution"));
- for (index = 0; index < this->m_groups.size(); index++) {
+ for (ExecutionGroup *executionGroup : m_groups) {
resolution[0] = 0;
resolution[1] = 0;
- ExecutionGroup *executionGroup = this->m_groups[index];
executionGroup->determineResolution(resolution);
if (rendering) {
@@ -108,14 +106,12 @@ ExecutionSystem::ExecutionSystem(RenderData *rd,
ExecutionSystem::~ExecutionSystem()
{
- unsigned int index;
- for (index = 0; index < this->m_operations.size(); index++) {
- NodeOperation *operation = this->m_operations[index];
+ for (NodeOperation *operation : m_operations) {
delete operation;
}
this->m_operations.clear();
- for (index = 0; index < this->m_groups.size(); index++) {
- ExecutionGroup *group = this->m_groups[index];
+
+ for (ExecutionGroup *group : m_groups) {
delete group;
}
this->m_groups.clear();
@@ -128,92 +124,98 @@ void ExecutionSystem::set_operations(const blender::Vector<NodeOperation *> &ope
m_groups = groups;
}
-void ExecutionSystem::execute()
+static void update_read_buffer_offset(blender::Vector<NodeOperation *> &operations)
{
- const bNodeTree *editingtree = this->m_context.getbNodeTree();
- editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Initializing execution"));
-
- DebugInfo::execute_started(this);
-
unsigned int order = 0;
- for (NodeOperation *operation : m_operations) {
+ for (NodeOperation *operation : operations) {
if (operation->isReadBufferOperation()) {
ReadBufferOperation *readOperation = (ReadBufferOperation *)operation;
readOperation->setOffset(order);
order++;
}
}
- unsigned int index;
+}
- // First allocale all write buffer
- for (index = 0; index < this->m_operations.size(); index++) {
- NodeOperation *operation = this->m_operations[index];
+static void init_write_operations_for_execution(blender::Vector<NodeOperation *> &operations,
+ const bNodeTree *bTree)
+{
+ for (NodeOperation *operation : operations) {
if (operation->isWriteBufferOperation()) {
- operation->setbNodeTree(this->m_context.getbNodeTree());
+ operation->setbNodeTree(bTree);
operation->initExecution();
}
}
- // Connect read buffers to their write buffers
- for (index = 0; index < this->m_operations.size(); index++) {
- NodeOperation *operation = this->m_operations[index];
+}
+
+static void link_write_buffers(blender::Vector<NodeOperation *> &operations)
+{
+ for (NodeOperation *operation : operations) {
if (operation->isReadBufferOperation()) {
- ReadBufferOperation *readOperation = (ReadBufferOperation *)operation;
+ ReadBufferOperation *readOperation = static_cast<ReadBufferOperation *>(operation);
readOperation->updateMemoryBuffer();
}
}
- // initialize other operations
- for (index = 0; index < this->m_operations.size(); index++) {
- NodeOperation *operation = this->m_operations[index];
+}
+
+static void init_non_write_operations_for_execution(blender::Vector<NodeOperation *> &operations,
+ const bNodeTree *bTree)
+{
+ for (NodeOperation *operation : operations) {
if (!operation->isWriteBufferOperation()) {
- operation->setbNodeTree(this->m_context.getbNodeTree());
+ operation->setbNodeTree(bTree);
operation->initExecution();
}
}
- for (index = 0; index < this->m_groups.size(); index++) {
- ExecutionGroup *executionGroup = this->m_groups[index];
- executionGroup->setChunksize(this->m_context.getChunksize());
- executionGroup->initExecution();
+}
+
+static void init_execution_groups_for_execution(blender::Vector<ExecutionGroup *> &groups,
+ const int chunk_size)
+{
+ for (ExecutionGroup *execution_group : groups) {
+ execution_group->setChunksize(chunk_size);
+ execution_group->initExecution();
}
+}
- WorkScheduler::start(this->m_context);
+void ExecutionSystem::execute()
+{
+ const bNodeTree *editingtree = this->m_context.getbNodeTree();
+ editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Initializing execution"));
+
+ DebugInfo::execute_started(this);
+ update_read_buffer_offset(m_operations);
+ init_write_operations_for_execution(m_operations, m_context.getbNodeTree());
+ link_write_buffers(m_operations);
+ init_non_write_operations_for_execution(m_operations, m_context.getbNodeTree());
+ init_execution_groups_for_execution(m_groups, m_context.getChunksize());
+
+ WorkScheduler::start(this->m_context);
execute_groups(CompositorPriority::High);
if (!this->getContext().isFastCalculation()) {
execute_groups(CompositorPriority::Medium);
execute_groups(CompositorPriority::Low);
}
-
WorkScheduler::finish();
WorkScheduler::stop();
editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | De-initializing execution"));
- for (index = 0; index < this->m_operations.size(); index++) {
- NodeOperation *operation = this->m_operations[index];
+
+ for (NodeOperation *operation : m_operations) {
operation->deinitExecution();
}
- for (index = 0; index < this->m_groups.size(); index++) {
- ExecutionGroup *executionGroup = this->m_groups[index];
- executionGroup->deinitExecution();
- }
-}
-void ExecutionSystem::execute_groups(CompositorPriority priority)
-{
- blender::Vector<ExecutionGroup *> execution_groups = find_output_execution_groups(priority);
- for (ExecutionGroup *group : execution_groups) {
- group->execute(this);
+ for (ExecutionGroup *execution_group : m_groups) {
+ execution_group->deinitExecution();
}
}
-blender::Vector<ExecutionGroup *> ExecutionSystem::find_output_execution_groups(
- CompositorPriority priority) const
+void ExecutionSystem::execute_groups(CompositorPriority priority)
{
- blender::Vector<ExecutionGroup *> result;
-
- for (ExecutionGroup *group : m_groups) {
- if (group->isOutputExecutionGroup() && group->getRenderPriotrity() == priority) {
- result.append(group);
+ for (ExecutionGroup *execution_group : m_groups) {
+ if (execution_group->isOutputExecutionGroup() &&
+ execution_group->getRenderPriority() == priority) {
+ execution_group->execute(this);
}
}
- return result;
}
diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.h b/source/blender/compositor/intern/COM_ExecutionSystem.h
index 6a50cc6906b..c12380fe839 100644
--- a/source/blender/compositor/intern/COM_ExecutionSystem.h
+++ b/source/blender/compositor/intern/COM_ExecutionSystem.h
@@ -135,12 +135,6 @@ class ExecutionSystem {
blender::Vector<ExecutionGroup *> m_groups;
private: // methods
- /**
- * find all execution group with output nodes
- */
- blender::Vector<ExecutionGroup *> find_output_execution_groups(
- CompositorPriority priority) const;
-
public:
/**
* \brief Create a new ExecutionSystem and initialize it with the
diff --git a/source/blender/compositor/intern/COM_MemoryProxy.h b/source/blender/compositor/intern/COM_MemoryProxy.h
index a40e6f95dce..ee98ff41630 100644
--- a/source/blender/compositor/intern/COM_MemoryProxy.h
+++ b/source/blender/compositor/intern/COM_MemoryProxy.h
@@ -45,16 +45,6 @@ class MemoryProxy {
ExecutionGroup *m_executor;
/**
- * \brief datatype of this MemoryProxy
- */
- /* DataType m_datatype; */ /* UNUSED */
-
- /**
- * \brief channel information of this buffer
- */
- /* ChannelInfo m_channelInfo[COM_NUMBER_OF_CHANNELS]; */ /* UNUSED */
-
- /**
* \brief the allocated memory
*/
MemoryBuffer *m_buffer;
diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.cc b/source/blender/compositor/intern/COM_OpenCLDevice.cc
index 9a6012e5c68..4ac6bd50380 100644
--- a/source/blender/compositor/intern/COM_OpenCLDevice.cc
+++ b/source/blender/compositor/intern/COM_OpenCLDevice.cc
@@ -43,16 +43,12 @@ OpenCLDevice::OpenCLDevice(cl_context context,
this->m_program = program;
this->m_queue = nullptr;
this->m_vendorID = vendorId;
-}
-bool OpenCLDevice::initialize()
-{
cl_int error;
this->m_queue = clCreateCommandQueue(this->m_context, this->m_device, 0, &error);
- return false;
}
-void OpenCLDevice::deinitialize()
+OpenCLDevice::~OpenCLDevice()
{
if (this->m_queue) {
clReleaseCommandQueue(this->m_queue);
diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.h b/source/blender/compositor/intern/COM_OpenCLDevice.h
index e4fd397b4e8..30d9a59d182 100644
--- a/source/blender/compositor/intern/COM_OpenCLDevice.h
+++ b/source/blender/compositor/intern/COM_OpenCLDevice.h
@@ -65,26 +65,13 @@ class OpenCLDevice : public Device {
* \param vendorID:
*/
OpenCLDevice(cl_context context, cl_device_id device, cl_program program, cl_int vendorId);
-
- /**
- * \brief initialize the device
- * During initialization the OpenCL cl_command_queue is created
- * the command queue is stored in the field queue.
- * \see queue
- */
- bool initialize();
-
- /**
- * \brief de-initialize the device
- * During de-initialization the command queue is cleared
- */
- void deinitialize();
+ ~OpenCLDevice();
/**
* \brief execute a WorkPackage
* \param work: the WorkPackage to execute
*/
- void execute(WorkPackage *work);
+ void execute(WorkPackage *work) override;
/**
* \brief determine an image format
diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc
index 2bc3ff936b1..7d9dd502762 100644
--- a/source/blender/compositor/intern/COM_WorkScheduler.cc
+++ b/source/blender/compositor/intern/COM_WorkScheduler.cc
@@ -70,7 +70,7 @@ static struct {
/** \brief list of all CPUDevices. for every hardware thread an instance of CPUDevice is
* created
*/
- blender::Vector<CPUDevice *> devices;
+ blender::Vector<CPUDevice> devices;
/** \brief list of all thread for every CPUDevice in cpudevices a thread exists. */
ListBase threads;
@@ -89,7 +89,7 @@ static struct {
cl_program program;
/** \brief list of all OpenCLDevices. for every OpenCL GPU device an instance of OpenCLDevice
* is created. */
- blender::Vector<OpenCLDevice *> devices;
+ blender::Vector<OpenCLDevice> devices;
/** \brief list of all thread for every GPUDevice in cpudevices a thread exists. */
ListBase threads;
/** \brief all scheduled work for the GPU. */
@@ -130,9 +130,8 @@ static void opencl_start(CompositorContext &context)
BLI_threadpool_init(&g_work_scheduler.opencl.threads,
thread_execute_gpu,
g_work_scheduler.opencl.devices.size());
- for (int index = 0; index < g_work_scheduler.opencl.devices.size(); index++) {
- Device *device = g_work_scheduler.opencl.devices[index];
- BLI_threadpool_insert(&g_work_scheduler.opencl.threads, device);
+ for (Device &device : g_work_scheduler.opencl.devices) {
+ BLI_threadpool_insert(&g_work_scheduler.opencl.threads, &device);
}
g_work_scheduler.opencl.active = true;
}
@@ -263,12 +262,10 @@ static void opencl_initialize(const bool use_opencl)
if (error2 != CL_SUCCESS) {
printf("CLERROR[%d]: %s\n", error2, clewErrorString(error2));
}
- OpenCLDevice *clDevice = new OpenCLDevice(g_work_scheduler.opencl.context,
- device,
- g_work_scheduler.opencl.program,
- vendorID);
- clDevice->initialize();
- g_work_scheduler.opencl.devices.append(clDevice);
+ g_work_scheduler.opencl.devices.append(OpenCLDevice(g_work_scheduler.opencl.context,
+ device,
+ g_work_scheduler.opencl.program,
+ vendorID));
}
}
MEM_freeN(cldevices);
@@ -282,25 +279,19 @@ static void opencl_initialize(const bool use_opencl)
static void opencl_deinitialize()
{
- /* Deinitialize OpenCL GPU's. */
- if (g_work_scheduler.opencl.initialized) {
- Device *device;
- while (!g_work_scheduler.opencl.devices.is_empty()) {
- device = g_work_scheduler.opencl.devices.pop_last();
- device->deinitialize();
- delete device;
- }
- if (g_work_scheduler.opencl.program) {
- clReleaseProgram(g_work_scheduler.opencl.program);
- g_work_scheduler.opencl.program = nullptr;
- }
- if (g_work_scheduler.opencl.context) {
- clReleaseContext(g_work_scheduler.opencl.context);
- g_work_scheduler.opencl.context = nullptr;
- }
+ g_work_scheduler.opencl.devices.clear_and_make_inline();
- g_work_scheduler.opencl.initialized = false;
+ if (g_work_scheduler.opencl.program) {
+ clReleaseProgram(g_work_scheduler.opencl.program);
+ g_work_scheduler.opencl.program = nullptr;
+ }
+
+ if (g_work_scheduler.opencl.context) {
+ clReleaseContext(g_work_scheduler.opencl.context);
+ g_work_scheduler.opencl.context = nullptr;
}
+
+ g_work_scheduler.opencl.initialized = false;
}
/* \} */
@@ -346,9 +337,8 @@ static void threading_model_queue_start()
BLI_threadpool_init(&g_work_scheduler.queue.threads,
threading_model_queue_execute,
g_work_scheduler.queue.devices.size());
- for (int index = 0; index < g_work_scheduler.queue.devices.size(); index++) {
- Device *device = g_work_scheduler.queue.devices[index];
- BLI_threadpool_insert(&g_work_scheduler.queue.threads, device);
+ for (Device &device : g_work_scheduler.queue.devices) {
+ BLI_threadpool_insert(&g_work_scheduler.queue.threads, &device);
}
}
@@ -369,25 +359,17 @@ static void threading_model_queue_initialize(const int num_cpu_threads)
{
/* Reinitialize if number of threads doesn't match. */
if (g_work_scheduler.queue.devices.size() != num_cpu_threads) {
- Device *device;
-
- while (!g_work_scheduler.queue.devices.is_empty()) {
- device = g_work_scheduler.queue.devices.pop_last();
- device->deinitialize();
- delete device;
- }
+ g_work_scheduler.queue.devices.clear();
if (g_work_scheduler.queue.initialized) {
BLI_thread_local_delete(g_thread_device);
+ g_work_scheduler.queue.initialized = false;
}
- g_work_scheduler.queue.initialized = false;
}
/* Initialize CPU threads. */
if (!g_work_scheduler.queue.initialized) {
for (int index = 0; index < num_cpu_threads; index++) {
- CPUDevice *device = new CPUDevice(index);
- device->initialize();
- g_work_scheduler.queue.devices.append(device);
+ g_work_scheduler.queue.devices.append(CPUDevice(index));
}
BLI_thread_local_create(g_thread_device);
g_work_scheduler.queue.initialized = true;
@@ -397,12 +379,8 @@ static void threading_model_queue_deinitialize()
{
/* deinitialize CPU threads */
if (g_work_scheduler.queue.initialized) {
- Device *device;
- while (!g_work_scheduler.queue.devices.is_empty()) {
- device = g_work_scheduler.queue.devices.pop_last();
- device->deinitialize();
- delete device;
- }
+ g_work_scheduler.queue.devices.clear_and_make_inline();
+
BLI_thread_local_delete(g_thread_device);
g_work_scheduler.queue.initialized = false;
}
diff --git a/source/blender/editors/curve/editcurve_select.c b/source/blender/editors/curve/editcurve_select.c
index d362ec23370..e3fc8b73172 100644
--- a/source/blender/editors/curve/editcurve_select.c
+++ b/source/blender/editors/curve/editcurve_select.c
@@ -578,8 +578,8 @@ static int de_select_all_exec(bContext *C, wmOperator *op)
changed = ED_curve_deselect_all(cu->editnurb);
break;
case SEL_INVERT:
- changed = ED_curve_select_swap(cu->editnurb,
- v3d->overlay.handle_display == CURVE_HANDLE_NONE);
+ changed = ED_curve_select_swap(
+ cu->editnurb, (v3d && (v3d->overlay.handle_display == CURVE_HANDLE_NONE)));
break;
}
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index 5c041134a74..574670de7ca 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -596,6 +596,21 @@ bool ED_gpencil_stroke_material_editable(Object *ob, const bGPDlayer *gpl, const
return true;
}
+/* Check whether given stroke is visible for the current material. */
+bool ED_gpencil_stroke_material_visible(Object *ob, const bGPDstroke *gps)
+{
+ /* check if the color is editable */
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
+
+ if (gp_style != NULL) {
+ if (gp_style->flag & GP_MATERIAL_HIDE) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
/* ******************************************************** */
/* Space Conversion */
diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h
index f3b5abb1072..e9ac21f60cf 100644
--- a/source/blender/editors/include/ED_gpencil.h
+++ b/source/blender/editors/include/ED_gpencil.h
@@ -150,6 +150,7 @@ bool ED_gpencil_stroke_can_use(const struct bContext *C, const struct bGPDstroke
bool ED_gpencil_stroke_material_editable(struct Object *ob,
const struct bGPDlayer *gpl,
const struct bGPDstroke *gps);
+bool ED_gpencil_stroke_material_visible(struct Object *ob, const struct bGPDstroke *gps);
/* ----------- Grease Pencil Operators ----------------- */
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index fbd7dcd61f2..279239fcc65 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -1816,6 +1816,29 @@ static void ui_but_validate(const uiBut *but)
}
#endif
+/**
+ * Check if the operator \a ot poll is successfull with the context given by \a but (optionally).
+ * \param but: The button that might store context. Can be NULL for convenience (e.g. if there is
+ * no button to take context from, but we still want to poll the operator).
+ */
+bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but)
+{
+ bool result;
+ int opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT;
+
+ if (but && but->context) {
+ CTX_store_set(C, but->context);
+ }
+
+ result = WM_operator_poll_context(C, ot, opcontext);
+
+ if (but && but->context) {
+ CTX_store_set(C, NULL);
+ }
+
+ return result;
+}
+
void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2])
{
wmWindow *window = CTX_wm_window(C);
@@ -1841,17 +1864,9 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
if (but->optype) {
wmOperatorType *ot = but->optype;
- if (but->context) {
- CTX_store_set((bContext *)C, but->context);
- }
-
- if (ot == NULL || WM_operator_poll_context((bContext *)C, ot, but->opcontext) == 0) {
+ if (ot == NULL || !ui_but_context_poll_operator((bContext *)C, ot, but)) {
but->flag |= UI_BUT_DISABLED;
}
-
- if (but->context) {
- CTX_store_set((bContext *)C, NULL);
- }
}
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c
index fb8d32b3b84..178f663ff58 100644
--- a/source/blender/editors/interface/interface_eyedropper.c
+++ b/source/blender/editors/interface/interface_eyedropper.c
@@ -153,7 +153,7 @@ uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *ev
{
bScreen *screen = CTX_wm_screen(C);
ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
- ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->x, event->y);
+ const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->x, event->y);
uiBut *but = ui_but_find_mouse_over(region, event);
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index ee5c3f53f5e..a5a5a69728e 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -1594,7 +1594,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void
if (done) {
wmWindow *win = CTX_wm_window(C);
- ARegion *region = CTX_wm_region(C);
+ const ARegion *region = CTX_wm_region(C);
uiBut *but = ui_but_find_mouse_over_ex(
region, drag_info->xy_init[0], drag_info->xy_init[1], true);
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 1d4a44e0c76..4c96512b4f3 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -647,6 +647,8 @@ extern bool ui_but_menu_draw_as_popover(const uiBut *but);
void ui_but_range_set_hard(uiBut *but);
void ui_but_range_set_soft(uiBut *but);
+bool ui_but_context_poll_operator(struct bContext *C, struct wmOperatorType *ot, const uiBut *but);
+
extern void ui_but_update(uiBut *but);
extern void ui_but_update_edited(uiBut *but);
extern bool ui_but_is_float(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
@@ -1108,11 +1110,11 @@ bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, in
uiBut *ui_list_find_mouse_over(struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
-uiBut *ui_but_find_mouse_over_ex(struct ARegion *region,
+uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,
const int x,
const int y,
const bool labeledit) ATTR_WARN_UNUSED_RESULT;
-uiBut *ui_but_find_mouse_over(struct ARegion *region,
+uiBut *ui_but_find_mouse_over(const struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_find_rect_over(const struct ARegion *region,
const rcti *rect_px) ATTR_WARN_UNUSED_RESULT;
diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c
index 83e48fad157..aa10d092f5e 100644
--- a/source/blender/editors/interface/interface_query.c
+++ b/source/blender/editors/interface/interface_query.c
@@ -265,7 +265,10 @@ bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEv
}
/* x and y are only used in case event is NULL... */
-uiBut *ui_but_find_mouse_over_ex(ARegion *region, const int x, const int y, const bool labeledit)
+uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
+ const int x,
+ const int y,
+ const bool labeledit)
{
uiBut *butover = NULL;
@@ -303,7 +306,7 @@ uiBut *ui_but_find_mouse_over_ex(ARegion *region, const int x, const int y, cons
return butover;
}
-uiBut *ui_but_find_mouse_over(ARegion *region, const wmEvent *event)
+uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event)
{
return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0);
}
diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c
index eaefc2c3736..ad0c523a594 100644
--- a/source/blender/editors/interface/interface_style.c
+++ b/source/blender/editors/interface/interface_style.c
@@ -23,6 +23,7 @@
#include <limits.h>
#include <math.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c
index 74668b2f3a3..ff42d434f29 100644
--- a/source/blender/editors/interface/interface_template_search_menu.c
+++ b/source/blender/editors/interface/interface_template_search_menu.c
@@ -21,6 +21,7 @@
* Accessed via the #WM_OT_search_menu operator.
*/
+#include <stdio.h>
#include <string.h>
#include "MEM_guardedalloc.h"
diff --git a/source/blender/editors/io/CMakeLists.txt b/source/blender/editors/io/CMakeLists.txt
index e7effd05d34..d45c7ca9b75 100644
--- a/source/blender/editors/io/CMakeLists.txt
+++ b/source/blender/editors/io/CMakeLists.txt
@@ -24,6 +24,7 @@ set(INC
../../depsgraph
../../io/alembic
../../io/collada
+ ../../io/gpencil
../../io/usd
../../makesdna
../../makesrna
@@ -39,12 +40,16 @@ set(SRC
io_alembic.c
io_cache.c
io_collada.c
+ io_gpencil_import.c
+ io_gpencil_export.c
+ io_gpencil_utils.c
io_ops.c
io_usd.c
io_alembic.h
io_cache.h
io_collada.h
+ io_gpencil.h
io_ops.h
io_usd.h
)
@@ -79,4 +84,14 @@ if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
+if(WITH_PUGIXML)
+ add_definitions(-DWITH_PUGIXML)
+endif()
+
+if(WITH_HARU)
+ add_definitions(-DWITH_HARU)
+endif()
+
+list(APPEND LIB bf_gpencil)
+
blender_add_lib(bf_editor_io "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/editors/io/io_gpencil.h b/source/blender/editors/io/io_gpencil.h
new file mode 100644
index 00000000000..98cb8b13310
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil.h
@@ -0,0 +1,45 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __IO_GPENCIL_H__
+#define __IO_GPENCIL_H__
+
+/** \file
+ * \ingroup editor/io
+ */
+
+struct ARegion;
+struct bContext;
+struct View3D;
+struct wmOperator;
+struct wmOperatorType;
+
+void WM_OT_gpencil_import_svg(struct wmOperatorType *ot);
+
+#ifdef WITH_PUGIXML
+void WM_OT_gpencil_export_svg(struct wmOperatorType *ot);
+#endif
+#ifdef WITH_HARU
+void WM_OT_gpencil_export_pdf(struct wmOperatorType *ot);
+#endif
+
+struct ARegion *get_invoke_region(struct bContext *C);
+struct View3D *get_invoke_view3d(struct bContext *C);
+
+#endif /* __IO_GPENCIL_H__ */
diff --git a/source/blender/editors/io/io_gpencil_export.c b/source/blender/editors/io/io_gpencil_export.c
new file mode 100644
index 00000000000..6f1e503403b
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil_export.c
@@ -0,0 +1,430 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editor/io
+ */
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+#include "BKE_screen.h"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "io_gpencil.h"
+
+#include "gpencil_io.h"
+
+/* Definition of enum elements to export. */
+/* Common props for exporting. */
+static void gpencil_export_common_props_definition(wmOperatorType *ot)
+{
+ static const EnumPropertyItem select_items[] = {
+ {GP_EXPORT_ACTIVE, "ACTIVE", 0, "Active", "Include only the active object"},
+ {GP_EXPORT_SELECTED, "SELECTED", 0, "Selected", "Include selected objects"},
+ {GP_EXPORT_VISIBLE, "VISIBLE", 0, "Visible", "Include all visible objects"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_boolean(ot->srna, "use_fill", true, "Fill", "Export strokes with fill enabled");
+ RNA_def_enum(ot->srna,
+ "selected_object_type",
+ select_items,
+ GP_EXPORT_SELECTED,
+ "Object",
+ "Which objects to include in the export");
+ RNA_def_float(ot->srna,
+ "stroke_sample",
+ 0.0f,
+ 0.0f,
+ 100.0f,
+ "Sampling",
+ "Precision of stroke sampling. Low values mean a more precise result, and zero "
+ "disables sampling",
+ 0.0f,
+ 100.0f);
+ RNA_def_boolean(ot->srna,
+ "use_normalized_thickness",
+ false,
+ "Normalize",
+ "Export strokes with constant thickness");
+}
+
+static void set_export_filepath(bContext *C, wmOperator *op)
+{
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ Main *bmain = CTX_data_main(C);
+ char filepath[FILE_MAX];
+
+ if (BKE_main_blendfile_path(bmain)[0] == '\0') {
+ BLI_strncpy(filepath, "untitled", sizeof(filepath));
+ }
+ else {
+ BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath));
+ }
+
+ BLI_path_extension_replace(filepath, sizeof(filepath), ".pdf");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ }
+}
+
+/* <-------- SVG single frame export. --------> */
+#ifdef WITH_PUGIXML
+static bool wm_gpencil_export_svg_common_check(bContext *UNUSED(C), wmOperator *op)
+{
+ char filepath[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+
+ if (!BLI_path_extension_check(filepath, ".svg")) {
+ BLI_path_extension_ensure(filepath, FILE_MAX, ".svg");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ return true;
+ }
+
+ return false;
+}
+
+static int wm_gpencil_export_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ set_export_filepath(C, op);
+
+ WM_event_add_fileselect(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wm_gpencil_export_svg_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ BKE_report(op->reports, RPT_ERROR, "No filename given");
+ return OPERATOR_CANCELLED;
+ }
+
+ ARegion *region = get_invoke_region(C);
+ if (region == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
+ return OPERATOR_CANCELLED;
+ }
+ View3D *v3d = get_invoke_view3d(C);
+
+ char filename[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filename);
+
+ const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
+ const bool use_norm_thickness = RNA_boolean_get(op->ptr, "use_normalized_thickness");
+ const eGpencilExportSelect select_mode = RNA_enum_get(op->ptr, "selected_object_type");
+
+ const bool use_clip_camera = RNA_boolean_get(op->ptr, "use_clip_camera");
+
+ /* Set flags. */
+ int flag = 0;
+ SET_FLAG_FROM_TEST(flag, use_fill, GP_EXPORT_FILL);
+ SET_FLAG_FROM_TEST(flag, use_norm_thickness, GP_EXPORT_NORM_THICKNESS);
+ SET_FLAG_FROM_TEST(flag, use_clip_camera, GP_EXPORT_CLIP_CAMERA);
+
+ GpencilIOParams params = {.C = C,
+ .region = region,
+ .v3d = v3d,
+ .ob = ob,
+ .mode = GP_EXPORT_TO_SVG,
+ .frame_start = CFRA,
+ .frame_end = CFRA,
+ .frame_cur = CFRA,
+ .flag = flag,
+ .scale = 1.0f,
+ .select_mode = select_mode,
+ .frame_mode = GP_EXPORT_FRAME_ACTIVE,
+ .stroke_sample = RNA_float_get(op->ptr, "stroke_sample"),
+ .resolution = 1.0f};
+
+ /* Do export. */
+ WM_cursor_wait(true);
+ const bool done = gpencil_io_export(filename, &params);
+ WM_cursor_wait(false);
+
+ if (!done) {
+ BKE_report(op->reports, RPT_WARNING, "Unable to export SVG");
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void ui_gpencil_export_svg_settings(uiLayout *layout, PointerRNA *imfptr)
+{
+ uiLayout *box, *row;
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+
+ box = uiLayoutBox(layout);
+
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Scene Options"), ICON_NONE);
+
+ row = uiLayoutRow(box, false);
+ uiItemR(row, imfptr, "selected_object_type", 0, NULL, ICON_NONE);
+
+ box = uiLayoutBox(layout);
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Export Options"), ICON_NONE);
+
+ uiLayout *col = uiLayoutColumn(box, false);
+ uiItemR(col, imfptr, "stroke_sample", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "use_fill", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "use_clip_camera", 0, NULL, ICON_NONE);
+}
+
+static void wm_gpencil_export_svg_draw(bContext *UNUSED(C), wmOperator *op)
+{
+ PointerRNA ptr;
+
+ RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+ ui_gpencil_export_svg_settings(op->layout, &ptr);
+}
+
+static bool wm_gpencil_export_svg_poll(bContext *C)
+{
+ if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_OT_gpencil_export_svg(wmOperatorType *ot)
+{
+ ot->name = "Export to SVG";
+ ot->description = "Export grease pencil to SVG";
+ ot->idname = "WM_OT_gpencil_export_svg";
+
+ ot->invoke = wm_gpencil_export_svg_invoke;
+ ot->exec = wm_gpencil_export_svg_exec;
+ ot->poll = wm_gpencil_export_svg_poll;
+ ot->ui = wm_gpencil_export_svg_draw;
+ ot->check = wm_gpencil_export_svg_common_check;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_OBJECT_IO,
+ FILE_BLENDER,
+ FILE_SAVE,
+ WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_ALPHA);
+
+ gpencil_export_common_props_definition(ot);
+
+ RNA_def_boolean(ot->srna,
+ "use_clip_camera",
+ false,
+ "Clip Camera",
+ "Clip drawings to camera size when export in camera view");
+}
+#endif
+
+/* <-------- PDF single frame export. --------> */
+#ifdef WITH_HARU
+static bool wm_gpencil_export_pdf_common_check(bContext *UNUSED(C), wmOperator *op)
+{
+
+ char filepath[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+
+ if (!BLI_path_extension_check(filepath, ".pdf")) {
+ BLI_path_extension_ensure(filepath, FILE_MAX, ".pdf");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ return true;
+ }
+
+ return false;
+}
+
+static int wm_gpencil_export_pdf_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ set_export_filepath(C, op);
+
+ WM_event_add_fileselect(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wm_gpencil_export_pdf_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ BKE_report(op->reports, RPT_ERROR, "No filename given");
+ return OPERATOR_CANCELLED;
+ }
+
+ ARegion *region = get_invoke_region(C);
+ if (region == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
+ return OPERATOR_CANCELLED;
+ }
+ View3D *v3d = get_invoke_view3d(C);
+
+ char filename[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filename);
+
+ const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
+ const bool use_norm_thickness = RNA_boolean_get(op->ptr, "use_normalized_thickness");
+ const short select_mode = RNA_enum_get(op->ptr, "selected_object_type");
+ const short frame_mode = RNA_enum_get(op->ptr, "frame_mode");
+
+ /* Set flags. */
+ int flag = 0;
+ SET_FLAG_FROM_TEST(flag, use_fill, GP_EXPORT_FILL);
+ SET_FLAG_FROM_TEST(flag, use_norm_thickness, GP_EXPORT_NORM_THICKNESS);
+
+ GpencilIOParams params = {.C = C,
+ .region = region,
+ .v3d = v3d,
+ .ob = ob,
+ .mode = GP_EXPORT_TO_PDF,
+ .frame_start = SFRA,
+ .frame_end = EFRA,
+ .frame_cur = CFRA,
+ .flag = flag,
+ .scale = 1.0f,
+ .select_mode = select_mode,
+ .frame_mode = frame_mode,
+ .stroke_sample = RNA_float_get(op->ptr, "stroke_sample"),
+ .resolution = 1.0f};
+
+ /* Do export. */
+ WM_cursor_wait(true);
+ const bool done = gpencil_io_export(filename, &params);
+ WM_cursor_wait(false);
+
+ if (!done) {
+ BKE_report(op->reports, RPT_WARNING, "Unable to export PDF");
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void ui_gpencil_export_pdf_settings(uiLayout *layout, PointerRNA *imfptr)
+{
+ uiLayout *box, *row, *col, *sub;
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+
+ box = uiLayoutBox(layout);
+
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Scene Options"), ICON_NONE);
+
+ row = uiLayoutRow(box, false);
+ uiItemR(row, imfptr, "selected_object_type", 0, NULL, ICON_NONE);
+
+ box = uiLayoutBox(layout);
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Export Options"), ICON_NONE);
+
+ col = uiLayoutColumn(box, false);
+ sub = uiLayoutColumn(col, true);
+ uiItemR(sub, imfptr, "frame_mode", 0, IFACE_("Frame"), ICON_NONE);
+
+ uiLayoutSetPropSep(box, true);
+
+ sub = uiLayoutColumn(col, true);
+ uiItemR(sub, imfptr, "stroke_sample", 0, NULL, ICON_NONE);
+ uiItemR(sub, imfptr, "use_fill", 0, NULL, ICON_NONE);
+ uiItemR(sub, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE);
+}
+
+static void wm_gpencil_export_pdf_draw(bContext *UNUSED(C), wmOperator *op)
+{
+ PointerRNA ptr;
+
+ RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+ ui_gpencil_export_pdf_settings(op->layout, &ptr);
+}
+
+static bool wm_gpencil_export_pdf_poll(bContext *C)
+{
+ if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_OT_gpencil_export_pdf(wmOperatorType *ot)
+{
+ ot->name = "Export to PDF";
+ ot->description = "Export grease pencil to PDF";
+ ot->idname = "WM_OT_gpencil_export_pdf";
+
+ ot->invoke = wm_gpencil_export_pdf_invoke;
+ ot->exec = wm_gpencil_export_pdf_exec;
+ ot->poll = wm_gpencil_export_pdf_poll;
+ ot->ui = wm_gpencil_export_pdf_draw;
+ ot->check = wm_gpencil_export_pdf_common_check;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_OBJECT_IO,
+ FILE_BLENDER,
+ FILE_SAVE,
+ WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_ALPHA);
+
+ static const EnumPropertyItem gpencil_export_frame_items[] = {
+ {GP_EXPORT_FRAME_ACTIVE, "ACTIVE", 0, "Active", "Include only active frame"},
+ {GP_EXPORT_FRAME_SELECTED, "SELECTED", 0, "Selected", "Include selected frames"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ gpencil_export_common_props_definition(ot);
+ ot->prop = RNA_def_enum(ot->srna,
+ "frame_mode",
+ gpencil_export_frame_items,
+ GP_EXPORT_ACTIVE,
+ "Frames",
+ "Which frames to include in the export");
+}
+#endif
diff --git a/source/blender/editors/io/io_gpencil_import.c b/source/blender/editors/io/io_gpencil_import.c
new file mode 100644
index 00000000000..9768da85940
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil_import.c
@@ -0,0 +1,195 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editor/io
+ */
+
+#include "BLI_path_util.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_report.h"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+
+#include "io_gpencil.h"
+
+#include "gpencil_io.h"
+
+/* <-------- SVG single frame import. --------> */
+static bool wm_gpencil_import_svg_common_check(bContext *UNUSED(C), wmOperator *op)
+{
+
+ char filepath[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+
+ if (!BLI_path_extension_check(filepath, ".svg")) {
+ BLI_path_extension_ensure(filepath, FILE_MAX, ".svg");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ return true;
+ }
+
+ return false;
+}
+
+static int wm_gpencil_import_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ WM_event_add_fileselect(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ BKE_report(op->reports, RPT_ERROR, "No filename given");
+ return OPERATOR_CANCELLED;
+ }
+
+ ARegion *region = get_invoke_region(C);
+ if (region == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
+ return OPERATOR_CANCELLED;
+ }
+ View3D *v3d = get_invoke_view3d(C);
+
+ char filename[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filename);
+
+ /* Set flags. */
+ int flag = 0;
+
+ const int resolution = RNA_int_get(op->ptr, "resolution");
+ const float scale = RNA_float_get(op->ptr, "scale");
+
+ GpencilIOParams params = {
+ .C = C,
+ .region = region,
+ .v3d = v3d,
+ .ob = NULL,
+ .mode = GP_IMPORT_FROM_SVG,
+ .frame_start = CFRA,
+ .frame_end = CFRA,
+ .frame_cur = CFRA,
+ .flag = flag,
+ .scale = scale,
+ .select_mode = 0,
+ .frame_mode = 0,
+ .stroke_sample = 0.0f,
+ .resolution = resolution,
+ };
+
+ /* Do Import. */
+ WM_cursor_wait(1);
+ const bool done = gpencil_io_import(filename, &params);
+ WM_cursor_wait(0);
+
+ if (!done) {
+ BKE_report(op->reports, RPT_WARNING, "Unable to import SVG");
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void ui_gpencil_import_svg_settings(uiLayout *layout, PointerRNA *imfptr)
+{
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+ uiLayout *col = uiLayoutColumn(layout, false);
+ uiItemR(col, imfptr, "resolution", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "scale", 0, NULL, ICON_NONE);
+}
+
+static void wm_gpencil_import_svg_draw(bContext *UNUSED(C), wmOperator *op)
+{
+ PointerRNA ptr;
+ RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+ ui_gpencil_import_svg_settings(op->layout, &ptr);
+}
+
+static bool wm_gpencil_import_svg_poll(bContext *C)
+{
+ if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_OT_gpencil_import_svg(wmOperatorType *ot)
+{
+ ot->name = "Import SVG";
+ ot->description = "Import SVG into grease pencil";
+ ot->idname = "WM_OT_gpencil_import_svg";
+
+ ot->invoke = wm_gpencil_import_svg_invoke;
+ ot->exec = wm_gpencil_import_svg_exec;
+ ot->poll = wm_gpencil_import_svg_poll;
+ ot->ui = wm_gpencil_import_svg_draw;
+ ot->check = wm_gpencil_import_svg_common_check;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_OBJECT_IO,
+ FILE_BLENDER,
+ FILE_OPENFILE,
+ WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_DEFAULT);
+
+ RNA_def_int(ot->srna,
+ "resolution",
+ 10,
+ 1,
+ 30,
+ "Resolution",
+ "Resolution of the generated strokes",
+ 1,
+ 20);
+
+ RNA_def_float(ot->srna,
+ "scale",
+ 10.0f,
+ 0.001f,
+ 100.0f,
+ "Scale",
+ "Scale of the final strokes",
+ 0.001f,
+ 100.0f);
+}
diff --git a/source/blender/editors/io/io_gpencil_utils.c b/source/blender/editors/io/io_gpencil_utils.c
new file mode 100644
index 00000000000..259a669519a
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil_utils.c
@@ -0,0 +1,64 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editor/io
+ */
+
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_screen.h"
+
+#include "WM_api.h"
+
+#include "io_gpencil.h"
+
+ARegion *get_invoke_region(bContext *C)
+{
+ bScreen *screen = CTX_wm_screen(C);
+ if (screen == NULL) {
+ return NULL;
+ }
+ ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
+ if (area == NULL) {
+ return NULL;
+ }
+
+ ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
+
+ return region;
+}
+
+View3D *get_invoke_view3d(bContext *C)
+{
+ bScreen *screen = CTX_wm_screen(C);
+ if (screen == NULL) {
+ return NULL;
+ }
+ ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
+ if (area == NULL) {
+ return NULL;
+ }
+ if (area) {
+ return area->spacedata.first;
+ }
+
+ return NULL;
+}
diff --git a/source/blender/editors/io/io_ops.c b/source/blender/editors/io/io_ops.c
index acb511a414d..9fa34a1c55d 100644
--- a/source/blender/editors/io/io_ops.c
+++ b/source/blender/editors/io/io_ops.c
@@ -38,6 +38,7 @@
#endif
#include "io_cache.h"
+#include "io_gpencil.h"
void ED_operatortypes_io(void)
{
@@ -54,6 +55,16 @@ void ED_operatortypes_io(void)
WM_operatortype_append(WM_OT_usd_export);
#endif
+ WM_operatortype_append(WM_OT_gpencil_import_svg);
+
+#ifdef WITH_PUGIXML
+ WM_operatortype_append(WM_OT_gpencil_export_svg);
+#endif
+
+#ifdef WITH_HARU
+ WM_operatortype_append(WM_OT_gpencil_export_pdf);
+#endif
+
WM_operatortype_append(CACHEFILE_OT_open);
WM_operatortype_append(CACHEFILE_OT_reload);
}
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
index e4527740164..50dc1af5ca8 100644
--- a/source/blender/editors/object/object_add.c
+++ b/source/blender/editors/object/object_add.c
@@ -1440,41 +1440,6 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
-static const EnumPropertyItem *object_gpencil_add_options(bContext *C,
- PointerRNA *UNUSED(ptr),
- PropertyRNA *UNUSED(prop),
- bool *r_free)
-{
- EnumPropertyItem *item = NULL;
- const EnumPropertyItem *item_ref = rna_enum_object_gpencil_type_items;
- int totitem = 0;
- int i = 0;
- int orig_count = RNA_enum_items_count(item_ref);
-
- /* Default types. */
- for (i = 0; i < orig_count; i++) {
- if (item_ref[i].value == GP_LRT_OBJECT || item_ref[i].value == GP_LRT_COLLECTION ||
- item_ref[i].value == GP_LRT_SCENE) {
- if (item_ref[i].value == GP_LRT_SCENE) {
- /* separator before line art types */
- RNA_enum_item_add_separator(&item, &totitem);
- }
- else if (item_ref[i].value == GP_LRT_OBJECT) {
- Object *ob = CTX_data_active_object(C);
- if (!ob || ob->type != OB_MESH) {
- continue;
- }
- }
- }
- RNA_enum_item_add(&item, &totitem, &item_ref[i]);
- }
-
- RNA_enum_item_end(&item, &totitem);
- *r_free = true;
-
- return item;
-}
-
void OBJECT_OT_gpencil_add(wmOperatorType *ot)
{
/* identifiers */
@@ -1495,7 +1460,6 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot)
ED_object_add_generic_props(ot, false);
ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", "");
- RNA_def_enum_funcs(ot->prop, object_gpencil_add_options);
}
/** \} */
diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c
index f5ec9a0e8a1..4c9f80bfa64 100644
--- a/source/blender/editors/space_file/filelist.c
+++ b/source/blender/editors/space_file/filelist.c
@@ -2543,7 +2543,7 @@ int ED_path_extension_type(const char *path)
if (BLI_path_extension_check(path, ".zip")) {
return FILE_TYPE_ARCHIVE;
}
- if (BLI_path_extension_check_n(path, ".obj", ".3ds", ".fbx", ".glb", ".gltf", NULL)) {
+ if (BLI_path_extension_check_n(path, ".obj", ".3ds", ".fbx", ".glb", ".gltf", ".svg", NULL)) {
return FILE_TYPE_OBJECT_IO;
}
if (BLI_path_extension_check_array(path, imb_ext_image)) {
diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c
index 9d6c81e00b5..f5b2eb01269 100644
--- a/source/blender/editors/space_node/drawnode.c
+++ b/source/blender/editors/space_node/drawnode.c
@@ -3431,13 +3431,13 @@ static void std_node_socket_draw(
}
break;
case SOCK_RGBA: {
- uiLayout *row = uiLayoutSplit(layout, 0.5f, false);
+ uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, 0);
uiItemR(row, ptr, "default_value", DEFAULT_FLAGS, "", 0);
break;
}
case SOCK_STRING: {
- uiLayout *row = uiLayoutSplit(layout, 0.5f, false);
+ uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, 0);
const bNodeTree *node_tree = (const bNodeTree *)node_ptr->owner_id;
diff --git a/source/blender/editors/space_node/node_add.c b/source/blender/editors/space_node/node_add.c
index a646804e0fd..c4fe9e9e531 100644
--- a/source/blender/editors/space_node/node_add.c
+++ b/source/blender/editors/space_node/node_add.c
@@ -472,6 +472,8 @@ static int node_add_object_exec(bContext *C, wmOperator *op)
snode_notify(C, snode);
snode_dag_update(C, snode);
+ ED_node_tag_update_nodetree(bmain, ntree, object_node);
+
return OPERATOR_FINISHED;
}
@@ -496,7 +498,8 @@ static int node_add_object_invoke(bContext *C, wmOperator *op, const wmEvent *ev
static bool node_add_object_poll(bContext *C)
{
const SpaceNode *snode = CTX_wm_space_node(C);
- return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY);
+ return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY) &&
+ !UI_but_active_drop_name(C);
}
void NODE_OT_add_object(wmOperatorType *ot)
@@ -568,6 +571,8 @@ static int node_add_texture_exec(bContext *C, wmOperator *op)
snode_notify(C, snode);
snode_dag_update(C, snode);
+ ED_node_tag_update_nodetree(bmain, ntree, texture_node);
+
return OPERATOR_FINISHED;
}
@@ -592,7 +597,8 @@ static int node_add_texture_invoke(bContext *C, wmOperator *op, const wmEvent *e
static bool node_add_texture_poll(bContext *C)
{
const SpaceNode *snode = CTX_wm_space_node(C);
- return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY);
+ return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY) &&
+ !UI_but_active_drop_name(C);
}
void NODE_OT_add_texture(wmOperatorType *ot)
@@ -670,6 +676,8 @@ static int node_add_collection_exec(bContext *C, wmOperator *op)
snode_notify(C, snode);
snode_dag_update(C, snode);
+ ED_node_tag_update_nodetree(bmain, ntree, collection_node);
+
return OPERATOR_FINISHED;
}
@@ -694,7 +702,8 @@ static int node_add_collection_invoke(bContext *C, wmOperator *op, const wmEvent
static bool node_add_collection_poll(bContext *C)
{
const SpaceNode *snode = CTX_wm_space_node(C);
- return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY);
+ return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY) &&
+ !UI_but_active_drop_name(C);
}
void NODE_OT_add_collection(wmOperatorType *ot)
diff --git a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc
index 89c9960a24f..f00cf3c34c0 100644
--- a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc
+++ b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc
@@ -148,6 +148,14 @@ void TreeDisplayViewLayer::add_layer_collections_recursive(ListBase &tree,
if (!exclude && show_objects_) {
add_layer_collection_objects(ten->subtree, *lc, *ten);
}
+
+ const bool lib_overrides_visible = !SUPPORT_FILTER_OUTLINER(&space_outliner_) ||
+ ((space_outliner_.filter & SO_FILTER_NO_LIB_OVERRIDE) == 0);
+
+ if (lib_overrides_visible && ID_IS_OVERRIDE_LIBRARY_REAL(&lc->collection->id)) {
+ outliner_add_element(
+ &space_outliner_, &ten->subtree, &lc->collection->id, ten, TSE_LIBRARY_OVERRIDE_BASE, 0);
+ }
}
}
diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c
index 9903711834a..da94eef4917 100644
--- a/source/blender/editors/util/ed_util.c
+++ b/source/blender/editors/util/ed_util.c
@@ -146,7 +146,7 @@ void ED_editors_init(bContext *C)
ED_object_wpaintmode_enter_ex(bmain, depsgraph, scene, ob);
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
else {
diff --git a/source/blender/editors/util/select_utils.c b/source/blender/editors/util/select_utils.c
index 85f48e6d397..14a6d751bb1 100644
--- a/source/blender/editors/util/select_utils.c
+++ b/source/blender/editors/util/select_utils.c
@@ -94,7 +94,7 @@ int ED_select_similar_compare_float(const float delta, const float thresh, const
case SIM_CMP_LT:
return ((delta - thresh) < FLT_EPSILON);
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
return 0;
}
}
@@ -124,7 +124,7 @@ bool ED_select_similar_compare_float_tree(const KDTree_1d *tree,
nearest_edge_length = FLT_MAX;
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
return false;
}
diff --git a/source/blender/editors/uvedit/uvedit_rip.c b/source/blender/editors/uvedit/uvedit_rip.c
index c8aa21191ba..e1b9a287457 100644
--- a/source/blender/editors/uvedit/uvedit_rip.c
+++ b/source/blender/editors/uvedit/uvedit_rip.c
@@ -169,7 +169,7 @@ static BMLoop *bm_loop_find_other_fan_loop_with_visible_face(BMLoop *l_src,
l_other = l_other->prev;
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
return l_other;
@@ -189,7 +189,7 @@ static BMLoop *bm_vert_step_fan_loop_uv(BMLoop *l, BMEdge **e_step, const int cd
l_next = l;
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
return NULL;
}
diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c
index f46975c9378..c10e132a4e2 100644
--- a/source/blender/editors/uvedit/uvedit_select.c
+++ b/source/blender/editors/uvedit/uvedit_select.c
@@ -2213,7 +2213,7 @@ static int uv_mouse_select_loop_generic_multi(bContext *C,
flush = uv_select_edgering(sima, scene, obedit, &hit, extend);
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
if (ts->uv_flag & UV_SYNC_SELECTION) {
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
index 4efc1d9eaae..2934b89c747 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
@@ -136,36 +136,39 @@ static void bakeModifier(Main *UNUSED(bmain),
bGPdata *gpd = ob->data;
int oldframe = (int)DEG_get_ctime(depsgraph);
- if (mmd->object == NULL) {
+ if ((mmd->object == NULL) || (mmd->object->type != OB_LATTICE)) {
return;
}
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
- /* apply lattice effects on this frame
- * NOTE: this assumes that we don't want lattice animation on non-keyframed frames
+ /* Apply lattice effects on this frame
+ * NOTE: this assumes that we don't want lattice animation on non-keyframed frames.
*/
CFRA = gpf->framenum;
BKE_scene_graph_update_for_newframe(depsgraph);
- /* recalculate lattice data */
- BKE_gpencil_lattice_init(ob);
+ /* Recalculate lattice data. */
+ if (mmd->cache_data) {
+ BKE_lattice_deform_data_destroy(mmd->cache_data);
+ }
+ mmd->cache_data = BKE_lattice_deform_data_create(mmd->object, ob);
- /* compute lattice effects on this frame */
+ /* Compute lattice effects on this frame. */
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
deformStroke(md, depsgraph, ob, gpl, gpf, gps);
}
}
}
- /* free lingering data */
+ /* Free lingering data. */
ldata = (struct LatticeDeformData *)mmd->cache_data;
if (ldata) {
BKE_lattice_deform_data_destroy(ldata);
mmd->cache_data = NULL;
}
- /* return frame state and DB to original state */
+ /* Return frame state and DB to original state. */
CFRA = oldframe;
BKE_scene_graph_update_for_newframe(depsgraph);
}
diff --git a/source/blender/gpu/intern/gpu_material_library.c b/source/blender/gpu/intern/gpu_material_library.c
index 64cd375d466..175facc0a8d 100644
--- a/source/blender/gpu/intern/gpu_material_library.c
+++ b/source/blender/gpu/intern/gpu_material_library.c
@@ -23,6 +23,7 @@
* GPU material library parsing and code generation.
*/
+#include <stdio.h>
#include <string.h>
#include "MEM_guardedalloc.h"
diff --git a/source/blender/gpu/intern/gpu_node_graph.c b/source/blender/gpu/intern/gpu_node_graph.c
index bf91a5bbb4d..b220c60e979 100644
--- a/source/blender/gpu/intern/gpu_node_graph.c
+++ b/source/blender/gpu/intern/gpu_node_graph.c
@@ -23,6 +23,7 @@
* Intermediate node graph for generating GLSL shaders.
*/
+#include <stdio.h>
#include <string.h>
#include "MEM_guardedalloc.h"
diff --git a/source/blender/imbuf/intern/thumbs.c b/source/blender/imbuf/intern/thumbs.c
index 0d2080b5f0a..61bc185eb8d 100644
--- a/source/blender/imbuf/intern/thumbs.c
+++ b/source/blender/imbuf/intern/thumbs.c
@@ -393,7 +393,7 @@ static ImBuf *thumb_create_ex(const char *file_path,
img = IMB_thumb_load_font(file_path, tsize, tsize);
break;
default:
- BLI_assert(0); /* This should never happen */
+ BLI_assert_unreachable(); /* This should never happen */
}
}
@@ -738,7 +738,7 @@ void IMB_thumb_path_unlock(const char *path)
if (thumb_locks.locked_paths) {
if (!BLI_gset_remove(thumb_locks.locked_paths, key, MEM_freeN)) {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
BLI_condition_notify_all(&thumb_locks.cond);
}
diff --git a/source/blender/imbuf/intern/thumbs_blend.c b/source/blender/imbuf/intern/thumbs_blend.c
index 106e4618847..b7b31b3e56a 100644
--- a/source/blender/imbuf/intern/thumbs_blend.c
+++ b/source/blender/imbuf/intern/thumbs_blend.c
@@ -18,6 +18,7 @@
* \ingroup imbuf
*/
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/source/blender/io/CMakeLists.txt b/source/blender/io/CMakeLists.txt
index 360cacc4360..f11ad7627b9 100644
--- a/source/blender/io/CMakeLists.txt
+++ b/source/blender/io/CMakeLists.txt
@@ -35,3 +35,5 @@ endif()
if(WITH_USD)
add_subdirectory(usd)
endif()
+
+add_subdirectory(gpencil)
diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp
index 644ecc18e9b..6ba31599fcd 100644
--- a/source/blender/io/collada/Materials.cpp
+++ b/source/blender/io/collada/Materials.cpp
@@ -25,6 +25,8 @@ MaterialNode::MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map
shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, "");
output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, "");
add_link(shader_node, 0, output_node, 0);
+
+ ntreeUpdateTree(CTX_data_main(C), ntree);
}
}
@@ -59,6 +61,8 @@ MaterialNode::MaterialNode(bContext *C,
shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, "");
output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, "");
add_link(shader_node, 0, output_node, 0);
+
+ ntreeUpdateTree(CTX_data_main(C), ntree);
#endif
}
diff --git a/source/blender/io/gpencil/CMakeLists.txt b/source/blender/io/gpencil/CMakeLists.txt
new file mode 100644
index 00000000000..11c9affbe5a
--- /dev/null
+++ b/source/blender/io/gpencil/CMakeLists.txt
@@ -0,0 +1,99 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# The Original Code is Copyright (C) 2006, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ .
+ ../common
+ ../../blenkernel
+ ../../blenlib
+ ../../blenloader
+ ../../bmesh
+ ../../depsgraph
+ ../../editors/include
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/clog
+ ../../../../intern/guardedalloc
+ ../../../../intern/utfconv
+)
+
+set(INC_SYS
+)
+
+set(SRC
+ intern/gpencil_io_capi.cc
+
+ # This line must be removed if NanoSVG is moved to extern
+ nanosvg/nanosvg.h
+
+ gpencil_io.h
+
+ intern/gpencil_io_base.h
+ intern/gpencil_io_base.cc
+
+ intern/gpencil_io_import_base.h
+ intern/gpencil_io_import_svg.h
+ intern/gpencil_io_import_base.cc
+ intern/gpencil_io_import_svg.cc
+
+ intern/gpencil_io_export_base.h
+)
+
+set(LIB
+ bf_blenkernel
+ bf_blenlib
+ bf_io_common
+)
+
+if(WITH_PUGIXML)
+ list(APPEND SRC
+ intern/gpencil_io_export_svg.h
+ intern/gpencil_io_export_svg.cc
+ )
+ list(APPEND INC_SYS
+ ${PUGIXML_INCLUDE_DIR}
+ )
+ list(APPEND LIB
+ ${PUGIXML_LIBRARIES}
+ )
+ add_definitions(-DWITH_PUGIXML)
+endif()
+
+if(WITH_HARU)
+ list(APPEND SRC
+ intern/gpencil_io_export_pdf.h
+ intern/gpencil_io_export_pdf.cc
+ )
+ list(APPEND INC_SYS
+ ${HARU_INCLUDE_DIRS}
+ )
+ list(APPEND LIB
+ ${HARU_LIBRARIES}
+ )
+ add_definitions(-DWITH_HARU)
+endif()
+
+
+list(APPEND LIB
+ ${BOOST_LIBRARIES}
+)
+
+blender_add_lib(bf_gpencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/io/gpencil/gpencil_io.h b/source/blender/io/gpencil/gpencil_io.h
new file mode 100644
index 00000000000..f4b2e59f8c5
--- /dev/null
+++ b/source/blender/io/gpencil/gpencil_io.h
@@ -0,0 +1,92 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ARegion;
+struct bContext;
+struct Object;
+struct View3D;
+
+typedef struct GpencilIOParams {
+ bContext *C;
+ ARegion *region;
+ View3D *v3d;
+ /** Grease pencil object. */
+ Object *ob;
+ /** Mode (see eGpencilIO_Modes). */
+ uint16_t mode;
+ int32_t frame_start;
+ int32_t frame_end;
+ int32_t frame_cur;
+ uint32_t flag;
+ float scale;
+ /** Select mode (see eGpencilExportSelect). */
+ uint16_t select_mode;
+ /** Frame mode (see eGpencilExportFrame). */
+ uint16_t frame_mode;
+ /** Stroke sampling factor. */
+ float stroke_sample;
+ int32_t resolution;
+} GpencilIOParams;
+
+/* GpencilIOParams->flag. */
+typedef enum eGpencilIOParams_Flag {
+ /* Export Filled strokes. */
+ GP_EXPORT_FILL = (1 << 0),
+ /* Export normalized thickness. */
+ GP_EXPORT_NORM_THICKNESS = (1 << 1),
+ /* Clip camera area. */
+ GP_EXPORT_CLIP_CAMERA = (1 << 2),
+} eGpencilIOParams_Flag;
+
+typedef enum eGpencilIO_Modes {
+ GP_EXPORT_TO_SVG = 0,
+ GP_EXPORT_TO_PDF = 1,
+
+ GP_IMPORT_FROM_SVG = 2,
+ /* Add new formats here. */
+} eGpencilIO_Modes;
+
+/* Object to be exported. */
+typedef enum eGpencilExportSelect {
+ GP_EXPORT_ACTIVE = 0,
+ GP_EXPORT_SELECTED = 1,
+ GP_EXPORT_VISIBLE = 2,
+} eGpencilExportSelect;
+
+/* Framerange to be exported. */
+typedef enum eGpencilExportFrame {
+ GP_EXPORT_FRAME_ACTIVE = 0,
+ GP_EXPORT_FRAME_SELECTED = 1,
+} eGpencilExportFrame;
+
+bool gpencil_io_export(const char *filename, struct GpencilIOParams *iparams);
+bool gpencil_io_import(const char *filename, struct GpencilIOParams *iparams);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc
new file mode 100644
index 00000000000..855252e648c
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc
@@ -0,0 +1,386 @@
+
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_path_util.h"
+#include "BLI_span.hh"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_layer_types.h"
+#include "DNA_material_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_camera.h"
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "UI_view2d.h"
+
+#include "ED_view3d.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "gpencil_io_base.h"
+
+using blender::Span;
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilIO::GpencilIO(const GpencilIOParams *iparams)
+{
+ params_ = *iparams;
+
+ /* Easy access data. */
+ bmain_ = CTX_data_main(params_.C);
+ depsgraph_ = CTX_data_depsgraph_pointer(params_.C);
+ scene_ = CTX_data_scene(params_.C);
+ rv3d_ = (RegionView3D *)params_.region->regiondata;
+ gpd_ = (params_.ob != nullptr) ? (bGPdata *)params_.ob->data : nullptr;
+ cfra_ = iparams->frame_cur;
+
+ /* Calculate camera matrix. */
+ Object *cam_ob = params_.v3d->camera;
+ if (cam_ob != nullptr) {
+ /* Set up parameters. */
+ CameraParams params;
+ BKE_camera_params_init(&params);
+ BKE_camera_params_from_object(&params, cam_ob);
+
+ /* Compute matrix, viewplane, .. */
+ RenderData *rd = &scene_->r;
+ BKE_camera_params_compute_viewplane(&params, rd->xsch, rd->ysch, rd->xasp, rd->yasp);
+ BKE_camera_params_compute_matrix(&params);
+
+ float viewmat[4][4];
+ invert_m4_m4(viewmat, cam_ob->obmat);
+
+ mul_m4_m4m4(persmat_, params.winmat, viewmat);
+ }
+ else {
+ unit_m4(persmat_);
+ }
+
+ winx_ = params_.region->winx;
+ winy_ = params_.region->winy;
+
+ /* Camera rectangle. */
+ if (rv3d_->persp == RV3D_CAMOB) {
+ render_x_ = (scene_->r.xsch * scene_->r.size) / 100;
+ render_y_ = (scene_->r.ysch * scene_->r.size) / 100;
+
+ ED_view3d_calc_camera_border(CTX_data_scene(params_.C),
+ depsgraph_,
+ params_.region,
+ params_.v3d,
+ rv3d_,
+ &camera_rect_,
+ true);
+ is_camera_ = true;
+ camera_ratio_ = render_x_ / (camera_rect_.xmax - camera_rect_.xmin);
+ offset_.x = camera_rect_.xmin;
+ offset_.y = camera_rect_.ymin;
+ }
+ else {
+ is_camera_ = false;
+ /* Calc selected object boundbox. Need set initial value to some variables. */
+ camera_ratio_ = 1.0f;
+ offset_.x = 0.0f;
+ offset_.y = 0.0f;
+
+ selected_objects_boundbox_calc();
+ rctf boundbox;
+ selected_objects_boundbox_get(&boundbox);
+
+ render_x_ = boundbox.xmax - boundbox.xmin;
+ render_y_ = boundbox.ymax - boundbox.ymin;
+ offset_.x = boundbox.xmin;
+ offset_.y = boundbox.ymin;
+ }
+}
+
+/** Create a list of selected objects sorted from back to front */
+void GpencilIO::create_object_list()
+{
+ ViewLayer *view_layer = CTX_data_view_layer(params_.C);
+
+ float3 camera_z_axis;
+ copy_v3_v3(camera_z_axis, rv3d_->viewinv[2]);
+ ob_list_.clear();
+
+ LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
+ Object *object = base->object;
+
+ if (object->type != OB_GPENCIL) {
+ continue;
+ }
+ if ((params_.select_mode == GP_EXPORT_ACTIVE) && (params_.ob != object)) {
+ continue;
+ }
+
+ if ((params_.select_mode == GP_EXPORT_SELECTED) && ((base->flag & BASE_SELECTED) == 0)) {
+ continue;
+ }
+
+ /* Save z-depth from view to sort from back to front. */
+ if (is_camera_) {
+ float camera_z = dot_v3v3(camera_z_axis, object->obmat[3]);
+ ObjectZ obz = {camera_z, object};
+ ob_list_.append(obz);
+ }
+ else {
+ float zdepth = 0;
+ if (rv3d_) {
+ if (rv3d_->is_persp) {
+ zdepth = ED_view3d_calc_zfac(rv3d_, object->obmat[3], nullptr);
+ }
+ else {
+ zdepth = -dot_v3v3(rv3d_->viewinv[2], object->obmat[3]);
+ }
+ ObjectZ obz = {zdepth * -1.0f, object};
+ ob_list_.append(obz);
+ }
+ }
+ }
+ /* Sort list of objects from point of view. */
+ std::sort(ob_list_.begin(), ob_list_.end(), [](const ObjectZ &obz1, const ObjectZ &obz2) {
+ return obz1.zdepth < obz2.zdepth;
+ });
+}
+
+/**
+ * Set file input_text full path.
+ * \param filename: Path of the file provided by save dialog.
+ */
+void GpencilIO::filename_set(const char *filename)
+{
+ BLI_strncpy(filename_, filename, FILE_MAX);
+ BLI_path_abs(filename_, BKE_main_blendfile_path(bmain_));
+}
+
+/** Convert to screenspace. */
+bool GpencilIO::gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co)
+{
+ float3 parent_co = diff_mat_ * co;
+ float2 screen_co;
+ eV3DProjTest test = (eV3DProjTest)(V3D_PROJ_RET_OK);
+ if (ED_view3d_project_float_global(params_.region, parent_co, screen_co, test) ==
+ V3D_PROJ_RET_OK) {
+ if (!ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1])) {
+ copy_v2_v2(r_co, screen_co);
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co[0] = winx_ - r_co[0];
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co[1] = winy_ - r_co[1];
+ }
+ /* Apply offset and scale. */
+ sub_v2_v2(r_co, &offset_.x);
+ mul_v2_fl(r_co, camera_ratio_);
+
+ return true;
+ }
+ }
+ r_co[0] = V2D_IS_CLIPPED;
+ r_co[1] = V2D_IS_CLIPPED;
+
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co[0] = winx_ - r_co[0];
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co[1] = winy_ - r_co[1];
+ }
+
+ return false;
+}
+
+/** Convert to render space. */
+float2 GpencilIO::gpencil_3D_point_to_render_space(const float3 co)
+{
+ float3 parent_co = diff_mat_ * co;
+ mul_m4_v3(persmat_, parent_co);
+
+ parent_co.x = parent_co.x / max_ff(FLT_MIN, parent_co[2]);
+ parent_co.y = parent_co.y / max_ff(FLT_MIN, parent_co[2]);
+
+ float2 r_co;
+ r_co.x = (parent_co.x + 1.0f) / 2.0f * (float)render_x_;
+ r_co.y = (parent_co.y + 1.0f) / 2.0f * (float)render_y_;
+
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co.x = (float)render_x_ - r_co.x;
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co.y = (float)render_y_ - r_co.y;
+ }
+
+ return r_co;
+}
+
+/** Convert to 2D. */
+float2 GpencilIO::gpencil_3D_point_to_2D(const float3 co)
+{
+ const bool is_camera = (bool)(rv3d_->persp == RV3D_CAMOB);
+ if (is_camera) {
+ return gpencil_3D_point_to_render_space(co);
+ }
+ float2 result;
+ gpencil_3D_point_to_screen_space(co, result);
+ return result;
+}
+
+/** Get radius of point. */
+float GpencilIO::stroke_point_radius_get(bGPDlayer *gpl, bGPDstroke *gps)
+{
+ bGPDspoint *pt = &gps->points[0];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+
+ /* Radius. */
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps, 3, diff_mat_.values);
+
+ pt = &gps_perimeter->points[0];
+ const float2 screen_ex = gpencil_3D_point_to_2D(&pt->x);
+
+ const float2 v1 = screen_co - screen_ex;
+ float radius = v1.length();
+ BKE_gpencil_free_stroke(gps_perimeter);
+
+ return MAX2(radius, 1.0f);
+}
+
+void GpencilIO::prepare_layer_export_matrix(Object *ob, bGPDlayer *gpl)
+{
+ BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob, gpl, diff_mat_.values);
+ diff_mat_ = diff_mat_ * float4x4(gpl->layer_invmat);
+}
+
+void GpencilIO::prepare_stroke_export_colors(Object *ob, bGPDstroke *gps)
+{
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
+
+ /* Stroke color. */
+ copy_v4_v4(stroke_color_, gp_style->stroke_rgba);
+ avg_opacity_ = 0;
+ /* Get average vertex color and apply. */
+ float avg_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
+ add_v4_v4(avg_color, pt.vert_color);
+ avg_opacity_ += pt.strength;
+ }
+
+ mul_v4_v4fl(avg_color, avg_color, 1.0f / (float)gps->totpoints);
+ interp_v3_v3v3(stroke_color_, stroke_color_, avg_color, avg_color[3]);
+ avg_opacity_ /= (float)gps->totpoints;
+
+ /* Fill color. */
+ copy_v4_v4(fill_color_, gp_style->fill_rgba);
+ /* Apply vertex color for fill. */
+ interp_v3_v3v3(fill_color_, fill_color_, gps->vert_color_fill, gps->vert_color_fill[3]);
+}
+
+float GpencilIO::stroke_average_opacity_get()
+{
+ return avg_opacity_;
+}
+
+bool GpencilIO::is_camera_mode()
+{
+ return is_camera_;
+}
+
+/* Calculate selected strokes boundbox. */
+void GpencilIO::selected_objects_boundbox_calc()
+{
+ const float gap = 10.0f;
+
+ float2 min, max;
+ INIT_MINMAX2(min, max);
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob_eval, gpl, diff_mat_.values);
+
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == nullptr) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints == 0) {
+ continue;
+ }
+ for (const bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
+ minmax_v2v2_v2(min, max, screen_co);
+ }
+ }
+ }
+ }
+ /* Add small gap. */
+ add_v2_fl(min, gap * -1.0f);
+ add_v2_fl(max, gap);
+
+ select_boundbox_.xmin = min[0];
+ select_boundbox_.ymin = min[1];
+ select_boundbox_.xmax = max[0];
+ select_boundbox_.ymax = max[1];
+}
+
+void GpencilIO::selected_objects_boundbox_get(rctf *boundbox)
+{
+ boundbox->xmin = select_boundbox_.xmin;
+ boundbox->xmax = select_boundbox_.xmax;
+ boundbox->ymin = select_boundbox_.ymin;
+ boundbox->ymax = select_boundbox_.ymax;
+}
+
+void GpencilIO::frame_number_set(const int value)
+{
+ cfra_ = value;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.h b/source/blender/io/gpencil/intern/gpencil_io_base.h
new file mode 100644
index 00000000000..986221618b7
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.h
@@ -0,0 +1,116 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_vector.hh"
+
+#include "DNA_space_types.h" /* for FILE_MAX */
+
+#include "gpencil_io.h"
+
+struct Depsgraph;
+struct Main;
+struct Object;
+struct RegionView3D;
+struct Scene;
+
+struct bGPdata;
+struct bGPDlayer;
+struct bGPDstroke;
+
+using blender::Vector;
+
+namespace blender::io::gpencil {
+
+class GpencilIO {
+ public:
+ GpencilIO(const GpencilIOParams *iparams);
+
+ void frame_number_set(const int value);
+
+ protected:
+ GpencilIOParams params_;
+
+ bool invert_axis_[2];
+ float4x4 diff_mat_;
+ char filename_[FILE_MAX];
+
+ /* Used for sorting objects. */
+ struct ObjectZ {
+ float zdepth;
+ struct Object *ob;
+ };
+
+ /** List of included objects. */
+ blender::Vector<ObjectZ> ob_list_;
+
+ /* Data for easy access. */
+ struct Depsgraph *depsgraph_;
+ struct bGPdata *gpd_;
+ struct Main *bmain_;
+ struct Scene *scene_;
+ struct RegionView3D *rv3d_;
+
+ int16_t winx_, winy_;
+ int16_t render_x_, render_y_;
+ float camera_ratio_;
+ rctf camera_rect_;
+
+ float2 offset_;
+
+ int cfra_;
+
+ float stroke_color_[4], fill_color_[4];
+
+ /* Geometry functions. */
+ bool gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co);
+ float2 gpencil_3D_point_to_render_space(const float3 co);
+ float2 gpencil_3D_point_to_2D(const float3 co);
+
+ float stroke_point_radius_get(struct bGPDlayer *gpl, struct bGPDstroke *gps);
+ void create_object_list();
+
+ bool is_camera_mode();
+
+ float stroke_average_opacity_get();
+
+ void prepare_layer_export_matrix(struct Object *ob, struct bGPDlayer *gpl);
+ void prepare_stroke_export_colors(struct Object *ob, struct bGPDstroke *gps);
+
+ void selected_objects_boundbox_calc();
+ void selected_objects_boundbox_get(rctf *boundbox);
+ void filename_set(const char *filename);
+
+ private:
+ float avg_opacity_;
+ bool is_camera_;
+ rctf select_boundbox_;
+
+ /* Camera matrix. */
+ float persmat_[4][4];
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
new file mode 100644
index 00000000000..231d23948ef
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
@@ -0,0 +1,204 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include <stdio.h>
+
+#include "BLI_listbase.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_main.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "../gpencil_io.h"
+
+#ifdef WITH_HARU
+# include "gpencil_io_export_pdf.h"
+#endif
+
+#ifdef WITH_PUGIXML
+# include "gpencil_io_export_svg.h"
+#endif
+
+#include "gpencil_io_import_svg.h"
+
+#ifdef WITH_HARU
+using blender::io::gpencil::GpencilExporterPDF;
+#endif
+#ifdef WITH_PUGIXML
+using blender::io::gpencil::GpencilExporterSVG;
+#endif
+using blender::io::gpencil::GpencilImporterSVG;
+
+/* Check if frame is included. */
+#ifdef WITH_HARU
+static bool is_keyframe_included(bGPdata *gpd_, const int32_t framenum, const bool use_selected)
+{
+ /* Check if exist a frame. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ if (gpf->framenum == framenum) {
+ if ((!use_selected) || (use_selected && (gpf->flag & GP_FRAME_SELECT))) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+/* Import frame. */
+static bool gpencil_io_import_frame(void *in_importer, const GpencilIOParams &iparams)
+{
+
+ bool result = false;
+ switch (iparams.mode) {
+ case GP_IMPORT_FROM_SVG: {
+ GpencilImporterSVG *importer = (GpencilImporterSVG *)in_importer;
+ result |= importer->read();
+ break;
+ }
+ /* Add new import formats here. */
+ default:
+ break;
+ }
+
+ return result;
+}
+
+/* Export frame in PDF. */
+#ifdef WITH_HARU
+static bool gpencil_io_export_pdf(Depsgraph *depsgraph,
+ Scene *scene,
+ Object *ob,
+ GpencilExporterPDF *exporter,
+ const GpencilIOParams *iparams)
+{
+ bool result = false;
+ Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
+
+ exporter->frame_number_set(iparams->frame_cur);
+ result |= exporter->new_document();
+
+ const bool use_frame_selected = (iparams->frame_mode == GP_EXPORT_FRAME_SELECTED);
+ if (use_frame_selected) {
+ for (int32_t i = iparams->frame_start; i < iparams->frame_end + 1; i++) {
+ if (!is_keyframe_included(gpd_eval, i, use_frame_selected)) {
+ continue;
+ }
+
+ CFRA = i;
+ BKE_scene_graph_update_for_newframe(depsgraph);
+ exporter->frame_number_set(i);
+ exporter->add_newpage();
+ exporter->add_body();
+ }
+ result = exporter->write();
+ /* Back to original frame. */
+ exporter->frame_number_set(iparams->frame_cur);
+ CFRA = iparams->frame_cur;
+ BKE_scene_graph_update_for_newframe(depsgraph);
+ }
+ else {
+ exporter->add_newpage();
+ exporter->add_body();
+ result = exporter->write();
+ }
+
+ return result;
+}
+#endif
+
+/* Export current frame in SVG. */
+#ifdef WITH_PUGIXML
+static bool gpencil_io_export_frame_svg(GpencilExporterSVG *exporter,
+ const GpencilIOParams *iparams,
+ const bool newpage,
+ const bool body,
+ const bool savepage)
+{
+ bool result = false;
+ exporter->frame_number_set(iparams->frame_cur);
+ if (newpage) {
+ result |= exporter->add_newpage();
+ }
+ if (body) {
+ result |= exporter->add_body();
+ }
+ if (savepage) {
+ result = exporter->write();
+ }
+ return result;
+}
+#endif
+
+/* Main import entry point function. */
+bool gpencil_io_import(const char *filename, GpencilIOParams *iparams)
+{
+ GpencilImporterSVG importer = GpencilImporterSVG(filename, iparams);
+
+ return gpencil_io_import_frame(&importer, *iparams);
+}
+
+/* Main export entry point function. */
+bool gpencil_io_export(const char *filename, GpencilIOParams *iparams)
+{
+ Depsgraph *depsgraph_ = CTX_data_depsgraph_pointer(iparams->C);
+ Scene *scene_ = CTX_data_scene(iparams->C);
+ Object *ob = CTX_data_active_object(iparams->C);
+
+ UNUSED_VARS(filename, depsgraph_, scene_, ob);
+
+ switch (iparams->mode) {
+#ifdef WITH_PUGIXML
+ case GP_EXPORT_TO_SVG: {
+ GpencilExporterSVG exporter = GpencilExporterSVG(filename, iparams);
+ return gpencil_io_export_frame_svg(&exporter, iparams, true, true, true);
+ break;
+ }
+#endif
+#ifdef WITH_HARU
+ case GP_EXPORT_TO_PDF: {
+ GpencilExporterPDF exporter = GpencilExporterPDF(filename, iparams);
+ return gpencil_io_export_pdf(depsgraph_, scene_, ob, &exporter, iparams);
+ break;
+ }
+#endif
+ /* Add new export formats here. */
+ default:
+ break;
+ }
+ return false;
+}
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_base.h b/source/blender/io/gpencil/intern/gpencil_io_export_base.h
new file mode 100644
index 00000000000..19a24a75fd2
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_base.h
@@ -0,0 +1,38 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "gpencil_io_base.h"
+
+namespace blender::io::gpencil {
+
+class GpencilExporter : public GpencilIO {
+
+ public:
+ GpencilExporter(const struct GpencilIOParams *iparams) : GpencilIO(iparams){};
+ virtual bool write() = 0;
+
+ protected:
+ private:
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc
new file mode 100644
index 00000000000..ba16d635c2d
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc
@@ -0,0 +1,311 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_math_vector.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+#include "ED_view3d.h"
+
+#ifdef WIN32
+# include "utfconv.h"
+#endif
+
+#include "UI_view2d.h"
+
+#include "gpencil_io.h"
+#include "gpencil_io_export_pdf.h"
+
+namespace blender ::io ::gpencil {
+
+static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void *UNUSED(user_data))
+{
+ printf("ERROR: error_no=%04X, detail_no=%u\n", (HPDF_UINT)error_no, (HPDF_UINT)detail_no);
+}
+
+/* Constructor. */
+GpencilExporterPDF::GpencilExporterPDF(const char *filename, const GpencilIOParams *iparams)
+ : GpencilExporter(iparams)
+{
+ filename_set(filename);
+
+ invert_axis_[0] = false;
+ invert_axis_[1] = false;
+
+ pdf_ = nullptr;
+ page_ = nullptr;
+ gstate_ = nullptr;
+}
+
+bool GpencilExporterPDF::new_document()
+{
+ return create_document();
+}
+
+bool GpencilExporterPDF::add_newpage()
+{
+ return add_page();
+}
+
+bool GpencilExporterPDF::add_body()
+{
+ export_gpencil_layers();
+ return true;
+}
+
+bool GpencilExporterPDF::write()
+{
+ /* Support unicode character paths on Windows. */
+ HPDF_STATUS res = 0;
+ /* TODO: It looks libharu does not support unicode. */
+ //#ifdef WIN32
+ // char filename_cstr[FILE_MAX];
+ // BLI_strncpy(filename_cstr, filename_, FILE_MAX);
+ //
+ // UTF16_ENCODE(filename_cstr);
+ // std::wstring wstr(filename_cstr_16);
+ // res = HPDF_SaveToFile(pdf_, wstr.c_str());
+ //
+ // UTF16_UN_ENCODE(filename_cstr);
+ //#else
+ res = HPDF_SaveToFile(pdf_, filename_);
+ //#endif
+
+ return (res == 0) ? true : false;
+}
+
+/* Create pdf document. */
+bool GpencilExporterPDF::create_document()
+{
+ pdf_ = HPDF_New(error_handler, nullptr);
+ if (!pdf_) {
+ std::cout << "error: cannot create PdfDoc object\n";
+ return false;
+ }
+ return true;
+}
+
+/* Add page. */
+bool GpencilExporterPDF::add_page()
+{
+ /* Add a new page object. */
+ page_ = HPDF_AddPage(pdf_);
+ if (!pdf_) {
+ std::cout << "error: cannot create PdfPage\n";
+ return false;
+ }
+
+ HPDF_Page_SetWidth(page_, render_x_);
+ HPDF_Page_SetHeight(page_, render_y_);
+
+ return true;
+}
+
+/* Main layer loop. */
+void GpencilExporterPDF::export_gpencil_layers()
+{
+ /* If is doing a set of frames, the list of objects can change for each frame. */
+ create_object_list();
+
+ const bool is_normalized = ((params_.flag & GP_EXPORT_NORM_THICKNESS) != 0);
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ prepare_layer_export_matrix(ob, gpl);
+
+ bGPDframe *gpf = gpl->actframe;
+ if ((gpf == nullptr) || (gpf->strokes.first == nullptr)) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints < 2) {
+ continue;
+ }
+ if (!ED_gpencil_stroke_material_visible(ob, gps)) {
+ continue;
+ }
+ /* Duplicate the stroke to apply any layer thickness change. */
+ bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob,
+ gps_duplicate->mat_nr + 1);
+
+ const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
+ (gp_style->stroke_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+ const bool is_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
+ (gp_style->fill_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+ prepare_stroke_export_colors(ob, gps_duplicate);
+
+ /* Apply layer thickness change. */
+ gps_duplicate->thickness += gpl->line_change;
+ /* Apply object scale to thickness. */
+ gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
+ CLAMP_MIN(gps_duplicate->thickness, 1.0f);
+ /* Fill. */
+ if ((is_fill) && (params_.flag & GP_EXPORT_FILL)) {
+ /* Fill is exported as polygon for fill and stroke in a different shape. */
+ export_stroke_to_polyline(gpl, gps_duplicate, is_stroke, true, false);
+ }
+
+ /* Stroke. */
+ if (is_stroke) {
+ if (is_normalized) {
+ export_stroke_to_polyline(gpl, gps_duplicate, is_stroke, false, true);
+ }
+ else {
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values);
+
+ /* Sample stroke. */
+ if (params_.stroke_sample > 0.0f) {
+ BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
+ }
+
+ export_stroke_to_polyline(gpl, gps_perimeter, is_stroke, false, false);
+
+ BKE_gpencil_free_stroke(gps_perimeter);
+ }
+ }
+ BKE_gpencil_free_stroke(gps_duplicate);
+ }
+ }
+ }
+}
+
+/**
+ * Export a stroke using polyline or polygon
+ * \param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterPDF::export_stroke_to_polyline(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ const bool is_stroke,
+ const bool do_fill,
+ const bool normalize)
+{
+ const bool cyclic = ((gps->flag & GP_STROKE_CYCLIC) != 0);
+ const float avg_pressure = BKE_gpencil_stroke_average_pressure_get(gps);
+
+ /* Get the thickness in pixels using a simple 1 point stroke. */
+ bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, false, false);
+ gps_temp->totpoints = 1;
+ gps_temp->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points");
+ const bGPDspoint *pt_src = &gps->points[0];
+ bGPDspoint *pt_dst = &gps_temp->points[0];
+ copy_v3_v3(&pt_dst->x, &pt_src->x);
+ pt_dst->pressure = avg_pressure;
+
+ const float radius = stroke_point_radius_get(gpl, gps_temp);
+
+ BKE_gpencil_free_stroke(gps_temp);
+
+ color_set(gpl, do_fill);
+
+ if (is_stroke && !do_fill) {
+ HPDF_Page_SetLineJoin(page_, HPDF_ROUND_JOIN);
+ HPDF_Page_SetLineWidth(page_, MAX2((radius * 2.0f) - gpl->line_change, 1.0f));
+ }
+
+ /* Loop all points. */
+ for (const int i : IndexRange(gps->totpoints)) {
+ bGPDspoint *pt = &gps->points[i];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+ if (i == 0) {
+ HPDF_Page_MoveTo(page_, screen_co.x, screen_co.y);
+ }
+ else {
+ HPDF_Page_LineTo(page_, screen_co.x, screen_co.y);
+ }
+ }
+ /* Close cyclic */
+ if (cyclic) {
+ HPDF_Page_ClosePath(page_);
+ }
+
+ if (do_fill || !normalize) {
+ HPDF_Page_Fill(page_);
+ }
+ else {
+ HPDF_Page_Stroke(page_);
+ }
+
+ HPDF_Page_GRestore(page_);
+}
+
+/**
+ * Set color
+ * @param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterPDF::color_set(bGPDlayer *gpl, const bool do_fill)
+{
+ const float fill_opacity = fill_color_[3] * gpl->opacity;
+ const float stroke_opacity = stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity;
+
+ HPDF_Page_GSave(page_);
+ gstate_ = HPDF_CreateExtGState(pdf_);
+
+ float col[3];
+ if (do_fill) {
+ interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ CLAMP3(col, 0.0f, 1.0f);
+
+ HPDF_ExtGState_SetAlphaFill(gstate_, clamp_f(fill_opacity, 0.0f, 1.0f));
+ HPDF_Page_SetRGBFill(page_, col[0], col[1], col[2]);
+ }
+ else {
+ interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ CLAMP3(col, 0.0f, 1.0f);
+
+ HPDF_ExtGState_SetAlphaFill(gstate_, clamp_f(stroke_opacity, 0.0f, 1.0f));
+ HPDF_ExtGState_SetAlphaStroke(gstate_, clamp_f(stroke_opacity, 0.0f, 1.0f));
+ HPDF_Page_SetRGBFill(page_, col[0], col[1], col[2]);
+ HPDF_Page_SetRGBStroke(page_, col[0], col[1], col[2]);
+ }
+ HPDF_Page_SetExtGState(page_, gstate_);
+}
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h
new file mode 100644
index 00000000000..009c05a8b49
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h
@@ -0,0 +1,67 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "gpencil_io_export_base.h"
+#include "hpdf.h"
+
+struct GpencilIOParams;
+struct bGPDlayer;
+struct bGPDstroke;
+
+#define PDF_EXPORTER_NAME "PDF Exporter for Grease Pencil"
+#define PDF_EXPORTER_VERSION "v1.0"
+
+namespace blender::io::gpencil {
+
+class GpencilExporterPDF : public GpencilExporter {
+
+ public:
+ GpencilExporterPDF(const char *filename, const struct GpencilIOParams *iparams);
+ bool new_document();
+ bool add_newpage();
+ bool add_body();
+ bool write();
+
+ protected:
+ private:
+ /* PDF document. */
+ HPDF_Doc pdf_;
+ /* PDF page. */
+ HPDF_Page page_;
+ /* State. */
+ HPDF_ExtGState gstate_;
+
+ bool create_document();
+ bool add_page();
+ void export_gpencil_layers();
+
+ void export_stroke_to_polyline(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ const bool is_stroke,
+ const bool do_fill,
+ const bool normalize);
+ void color_set(bGPDlayer *gpl, const bool do_fill);
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc
new file mode 100644
index 00000000000..89584cd242f
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc
@@ -0,0 +1,464 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_math_vector.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+#include "ED_view3d.h"
+
+#ifdef WIN32
+# include "utfconv.h"
+#endif
+
+#include "UI_view2d.h"
+
+#include "gpencil_io.h"
+#include "gpencil_io_export_svg.h"
+
+#include "pugixml.hpp"
+
+namespace blender ::io ::gpencil {
+
+/* Constructor. */
+GpencilExporterSVG::GpencilExporterSVG(const char *filename, const GpencilIOParams *iparams)
+ : GpencilExporter(iparams)
+{
+ filename_set(filename);
+
+ invert_axis_[0] = false;
+ invert_axis_[1] = true;
+}
+
+bool GpencilExporterSVG::add_newpage()
+{
+ create_document_header();
+ return true;
+}
+
+bool GpencilExporterSVG::add_body()
+{
+ export_gpencil_layers();
+ return true;
+}
+
+bool GpencilExporterSVG::write()
+{
+ bool result = true;
+/* Support unicode character paths on Windows. */
+#ifdef WIN32
+ char filename_cstr[FILE_MAX];
+ BLI_strncpy(filename_cstr, filename_, FILE_MAX);
+
+ UTF16_ENCODE(filename_cstr);
+ std::wstring wstr(filename_cstr_16);
+ result = main_doc_.save_file(wstr.c_str());
+
+ UTF16_UN_ENCODE(filename_cstr);
+#else
+ result = main_doc_.save_file(filename_);
+#endif
+
+ return result;
+}
+
+/* Create document header and main svg node. */
+void GpencilExporterSVG::create_document_header()
+{
+ /* Add a custom document declaration node. */
+ pugi::xml_node decl = main_doc_.prepend_child(pugi::node_declaration);
+ decl.append_attribute("version") = "1.0";
+ decl.append_attribute("encoding") = "UTF-8";
+
+ pugi::xml_node comment = main_doc_.append_child(pugi::node_comment);
+ char txt[128];
+ sprintf(txt, " Generator: Blender, %s - %s ", SVG_EXPORTER_NAME, SVG_EXPORTER_VERSION);
+ comment.set_value(txt);
+
+ pugi::xml_node doctype = main_doc_.append_child(pugi::node_doctype);
+ doctype.set_value(
+ "svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
+ "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"");
+
+ main_node_ = main_doc_.append_child("svg");
+ main_node_.append_attribute("version").set_value("1.0");
+ main_node_.append_attribute("x").set_value("0px");
+ main_node_.append_attribute("y").set_value("0px");
+
+ std::string width;
+ std::string height;
+
+ width = std::to_string(render_x_);
+ height = std::to_string(render_y_);
+
+ main_node_.append_attribute("width").set_value((width + "px").c_str());
+ main_node_.append_attribute("height").set_value((height + "px").c_str());
+ std::string viewbox = "0 0 " + width + " " + height;
+ main_node_.append_attribute("viewBox").set_value(viewbox.c_str());
+}
+
+/* Main layer loop. */
+void GpencilExporterSVG::export_gpencil_layers()
+{
+ const bool is_clipping = is_camera_mode() && (params_.flag & GP_EXPORT_CLIP_CAMERA) != 0;
+
+ /* If is doing a set of frames, the list of objects can change for each frame. */
+ create_object_list();
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+
+ /* Camera clipping. */
+ if (is_clipping) {
+ pugi::xml_node clip_node = main_node_.append_child("clipPath");
+ clip_node.append_attribute("id").set_value(("clip-path" + std::to_string(cfra_)).c_str());
+
+ add_rect(clip_node, 0, 0, render_x_, render_y_, 0.0f, "#000000");
+ }
+
+ frame_node_ = main_node_.append_child("g");
+ std::string frametxt = "blender_frame_" + std::to_string(cfra_);
+ frame_node_.append_attribute("id").set_value(frametxt.c_str());
+
+ /* Clip area. */
+ if (is_clipping) {
+ frame_node_.append_attribute("clip-path")
+ .set_value(("url(#clip-path" + std::to_string(cfra_) + ")").c_str());
+ }
+
+ pugi::xml_node ob_node = frame_node_.append_child("g");
+
+ char obtxt[96];
+ sprintf(obtxt, "blender_object_%s", ob->id.name + 2);
+ ob_node.append_attribute("id").set_value(obtxt);
+
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ prepare_layer_export_matrix(ob, gpl);
+
+ bGPDframe *gpf = gpl->actframe;
+ if ((gpf == nullptr) || (gpf->strokes.first == nullptr)) {
+ continue;
+ }
+
+ /* Layer node. */
+ std::string txt = "Layer: ";
+ txt.append(gpl->info);
+ ob_node.append_child(pugi::node_comment).set_value(txt.c_str());
+
+ pugi::xml_node node_gpl = ob_node.append_child("g");
+ node_gpl.append_attribute("id").set_value(gpl->info);
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints < 2) {
+ continue;
+ }
+ if (!ED_gpencil_stroke_material_visible(ob, gps)) {
+ continue;
+ }
+
+ /* Duplicate the stroke to apply any layer thickness change. */
+ bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
+
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob,
+ gps_duplicate->mat_nr + 1);
+
+ const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
+ (gp_style->stroke_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+ const bool is_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
+ (gp_style->fill_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+
+ prepare_stroke_export_colors(ob, gps_duplicate);
+
+ /* Apply layer thickness change. */
+ gps_duplicate->thickness += gpl->line_change;
+ /* Apply object scale to thickness. */
+ gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
+ CLAMP_MIN(gps_duplicate->thickness, 1.0f);
+
+ const bool is_normalized = ((params_.flag & GP_EXPORT_NORM_THICKNESS) != 0) ||
+ BKE_gpencil_stroke_is_pressure_constant(gps);
+
+ /* Fill. */
+ if ((is_fill) && (params_.flag & GP_EXPORT_FILL)) {
+ /* Fill is always exported as polygon because the stroke of the fill is done
+ * in a different SVG command. */
+ export_stroke_to_polyline(gpl, gps_duplicate, node_gpl, is_stroke, true);
+ }
+
+ /* Stroke. */
+ if (is_stroke) {
+ if (is_normalized) {
+ export_stroke_to_polyline(gpl, gps_duplicate, node_gpl, is_stroke, false);
+ }
+ else {
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values);
+
+ /* Sample stroke. */
+ if (params_.stroke_sample > 0.0f) {
+ BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
+ }
+
+ export_stroke_to_path(gpl, gps_perimeter, node_gpl, false);
+
+ BKE_gpencil_free_stroke(gps_perimeter);
+ }
+ }
+
+ BKE_gpencil_free_stroke(gps_duplicate);
+ }
+ }
+ }
+}
+
+/**
+ * Export a stroke using SVG path
+ * \param node_gpl: Node of the layer.
+ * \param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterSVG::export_stroke_to_path(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool do_fill)
+{
+ pugi::xml_node node_gps = node_gpl.append_child("path");
+
+ float col[3];
+ std::string stroke_hex;
+ if (do_fill) {
+ node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
+
+ interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ }
+ else {
+ node_gps.append_attribute("fill-opacity")
+ .set_value(stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity);
+
+ interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ }
+
+ linearrgb_to_srgb_v3_v3(col, col);
+ stroke_hex = rgb_to_hexstr(col);
+
+ node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("stroke").set_value("none");
+
+ std::string txt = "M";
+ for (const int i : IndexRange(gps->totpoints)) {
+ if (i > 0) {
+ txt.append("L");
+ }
+ bGPDspoint &pt = gps->points[i];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
+ txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
+ }
+ /* Close patch (cyclic)*/
+ if (gps->flag & GP_STROKE_CYCLIC) {
+ txt.append("z");
+ }
+
+ node_gps.append_attribute("d").set_value(txt.c_str());
+}
+
+/**
+ * Export a stroke using polyline or polygon
+ * \param node_gpl: Node of the layer.
+ * \param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterSVG::export_stroke_to_polyline(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool is_stroke,
+ const bool do_fill)
+{
+ const bool cyclic = ((gps->flag & GP_STROKE_CYCLIC) != 0);
+ const float avg_pressure = BKE_gpencil_stroke_average_pressure_get(gps);
+
+ /* Get the thickness in pixels using a simple 1 point stroke. */
+ bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, false, false);
+ gps_temp->totpoints = 1;
+ gps_temp->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points");
+ bGPDspoint *pt_src = &gps->points[0];
+ bGPDspoint *pt_dst = &gps_temp->points[0];
+ copy_v3_v3(&pt_dst->x, &pt_src->x);
+ pt_dst->pressure = avg_pressure;
+
+ const float radius = stroke_point_radius_get(gpl, gps_temp);
+
+ BKE_gpencil_free_stroke(gps_temp);
+
+ pugi::xml_node node_gps = node_gpl.append_child(do_fill || cyclic ? "polygon" : "polyline");
+
+ color_string_set(gpl, gps, node_gps, do_fill);
+
+ if (is_stroke && !do_fill) {
+ node_gps.append_attribute("stroke-width").set_value((radius * 2.0f) - gpl->line_change);
+ }
+
+ std::string txt;
+ for (const int i : IndexRange(gps->totpoints)) {
+ if (i > 0) {
+ txt.append(" ");
+ }
+ bGPDspoint *pt = &gps->points[i];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+ txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
+ }
+
+ node_gps.append_attribute("points").set_value(txt.c_str());
+}
+
+/**
+ * Set color SVG string for stroke
+ * \param node_gps: Stroke node
+ * @param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterSVG::color_string_set(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ pugi::xml_node node_gps,
+ const bool do_fill)
+{
+ const bool round_cap = (gps->caps[0] == GP_STROKE_CAP_ROUND ||
+ gps->caps[1] == GP_STROKE_CAP_ROUND);
+
+ float col[3];
+ if (do_fill) {
+ interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ std::string stroke_hex = rgb_to_hexstr(col);
+ node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("stroke").set_value("none");
+ node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
+ }
+ else {
+ interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ std::string stroke_hex = rgb_to_hexstr(col);
+ node_gps.append_attribute("stroke").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("stroke-opacity")
+ .set_value(stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity);
+
+ if (gps->totpoints > 1) {
+ node_gps.append_attribute("fill").set_value("none");
+ node_gps.append_attribute("stroke-linecap").set_value(round_cap ? "round" : "square");
+ }
+ else {
+ node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
+ }
+ }
+}
+
+/**
+ * Create a SVG rectangle
+ * \param node: Parent node
+ * \param x: X location
+ * \param y: Y location
+ * \param width: width of the recntagle
+ * \param height: Height of the rectangle
+ * \param thickness: Thickness of the line
+ * \param hexcolor: Color of the line
+ */
+void GpencilExporterSVG::add_rect(pugi::xml_node node,
+ float x,
+ float y,
+ float width,
+ float height,
+ float thickness,
+ std::string hexcolor)
+{
+ pugi::xml_node rect_node = node.append_child("rect");
+ rect_node.append_attribute("x").set_value(x);
+ rect_node.append_attribute("y").set_value(y);
+ rect_node.append_attribute("width").set_value(width);
+ rect_node.append_attribute("height").set_value(height);
+ rect_node.append_attribute("fill").set_value("none");
+ if (thickness > 0.0f) {
+ rect_node.append_attribute("stroke").set_value(hexcolor.c_str());
+ rect_node.append_attribute("stroke-width").set_value(thickness);
+ }
+}
+
+/**
+ * Create SVG text
+ * \param node: Parent node
+ * \param x: X location
+ * \param y: Y location
+ * \param text: Text to include
+ * \param size: Size of th etext
+ * \param hexcolor: Color of the text
+ */
+void GpencilExporterSVG::add_text(pugi::xml_node node,
+ float x,
+ float y,
+ std::string text,
+ const float size,
+ std::string hexcolor)
+{
+ pugi::xml_node nodetxt = node.append_child("text");
+
+ nodetxt.append_attribute("x").set_value(x);
+ nodetxt.append_attribute("y").set_value(y);
+ // nodetxt.append_attribute("font-family").set_value("'system-ui'");
+ nodetxt.append_attribute("font-size").set_value(size);
+ nodetxt.append_attribute("fill").set_value(hexcolor.c_str());
+ nodetxt.text().set(text.c_str());
+}
+
+/** Convert a color to Hex value (#FFFFFF). */
+std::string GpencilExporterSVG::rgb_to_hexstr(float color[3])
+{
+ uint8_t r = color[0] * 255.0f;
+ uint8_t g = color[1] * 255.0f;
+ uint8_t b = color[2] * 255.0f;
+ char hex_string[20];
+ sprintf(hex_string, "#%02X%02X%02X", r, g, b);
+
+ std::string hexstr = hex_string;
+
+ return hexstr;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.h b/source/blender/io/gpencil/intern/gpencil_io_export_svg.h
new file mode 100644
index 00000000000..f564736c16e
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.h
@@ -0,0 +1,89 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "BLI_path_util.h"
+
+#include "gpencil_io_export_base.h"
+#include "pugixml.hpp"
+
+struct GpencilIOParams;
+
+#define SVG_EXPORTER_NAME "SVG Export for Grease Pencil"
+#define SVG_EXPORTER_VERSION "v1.0"
+
+namespace blender::io::gpencil {
+
+class GpencilExporterSVG : public GpencilExporter {
+
+ public:
+ GpencilExporterSVG(const char *filename, const struct GpencilIOParams *iparams);
+ bool add_newpage();
+ bool add_body();
+ bool write();
+
+ protected:
+ static void add_rect(pugi::xml_node node,
+ float x,
+ float y,
+ float width,
+ float height,
+ float thickness,
+ std::string hexcolor);
+
+ static void add_text(pugi::xml_node node,
+ float x,
+ float y,
+ std::string text,
+ const float size,
+ std::string hexcolor);
+
+ private:
+ /* XML doc. */
+ pugi::xml_document main_doc_;
+ /* Main document node. */
+ pugi::xml_node main_node_;
+ /** Frame node */
+ pugi::xml_node frame_node_;
+ void create_document_header();
+ void export_gpencil_layers();
+
+ void export_stroke_to_path(struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool is_fill);
+
+ void export_stroke_to_polyline(struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool is_stroke,
+ const bool is_fill);
+
+ void color_string_set(struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ pugi::xml_node node_gps,
+ const bool is_fill);
+
+ std::string rgb_to_hexstr(float color[3]);
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc
new file mode 100644
index 00000000000..2e7cfdeb5cd
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc
@@ -0,0 +1,83 @@
+
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "BLI_math_vector.h"
+
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_material.h"
+
+#include "ED_gpencil.h"
+
+#include "gpencil_io_import_base.h"
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilImporter::GpencilImporter(const GpencilIOParams *iparams) : GpencilIO(iparams)
+{
+ /* Nothing to do yet */
+}
+
+Object *GpencilImporter::create_object()
+{
+ const float *cur = scene_->cursor.location;
+ ushort local_view_bits = (params_.v3d && params_.v3d->localvd) ? params_.v3d->local_view_uuid :
+ (ushort)0;
+ Object *ob_gpencil = ED_gpencil_add_object(params_.C, cur, local_view_bits);
+
+ return ob_gpencil;
+}
+
+int32_t GpencilImporter::create_material(const char *name, const bool stroke, const bool fill)
+{
+ const float default_stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+ const float default_fill_color[4] = {0.5f, 0.5f, 0.5f, 1.0f};
+ int32_t mat_index = BKE_gpencil_material_find_index_by_name_prefix(params_.ob, name);
+ /* Stroke and Fill material. */
+ if (mat_index == -1) {
+ int32_t new_idx;
+ Material *mat_gp = BKE_gpencil_object_material_new(bmain_, params_.ob, name, &new_idx);
+ MaterialGPencilStyle *gp_style = mat_gp->gp_style;
+ gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
+ gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
+
+ copy_v4_v4(gp_style->stroke_rgba, default_stroke_color);
+ copy_v4_v4(gp_style->fill_rgba, default_fill_color);
+ if (stroke) {
+ gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
+ }
+ if (fill) {
+ gp_style->flag |= GP_MATERIAL_FILL_SHOW;
+ }
+ mat_index = params_.ob->totcol - 1;
+ }
+
+ return mat_index;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.h b/source/blender/io/gpencil/intern/gpencil_io_import_base.h
new file mode 100644
index 00000000000..efe6264e4e9
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.h
@@ -0,0 +1,41 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "gpencil_io_base.h"
+
+namespace blender::io::gpencil {
+
+class GpencilImporter : public GpencilIO {
+
+ public:
+ GpencilImporter(const struct GpencilIOParams *iparams);
+ virtual bool read() = 0;
+
+ protected:
+ struct Object *create_object();
+ int32_t create_material(const char *name, const bool stroke, const bool fill);
+
+ private:
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc
new file mode 100644
index 00000000000..7f450477ac2
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc
@@ -0,0 +1,253 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float3.hh"
+#include "BLI_math.h"
+#include "BLI_span.hh"
+
+#include "DNA_gpencil_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+
+#include "gpencil_io.h"
+#include "gpencil_io_import_svg.h"
+
+/* Custom flags for NanoSVG. */
+#define NANOSVG_ALL_COLOR_KEYWORDS
+#define NANOSVG_IMPLEMENTATION
+
+#include "nanosvg/nanosvg.h"
+
+using blender::MutableSpan;
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilImporterSVG::GpencilImporterSVG(const char *filename, const GpencilIOParams *iparams)
+ : GpencilImporter(iparams)
+{
+ filename_set(filename);
+}
+
+bool GpencilImporterSVG::read()
+{
+ bool result = true;
+ NSVGimage *svg_data = nullptr;
+ svg_data = nsvgParseFromFile(filename_, "mm", 96.0f);
+ if (svg_data == nullptr) {
+ std::cout << " Could not open SVG.\n ";
+ return false;
+ }
+
+ /* Create grease pencil object. */
+ params_.ob = create_object();
+ if (params_.ob == nullptr) {
+ std::cout << "Unable to create new object.\n";
+ if (svg_data) {
+ nsvgDelete(svg_data);
+ }
+
+ return false;
+ }
+ gpd_ = (bGPdata *)params_.ob->data;
+
+ /* Grease pencil is rotated 90 degrees in X axis by default. */
+ float matrix[4][4];
+ const float3 scale = float3(params_.scale);
+ unit_m4(matrix);
+ rotate_m4(matrix, 'X', DEG2RADF(-90.0f));
+ rescale_m4(matrix, scale);
+
+ /* Loop all shapes. */
+ char prv_id[70] = {"*"};
+ int prefix = 0;
+ for (NSVGshape *shape = svg_data->shapes; shape; shape = shape->next) {
+ char *layer_id = (shape->id_parent[0] == '\0') ? BLI_sprintfN("Layer_%03d", prefix) :
+ BLI_sprintfN("%s", shape->id_parent);
+ if (!STREQ(prv_id, layer_id)) {
+ prefix++;
+ MEM_freeN(layer_id);
+ layer_id = (shape->id_parent[0] == '\0') ? BLI_sprintfN("Layer_%03d", prefix) :
+ BLI_sprintfN("%s", shape->id_parent);
+ strcpy(prv_id, layer_id);
+ }
+
+ /* Check if the layer exist and create if needed. */
+ bGPDlayer *gpl = (bGPDlayer *)BLI_findstring(
+ &gpd_->layers, layer_id, offsetof(bGPDlayer, info));
+ if (gpl == nullptr) {
+ gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true);
+ /* Disable lights. */
+ gpl->flag &= ~GP_LAYER_USE_LIGHTS;
+ }
+ MEM_freeN(layer_id);
+
+ /* Check frame. */
+ bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, cfra_, GP_GETFRAME_ADD_NEW);
+ /* Create materials. */
+ bool is_stroke = (bool)shape->stroke.type;
+ bool is_fill = (bool)shape->fill.type;
+ if ((!is_stroke) && (!is_fill)) {
+ is_stroke = true;
+ }
+
+ /* Create_shape materials. */
+ const char *const mat_names[] = {"Stroke", "Fill"};
+ int index = 0;
+ if ((is_stroke) && (is_fill)) {
+ index = 0;
+ is_fill = false;
+ }
+ else if ((!is_stroke) && (is_fill)) {
+ index = 1;
+ }
+ int32_t mat_index = create_material(mat_names[index], is_stroke, is_fill);
+
+ /* Loop all paths to create the stroke data. */
+ for (NSVGpath *path = shape->paths; path; path = path->next) {
+ create_stroke(gpd_, gpf, shape, path, mat_index, matrix);
+ }
+ }
+
+ /* Free SVG memory. */
+ nsvgDelete(svg_data);
+
+ /* Calculate bounding box and move all points to new origin center. */
+ float gp_center[3];
+ BKE_gpencil_centroid_3d(gpd_, gp_center);
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ for (bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
+ sub_v3_v3(&pt.x, gp_center);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void GpencilImporterSVG::create_stroke(bGPdata *gpd,
+ bGPDframe *gpf,
+ NSVGshape *shape,
+ NSVGpath *path,
+ const int32_t mat_index,
+ const float matrix[4][4])
+{
+ const bool is_stroke = (bool)shape->stroke.type;
+ const bool is_fill = (bool)shape->fill.type;
+
+ const int edges = params_.resolution;
+ const float step = 1.0f / (float)(edges - 1);
+
+ const int totpoints = (path->npts / 3) * params_.resolution;
+
+ bGPDstroke *gps = BKE_gpencil_stroke_new(mat_index, totpoints, 1.0f);
+ BLI_addtail(&gpf->strokes, gps);
+
+ if (path->closed == '1') {
+ gps->flag |= GP_STROKE_CYCLIC;
+ }
+ if (is_stroke) {
+ gps->thickness = shape->strokeWidth * params_.scale;
+ }
+ /* Apply Fill vertex color. */
+ if (is_fill) {
+ NSVGpaint fill = shape->fill;
+ convert_color(fill.color, gps->vert_color_fill);
+ gps->fill_opacity_fac = gps->vert_color_fill[3];
+ gps->vert_color_fill[3] = 1.0f;
+ }
+
+ int start_index = 0;
+ for (int i = 0; i < path->npts - 1; i += 3) {
+ float *p = &path->pts[i * 2];
+ float a = 0.0f;
+ for (int v = 0; v < edges; v++) {
+ bGPDspoint *pt = &gps->points[start_index];
+ pt->strength = shape->opacity;
+ pt->pressure = 1.0f;
+ pt->z = 0.0f;
+ /* TODO: (antoniov) Can be improved loading curve data instead of loading strokes. */
+ interp_v2_v2v2v2v2_cubic(&pt->x, &p[0], &p[2], &p[4], &p[6], a);
+
+ /* Scale from milimeters. */
+ mul_v3_fl(&pt->x, 0.001f);
+ mul_m4_v3(matrix, &pt->x);
+
+ /* Apply color to vertex color. */
+ if (is_fill) {
+ NSVGpaint fill = shape->fill;
+ convert_color(fill.color, pt->vert_color);
+ }
+ if (is_stroke) {
+ NSVGpaint stroke = shape->stroke;
+ convert_color(stroke.color, pt->vert_color);
+ gps->fill_opacity_fac = pt->vert_color[3];
+ }
+ pt->vert_color[3] = 1.0f;
+
+ a += step;
+ start_index++;
+ }
+ }
+
+ /* Cleanup and recalculate geometry. */
+ BKE_gpencil_stroke_merge_distance(gpd, gpf, gps, 0.001f, true);
+ BKE_gpencil_stroke_geometry_update(gpd, gps);
+}
+
+/* Unpack internal NanoSVG color. */
+static void unpack_nano_color(const unsigned int pack, float r_col[4])
+{
+ unsigned char rgb_u[4];
+
+ rgb_u[0] = ((pack) >> 0) & 0xFF;
+ rgb_u[1] = ((pack) >> 8) & 0xFF;
+ rgb_u[2] = ((pack) >> 16) & 0xFF;
+ rgb_u[3] = ((pack) >> 24) & 0xFF;
+
+ r_col[0] = (float)rgb_u[0] / 255.0f;
+ r_col[1] = (float)rgb_u[1] / 255.0f;
+ r_col[2] = (float)rgb_u[2] / 255.0f;
+ r_col[3] = (float)rgb_u[3] / 255.0f;
+}
+
+void GpencilImporterSVG::convert_color(const int32_t color, float r_linear_rgba[4])
+{
+ float rgba[4];
+ unpack_nano_color(color, rgba);
+
+ srgb_to_linearrgb_v3_v3(r_linear_rgba, rgba);
+ r_linear_rgba[3] = rgba[3];
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.h b/source/blender/io/gpencil/intern/gpencil_io_import_svg.h
new file mode 100644
index 00000000000..6a34ec8423b
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.h
@@ -0,0 +1,56 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "gpencil_io_import_base.h"
+
+struct GpencilIOParams;
+struct NSVGshape;
+struct NSVGpath;
+struct bGPdata;
+struct bGPDframe;
+
+#define SVG_IMPORTER_NAME "SVG Import for Grease Pencil"
+#define SVG_IMPORTER_VERSION "v1.0"
+
+namespace blender::io::gpencil {
+
+class GpencilImporterSVG : public GpencilImporter {
+
+ public:
+ GpencilImporterSVG(const char *filename, const struct GpencilIOParams *iparams);
+
+ bool read();
+
+ protected:
+ private:
+ void create_stroke(struct bGPdata *gpd_,
+ struct bGPDframe *gpf,
+ struct NSVGshape *shape,
+ struct NSVGpath *path,
+ const int32_t mat_index,
+ const float matrix[4][4]);
+
+ void convert_color(const int32_t color, float r_linear_rgba[4]);
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/nanosvg/nanosvg.h b/source/blender/io/gpencil/nanosvg/nanosvg.h
new file mode 100644
index 00000000000..1009d684f7c
--- /dev/null
+++ b/source/blender/io/gpencil/nanosvg/nanosvg.h
@@ -0,0 +1,3313 @@
+/*
+ * Copyright (c) 2013-14 Mikko Mononen memon@inside.org
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
+ * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)
+ *
+ * Arc calculation code based on canvg (https://code.google.com/p/canvg/)
+ *
+ * Bounding box calculation based on
+ * http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+ *
+ * This is a modified version for Blender used by importers.
+ *
+ */
+
+#ifndef NANOSVG_H
+#define NANOSVG_H
+
+#ifndef NANOSVG_CPLUSPLUS
+# ifdef __cplusplus
+extern "C" {
+# endif
+#endif
+
+// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of
+// cubic bezier shapes.
+//
+// The library suits well for anything from rendering scalable icons in your editor application to
+// prototyping a game.
+//
+// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create
+// a pull request!
+//
+// The shapes in the SVG images are transformed by the viewBox and converted to specified units.
+// That is, you should get the same looking data as your designed in your favorite app.
+//
+// NanoSVG can return the paths in few different units. For example if you want to render an image,
+// you may choose to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you
+// may want to use millimeters.
+//
+// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'.
+// DPI (dots-per-inch) controls how the unit conversion is done.
+//
+// If you don't know or care about the units stuff, "px" and 96 should get you going.
+
+/* Example Usage:
+ // Load SVG
+ NSVGimage* image;
+ image = nsvgParseFromFile("test.svg", "px", 96);
+ printf("size: %f x %f\n", image->width, image->height);
+ // Use...
+ for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {
+ for (NSVGpath *path = shape->paths; path != NULL; path = path->next) {
+ for (int i = 0; i < path->npts-1; i += 3) {
+ float* p = &path->pts[i*2];
+ drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]);
+ }
+ }
+ }
+ // Delete
+ nsvgDelete(image);
+*/
+
+enum NSVGpaintType {
+ NSVG_PAINT_NONE = 0,
+ NSVG_PAINT_COLOR = 1,
+ NSVG_PAINT_LINEAR_GRADIENT = 2,
+ NSVG_PAINT_RADIAL_GRADIENT = 3
+};
+
+enum NSVGspreadType { NSVG_SPREAD_PAD = 0, NSVG_SPREAD_REFLECT = 1, NSVG_SPREAD_REPEAT = 2 };
+
+enum NSVGlineJoin { NSVG_JOIN_MITER = 0, NSVG_JOIN_ROUND = 1, NSVG_JOIN_BEVEL = 2 };
+
+enum NSVGlineCap { NSVG_CAP_BUTT = 0, NSVG_CAP_ROUND = 1, NSVG_CAP_SQUARE = 2 };
+
+enum NSVGfillRule { NSVG_FILLRULE_NONZERO = 0, NSVG_FILLRULE_EVENODD = 1 };
+
+enum NSVGflags { NSVG_FLAGS_VISIBLE = 0x01 };
+
+typedef struct NSVGgradientStop {
+ unsigned int color;
+ float offset;
+} NSVGgradientStop;
+
+typedef struct NSVGgradient {
+ float xform[6];
+ char spread;
+ float fx, fy;
+ int nstops;
+ NSVGgradientStop stops[1];
+} NSVGgradient;
+
+typedef struct NSVGpaint {
+ char type;
+ union {
+ unsigned int color;
+ NSVGgradient *gradient;
+ };
+} NSVGpaint;
+
+typedef struct NSVGpath {
+ float *pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...
+ int npts; // Total number of bezier points.
+ char closed; // Flag indicating if shapes should be treated as closed.
+ float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy].
+ struct NSVGpath *next; // Pointer to next path, or NULL if last element.
+} NSVGpath;
+
+typedef struct NSVGshape {
+ char id[64]; // Optional 'id' attr of the shape or its group
+ /* Blender: Parent ID used for layer creation. */
+ char id_parent[64];
+ NSVGpaint fill; // Fill paint
+ NSVGpaint stroke; // Stroke paint
+ float opacity; // Opacity of the shape.
+ float strokeWidth; // Stroke width (scaled).
+ float strokeDashOffset; // Stroke dash offset (scaled).
+ float strokeDashArray[8]; // Stroke dash array (scaled).
+ char strokeDashCount; // Number of dash values in dash array.
+ char strokeLineJoin; // Stroke join type.
+ char strokeLineCap; // Stroke cap type.
+ float miterLimit; // Miter limit
+ char fillRule; // Fill rule, see NSVGfillRule.
+ unsigned char flags; // Logical or of NSVG_FLAGS_* flags
+ float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy].
+ NSVGpath *paths; // Linked list of paths in the image.
+ struct NSVGshape *next; // Pointer to next shape, or NULL if last element.
+} NSVGshape;
+
+typedef struct NSVGimage {
+ float width; // Width of the image.
+ float height; // Height of the image.
+ NSVGshape *shapes; // Linked list of shapes in the image.
+} NSVGimage;
+
+// Parses SVG file from a file, returns SVG image as paths.
+NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi);
+
+// Parses SVG file from a null terminated string, returns SVG image as paths.
+// Important note: changes the string.
+NSVGimage *nsvgParse(char *input, const char *units, float dpi);
+
+// Duplicates a path.
+NSVGpath *nsvgDuplicatePath(NSVGpath *p);
+
+// Deletes an image.
+void nsvgDelete(NSVGimage *image);
+
+#ifndef NANOSVG_CPLUSPLUS
+# ifdef __cplusplus
+}
+# endif
+#endif
+
+#endif // NANOSVG_H
+
+#ifdef NANOSVG_IMPLEMENTATION
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NSVG_PI (3.14159265358979323846264338327f)
+#define NSVG_KAPPA90 \
+ (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs.
+
+#define NSVG_ALIGN_MIN 0
+#define NSVG_ALIGN_MID 1
+#define NSVG_ALIGN_MAX 2
+#define NSVG_ALIGN_NONE 0
+#define NSVG_ALIGN_MEET 1
+#define NSVG_ALIGN_SLICE 2
+
+#define NSVG_NOTUSED(v) \
+ do { \
+ (void)(1 ? (void)0 : ((void)(v))); \
+ } while (0)
+#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16))
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4996) // Switch off security warnings
+# pragma warning(disable : 4100) // Switch off unreferenced formal parameter warnings
+# ifdef __cplusplus
+# define NSVG_INLINE inline
+# else
+# define NSVG_INLINE
+# endif
+#else
+# define NSVG_INLINE inline
+#endif
+
+static int nsvg__isspace(char c)
+{
+ return strchr(" \t\n\v\f\r", c) != 0;
+}
+
+static int nsvg__isdigit(char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+static NSVG_INLINE float nsvg__minf(float a, float b)
+{
+ return a < b ? a : b;
+}
+static NSVG_INLINE float nsvg__maxf(float a, float b)
+{
+ return a > b ? a : b;
+}
+
+// Simple XML parser
+
+#define NSVG_XML_TAG 1
+#define NSVG_XML_CONTENT 2
+#define NSVG_XML_MAX_ATTRIBS 256
+
+static void nsvg__parseContent(char *s, void (*contentCb)(void *ud, const char *s), void *ud)
+{
+ // Trim start white spaces
+ while (*s && nsvg__isspace(*s))
+ s++;
+ if (!*s)
+ return;
+
+ if (contentCb)
+ (*contentCb)(ud, s);
+}
+
+static void nsvg__parseElement(char *s,
+ void (*startelCb)(void *ud, const char *el, const char **attr),
+ void (*endelCb)(void *ud, const char *el),
+ void *ud)
+{
+ const char *attr[NSVG_XML_MAX_ATTRIBS];
+ int nattr = 0;
+ char *name;
+ int start = 0;
+ int end = 0;
+ char quote;
+
+ // Skip white space after the '<'
+ while (*s && nsvg__isspace(*s))
+ s++;
+
+ // Check if the tag is end tag
+ if (*s == '/') {
+ s++;
+ end = 1;
+ }
+ else {
+ start = 1;
+ }
+
+ // Skip comments, data and preprocessor stuff.
+ if (!*s || *s == '?' || *s == '!')
+ return;
+
+ // Get tag name
+ name = s;
+ while (*s && !nsvg__isspace(*s))
+ s++;
+ if (*s) {
+ *s++ = '\0';
+ }
+
+ // Get attribs
+ while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS - 3) {
+ char *name = NULL;
+ char *value = NULL;
+
+ // Skip white space before the attrib name
+ while (*s && nsvg__isspace(*s))
+ s++;
+ if (!*s)
+ break;
+ if (*s == '/') {
+ end = 1;
+ break;
+ }
+ name = s;
+ // Find end of the attrib name.
+ while (*s && !nsvg__isspace(*s) && *s != '=')
+ s++;
+ if (*s) {
+ *s++ = '\0';
+ }
+ // Skip until the beginning of the value.
+ while (*s && *s != '\"' && *s != '\'')
+ s++;
+ if (!*s)
+ break;
+ quote = *s;
+ s++;
+ // Store value and find the end of it.
+ value = s;
+ while (*s && *s != quote)
+ s++;
+ if (*s) {
+ *s++ = '\0';
+ }
+
+ // Store only well formed attributes
+ if (name && value) {
+ attr[nattr++] = name;
+ attr[nattr++] = value;
+ }
+ }
+
+ // List terminator
+ attr[nattr++] = 0;
+ attr[nattr++] = 0;
+
+ // Call callbacks.
+ if (start && startelCb)
+ (*startelCb)(ud, name, attr);
+ if (end && endelCb)
+ (*endelCb)(ud, name);
+}
+
+static int nsvg__parseXML(char *input,
+ void (*startelCb)(void *ud, const char *el, const char **attr),
+ void (*endelCb)(void *ud, const char *el),
+ void (*contentCb)(void *ud, const char *s),
+ void *ud)
+{
+ char *s = input;
+ char *mark = s;
+ int state = NSVG_XML_CONTENT;
+ while (*s) {
+ if (*s == '<' && state == NSVG_XML_CONTENT) {
+ // Start of a tag
+ *s++ = '\0';
+ nsvg__parseContent(mark, contentCb, ud);
+ mark = s;
+ state = NSVG_XML_TAG;
+ }
+ else if (*s == '>' && state == NSVG_XML_TAG) {
+ // Start of a content or new tag.
+ *s++ = '\0';
+ nsvg__parseElement(mark, startelCb, endelCb, ud);
+ mark = s;
+ state = NSVG_XML_CONTENT;
+ }
+ else {
+ s++;
+ }
+ }
+
+ return 1;
+}
+
+/* Simple SVG parser. */
+
+#define NSVG_MAX_ATTR 128
+#define NSVG_MAX_BREADCRUMB 5
+
+enum NSVGgradientUnits { NSVG_USER_SPACE = 0, NSVG_OBJECT_SPACE = 1 };
+
+#define NSVG_MAX_DASHES 8
+
+enum NSVGunits {
+ NSVG_UNITS_USER,
+ NSVG_UNITS_PX,
+ NSVG_UNITS_PT,
+ NSVG_UNITS_PC,
+ NSVG_UNITS_MM,
+ NSVG_UNITS_CM,
+ NSVG_UNITS_IN,
+ NSVG_UNITS_PERCENT,
+ NSVG_UNITS_EM,
+ NSVG_UNITS_EX
+};
+
+typedef struct NSVGcoordinate {
+ float value;
+ int units;
+} NSVGcoordinate;
+
+typedef struct NSVGlinearData {
+ NSVGcoordinate x1, y1, x2, y2;
+} NSVGlinearData;
+
+typedef struct NSVGradialData {
+ NSVGcoordinate cx, cy, r, fx, fy;
+} NSVGradialData;
+
+typedef struct NSVGgradientData {
+ char id[64];
+ char ref[64];
+ char type;
+ union {
+ NSVGlinearData linear;
+ NSVGradialData radial;
+ };
+ char spread;
+ char units;
+ float xform[6];
+ int nstops;
+ NSVGgradientStop *stops;
+ struct NSVGgradientData *next;
+} NSVGgradientData;
+
+typedef struct NSVGattrib {
+ char id[64];
+ float xform[6];
+ unsigned int fillColor;
+ unsigned int strokeColor;
+ float opacity;
+ float fillOpacity;
+ float strokeOpacity;
+ char fillGradient[64];
+ char strokeGradient[64];
+ float strokeWidth;
+ float strokeDashOffset;
+ float strokeDashArray[NSVG_MAX_DASHES];
+ int strokeDashCount;
+ char strokeLineJoin;
+ char strokeLineCap;
+ float miterLimit;
+ char fillRule;
+ float fontSize;
+ unsigned int stopColor;
+ float stopOpacity;
+ float stopOffset;
+ char hasFill;
+ char hasStroke;
+ char visible;
+} NSVGattrib;
+
+typedef struct NSVGparser {
+ NSVGattrib attr[NSVG_MAX_ATTR];
+ int attrHead;
+ float *pts;
+ int npts;
+ int cpts;
+ NSVGpath *plist;
+ NSVGimage *image;
+ NSVGgradientData *gradients;
+ NSVGshape *shapesTail;
+ float viewMinx, viewMiny, viewWidth, viewHeight;
+ int alignX, alignY, alignType;
+ float dpi;
+ char pathFlag;
+ char defsFlag;
+ /** Blender breadscrum for layers. */
+ char breadcrumb[NSVG_MAX_BREADCRUMB][64];
+ /** Blender number of elements in breadscrum. */
+ int breadcrumb_len;
+} NSVGparser;
+
+static void nsvg__xformIdentity(float *t)
+{
+ t[0] = 1.0f;
+ t[1] = 0.0f;
+ t[2] = 0.0f;
+ t[3] = 1.0f;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetTranslation(float *t, float tx, float ty)
+{
+ t[0] = 1.0f;
+ t[1] = 0.0f;
+ t[2] = 0.0f;
+ t[3] = 1.0f;
+ t[4] = tx;
+ t[5] = ty;
+}
+
+static void nsvg__xformSetScale(float *t, float sx, float sy)
+{
+ t[0] = sx;
+ t[1] = 0.0f;
+ t[2] = 0.0f;
+ t[3] = sy;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetSkewX(float *t, float a)
+{
+ t[0] = 1.0f;
+ t[1] = 0.0f;
+ t[2] = tanf(a);
+ t[3] = 1.0f;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetSkewY(float *t, float a)
+{
+ t[0] = 1.0f;
+ t[1] = tanf(a);
+ t[2] = 0.0f;
+ t[3] = 1.0f;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetRotation(float *t, float a)
+{
+ float cs = cosf(a), sn = sinf(a);
+ t[0] = cs;
+ t[1] = sn;
+ t[2] = -sn;
+ t[3] = cs;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformMultiply(float *t, float *s)
+{
+ float t0 = t[0] * s[0] + t[1] * s[2];
+ float t2 = t[2] * s[0] + t[3] * s[2];
+ float t4 = t[4] * s[0] + t[5] * s[2] + s[4];
+ t[1] = t[0] * s[1] + t[1] * s[3];
+ t[3] = t[2] * s[1] + t[3] * s[3];
+ t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
+ t[0] = t0;
+ t[2] = t2;
+ t[4] = t4;
+}
+
+static void nsvg__xformInverse(float *inv, float *t)
+{
+ double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
+ if (det > -1e-6 && det < 1e-6) {
+ nsvg__xformIdentity(t);
+ return;
+ }
+ invdet = 1.0 / det;
+ inv[0] = (float)(t[3] * invdet);
+ inv[2] = (float)(-t[2] * invdet);
+ inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
+ inv[1] = (float)(-t[1] * invdet);
+ inv[3] = (float)(t[0] * invdet);
+ inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
+}
+
+static void nsvg__xformPremultiply(float *t, float *s)
+{
+ float s2[6];
+ memcpy(s2, s, sizeof(float) * 6);
+ nsvg__xformMultiply(s2, t);
+ memcpy(t, s2, sizeof(float) * 6);
+}
+
+static void nsvg__xformPoint(float *dx, float *dy, float x, float y, float *t)
+{
+ *dx = x * t[0] + y * t[2] + t[4];
+ *dy = x * t[1] + y * t[3] + t[5];
+}
+
+static void nsvg__xformVec(float *dx, float *dy, float x, float y, float *t)
+{
+ *dx = x * t[0] + y * t[2];
+ *dy = x * t[1] + y * t[3];
+}
+
+#define NSVG_EPSILON (1e-12)
+
+static int nsvg__ptInBounds(float *pt, float *bounds)
+{
+ return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];
+}
+
+static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3)
+{
+ double it = 1.0 - t;
+ return it * it * it * p0 + 3.0 * it * it * t * p1 + 3.0 * it * t * t * p2 + t * t * t * p3;
+}
+
+static void nsvg__curveBounds(float *bounds, float *curve)
+{
+ int i, j, count;
+ double roots[2], a, b, c, b2ac, t, v;
+ float *v0 = &curve[0];
+ float *v1 = &curve[2];
+ float *v2 = &curve[4];
+ float *v3 = &curve[6];
+
+ // Start the bounding box by end points
+ bounds[0] = nsvg__minf(v0[0], v3[0]);
+ bounds[1] = nsvg__minf(v0[1], v3[1]);
+ bounds[2] = nsvg__maxf(v0[0], v3[0]);
+ bounds[3] = nsvg__maxf(v0[1], v3[1]);
+
+ // Bezier curve fits inside the convex hull of it's control points.
+ // If control points are inside the bounds, we're done.
+ if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds))
+ return;
+
+ // Add bezier curve inflection points in X and Y.
+ for (i = 0; i < 2; i++) {
+ a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i];
+ b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i];
+ c = 3.0 * v1[i] - 3.0 * v0[i];
+ count = 0;
+ if (fabs(a) < NSVG_EPSILON) {
+ if (fabs(b) > NSVG_EPSILON) {
+ t = -c / b;
+ if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON)
+ roots[count++] = t;
+ }
+ }
+ else {
+ b2ac = b * b - 4.0 * c * a;
+ if (b2ac > NSVG_EPSILON) {
+ t = (-b + sqrt(b2ac)) / (2.0 * a);
+ if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON)
+ roots[count++] = t;
+ t = (-b - sqrt(b2ac)) / (2.0 * a);
+ if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON)
+ roots[count++] = t;
+ }
+ }
+ for (j = 0; j < count; j++) {
+ v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]);
+ bounds[0 + i] = nsvg__minf(bounds[0 + i], (float)v);
+ bounds[2 + i] = nsvg__maxf(bounds[2 + i], (float)v);
+ }
+ }
+}
+
+static NSVGparser *nsvg__createParser()
+{
+ NSVGparser *p;
+ p = (NSVGparser *)malloc(sizeof(NSVGparser));
+ if (p == NULL)
+ goto error;
+ memset(p, 0, sizeof(NSVGparser));
+
+ p->image = (NSVGimage *)malloc(sizeof(NSVGimage));
+ if (p->image == NULL)
+ goto error;
+ memset(p->image, 0, sizeof(NSVGimage));
+
+ // Init style
+ nsvg__xformIdentity(p->attr[0].xform);
+ memset(p->attr[0].id, 0, sizeof p->attr[0].id);
+ p->attr[0].fillColor = NSVG_RGB(0, 0, 0);
+ p->attr[0].strokeColor = NSVG_RGB(0, 0, 0);
+ p->attr[0].opacity = 1;
+ p->attr[0].fillOpacity = 1;
+ p->attr[0].strokeOpacity = 1;
+ p->attr[0].stopOpacity = 1;
+ p->attr[0].strokeWidth = 1;
+ p->attr[0].strokeLineJoin = NSVG_JOIN_MITER;
+ p->attr[0].strokeLineCap = NSVG_CAP_BUTT;
+ p->attr[0].miterLimit = 4;
+ p->attr[0].fillRule = NSVG_FILLRULE_NONZERO;
+ p->attr[0].hasFill = 1;
+ p->attr[0].visible = 1;
+
+ return p;
+
+error:
+ if (p) {
+ if (p->image)
+ free(p->image);
+ free(p);
+ }
+ return NULL;
+}
+
+static void nsvg__deletePaths(NSVGpath *path)
+{
+ while (path) {
+ NSVGpath *next = path->next;
+ if (path->pts != NULL)
+ free(path->pts);
+ free(path);
+ path = next;
+ }
+}
+
+static void nsvg__deletePaint(NSVGpaint *paint)
+{
+ if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT)
+ free(paint->gradient);
+}
+
+static void nsvg__deleteGradientData(NSVGgradientData *grad)
+{
+ NSVGgradientData *next;
+ while (grad != NULL) {
+ next = grad->next;
+ free(grad->stops);
+ free(grad);
+ grad = next;
+ }
+}
+
+static void nsvg__deleteParser(NSVGparser *p)
+{
+ if (p != NULL) {
+ nsvg__deletePaths(p->plist);
+ nsvg__deleteGradientData(p->gradients);
+ nsvgDelete(p->image);
+ free(p->pts);
+ free(p);
+ }
+}
+
+static void nsvg__resetPath(NSVGparser *p)
+{
+ p->npts = 0;
+}
+
+static void nsvg__addPoint(NSVGparser *p, float x, float y)
+{
+ if (p->npts + 1 > p->cpts) {
+ p->cpts = p->cpts ? p->cpts * 2 : 8;
+ p->pts = (float *)realloc(p->pts, p->cpts * 2 * sizeof(float));
+ if (!p->pts)
+ return;
+ }
+ p->pts[p->npts * 2 + 0] = x;
+ p->pts[p->npts * 2 + 1] = y;
+ p->npts++;
+}
+
+static void nsvg__moveTo(NSVGparser *p, float x, float y)
+{
+ if (p->npts > 0) {
+ p->pts[(p->npts - 1) * 2 + 0] = x;
+ p->pts[(p->npts - 1) * 2 + 1] = y;
+ }
+ else {
+ nsvg__addPoint(p, x, y);
+ }
+}
+
+static void nsvg__lineTo(NSVGparser *p, float x, float y)
+{
+ float px, py, dx, dy;
+ if (p->npts > 0) {
+ px = p->pts[(p->npts - 1) * 2 + 0];
+ py = p->pts[(p->npts - 1) * 2 + 1];
+ dx = x - px;
+ dy = y - py;
+ nsvg__addPoint(p, px + dx / 3.0f, py + dy / 3.0f);
+ nsvg__addPoint(p, x - dx / 3.0f, y - dy / 3.0f);
+ nsvg__addPoint(p, x, y);
+ }
+}
+
+static void nsvg__cubicBezTo(
+ NSVGparser *p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)
+{
+ if (p->npts > 0) {
+ nsvg__addPoint(p, cpx1, cpy1);
+ nsvg__addPoint(p, cpx2, cpy2);
+ nsvg__addPoint(p, x, y);
+ }
+}
+
+static NSVGattrib *nsvg__getAttr(NSVGparser *p)
+{
+ return &p->attr[p->attrHead];
+}
+
+static void nsvg__pushAttr(NSVGparser *p)
+{
+ if (p->attrHead < NSVG_MAX_ATTR - 1) {
+ p->attrHead++;
+ memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead - 1], sizeof(NSVGattrib));
+ }
+}
+
+static void nsvg__popAttr(NSVGparser *p)
+{
+ if (p->attrHead > 0)
+ p->attrHead--;
+}
+
+static float nsvg__actualOrigX(NSVGparser *p)
+{
+ return p->viewMinx;
+}
+
+static float nsvg__actualOrigY(NSVGparser *p)
+{
+ return p->viewMiny;
+}
+
+static float nsvg__actualWidth(NSVGparser *p)
+{
+ return p->viewWidth;
+}
+
+static float nsvg__actualHeight(NSVGparser *p)
+{
+ return p->viewHeight;
+}
+
+static float nsvg__actualLength(NSVGparser *p)
+{
+ float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p);
+ return sqrtf(w * w + h * h) / sqrtf(2.0f);
+}
+
+static float nsvg__convertToPixels(NSVGparser *p, NSVGcoordinate c, float orig, float length)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ switch (c.units) {
+ case NSVG_UNITS_USER:
+ return c.value;
+ case NSVG_UNITS_PX:
+ return c.value;
+ case NSVG_UNITS_PT:
+ return c.value / 72.0f * p->dpi;
+ case NSVG_UNITS_PC:
+ return c.value / 6.0f * p->dpi;
+ case NSVG_UNITS_MM:
+ return c.value / 25.4f * p->dpi;
+ case NSVG_UNITS_CM:
+ return c.value / 2.54f * p->dpi;
+ case NSVG_UNITS_IN:
+ return c.value * p->dpi;
+ case NSVG_UNITS_EM:
+ return c.value * attr->fontSize;
+ case NSVG_UNITS_EX:
+ return c.value * attr->fontSize * 0.52f; // x-height of Helvetica.
+ case NSVG_UNITS_PERCENT:
+ return orig + c.value / 100.0f * length;
+ default:
+ return c.value;
+ }
+ return c.value;
+}
+
+static NSVGgradientData *nsvg__findGradientData(NSVGparser *p, const char *id)
+{
+ NSVGgradientData *grad = p->gradients;
+ if (id == NULL || *id == '\0')
+ return NULL;
+ while (grad != NULL) {
+ if (strcmp(grad->id, id) == 0)
+ return grad;
+ grad = grad->next;
+ }
+ return NULL;
+}
+
+static NSVGgradient *nsvg__createGradient(NSVGparser *p,
+ const char *id,
+ const float *localBounds,
+ char *paintType)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ NSVGgradientData *data = NULL;
+ NSVGgradientData *ref = NULL;
+ NSVGgradientStop *stops = NULL;
+ NSVGgradient *grad;
+ float ox, oy, sw, sh, sl;
+ int nstops = 0;
+ int refIter;
+
+ data = nsvg__findGradientData(p, id);
+ if (data == NULL)
+ return NULL;
+
+ // TODO: use ref to fill in all unset values too.
+ ref = data;
+ refIter = 0;
+ while (ref != NULL) {
+ NSVGgradientData *nextRef = NULL;
+ if (stops == NULL && ref->stops != NULL) {
+ stops = ref->stops;
+ nstops = ref->nstops;
+ break;
+ }
+ nextRef = nsvg__findGradientData(p, ref->ref);
+ if (nextRef == ref)
+ break; // prevent infite loops on malformed data
+ ref = nextRef;
+ refIter++;
+ if (refIter > 32)
+ break; // prevent infite loops on malformed data
+ }
+ if (stops == NULL)
+ return NULL;
+
+ grad = (NSVGgradient *)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop) * (nstops - 1));
+ if (grad == NULL)
+ return NULL;
+
+ // The shape width and height.
+ if (data->units == NSVG_OBJECT_SPACE) {
+ ox = localBounds[0];
+ oy = localBounds[1];
+ sw = localBounds[2] - localBounds[0];
+ sh = localBounds[3] - localBounds[1];
+ }
+ else {
+ ox = nsvg__actualOrigX(p);
+ oy = nsvg__actualOrigY(p);
+ sw = nsvg__actualWidth(p);
+ sh = nsvg__actualHeight(p);
+ }
+ sl = sqrtf(sw * sw + sh * sh) / sqrtf(2.0f);
+
+ if (data->type == NSVG_PAINT_LINEAR_GRADIENT) {
+ float x1, y1, x2, y2, dx, dy;
+ x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw);
+ y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh);
+ x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw);
+ y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh);
+ // Calculate transform aligned to the line
+ dx = x2 - x1;
+ dy = y2 - y1;
+ grad->xform[0] = dy;
+ grad->xform[1] = -dx;
+ grad->xform[2] = dx;
+ grad->xform[3] = dy;
+ grad->xform[4] = x1;
+ grad->xform[5] = y1;
+ }
+ else {
+ float cx, cy, fx, fy, r;
+ cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw);
+ cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh);
+ fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw);
+ fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh);
+ r = nsvg__convertToPixels(p, data->radial.r, 0, sl);
+ // Calculate transform aligned to the circle
+ grad->xform[0] = r;
+ grad->xform[1] = 0;
+ grad->xform[2] = 0;
+ grad->xform[3] = r;
+ grad->xform[4] = cx;
+ grad->xform[5] = cy;
+ grad->fx = fx / r;
+ grad->fy = fy / r;
+ }
+
+ nsvg__xformMultiply(grad->xform, data->xform);
+ nsvg__xformMultiply(grad->xform, attr->xform);
+
+ grad->spread = data->spread;
+ memcpy(grad->stops, stops, nstops * sizeof(NSVGgradientStop));
+ grad->nstops = nstops;
+
+ *paintType = data->type;
+
+ return grad;
+}
+
+static float nsvg__getAverageScale(float *t)
+{
+ float sx = sqrtf(t[0] * t[0] + t[2] * t[2]);
+ float sy = sqrtf(t[1] * t[1] + t[3] * t[3]);
+ return (sx + sy) * 0.5f;
+}
+
+static void nsvg__getLocalBounds(float *bounds, NSVGshape *shape, float *xform)
+{
+ NSVGpath *path;
+ float curve[4 * 2], curveBounds[4];
+ int i, first = 1;
+ for (path = shape->paths; path != NULL; path = path->next) {
+ nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform);
+ for (i = 0; i < path->npts - 1; i += 3) {
+ nsvg__xformPoint(
+ &curve[2], &curve[3], path->pts[(i + 1) * 2], path->pts[(i + 1) * 2 + 1], xform);
+ nsvg__xformPoint(
+ &curve[4], &curve[5], path->pts[(i + 2) * 2], path->pts[(i + 2) * 2 + 1], xform);
+ nsvg__xformPoint(
+ &curve[6], &curve[7], path->pts[(i + 3) * 2], path->pts[(i + 3) * 2 + 1], xform);
+ nsvg__curveBounds(curveBounds, curve);
+ if (first) {
+ bounds[0] = curveBounds[0];
+ bounds[1] = curveBounds[1];
+ bounds[2] = curveBounds[2];
+ bounds[3] = curveBounds[3];
+ first = 0;
+ }
+ else {
+ bounds[0] = nsvg__minf(bounds[0], curveBounds[0]);
+ bounds[1] = nsvg__minf(bounds[1], curveBounds[1]);
+ bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]);
+ bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]);
+ }
+ curve[0] = curve[6];
+ curve[1] = curve[7];
+ }
+ }
+}
+
+static void nsvg__addShape(NSVGparser *p)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ float scale = 1.0f;
+ NSVGshape *shape;
+ NSVGpath *path;
+ int i;
+
+ if (p->plist == NULL)
+ return;
+
+ shape = (NSVGshape *)malloc(sizeof(NSVGshape));
+ if (shape == NULL)
+ goto error;
+ memset(shape, 0, sizeof(NSVGshape));
+
+ memcpy(shape->id, attr->id, sizeof shape->id);
+ /* Copy parent id from breadcrumb. */
+ if (p->breadcrumb_len > 0) {
+ memcpy(shape->id_parent, p->breadcrumb[0], sizeof shape->id_parent);
+ }
+ else {
+ memcpy(shape->id_parent, attr->id, sizeof shape->id_parent);
+ }
+
+ scale = nsvg__getAverageScale(attr->xform);
+ shape->strokeWidth = attr->strokeWidth * scale;
+ shape->strokeDashOffset = attr->strokeDashOffset * scale;
+ shape->strokeDashCount = (char)attr->strokeDashCount;
+ for (i = 0; i < attr->strokeDashCount; i++)
+ shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale;
+ shape->strokeLineJoin = attr->strokeLineJoin;
+ shape->strokeLineCap = attr->strokeLineCap;
+ shape->miterLimit = attr->miterLimit;
+ shape->fillRule = attr->fillRule;
+ shape->opacity = attr->opacity;
+
+ shape->paths = p->plist;
+ p->plist = NULL;
+
+ // Calculate shape bounds
+ shape->bounds[0] = shape->paths->bounds[0];
+ shape->bounds[1] = shape->paths->bounds[1];
+ shape->bounds[2] = shape->paths->bounds[2];
+ shape->bounds[3] = shape->paths->bounds[3];
+ for (path = shape->paths->next; path != NULL; path = path->next) {
+ shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]);
+ shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]);
+ shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]);
+ shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]);
+ }
+
+ // Set fill
+ if (attr->hasFill == 0) {
+ shape->fill.type = NSVG_PAINT_NONE;
+ }
+ else if (attr->hasFill == 1) {
+ shape->fill.type = NSVG_PAINT_COLOR;
+ shape->fill.color = attr->fillColor;
+ shape->fill.color |= (unsigned int)(attr->fillOpacity * 255) << 24;
+ }
+ else if (attr->hasFill == 2) {
+ float inv[6], localBounds[4];
+ nsvg__xformInverse(inv, attr->xform);
+ nsvg__getLocalBounds(localBounds, shape, inv);
+ shape->fill.gradient = nsvg__createGradient(
+ p, attr->fillGradient, localBounds, &shape->fill.type);
+ if (shape->fill.gradient == NULL) {
+ shape->fill.type = NSVG_PAINT_NONE;
+ }
+ }
+
+ // Set stroke
+ if (attr->hasStroke == 0) {
+ shape->stroke.type = NSVG_PAINT_NONE;
+ }
+ else if (attr->hasStroke == 1) {
+ shape->stroke.type = NSVG_PAINT_COLOR;
+ shape->stroke.color = attr->strokeColor;
+ shape->stroke.color |= (unsigned int)(attr->strokeOpacity * 255) << 24;
+ }
+ else if (attr->hasStroke == 2) {
+ float inv[6], localBounds[4];
+ nsvg__xformInverse(inv, attr->xform);
+ nsvg__getLocalBounds(localBounds, shape, inv);
+ shape->stroke.gradient = nsvg__createGradient(
+ p, attr->strokeGradient, localBounds, &shape->stroke.type);
+ if (shape->stroke.gradient == NULL)
+ shape->stroke.type = NSVG_PAINT_NONE;
+ }
+
+ // Set flags
+ shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00);
+
+ // Add to tail
+ if (p->image->shapes == NULL)
+ p->image->shapes = shape;
+ else
+ p->shapesTail->next = shape;
+ p->shapesTail = shape;
+
+ return;
+
+error:
+ if (shape)
+ free(shape);
+}
+
+static void nsvg__addPath(NSVGparser *p, char closed)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ NSVGpath *path = NULL;
+ float bounds[4];
+ float *curve;
+ int i;
+
+ if (p->npts < 4)
+ return;
+
+ if (closed)
+ nsvg__lineTo(p, p->pts[0], p->pts[1]);
+
+ // Expect 1 + N*3 points (N = number of cubic bezier segments).
+ if ((p->npts % 3) != 1)
+ return;
+
+ path = (NSVGpath *)malloc(sizeof(NSVGpath));
+ if (path == NULL)
+ goto error;
+ memset(path, 0, sizeof(NSVGpath));
+
+ path->pts = (float *)malloc(p->npts * 2 * sizeof(float));
+ if (path->pts == NULL)
+ goto error;
+ path->closed = closed;
+ path->npts = p->npts;
+
+ // Transform path.
+ for (i = 0; i < p->npts; ++i)
+ nsvg__xformPoint(
+ &path->pts[i * 2], &path->pts[i * 2 + 1], p->pts[i * 2], p->pts[i * 2 + 1], attr->xform);
+
+ // Find bounds
+ for (i = 0; i < path->npts - 1; i += 3) {
+ curve = &path->pts[i * 2];
+ nsvg__curveBounds(bounds, curve);
+ if (i == 0) {
+ path->bounds[0] = bounds[0];
+ path->bounds[1] = bounds[1];
+ path->bounds[2] = bounds[2];
+ path->bounds[3] = bounds[3];
+ }
+ else {
+ path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]);
+ path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]);
+ path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]);
+ path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]);
+ }
+ }
+
+ path->next = p->plist;
+ p->plist = path;
+
+ return;
+
+error:
+ if (path != NULL) {
+ if (path->pts != NULL)
+ free(path->pts);
+ free(path);
+ }
+}
+
+// We roll our own string to float because the std library one uses locale and messes things up.
+static double nsvg__atof(const char *s)
+{
+ char *cur = (char *)s;
+ char *end = NULL;
+ double res = 0.0, sign = 1.0;
+ long long intPart = 0, fracPart = 0;
+ char hasIntPart = 0, hasFracPart = 0;
+
+ // Parse optional sign
+ if (*cur == '+') {
+ cur++;
+ }
+ else if (*cur == '-') {
+ sign = -1;
+ cur++;
+ }
+
+ // Parse integer part
+ if (nsvg__isdigit(*cur)) {
+ // Parse digit sequence
+ intPart = strtoll(cur, &end, 10);
+ if (cur != end) {
+ res = (double)intPart;
+ hasIntPart = 1;
+ cur = end;
+ }
+ }
+
+ // Parse fractional part.
+ if (*cur == '.') {
+ cur++; // Skip '.'
+ if (nsvg__isdigit(*cur)) {
+ // Parse digit sequence
+ fracPart = strtoll(cur, &end, 10);
+ if (cur != end) {
+ res += (double)fracPart / pow(10.0, (double)(end - cur));
+ hasFracPart = 1;
+ cur = end;
+ }
+ }
+ }
+
+ // A valid number should have integer or fractional part.
+ if (!hasIntPart && !hasFracPart)
+ return 0.0;
+
+ // Parse optional exponent
+ if (*cur == 'e' || *cur == 'E') {
+ long expPart = 0;
+ cur++; // skip 'E'
+ expPart = strtol(cur, &end, 10); // Parse digit sequence with sign
+ if (cur != end) {
+ res *= pow(10.0, (double)expPart);
+ }
+ }
+
+ return res * sign;
+}
+
+static const char *nsvg__parseNumber(const char *s, char *it, const int size)
+{
+ const int last = size - 1;
+ int i = 0;
+
+ // sign
+ if (*s == '-' || *s == '+') {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ // integer part
+ while (*s && nsvg__isdigit(*s)) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ if (*s == '.') {
+ // decimal point
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ // fraction part
+ while (*s && nsvg__isdigit(*s)) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ }
+ // exponent
+ if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ if (*s == '-' || *s == '+') {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ while (*s && nsvg__isdigit(*s)) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ }
+ it[i] = '\0';
+
+ return s;
+}
+
+static const char *nsvg__getNextPathItem(const char *s, char *it)
+{
+ it[0] = '\0';
+ // Skip white spaces and commas
+ while (*s && (nsvg__isspace(*s) || *s == ','))
+ s++;
+ if (!*s)
+ return s;
+ if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) {
+ s = nsvg__parseNumber(s, it, 64);
+ }
+ else {
+ // Parse command
+ it[0] = *s++;
+ it[1] = '\0';
+ return s;
+ }
+
+ return s;
+}
+
+static unsigned int nsvg__parseColorHex(const char *str)
+{
+ unsigned int c = 0, r = 0, g = 0, b = 0;
+ int n = 0;
+ str++; // skip #
+ // Calculate number of characters.
+ while (str[n] && !nsvg__isspace(str[n]))
+ n++;
+ if (n == 6) {
+ sscanf(str, "%x", &c);
+ }
+ else if (n == 3) {
+ sscanf(str, "%x", &c);
+ c = (c & 0xf) | ((c & 0xf0) << 4) | ((c & 0xf00) << 8);
+ c |= c << 4;
+ }
+ r = (c >> 16) & 0xff;
+ g = (c >> 8) & 0xff;
+ b = c & 0xff;
+ return NSVG_RGB(r, g, b);
+}
+
+static unsigned int nsvg__parseColorRGB(const char *str)
+{
+ int r = -1, g = -1, b = -1;
+ char s1[32] = "", s2[32] = "";
+ sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b);
+ if (strchr(s1, '%')) {
+ return NSVG_RGB((r * 255) / 100, (g * 255) / 100, (b * 255) / 100);
+ }
+ else {
+ return NSVG_RGB(r, g, b);
+ }
+}
+
+typedef struct NSVGNamedColor {
+ const char *name;
+ unsigned int color;
+} NSVGNamedColor;
+
+NSVGNamedColor nsvg__colors[] = {
+
+ {"red", NSVG_RGB(255, 0, 0)},
+ {"green", NSVG_RGB(0, 128, 0)},
+ {"blue", NSVG_RGB(0, 0, 255)},
+ {"yellow", NSVG_RGB(255, 255, 0)},
+ {"cyan", NSVG_RGB(0, 255, 255)},
+ {"magenta", NSVG_RGB(255, 0, 255)},
+ {"black", NSVG_RGB(0, 0, 0)},
+ {"grey", NSVG_RGB(128, 128, 128)},
+ {"gray", NSVG_RGB(128, 128, 128)},
+ {"white", NSVG_RGB(255, 255, 255)},
+
+#ifdef NANOSVG_ALL_COLOR_KEYWORDS
+ {"aliceblue", NSVG_RGB(240, 248, 255)},
+ {"antiquewhite", NSVG_RGB(250, 235, 215)},
+ {"aqua", NSVG_RGB(0, 255, 255)},
+ {"aquamarine", NSVG_RGB(127, 255, 212)},
+ {"azure", NSVG_RGB(240, 255, 255)},
+ {"beige", NSVG_RGB(245, 245, 220)},
+ {"bisque", NSVG_RGB(255, 228, 196)},
+ {"blanchedalmond", NSVG_RGB(255, 235, 205)},
+ {"blueviolet", NSVG_RGB(138, 43, 226)},
+ {"brown", NSVG_RGB(165, 42, 42)},
+ {"burlywood", NSVG_RGB(222, 184, 135)},
+ {"cadetblue", NSVG_RGB(95, 158, 160)},
+ {"chartreuse", NSVG_RGB(127, 255, 0)},
+ {"chocolate", NSVG_RGB(210, 105, 30)},
+ {"coral", NSVG_RGB(255, 127, 80)},
+ {"cornflowerblue", NSVG_RGB(100, 149, 237)},
+ {"cornsilk", NSVG_RGB(255, 248, 220)},
+ {"crimson", NSVG_RGB(220, 20, 60)},
+ {"darkblue", NSVG_RGB(0, 0, 139)},
+ {"darkcyan", NSVG_RGB(0, 139, 139)},
+ {"darkgoldenrod", NSVG_RGB(184, 134, 11)},
+ {"darkgray", NSVG_RGB(169, 169, 169)},
+ {"darkgreen", NSVG_RGB(0, 100, 0)},
+ {"darkgrey", NSVG_RGB(169, 169, 169)},
+ {"darkkhaki", NSVG_RGB(189, 183, 107)},
+ {"darkmagenta", NSVG_RGB(139, 0, 139)},
+ {"darkolivegreen", NSVG_RGB(85, 107, 47)},
+ {"darkorange", NSVG_RGB(255, 140, 0)},
+ {"darkorchid", NSVG_RGB(153, 50, 204)},
+ {"darkred", NSVG_RGB(139, 0, 0)},
+ {"darksalmon", NSVG_RGB(233, 150, 122)},
+ {"darkseagreen", NSVG_RGB(143, 188, 143)},
+ {"darkslateblue", NSVG_RGB(72, 61, 139)},
+ {"darkslategray", NSVG_RGB(47, 79, 79)},
+ {"darkslategrey", NSVG_RGB(47, 79, 79)},
+ {"darkturquoise", NSVG_RGB(0, 206, 209)},
+ {"darkviolet", NSVG_RGB(148, 0, 211)},
+ {"deeppink", NSVG_RGB(255, 20, 147)},
+ {"deepskyblue", NSVG_RGB(0, 191, 255)},
+ {"dimgray", NSVG_RGB(105, 105, 105)},
+ {"dimgrey", NSVG_RGB(105, 105, 105)},
+ {"dodgerblue", NSVG_RGB(30, 144, 255)},
+ {"firebrick", NSVG_RGB(178, 34, 34)},
+ {"floralwhite", NSVG_RGB(255, 250, 240)},
+ {"forestgreen", NSVG_RGB(34, 139, 34)},
+ {"fuchsia", NSVG_RGB(255, 0, 255)},
+ {"gainsboro", NSVG_RGB(220, 220, 220)},
+ {"ghostwhite", NSVG_RGB(248, 248, 255)},
+ {"gold", NSVG_RGB(255, 215, 0)},
+ {"goldenrod", NSVG_RGB(218, 165, 32)},
+ {"greenyellow", NSVG_RGB(173, 255, 47)},
+ {"honeydew", NSVG_RGB(240, 255, 240)},
+ {"hotpink", NSVG_RGB(255, 105, 180)},
+ {"indianred", NSVG_RGB(205, 92, 92)},
+ {"indigo", NSVG_RGB(75, 0, 130)},
+ {"ivory", NSVG_RGB(255, 255, 240)},
+ {"khaki", NSVG_RGB(240, 230, 140)},
+ {"lavender", NSVG_RGB(230, 230, 250)},
+ {"lavenderblush", NSVG_RGB(255, 240, 245)},
+ {"lawngreen", NSVG_RGB(124, 252, 0)},
+ {"lemonchiffon", NSVG_RGB(255, 250, 205)},
+ {"lightblue", NSVG_RGB(173, 216, 230)},
+ {"lightcoral", NSVG_RGB(240, 128, 128)},
+ {"lightcyan", NSVG_RGB(224, 255, 255)},
+ {"lightgoldenrodyellow", NSVG_RGB(250, 250, 210)},
+ {"lightgray", NSVG_RGB(211, 211, 211)},
+ {"lightgreen", NSVG_RGB(144, 238, 144)},
+ {"lightgrey", NSVG_RGB(211, 211, 211)},
+ {"lightpink", NSVG_RGB(255, 182, 193)},
+ {"lightsalmon", NSVG_RGB(255, 160, 122)},
+ {"lightseagreen", NSVG_RGB(32, 178, 170)},
+ {"lightskyblue", NSVG_RGB(135, 206, 250)},
+ {"lightslategray", NSVG_RGB(119, 136, 153)},
+ {"lightslategrey", NSVG_RGB(119, 136, 153)},
+ {"lightsteelblue", NSVG_RGB(176, 196, 222)},
+ {"lightyellow", NSVG_RGB(255, 255, 224)},
+ {"lime", NSVG_RGB(0, 255, 0)},
+ {"limegreen", NSVG_RGB(50, 205, 50)},
+ {"linen", NSVG_RGB(250, 240, 230)},
+ {"maroon", NSVG_RGB(128, 0, 0)},
+ {"mediumaquamarine", NSVG_RGB(102, 205, 170)},
+ {"mediumblue", NSVG_RGB(0, 0, 205)},
+ {"mediumorchid", NSVG_RGB(186, 85, 211)},
+ {"mediumpurple", NSVG_RGB(147, 112, 219)},
+ {"mediumseagreen", NSVG_RGB(60, 179, 113)},
+ {"mediumslateblue", NSVG_RGB(123, 104, 238)},
+ {"mediumspringgreen", NSVG_RGB(0, 250, 154)},
+ {"mediumturquoise", NSVG_RGB(72, 209, 204)},
+ {"mediumvioletred", NSVG_RGB(199, 21, 133)},
+ {"midnightblue", NSVG_RGB(25, 25, 112)},
+ {"mintcream", NSVG_RGB(245, 255, 250)},
+ {"mistyrose", NSVG_RGB(255, 228, 225)},
+ {"moccasin", NSVG_RGB(255, 228, 181)},
+ {"navajowhite", NSVG_RGB(255, 222, 173)},
+ {"navy", NSVG_RGB(0, 0, 128)},
+ {"oldlace", NSVG_RGB(253, 245, 230)},
+ {"olive", NSVG_RGB(128, 128, 0)},
+ {"olivedrab", NSVG_RGB(107, 142, 35)},
+ {"orange", NSVG_RGB(255, 165, 0)},
+ {"orangered", NSVG_RGB(255, 69, 0)},
+ {"orchid", NSVG_RGB(218, 112, 214)},
+ {"palegoldenrod", NSVG_RGB(238, 232, 170)},
+ {"palegreen", NSVG_RGB(152, 251, 152)},
+ {"paleturquoise", NSVG_RGB(175, 238, 238)},
+ {"palevioletred", NSVG_RGB(219, 112, 147)},
+ {"papayawhip", NSVG_RGB(255, 239, 213)},
+ {"peachpuff", NSVG_RGB(255, 218, 185)},
+ {"peru", NSVG_RGB(205, 133, 63)},
+ {"pink", NSVG_RGB(255, 192, 203)},
+ {"plum", NSVG_RGB(221, 160, 221)},
+ {"powderblue", NSVG_RGB(176, 224, 230)},
+ {"purple", NSVG_RGB(128, 0, 128)},
+ {"rosybrown", NSVG_RGB(188, 143, 143)},
+ {"royalblue", NSVG_RGB(65, 105, 225)},
+ {"saddlebrown", NSVG_RGB(139, 69, 19)},
+ {"salmon", NSVG_RGB(250, 128, 114)},
+ {"sandybrown", NSVG_RGB(244, 164, 96)},
+ {"seagreen", NSVG_RGB(46, 139, 87)},
+ {"seashell", NSVG_RGB(255, 245, 238)},
+ {"sienna", NSVG_RGB(160, 82, 45)},
+ {"silver", NSVG_RGB(192, 192, 192)},
+ {"skyblue", NSVG_RGB(135, 206, 235)},
+ {"slateblue", NSVG_RGB(106, 90, 205)},
+ {"slategray", NSVG_RGB(112, 128, 144)},
+ {"slategrey", NSVG_RGB(112, 128, 144)},
+ {"snow", NSVG_RGB(255, 250, 250)},
+ {"springgreen", NSVG_RGB(0, 255, 127)},
+ {"steelblue", NSVG_RGB(70, 130, 180)},
+ {"tan", NSVG_RGB(210, 180, 140)},
+ {"teal", NSVG_RGB(0, 128, 128)},
+ {"thistle", NSVG_RGB(216, 191, 216)},
+ {"tomato", NSVG_RGB(255, 99, 71)},
+ {"turquoise", NSVG_RGB(64, 224, 208)},
+ {"violet", NSVG_RGB(238, 130, 238)},
+ {"wheat", NSVG_RGB(245, 222, 179)},
+ {"whitesmoke", NSVG_RGB(245, 245, 245)},
+ {"yellowgreen", NSVG_RGB(154, 205, 50)},
+#endif
+};
+
+static unsigned int nsvg__parseColorName(const char *str)
+{
+ int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor);
+
+ for (i = 0; i < ncolors; i++) {
+ if (strcmp(nsvg__colors[i].name, str) == 0) {
+ return nsvg__colors[i].color;
+ }
+ }
+
+ return NSVG_RGB(128, 128, 128);
+}
+
+static unsigned int nsvg__parseColor(const char *str)
+{
+ size_t len = 0;
+ while (*str == ' ')
+ ++str;
+ len = strlen(str);
+ if (len >= 1 && *str == '#')
+ return nsvg__parseColorHex(str);
+ else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(')
+ return nsvg__parseColorRGB(str);
+ return nsvg__parseColorName(str);
+}
+
+static float nsvg__parseOpacity(const char *str)
+{
+ float val = nsvg__atof(str);
+ if (val < 0.0f)
+ val = 0.0f;
+ if (val > 1.0f)
+ val = 1.0f;
+ return val;
+}
+
+static float nsvg__parseMiterLimit(const char *str)
+{
+ float val = nsvg__atof(str);
+ if (val < 0.0f)
+ val = 0.0f;
+ return val;
+}
+
+static int nsvg__parseUnits(const char *units)
+{
+ if (units[0] == 'p' && units[1] == 'x')
+ return NSVG_UNITS_PX;
+ else if (units[0] == 'p' && units[1] == 't')
+ return NSVG_UNITS_PT;
+ else if (units[0] == 'p' && units[1] == 'c')
+ return NSVG_UNITS_PC;
+ else if (units[0] == 'm' && units[1] == 'm')
+ return NSVG_UNITS_MM;
+ else if (units[0] == 'c' && units[1] == 'm')
+ return NSVG_UNITS_CM;
+ else if (units[0] == 'i' && units[1] == 'n')
+ return NSVG_UNITS_IN;
+ else if (units[0] == '%')
+ return NSVG_UNITS_PERCENT;
+ else if (units[0] == 'e' && units[1] == 'm')
+ return NSVG_UNITS_EM;
+ else if (units[0] == 'e' && units[1] == 'x')
+ return NSVG_UNITS_EX;
+ return NSVG_UNITS_USER;
+}
+
+static int nsvg__isCoordinate(const char *s)
+{
+ // optional sign
+ if (*s == '-' || *s == '+')
+ s++;
+ // must have at least one digit
+ return nsvg__isdigit(*s);
+}
+
+static NSVGcoordinate nsvg__parseCoordinateRaw(const char *str)
+{
+ NSVGcoordinate coord = {0, NSVG_UNITS_USER};
+ char buf[64];
+ coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64));
+ coord.value = nsvg__atof(buf);
+ return coord;
+}
+
+static NSVGcoordinate nsvg__coord(float v, int units)
+{
+ NSVGcoordinate coord = {v, units};
+ return coord;
+}
+
+static float nsvg__parseCoordinate(NSVGparser *p, const char *str, float orig, float length)
+{
+ NSVGcoordinate coord = nsvg__parseCoordinateRaw(str);
+ return nsvg__convertToPixels(p, coord, orig, length);
+}
+
+static int nsvg__parseTransformArgs(const char *str, float *args, int maxNa, int *na)
+{
+ const char *end;
+ const char *ptr;
+ char it[64];
+
+ *na = 0;
+ ptr = str;
+ while (*ptr && *ptr != '(')
+ ++ptr;
+ if (*ptr == 0)
+ return 1;
+ end = ptr;
+ while (*end && *end != ')')
+ ++end;
+ if (*end == 0)
+ return 1;
+
+ while (ptr < end) {
+ if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) {
+ if (*na >= maxNa)
+ return 0;
+ ptr = nsvg__parseNumber(ptr, it, 64);
+ args[(*na)++] = (float)nsvg__atof(it);
+ }
+ else {
+ ++ptr;
+ }
+ }
+ return (int)(end - str);
+}
+
+static int nsvg__parseMatrix(float *xform, const char *str)
+{
+ float t[6];
+ int na = 0;
+ int len = nsvg__parseTransformArgs(str, t, 6, &na);
+ if (na != 6)
+ return len;
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseTranslate(float *xform, const char *str)
+{
+ float args[2];
+ float t[6];
+ int na = 0;
+ int len = nsvg__parseTransformArgs(str, args, 2, &na);
+ if (na == 1)
+ args[1] = 0.0;
+
+ nsvg__xformSetTranslation(t, args[0], args[1]);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseScale(float *xform, const char *str)
+{
+ float args[2];
+ int na = 0;
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 2, &na);
+ if (na == 1)
+ args[1] = args[0];
+ nsvg__xformSetScale(t, args[0], args[1]);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseSkewX(float *xform, const char *str)
+{
+ float args[1];
+ int na = 0;
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 1, &na);
+ nsvg__xformSetSkewX(t, args[0] / 180.0f * NSVG_PI);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseSkewY(float *xform, const char *str)
+{
+ float args[1];
+ int na = 0;
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 1, &na);
+ nsvg__xformSetSkewY(t, args[0] / 180.0f * NSVG_PI);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseRotate(float *xform, const char *str)
+{
+ float args[3];
+ int na = 0;
+ float m[6];
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 3, &na);
+ if (na == 1)
+ args[1] = args[2] = 0.0f;
+ nsvg__xformIdentity(m);
+
+ if (na > 1) {
+ nsvg__xformSetTranslation(t, -args[1], -args[2]);
+ nsvg__xformMultiply(m, t);
+ }
+
+ nsvg__xformSetRotation(t, args[0] / 180.0f * NSVG_PI);
+ nsvg__xformMultiply(m, t);
+
+ if (na > 1) {
+ nsvg__xformSetTranslation(t, args[1], args[2]);
+ nsvg__xformMultiply(m, t);
+ }
+
+ memcpy(xform, m, sizeof(float) * 6);
+
+ return len;
+}
+
+static void nsvg__parseTransform(float *xform, const char *str)
+{
+ float t[6];
+ int len;
+ nsvg__xformIdentity(xform);
+ while (*str) {
+ if (strncmp(str, "matrix", 6) == 0)
+ len = nsvg__parseMatrix(t, str);
+ else if (strncmp(str, "translate", 9) == 0)
+ len = nsvg__parseTranslate(t, str);
+ else if (strncmp(str, "scale", 5) == 0)
+ len = nsvg__parseScale(t, str);
+ else if (strncmp(str, "rotate", 6) == 0)
+ len = nsvg__parseRotate(t, str);
+ else if (strncmp(str, "skewX", 5) == 0)
+ len = nsvg__parseSkewX(t, str);
+ else if (strncmp(str, "skewY", 5) == 0)
+ len = nsvg__parseSkewY(t, str);
+ else {
+ ++str;
+ continue;
+ }
+ if (len != 0) {
+ str += len;
+ }
+ else {
+ ++str;
+ continue;
+ }
+
+ nsvg__xformPremultiply(xform, t);
+ }
+}
+
+static void nsvg__parseUrl(char *id, const char *str)
+{
+ int i = 0;
+ str += 4; // "url(";
+ if (*str == '#')
+ str++;
+ while (i < 63 && *str != ')') {
+ id[i] = *str++;
+ i++;
+ }
+ id[i] = '\0';
+}
+
+static char nsvg__parseLineCap(const char *str)
+{
+ if (strcmp(str, "butt") == 0)
+ return NSVG_CAP_BUTT;
+ else if (strcmp(str, "round") == 0)
+ return NSVG_CAP_ROUND;
+ else if (strcmp(str, "square") == 0)
+ return NSVG_CAP_SQUARE;
+ // TODO: handle inherit.
+ return NSVG_CAP_BUTT;
+}
+
+static char nsvg__parseLineJoin(const char *str)
+{
+ if (strcmp(str, "miter") == 0)
+ return NSVG_JOIN_MITER;
+ else if (strcmp(str, "round") == 0)
+ return NSVG_JOIN_ROUND;
+ else if (strcmp(str, "bevel") == 0)
+ return NSVG_JOIN_BEVEL;
+ // TODO: handle inherit.
+ return NSVG_JOIN_MITER;
+}
+
+static char nsvg__parseFillRule(const char *str)
+{
+ if (strcmp(str, "nonzero") == 0)
+ return NSVG_FILLRULE_NONZERO;
+ else if (strcmp(str, "evenodd") == 0)
+ return NSVG_FILLRULE_EVENODD;
+ // TODO: handle inherit.
+ return NSVG_FILLRULE_NONZERO;
+}
+
+static const char *nsvg__getNextDashItem(const char *s, char *it)
+{
+ int n = 0;
+ it[0] = '\0';
+ // Skip white spaces and commas
+ while (*s && (nsvg__isspace(*s) || *s == ','))
+ s++;
+ // Advance until whitespace, comma or end.
+ while (*s && (!nsvg__isspace(*s) && *s != ',')) {
+ if (n < 63)
+ it[n++] = *s;
+ s++;
+ }
+ it[n++] = '\0';
+ return s;
+}
+
+static int nsvg__parseStrokeDashArray(NSVGparser *p, const char *str, float *strokeDashArray)
+{
+ char item[64];
+ int count = 0, i;
+ float sum = 0.0f;
+
+ // Handle "none"
+ if (str[0] == 'n')
+ return 0;
+
+ // Parse dashes
+ while (*str) {
+ str = nsvg__getNextDashItem(str, item);
+ if (!*item)
+ break;
+ if (count < NSVG_MAX_DASHES)
+ strokeDashArray[count++] = fabsf(
+ nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p)));
+ }
+
+ for (i = 0; i < count; i++)
+ sum += strokeDashArray[i];
+ if (sum <= 1e-6f)
+ count = 0;
+
+ return count;
+}
+
+static void nsvg__parseStyle(NSVGparser *p, const char *str);
+
+static int nsvg__parseAttr(NSVGparser *p, const char *name, const char *value)
+{
+ float xform[6];
+ NSVGattrib *attr = nsvg__getAttr(p);
+ if (!attr)
+ return 0;
+
+ if (strcmp(name, "style") == 0) {
+ nsvg__parseStyle(p, value);
+ }
+ else if (strcmp(name, "display") == 0) {
+ if (strcmp(value, "none") == 0)
+ attr->visible = 0;
+ // Don't reset ->visible on display:inline, one display:none hides the whole subtree
+ }
+ else if (strcmp(name, "fill") == 0) {
+ if (strcmp(value, "none") == 0) {
+ attr->hasFill = 0;
+ }
+ else if (strncmp(value, "url(", 4) == 0) {
+ attr->hasFill = 2;
+ nsvg__parseUrl(attr->fillGradient, value);
+ }
+ else {
+ attr->hasFill = 1;
+ attr->fillColor = nsvg__parseColor(value);
+ }
+ }
+ else if (strcmp(name, "opacity") == 0) {
+ attr->opacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "fill-opacity") == 0) {
+ attr->fillOpacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "stroke") == 0) {
+ if (strcmp(value, "none") == 0) {
+ attr->hasStroke = 0;
+ }
+ else if (strncmp(value, "url(", 4) == 0) {
+ attr->hasStroke = 2;
+ nsvg__parseUrl(attr->strokeGradient, value);
+ }
+ else {
+ attr->hasStroke = 1;
+ attr->strokeColor = nsvg__parseColor(value);
+ }
+ }
+ else if (strcmp(name, "stroke-width") == 0) {
+ attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
+ }
+ else if (strcmp(name, "stroke-dasharray") == 0) {
+ attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray);
+ }
+ else if (strcmp(name, "stroke-dashoffset") == 0) {
+ attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
+ }
+ else if (strcmp(name, "stroke-opacity") == 0) {
+ attr->strokeOpacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "stroke-linecap") == 0) {
+ attr->strokeLineCap = nsvg__parseLineCap(value);
+ }
+ else if (strcmp(name, "stroke-linejoin") == 0) {
+ attr->strokeLineJoin = nsvg__parseLineJoin(value);
+ }
+ else if (strcmp(name, "stroke-miterlimit") == 0) {
+ attr->miterLimit = nsvg__parseMiterLimit(value);
+ }
+ else if (strcmp(name, "fill-rule") == 0) {
+ attr->fillRule = nsvg__parseFillRule(value);
+ }
+ else if (strcmp(name, "font-size") == 0) {
+ attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
+ }
+ else if (strcmp(name, "transform") == 0) {
+ nsvg__parseTransform(xform, value);
+ nsvg__xformPremultiply(attr->xform, xform);
+ }
+ else if (strcmp(name, "stop-color") == 0) {
+ attr->stopColor = nsvg__parseColor(value);
+ }
+ else if (strcmp(name, "stop-opacity") == 0) {
+ attr->stopOpacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "offset") == 0) {
+ attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);
+ }
+ else if (strcmp(name, "id") == 0) {
+ strncpy(attr->id, value, 63);
+ attr->id[63] = '\0';
+ }
+ else {
+ return 0;
+ }
+ return 1;
+}
+
+static int nsvg__parseNameValue(NSVGparser *p, const char *start, const char *end)
+{
+ const char *str;
+ const char *val;
+ char name[512];
+ char value[512];
+ int n;
+
+ str = start;
+ while (str < end && *str != ':')
+ ++str;
+
+ val = str;
+
+ // Right Trim
+ while (str > start && (*str == ':' || nsvg__isspace(*str)))
+ --str;
+ ++str;
+
+ n = (int)(str - start);
+ if (n > 511)
+ n = 511;
+ if (n)
+ memcpy(name, start, n);
+ name[n] = 0;
+
+ while (val < end && (*val == ':' || nsvg__isspace(*val)))
+ ++val;
+
+ n = (int)(end - val);
+ if (n > 511)
+ n = 511;
+ if (n)
+ memcpy(value, val, n);
+ value[n] = 0;
+
+ return nsvg__parseAttr(p, name, value);
+}
+
+static void nsvg__parseStyle(NSVGparser *p, const char *str)
+{
+ const char *start;
+ const char *end;
+
+ while (*str) {
+ // Left Trim
+ while (*str && nsvg__isspace(*str))
+ ++str;
+ start = str;
+ while (*str && *str != ';')
+ ++str;
+ end = str;
+
+ // Right Trim
+ while (end > start && (*end == ';' || nsvg__isspace(*end)))
+ --end;
+ ++end;
+
+ nsvg__parseNameValue(p, start, end);
+ if (*str)
+ ++str;
+ }
+}
+
+static void nsvg__parseAttribs(NSVGparser *p, const char **attr)
+{
+ int i;
+ for (i = 0; attr[i]; i += 2) {
+ if (strcmp(attr[i], "style") == 0)
+ nsvg__parseStyle(p, attr[i + 1]);
+ else
+ nsvg__parseAttr(p, attr[i], attr[i + 1]);
+ }
+}
+
+static int nsvg__getArgsPerElement(char cmd)
+{
+ switch (cmd) {
+ case 'v':
+ case 'V':
+ case 'h':
+ case 'H':
+ return 1;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ return 2;
+ case 'q':
+ case 'Q':
+ case 's':
+ case 'S':
+ return 4;
+ case 'c':
+ case 'C':
+ return 6;
+ case 'a':
+ case 'A':
+ return 7;
+ case 'z':
+ case 'Z':
+ return 0;
+ }
+ return -1;
+}
+
+static void nsvg__pathMoveTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel) {
+ *cpx += args[0];
+ *cpy += args[1];
+ }
+ else {
+ *cpx = args[0];
+ *cpy = args[1];
+ }
+ nsvg__moveTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel) {
+ *cpx += args[0];
+ *cpy += args[1];
+ }
+ else {
+ *cpx = args[0];
+ *cpy = args[1];
+ }
+ nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathHLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel)
+ *cpx += args[0];
+ else
+ *cpx = args[0];
+ nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathVLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel)
+ *cpy += args[0];
+ else
+ *cpy = args[0];
+ nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathCubicBezTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x2, y2, cx1, cy1, cx2, cy2;
+
+ if (rel) {
+ cx1 = *cpx + args[0];
+ cy1 = *cpy + args[1];
+ cx2 = *cpx + args[2];
+ cy2 = *cpy + args[3];
+ x2 = *cpx + args[4];
+ y2 = *cpy + args[5];
+ }
+ else {
+ cx1 = args[0];
+ cy1 = args[1];
+ cx2 = args[2];
+ cy2 = args[3];
+ x2 = args[4];
+ y2 = args[5];
+ }
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx2;
+ *cpy2 = cy2;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__pathCubicBezShortTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x1, y1, x2, y2, cx1, cy1, cx2, cy2;
+
+ x1 = *cpx;
+ y1 = *cpy;
+ if (rel) {
+ cx2 = *cpx + args[0];
+ cy2 = *cpy + args[1];
+ x2 = *cpx + args[2];
+ y2 = *cpy + args[3];
+ }
+ else {
+ cx2 = args[0];
+ cy2 = args[1];
+ x2 = args[2];
+ y2 = args[3];
+ }
+
+ cx1 = 2 * x1 - *cpx2;
+ cy1 = 2 * y1 - *cpy2;
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx2;
+ *cpy2 = cy2;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__pathQuadBezTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x1, y1, x2, y2, cx, cy;
+ float cx1, cy1, cx2, cy2;
+
+ x1 = *cpx;
+ y1 = *cpy;
+ if (rel) {
+ cx = *cpx + args[0];
+ cy = *cpy + args[1];
+ x2 = *cpx + args[2];
+ y2 = *cpy + args[3];
+ }
+ else {
+ cx = args[0];
+ cy = args[1];
+ x2 = args[2];
+ y2 = args[3];
+ }
+
+ // Convert to cubic bezier
+ cx1 = x1 + 2.0f / 3.0f * (cx - x1);
+ cy1 = y1 + 2.0f / 3.0f * (cy - y1);
+ cx2 = x2 + 2.0f / 3.0f * (cx - x2);
+ cy2 = y2 + 2.0f / 3.0f * (cy - y2);
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx;
+ *cpy2 = cy;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__pathQuadBezShortTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x1, y1, x2, y2, cx, cy;
+ float cx1, cy1, cx2, cy2;
+
+ x1 = *cpx;
+ y1 = *cpy;
+ if (rel) {
+ x2 = *cpx + args[0];
+ y2 = *cpy + args[1];
+ }
+ else {
+ x2 = args[0];
+ y2 = args[1];
+ }
+
+ cx = 2 * x1 - *cpx2;
+ cy = 2 * y1 - *cpy2;
+
+ // Convert to cubix bezier
+ cx1 = x1 + 2.0f / 3.0f * (cx - x1);
+ cy1 = y1 + 2.0f / 3.0f * (cy - y1);
+ cx2 = x2 + 2.0f / 3.0f * (cx - x2);
+ cy2 = y2 + 2.0f / 3.0f * (cy - y2);
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx;
+ *cpy2 = cy;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static float nsvg__sqr(float x)
+{
+ return x * x;
+}
+static float nsvg__vmag(float x, float y)
+{
+ return sqrtf(x * x + y * y);
+}
+
+static float nsvg__vecrat(float ux, float uy, float vx, float vy)
+{
+ return (ux * vx + uy * vy) / (nsvg__vmag(ux, uy) * nsvg__vmag(vx, vy));
+}
+
+static float nsvg__vecang(float ux, float uy, float vx, float vy)
+{
+ float r = nsvg__vecrat(ux, uy, vx, vy);
+ if (r < -1.0f)
+ r = -1.0f;
+ if (r > 1.0f)
+ r = 1.0f;
+ return ((ux * vy < uy * vx) ? -1.0f : 1.0f) * acosf(r);
+}
+
+static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ // Ported from canvg (https://code.google.com/p/canvg/)
+ float rx, ry, rotx;
+ float x1, y1, x2, y2, cx, cy, dx, dy, d;
+ float x1p, y1p, cxp, cyp, s, sa, sb;
+ float ux, uy, vx, vy, a1, da;
+ float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
+ float sinrx, cosrx;
+ int fa, fs;
+ int i, ndivs;
+ float hda, kappa;
+
+ rx = fabsf(args[0]); // y radius
+ ry = fabsf(args[1]); // x radius
+ rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle
+ fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc
+ fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction
+ x1 = *cpx; // start point
+ y1 = *cpy;
+ if (rel) { // end point
+ x2 = *cpx + args[5];
+ y2 = *cpy + args[6];
+ }
+ else {
+ x2 = args[5];
+ y2 = args[6];
+ }
+
+ dx = x1 - x2;
+ dy = y1 - y2;
+ d = sqrtf(dx * dx + dy * dy);
+ if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) {
+ // The arc degenerates to a line
+ nsvg__lineTo(p, x2, y2);
+ *cpx = x2;
+ *cpy = y2;
+ return;
+ }
+
+ sinrx = sinf(rotx);
+ cosrx = cosf(rotx);
+
+ // Convert to center point parameterization.
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ // 1) Compute x1', y1'
+ x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;
+ y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;
+ d = nsvg__sqr(x1p) / nsvg__sqr(rx) + nsvg__sqr(y1p) / nsvg__sqr(ry);
+ if (d > 1) {
+ d = sqrtf(d);
+ rx *= d;
+ ry *= d;
+ }
+ // 2) Compute cx', cy'
+ s = 0.0f;
+ sa = nsvg__sqr(rx) * nsvg__sqr(ry) - nsvg__sqr(rx) * nsvg__sqr(y1p) -
+ nsvg__sqr(ry) * nsvg__sqr(x1p);
+ sb = nsvg__sqr(rx) * nsvg__sqr(y1p) + nsvg__sqr(ry) * nsvg__sqr(x1p);
+ if (sa < 0.0f)
+ sa = 0.0f;
+ if (sb > 0.0f)
+ s = sqrtf(sa / sb);
+ if (fa == fs)
+ s = -s;
+ cxp = s * rx * y1p / ry;
+ cyp = s * -ry * x1p / rx;
+
+ // 3) Compute cx,cy from cx',cy'
+ cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp;
+ cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp;
+
+ // 4) Calculate theta1, and delta theta.
+ ux = (x1p - cxp) / rx;
+ uy = (y1p - cyp) / ry;
+ vx = (-x1p - cxp) / rx;
+ vy = (-y1p - cyp) / ry;
+ a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle
+ da = nsvg__vecang(ux, uy, vx, vy); // Delta angle
+
+ // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;
+ // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;
+
+ if (fs == 0 && da > 0)
+ da -= 2 * NSVG_PI;
+ else if (fs == 1 && da < 0)
+ da += 2 * NSVG_PI;
+
+ // Approximate the arc using cubic spline segments.
+ t[0] = cosrx;
+ t[1] = sinrx;
+ t[2] = -sinrx;
+ t[3] = cosrx;
+ t[4] = cx;
+ t[5] = cy;
+
+ // Split arc into max 90 degree segments.
+ // The loop assumes an iteration per end point (including start and end), this +1.
+ ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f);
+ hda = (da / (float)ndivs) / 2.0f;
+ kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
+ if (da < 0.0f)
+ kappa = -kappa;
+
+ for (i = 0; i <= ndivs; i++) {
+ a = a1 + da * ((float)i / (float)ndivs);
+ dx = cosf(a);
+ dy = sinf(a);
+ nsvg__xformPoint(&x, &y, dx * rx, dy * ry, t); // position
+ nsvg__xformVec(&tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t); // tangent
+ if (i > 0)
+ nsvg__cubicBezTo(p, px + ptanx, py + ptany, x - tanx, y - tany, x, y);
+ px = x;
+ py = y;
+ ptanx = tanx;
+ ptany = tany;
+ }
+
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__parsePath(NSVGparser *p, const char **attr)
+{
+ const char *s = NULL;
+ char cmd = '\0';
+ float args[10];
+ int nargs;
+ int rargs = 0;
+ char initPoint;
+ float cpx, cpy, cpx2, cpy2;
+ const char *tmp[4];
+ char closedFlag;
+ int i;
+ char item[64];
+
+ for (i = 0; attr[i]; i += 2) {
+ if (strcmp(attr[i], "d") == 0) {
+ s = attr[i + 1];
+ }
+ else {
+ tmp[0] = attr[i];
+ tmp[1] = attr[i + 1];
+ tmp[2] = 0;
+ tmp[3] = 0;
+ nsvg__parseAttribs(p, tmp);
+ }
+ }
+
+ if (s) {
+ nsvg__resetPath(p);
+ cpx = 0;
+ cpy = 0;
+ cpx2 = 0;
+ cpy2 = 0;
+ initPoint = 0;
+ closedFlag = 0;
+ nargs = 0;
+
+ while (*s) {
+ s = nsvg__getNextPathItem(s, item);
+ if (!*item)
+ break;
+ if (cmd != '\0' && nsvg__isCoordinate(item)) {
+ if (nargs < 10)
+ args[nargs++] = (float)nsvg__atof(item);
+ if (nargs >= rargs) {
+ switch (cmd) {
+ case 'm':
+ case 'M':
+ nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
+ // Moveto can be followed by multiple coordinate pairs,
+ // which should be treated as linetos.
+ cmd = (cmd == 'm') ? 'l' : 'L';
+ rargs = nsvg__getArgsPerElement(cmd);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ initPoint = 1;
+ break;
+ case 'l':
+ case 'L':
+ nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ case 'H':
+ case 'h':
+ nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ case 'V':
+ case 'v':
+ nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ case 'C':
+ case 'c':
+ nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
+ break;
+ case 'S':
+ case 's':
+ nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
+ break;
+ case 'Q':
+ case 'q':
+ nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
+ break;
+ case 'T':
+ case 't':
+ nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0);
+ break;
+ case 'A':
+ case 'a':
+ nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ default:
+ if (nargs >= 2) {
+ cpx = args[nargs - 2];
+ cpy = args[nargs - 1];
+ cpx2 = cpx;
+ cpy2 = cpy;
+ }
+ break;
+ }
+ nargs = 0;
+ }
+ }
+ else {
+ cmd = item[0];
+ if (cmd == 'M' || cmd == 'm') {
+ // Commit path.
+ if (p->npts > 0)
+ nsvg__addPath(p, closedFlag);
+ // Start new subpath.
+ nsvg__resetPath(p);
+ closedFlag = 0;
+ nargs = 0;
+ }
+ else if (initPoint == 0) {
+ // Do not allow other commands until initial point has been set (moveTo called once).
+ cmd = '\0';
+ }
+ if (cmd == 'Z' || cmd == 'z') {
+ closedFlag = 1;
+ // Commit path.
+ if (p->npts > 0) {
+ // Move current point to first point
+ cpx = p->pts[0];
+ cpy = p->pts[1];
+ cpx2 = cpx;
+ cpy2 = cpy;
+ nsvg__addPath(p, closedFlag);
+ }
+ // Start new subpath.
+ nsvg__resetPath(p);
+ nsvg__moveTo(p, cpx, cpy);
+ closedFlag = 0;
+ nargs = 0;
+ }
+ rargs = nsvg__getArgsPerElement(cmd);
+ if (rargs == -1) {
+ // Command not recognized
+ cmd = '\0';
+ rargs = 0;
+ }
+ }
+ }
+ // Commit path.
+ if (p->npts)
+ nsvg__addPath(p, closedFlag);
+ }
+
+ nsvg__addShape(p);
+}
+
+static void nsvg__parseRect(NSVGparser *p, const char **attr)
+{
+ float x = 0.0f;
+ float y = 0.0f;
+ float w = 0.0f;
+ float h = 0.0f;
+ float rx = -1.0f; // marks not set
+ float ry = -1.0f;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "x") == 0)
+ x = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "y") == 0)
+ y = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "width") == 0)
+ w = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p));
+ if (strcmp(attr[i], "height") == 0)
+ h = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p));
+ if (strcmp(attr[i], "rx") == 0)
+ rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p)));
+ if (strcmp(attr[i], "ry") == 0)
+ ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p)));
+ }
+ }
+
+ if (rx < 0.0f && ry > 0.0f)
+ rx = ry;
+ if (ry < 0.0f && rx > 0.0f)
+ ry = rx;
+ if (rx < 0.0f)
+ rx = 0.0f;
+ if (ry < 0.0f)
+ ry = 0.0f;
+ if (rx > w / 2.0f)
+ rx = w / 2.0f;
+ if (ry > h / 2.0f)
+ ry = h / 2.0f;
+
+ if (w != 0.0f && h != 0.0f) {
+ nsvg__resetPath(p);
+
+ if (rx < 0.00001f || ry < 0.0001f) {
+ nsvg__moveTo(p, x, y);
+ nsvg__lineTo(p, x + w, y);
+ nsvg__lineTo(p, x + w, y + h);
+ nsvg__lineTo(p, x, y + h);
+ }
+ else {
+ // Rounded rectangle
+ nsvg__moveTo(p, x + rx, y);
+ nsvg__lineTo(p, x + w - rx, y);
+ nsvg__cubicBezTo(p,
+ x + w - rx * (1 - NSVG_KAPPA90),
+ y,
+ x + w,
+ y + ry * (1 - NSVG_KAPPA90),
+ x + w,
+ y + ry);
+ nsvg__lineTo(p, x + w, y + h - ry);
+ nsvg__cubicBezTo(p,
+ x + w,
+ y + h - ry * (1 - NSVG_KAPPA90),
+ x + w - rx * (1 - NSVG_KAPPA90),
+ y + h,
+ x + w - rx,
+ y + h);
+ nsvg__lineTo(p, x + rx, y + h);
+ nsvg__cubicBezTo(p,
+ x + rx * (1 - NSVG_KAPPA90),
+ y + h,
+ x,
+ y + h - ry * (1 - NSVG_KAPPA90),
+ x,
+ y + h - ry);
+ nsvg__lineTo(p, x, y + ry);
+ nsvg__cubicBezTo(
+ p, x, y + ry * (1 - NSVG_KAPPA90), x + rx * (1 - NSVG_KAPPA90), y, x + rx, y);
+ }
+
+ nsvg__addPath(p, 1);
+
+ nsvg__addShape(p);
+ }
+}
+
+static void nsvg__parseCircle(NSVGparser *p, const char **attr)
+{
+ float cx = 0.0f;
+ float cy = 0.0f;
+ float r = 0.0f;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "cx") == 0)
+ cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "cy") == 0)
+ cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "r") == 0)
+ r = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualLength(p)));
+ }
+ }
+
+ if (r > 0.0f) {
+ nsvg__resetPath(p);
+
+ nsvg__moveTo(p, cx + r, cy);
+ nsvg__cubicBezTo(p, cx + r, cy + r * NSVG_KAPPA90, cx + r * NSVG_KAPPA90, cy + r, cx, cy + r);
+ nsvg__cubicBezTo(p, cx - r * NSVG_KAPPA90, cy + r, cx - r, cy + r * NSVG_KAPPA90, cx - r, cy);
+ nsvg__cubicBezTo(p, cx - r, cy - r * NSVG_KAPPA90, cx - r * NSVG_KAPPA90, cy - r, cx, cy - r);
+ nsvg__cubicBezTo(p, cx + r * NSVG_KAPPA90, cy - r, cx + r, cy - r * NSVG_KAPPA90, cx + r, cy);
+
+ nsvg__addPath(p, 1);
+
+ nsvg__addShape(p);
+ }
+}
+
+static void nsvg__parseEllipse(NSVGparser *p, const char **attr)
+{
+ float cx = 0.0f;
+ float cy = 0.0f;
+ float rx = 0.0f;
+ float ry = 0.0f;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "cx") == 0)
+ cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "cy") == 0)
+ cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "rx") == 0)
+ rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p)));
+ if (strcmp(attr[i], "ry") == 0)
+ ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p)));
+ }
+ }
+
+ if (rx > 0.0f && ry > 0.0f) {
+
+ nsvg__resetPath(p);
+
+ nsvg__moveTo(p, cx + rx, cy);
+ nsvg__cubicBezTo(
+ p, cx + rx, cy + ry * NSVG_KAPPA90, cx + rx * NSVG_KAPPA90, cy + ry, cx, cy + ry);
+ nsvg__cubicBezTo(
+ p, cx - rx * NSVG_KAPPA90, cy + ry, cx - rx, cy + ry * NSVG_KAPPA90, cx - rx, cy);
+ nsvg__cubicBezTo(
+ p, cx - rx, cy - ry * NSVG_KAPPA90, cx - rx * NSVG_KAPPA90, cy - ry, cx, cy - ry);
+ nsvg__cubicBezTo(
+ p, cx + rx * NSVG_KAPPA90, cy - ry, cx + rx, cy - ry * NSVG_KAPPA90, cx + rx, cy);
+
+ nsvg__addPath(p, 1);
+
+ nsvg__addShape(p);
+ }
+}
+
+static void nsvg__parseLine(NSVGparser *p, const char **attr)
+{
+ float x1 = 0.0;
+ float y1 = 0.0;
+ float x2 = 0.0;
+ float y2 = 0.0;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "x1") == 0)
+ x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "y1") == 0)
+ y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "x2") == 0)
+ x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "y2") == 0)
+ y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ }
+ }
+
+ nsvg__resetPath(p);
+
+ nsvg__moveTo(p, x1, y1);
+ nsvg__lineTo(p, x2, y2);
+
+ nsvg__addPath(p, 0);
+
+ nsvg__addShape(p);
+}
+
+static void nsvg__parsePoly(NSVGparser *p, const char **attr, int closeFlag)
+{
+ int i;
+ const char *s;
+ float args[2];
+ int nargs, npts = 0;
+ char item[64];
+
+ nsvg__resetPath(p);
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "points") == 0) {
+ s = attr[i + 1];
+ nargs = 0;
+ while (*s) {
+ s = nsvg__getNextPathItem(s, item);
+ args[nargs++] = (float)nsvg__atof(item);
+ if (nargs >= 2) {
+ if (npts == 0)
+ nsvg__moveTo(p, args[0], args[1]);
+ else
+ nsvg__lineTo(p, args[0], args[1]);
+ nargs = 0;
+ npts++;
+ }
+ }
+ }
+ }
+ }
+
+ nsvg__addPath(p, (char)closeFlag);
+
+ nsvg__addShape(p);
+}
+
+static void nsvg__parseSVG(NSVGparser *p, const char **attr)
+{
+ int i;
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "width") == 0) {
+ p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
+ }
+ else if (strcmp(attr[i], "height") == 0) {
+ p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
+ }
+ else if (strcmp(attr[i], "viewBox") == 0) {
+ const char *s = attr[i + 1];
+ char buf[64];
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewMinx = nsvg__atof(buf);
+ while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ','))
+ s++;
+ if (!*s)
+ return;
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewMiny = nsvg__atof(buf);
+ while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ','))
+ s++;
+ if (!*s)
+ return;
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewWidth = nsvg__atof(buf);
+ while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ','))
+ s++;
+ if (!*s)
+ return;
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewHeight = nsvg__atof(buf);
+ }
+ else if (strcmp(attr[i], "preserveAspectRatio") == 0) {
+ if (strstr(attr[i + 1], "none") != 0) {
+ // No uniform scaling
+ p->alignType = NSVG_ALIGN_NONE;
+ }
+ else {
+ // Parse X align
+ if (strstr(attr[i + 1], "xMin") != 0)
+ p->alignX = NSVG_ALIGN_MIN;
+ else if (strstr(attr[i + 1], "xMid") != 0)
+ p->alignX = NSVG_ALIGN_MID;
+ else if (strstr(attr[i + 1], "xMax") != 0)
+ p->alignX = NSVG_ALIGN_MAX;
+ // Parse X align
+ if (strstr(attr[i + 1], "yMin") != 0)
+ p->alignY = NSVG_ALIGN_MIN;
+ else if (strstr(attr[i + 1], "yMid") != 0)
+ p->alignY = NSVG_ALIGN_MID;
+ else if (strstr(attr[i + 1], "yMax") != 0)
+ p->alignY = NSVG_ALIGN_MAX;
+ // Parse meet/slice
+ p->alignType = NSVG_ALIGN_MEET;
+ if (strstr(attr[i + 1], "slice") != 0)
+ p->alignType = NSVG_ALIGN_SLICE;
+ }
+ }
+ }
+ }
+}
+
+static void nsvg__parseGradient(NSVGparser *p, const char **attr, char type)
+{
+ int i;
+ NSVGgradientData *grad = (NSVGgradientData *)malloc(sizeof(NSVGgradientData));
+ if (grad == NULL)
+ return;
+ memset(grad, 0, sizeof(NSVGgradientData));
+ grad->units = NSVG_OBJECT_SPACE;
+ grad->type = type;
+ if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) {
+ grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
+ grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
+ grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT);
+ grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
+ }
+ else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) {
+ grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
+ grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
+ grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
+ }
+
+ nsvg__xformIdentity(grad->xform);
+
+ for (i = 0; attr[i]; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ strncpy(grad->id, attr[i + 1], 63);
+ grad->id[63] = '\0';
+ }
+ else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "gradientUnits") == 0) {
+ if (strcmp(attr[i + 1], "objectBoundingBox") == 0)
+ grad->units = NSVG_OBJECT_SPACE;
+ else
+ grad->units = NSVG_USER_SPACE;
+ }
+ else if (strcmp(attr[i], "gradientTransform") == 0) {
+ nsvg__parseTransform(grad->xform, attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "cx") == 0) {
+ grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "cy") == 0) {
+ grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "r") == 0) {
+ grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "fx") == 0) {
+ grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "fy") == 0) {
+ grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "x1") == 0) {
+ grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "y1") == 0) {
+ grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "x2") == 0) {
+ grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "y2") == 0) {
+ grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "spreadMethod") == 0) {
+ if (strcmp(attr[i + 1], "pad") == 0)
+ grad->spread = NSVG_SPREAD_PAD;
+ else if (strcmp(attr[i + 1], "reflect") == 0)
+ grad->spread = NSVG_SPREAD_REFLECT;
+ else if (strcmp(attr[i + 1], "repeat") == 0)
+ grad->spread = NSVG_SPREAD_REPEAT;
+ }
+ else if (strcmp(attr[i], "xlink:href") == 0) {
+ const char *href = attr[i + 1];
+ strncpy(grad->ref, href + 1, 62);
+ grad->ref[62] = '\0';
+ }
+ }
+ }
+
+ grad->next = p->gradients;
+ p->gradients = grad;
+}
+
+static void nsvg__parseGradientStop(NSVGparser *p, const char **attr)
+{
+ NSVGattrib *curAttr = nsvg__getAttr(p);
+ NSVGgradientData *grad;
+ NSVGgradientStop *stop;
+ int i, idx;
+
+ curAttr->stopOffset = 0;
+ curAttr->stopColor = 0;
+ curAttr->stopOpacity = 1.0f;
+
+ for (i = 0; attr[i]; i += 2) {
+ nsvg__parseAttr(p, attr[i], attr[i + 1]);
+ }
+
+ // Add stop to the last gradient.
+ grad = p->gradients;
+ if (grad == NULL)
+ return;
+
+ grad->nstops++;
+ grad->stops = (NSVGgradientStop *)realloc(grad->stops, sizeof(NSVGgradientStop) * grad->nstops);
+ if (grad->stops == NULL)
+ return;
+
+ // Insert
+ idx = grad->nstops - 1;
+ for (i = 0; i < grad->nstops - 1; i++) {
+ if (curAttr->stopOffset < grad->stops[i].offset) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx != grad->nstops - 1) {
+ for (i = grad->nstops - 1; i > idx; i--)
+ grad->stops[i] = grad->stops[i - 1];
+ }
+
+ stop = &grad->stops[idx];
+ stop->color = curAttr->stopColor;
+ stop->color |= (unsigned int)(curAttr->stopOpacity * 255) << 24;
+ stop->offset = curAttr->stopOffset;
+}
+
+static void nsvg__startElement(void *ud, const char *el, const char **attr)
+{
+ NSVGparser *p = (NSVGparser *)ud;
+
+ if (p->defsFlag) {
+ // Skip everything but gradients in defs
+ if (strcmp(el, "linearGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
+ }
+ else if (strcmp(el, "radialGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
+ }
+ else if (strcmp(el, "stop") == 0) {
+ nsvg__parseGradientStop(p, attr);
+ }
+ return;
+ }
+
+ if (strcmp(el, "g") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseAttribs(p, attr);
+
+ /* Save the breadcrumb of groups. */
+ if (p->breadcrumb_len < NSVG_MAX_BREADCRUMB) {
+ NSVGattrib *attr_id = nsvg__getAttr(p);
+ memcpy(
+ p->breadcrumb[p->breadcrumb_len], attr_id->id, sizeof(p->breadcrumb[p->breadcrumb_len]));
+ p->breadcrumb_len++;
+ }
+ }
+ else if (strcmp(el, "path") == 0) {
+ if (p->pathFlag) // Do not allow nested paths.
+ return;
+ nsvg__pushAttr(p);
+ nsvg__parsePath(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "rect") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseRect(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "circle") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseCircle(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "ellipse") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseEllipse(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "line") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseLine(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "polyline") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parsePoly(p, attr, 0);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "polygon") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parsePoly(p, attr, 1);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "linearGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
+ }
+ else if (strcmp(el, "radialGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
+ }
+ else if (strcmp(el, "stop") == 0) {
+ nsvg__parseGradientStop(p, attr);
+ }
+ else if (strcmp(el, "defs") == 0) {
+ p->defsFlag = 1;
+ }
+ else if (strcmp(el, "svg") == 0) {
+ nsvg__parseSVG(p, attr);
+ }
+}
+
+static void nsvg__endElement(void *ud, const char *el)
+{
+ NSVGparser *p = (NSVGparser *)ud;
+
+ if (strcmp(el, "g") == 0) {
+ /* Remove the breadcrumb level. */
+ if (p->breadcrumb_len > 0) {
+ p->breadcrumb[p->breadcrumb_len - 1][0] = '\0';
+ p->breadcrumb_len--;
+ }
+
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "path") == 0) {
+ p->pathFlag = 0;
+ }
+ else if (strcmp(el, "defs") == 0) {
+ p->defsFlag = 0;
+ }
+}
+
+static void nsvg__content(void *ud, const char *s)
+{
+ NSVG_NOTUSED(ud);
+ NSVG_NOTUSED(s);
+ // empty
+}
+
+static void nsvg__imageBounds(NSVGparser *p, float *bounds)
+{
+ NSVGshape *shape;
+ shape = p->image->shapes;
+ if (shape == NULL) {
+ bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0;
+ return;
+ }
+ bounds[0] = shape->bounds[0];
+ bounds[1] = shape->bounds[1];
+ bounds[2] = shape->bounds[2];
+ bounds[3] = shape->bounds[3];
+ for (shape = shape->next; shape != NULL; shape = shape->next) {
+ bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]);
+ bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]);
+ bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]);
+ bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]);
+ }
+}
+
+static float nsvg__viewAlign(float content, float container, int type)
+{
+ if (type == NSVG_ALIGN_MIN)
+ return 0;
+ else if (type == NSVG_ALIGN_MAX)
+ return container - content;
+ // mid
+ return (container - content) * 0.5f;
+}
+
+static void nsvg__scaleGradient(NSVGgradient *grad, float tx, float ty, float sx, float sy)
+{
+ float t[6];
+ nsvg__xformSetTranslation(t, tx, ty);
+ nsvg__xformMultiply(grad->xform, t);
+
+ nsvg__xformSetScale(t, sx, sy);
+ nsvg__xformMultiply(grad->xform, t);
+}
+
+static void nsvg__scaleToViewbox(NSVGparser *p, const char *units)
+{
+ NSVGshape *shape;
+ NSVGpath *path;
+ float tx, ty, sx, sy, us, bounds[4], t[6], avgs;
+ int i;
+ float *pt;
+
+ // Guess image size if not set completely.
+ nsvg__imageBounds(p, bounds);
+
+ if (p->viewWidth == 0) {
+ if (p->image->width > 0) {
+ p->viewWidth = p->image->width;
+ }
+ else {
+ p->viewMinx = bounds[0];
+ p->viewWidth = bounds[2] - bounds[0];
+ }
+ }
+ if (p->viewHeight == 0) {
+ if (p->image->height > 0) {
+ p->viewHeight = p->image->height;
+ }
+ else {
+ p->viewMiny = bounds[1];
+ p->viewHeight = bounds[3] - bounds[1];
+ }
+ }
+ if (p->image->width == 0)
+ p->image->width = p->viewWidth;
+ if (p->image->height == 0)
+ p->image->height = p->viewHeight;
+
+ tx = -p->viewMinx;
+ ty = -p->viewMiny;
+ sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0;
+ sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0;
+ // Unit scaling
+ us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);
+
+ // Fix aspect ratio
+ if (p->alignType == NSVG_ALIGN_MEET) {
+ // fit whole image into viewbox
+ sx = sy = nsvg__minf(sx, sy);
+ tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx;
+ ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy;
+ }
+ else if (p->alignType == NSVG_ALIGN_SLICE) {
+ // fill whole viewbox with image
+ sx = sy = nsvg__maxf(sx, sy);
+ tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx;
+ ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy;
+ }
+
+ // Transform
+ sx *= us;
+ sy *= us;
+ avgs = (sx + sy) / 2.0f;
+ for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
+ shape->bounds[0] = (shape->bounds[0] + tx) * sx;
+ shape->bounds[1] = (shape->bounds[1] + ty) * sy;
+ shape->bounds[2] = (shape->bounds[2] + tx) * sx;
+ shape->bounds[3] = (shape->bounds[3] + ty) * sy;
+ for (path = shape->paths; path != NULL; path = path->next) {
+ path->bounds[0] = (path->bounds[0] + tx) * sx;
+ path->bounds[1] = (path->bounds[1] + ty) * sy;
+ path->bounds[2] = (path->bounds[2] + tx) * sx;
+ path->bounds[3] = (path->bounds[3] + ty) * sy;
+ for (i = 0; i < path->npts; i++) {
+ pt = &path->pts[i * 2];
+ pt[0] = (pt[0] + tx) * sx;
+ pt[1] = (pt[1] + ty) * sy;
+ }
+ }
+
+ if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT ||
+ shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) {
+ nsvg__scaleGradient(shape->fill.gradient, tx, ty, sx, sy);
+ memcpy(t, shape->fill.gradient->xform, sizeof(float) * 6);
+ nsvg__xformInverse(shape->fill.gradient->xform, t);
+ }
+ if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT ||
+ shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) {
+ nsvg__scaleGradient(shape->stroke.gradient, tx, ty, sx, sy);
+ memcpy(t, shape->stroke.gradient->xform, sizeof(float) * 6);
+ nsvg__xformInverse(shape->stroke.gradient->xform, t);
+ }
+
+ shape->strokeWidth *= avgs;
+ shape->strokeDashOffset *= avgs;
+ for (i = 0; i < shape->strokeDashCount; i++)
+ shape->strokeDashArray[i] *= avgs;
+ }
+}
+
+NSVGimage *nsvgParse(char *input, const char *units, float dpi)
+{
+ NSVGparser *p;
+ NSVGimage *ret = 0;
+
+ p = nsvg__createParser();
+ if (p == NULL) {
+ return NULL;
+ }
+ p->dpi = dpi;
+
+ nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p);
+
+ // Scale to viewBox
+ nsvg__scaleToViewbox(p, units);
+
+ ret = p->image;
+ p->image = NULL;
+
+ nsvg__deleteParser(p);
+
+ return ret;
+}
+
+NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi)
+{
+ FILE *fp = NULL;
+ size_t size;
+ char *data = NULL;
+ NSVGimage *image = NULL;
+
+ fp = fopen(filename, "rb");
+ if (!fp)
+ goto error;
+ fseek(fp, 0, SEEK_END);
+ size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ data = (char *)malloc(size + 1);
+ if (data == NULL)
+ goto error;
+ if (fread(data, 1, size, fp) != size)
+ goto error;
+ data[size] = '\0'; // Must be null terminated.
+ fclose(fp);
+ image = nsvgParse(data, units, dpi);
+ free(data);
+
+ return image;
+
+error:
+ if (fp)
+ fclose(fp);
+ if (data)
+ free(data);
+ if (image)
+ nsvgDelete(image);
+ return NULL;
+}
+
+NSVGpath *nsvgDuplicatePath(NSVGpath *p)
+{
+ NSVGpath *res = NULL;
+
+ if (p == NULL)
+ return NULL;
+
+ res = (NSVGpath *)malloc(sizeof(NSVGpath));
+ if (res == NULL)
+ goto error;
+ memset(res, 0, sizeof(NSVGpath));
+
+ res->pts = (float *)malloc(p->npts * 2 * sizeof(float));
+ if (res->pts == NULL)
+ goto error;
+ memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2);
+ res->npts = p->npts;
+
+ memcpy(res->bounds, p->bounds, sizeof(p->bounds));
+
+ res->closed = p->closed;
+
+ return res;
+
+error:
+ if (res != NULL) {
+ free(res->pts);
+ free(res);
+ }
+ return NULL;
+}
+
+void nsvgDelete(NSVGimage *image)
+{
+ NSVGshape *snext, *shape;
+ if (image == NULL)
+ return;
+ shape = image->shapes;
+ while (shape != NULL) {
+ snext = shape->next;
+ nsvg__deletePaths(shape->paths);
+ nsvg__deletePaint(&shape->fill);
+ nsvg__deletePaint(&shape->stroke);
+ free(shape);
+ shape = snext;
+ }
+ free(image);
+}
+
+#endif
diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h
index 077f9bf8bdc..d88db091cc2 100644
--- a/source/blender/makesdna/DNA_ID.h
+++ b/source/blender/makesdna/DNA_ID.h
@@ -763,19 +763,53 @@ typedef enum IDRecalcFlag {
* See e.g. how #BKE_library_unused_linked_data_set_tag is doing this.
*/
enum {
+ /* Special case: Library, should never ever depend on any other type. */
INDEX_ID_LI = 0,
- INDEX_ID_IP,
+
+ /* Animation types, might be used by almost all other types. */
+ INDEX_ID_IP, /* Deprecated. */
INDEX_ID_AC,
- INDEX_ID_KE,
- INDEX_ID_PAL,
+
+ /* Grease Pencil, special case, should be with the other obdata, but it can also be used by many
+ * other ID types, including node trees e.g.
+ * So there is no proper place for those, for now keep close to the lower end of the processing
+ * hierarchy, but we may want to re-evaluate that at some point. */
INDEX_ID_GD,
+
+ /* Node trees, abstraction for procedural data, potentially used by many other ID types.
+ *
+ * NOTE: While node trees can also use many other ID types, they should not /own/ any of those,
+ * while they are being owned by many other ID types. This is why they are placed here. */
INDEX_ID_NT,
+
+ /* File-wrapper types, those usually 'embed' external files in Blender, with no dependencies to
+ * other ID types. */
+ INDEX_ID_VF,
+ INDEX_ID_TXT,
+ INDEX_ID_SO,
+
+ /* Image/movie types, can be used by shading ID types, but also directly by Objects, Scenes, etc.
+ */
+ INDEX_ID_MSK,
INDEX_ID_IM,
+ INDEX_ID_MC,
+
+ /* Shading types. */
INDEX_ID_TE,
INDEX_ID_MA,
- INDEX_ID_VF,
- INDEX_ID_AR,
+ INDEX_ID_LS,
+ INDEX_ID_WO,
+
+ /* Simulation-related types. */
INDEX_ID_CF,
+ INDEX_ID_SIM,
+ INDEX_ID_PA,
+
+ /* Shape Keys snow-flake, can be used by several obdata types. */
+ INDEX_ID_KE,
+
+ /* Object data types. */
+ INDEX_ID_AR,
INDEX_ID_ME,
INDEX_ID_CU,
INDEX_ID_MB,
@@ -785,26 +819,28 @@ enum {
INDEX_ID_LT,
INDEX_ID_LA,
INDEX_ID_CA,
- INDEX_ID_TXT,
- INDEX_ID_SO,
- INDEX_ID_GR,
- INDEX_ID_PC,
- INDEX_ID_BR,
- INDEX_ID_PA,
INDEX_ID_SPK,
INDEX_ID_LP,
- INDEX_ID_WO,
- INDEX_ID_MC,
- INDEX_ID_SCR,
+
+ /* Collection and object types. */
INDEX_ID_OB,
- INDEX_ID_LS,
+ INDEX_ID_GR,
+
+ /* Preset-like, not-really-data types, can use many other ID types but should never be used by
+ * any actual data type (besides Scene, due to tool settings). */
+ INDEX_ID_PAL,
+ INDEX_ID_PC,
+ INDEX_ID_BR,
+
+ /* Scene, after preset-like ID types because of tool settings. */
INDEX_ID_SCE,
+
+ /* UI-related types, should never be used by any other data type. */
+ INDEX_ID_SCR,
INDEX_ID_WS,
INDEX_ID_WM,
- /* TODO: This should probably be tweaked, #Mask and #Simulation are rather low-level types that
- * should most likely be defined //before// #Object and geometry type indices? */
- INDEX_ID_MSK,
- INDEX_ID_SIM,
+
+ /* Special values. */
INDEX_ID_NULL,
INDEX_ID_MAX,
};
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index b04fea11a4d..b53318cfbfe 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -160,6 +160,7 @@ const EnumPropertyItem rna_enum_object_gpencil_type_items[] = {
{GP_EMPTY, "EMPTY", ICON_EMPTY_AXIS, "Blank", "Create an empty grease pencil object"},
{GP_STROKE, "STROKE", ICON_STROKE, "Stroke", "Create a simple stroke with basic colors"},
{GP_MONKEY, "MONKEY", ICON_MONKEY, "Monkey", "Construct a Suzanne grease pencil object"},
+ {0, "", 0, NULL, NULL},
{GP_LRT_SCENE,
"LRT_SCENE",
ICON_SCENE_DATA,
diff --git a/source/blender/modifiers/intern/MOD_subsurf.c b/source/blender/modifiers/intern/MOD_subsurf.c
index d089894a6e9..c3611488f5f 100644
--- a/source/blender/modifiers/intern/MOD_subsurf.c
+++ b/source/blender/modifiers/intern/MOD_subsurf.c
@@ -22,6 +22,7 @@
*/
#include <stddef.h>
+#include <stdio.h>
#include <string.h>
#include "MEM_guardedalloc.h"
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
index 72b05dcce70..e9228a2942b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
@@ -223,8 +223,10 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top,
transform.values,
true);
+ BMeshToMeshParams params{};
+ params.calc_object_remap = false;
Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr);
+ BM_mesh_bm_to_me(nullptr, bm, mesh, &params);
BM_mesh_free(bm);
return mesh;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc
index 1803a13f651..f8a9bfd2ed1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc
@@ -50,8 +50,10 @@ static Mesh *create_cube_mesh(const float size)
size,
true);
+ BMeshToMeshParams params{};
+ params.calc_object_remap = false;
Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr);
+ BM_mesh_bm_to_me(nullptr, bm, mesh, &params);
BM_mesh_free(bm);
return mesh;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc
index f16b37fe977..242cc6ed7df 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc
@@ -52,8 +52,10 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius)
transform.values,
true);
+ BMeshToMeshParams params{};
+ params.calc_object_remap = false;
Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr);
+ BM_mesh_bm_to_me(nullptr, bm, mesh, &params);
BM_mesh_free(bm);
return mesh;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
index b7d249c18bc..8efba91da1a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
@@ -85,8 +85,10 @@ static Mesh *create_uv_sphere_mesh_bmesh(const float radius, const int segments,
transform.values,
true);
+ BMeshToMeshParams params{};
+ params.calc_object_remap = false;
Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr);
+ BM_mesh_bm_to_me(nullptr, bm, mesh, &params);
BM_mesh_free(bm);
return mesh;
diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c
index 5fca6f4cec6..563a76ac824 100644
--- a/source/blender/python/bmesh/bmesh_py_types.c
+++ b/source/blender/python/bmesh/bmesh_py_types.c
@@ -3984,7 +3984,7 @@ PyObject *BPy_BMElem_CreatePyObject(BMesh *bm, BMHeader *ele)
case BM_LOOP:
return BPy_BMLoop_CreatePyObject(bm, (BMLoop *)ele);
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
PyErr_SetString(PyExc_SystemError, "internal error");
return NULL;
}
diff --git a/source/blender/python/bmesh/bmesh_py_types_customdata.c b/source/blender/python/bmesh/bmesh_py_types_customdata.c
index 471a311c411..78c43d18609 100644
--- a/source/blender/python/bmesh/bmesh_py_types_customdata.c
+++ b/source/blender/python/bmesh/bmesh_py_types_customdata.c
@@ -56,7 +56,7 @@ static CustomData *bpy_bm_customdata_get(BMesh *bm, char htype)
return &bm->ldata;
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return NULL;
}
@@ -958,7 +958,7 @@ PyObject *BPy_BMLayerAccess_CreatePyObject(BMesh *bm, const char htype)
type = &BPy_BMLayerAccessLoop_Type;
break;
default: {
- BLI_assert(0);
+ BLI_assert_unreachable();
type = NULL;
break;
}
diff --git a/source/blender/python/generic/imbuf_py_api.c b/source/blender/python/generic/imbuf_py_api.c
index 5b4a4fd237e..97a66bc23c0 100644
--- a/source/blender/python/generic/imbuf_py_api.c
+++ b/source/blender/python/generic/imbuf_py_api.c
@@ -123,7 +123,7 @@ static PyObject *py_imbuf_resize(Py_ImBuf *self, PyObject *args, PyObject *kw)
IMB_scaleImBuf(self->ibuf, UNPACK2(size));
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
Py_RETURN_NONE;
}
diff --git a/source/blender/python/gpu/gpu_py_matrix.c b/source/blender/python/gpu/gpu_py_matrix.c
index df7c82379b3..b00a13d5be8 100644
--- a/source/blender/python/gpu/gpu_py_matrix.c
+++ b/source/blender/python/gpu/gpu_py_matrix.c
@@ -201,7 +201,7 @@ static PyObject *pygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *sel
self->level = GPU_matrix_stack_level_get_projection();
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
Py_RETURN_NONE;
}
@@ -234,7 +234,7 @@ static PyObject *pygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self
}
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
finally:
Py_RETURN_NONE;
diff --git a/source/blender/python/gpu/gpu_py_vertex_buffer.c b/source/blender/python/gpu/gpu_py_vertex_buffer.c
index 111fa114c43..3c7038186b9 100644
--- a/source/blender/python/gpu/gpu_py_vertex_buffer.c
+++ b/source/blender/python/gpu/gpu_py_vertex_buffer.c
@@ -70,7 +70,8 @@
break; \
} \
default: \
- BLI_assert(0); \
+ BLI_assert_unreachable(); \
+ break; \
} \
((void)0)
diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt
index c7816aed3c1..9ac8d4d9f47 100644
--- a/source/blender/python/intern/CMakeLists.txt
+++ b/source/blender/python/intern/CMakeLists.txt
@@ -363,4 +363,12 @@ if(WITH_POTRACE)
add_definitions(-DWITH_POTRACE)
endif()
+if(WITH_PUGIXML)
+ add_definitions(-DWITH_PUGIXML)
+endif()
+
+if(WITH_HARU)
+ add_definitions(-DWITH_HARU)
+endif()
+
blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/python/intern/bpy_app_build_options.c b/source/blender/python/intern/bpy_app_build_options.c
index 9f12c9f80f1..676d1b8045f 100644
--- a/source/blender/python/intern/bpy_app_build_options.c
+++ b/source/blender/python/intern/bpy_app_build_options.c
@@ -65,6 +65,8 @@ static PyStructSequence_Field app_builtopts_info_fields[] = {
{"fluid", NULL},
{"xr_openxr", NULL},
{"potrace", NULL},
+ {"pugixml", NULL},
+ {"haru", NULL},
/* Sentinel (this line prevents `clang-format` wrapping into columns). */
{NULL},
};
@@ -311,6 +313,18 @@ static PyObject *make_builtopts_info(void)
SetObjIncref(Py_False);
#endif
+#ifdef WITH_PUGIXML
+ SetObjIncref(Py_True);
+#else
+ SetObjIncref(Py_False);
+#endif
+
+#ifdef WITH_HARU
+ SetObjIncref(Py_True);
+#else
+ SetObjIncref(Py_False);
+#endif
+
#undef SetObjIncref
return builtopts_info;
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c
index fab73d0f3dc..49ac2662a31 100644
--- a/source/blender/python/intern/bpy_rna.c
+++ b/source/blender/python/intern/bpy_rna.c
@@ -1345,7 +1345,7 @@ BLI_bitmap *pyrna_set_to_enum_bitmap(const EnumPropertyItem *items,
index = (int)ret_convert.as_unsigned;
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
BLI_assert(index < bitmap_size);
@@ -5550,7 +5550,7 @@ static PyObject *pyprop_array_foreach_getset(BPy_PropertyArrayRNA *self,
case PROP_POINTER:
case PROP_COLLECTION:
/* Should never happen. */
- BLI_assert(false);
+ BLI_assert_unreachable();
break;
}
@@ -5595,7 +5595,7 @@ static PyObject *pyprop_array_foreach_getset(BPy_PropertyArrayRNA *self,
case PROP_POINTER:
case PROP_COLLECTION:
/* Should never happen. */
- BLI_assert(false);
+ BLI_assert_unreachable();
break;
}
diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c
index 016ba462030..161d2f41592 100644
--- a/source/blender/python/mathutils/mathutils_Matrix.c
+++ b/source/blender/python/mathutils/mathutils_Matrix.c
@@ -1049,7 +1049,8 @@ static void adjoint_matrix_n(float *mat_dst, const float *mat_src, const ushort
break;
}
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
+ break;
}
}
@@ -1159,7 +1160,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat)
break;
}
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c
index 4db7350544b..cc11796496c 100644
--- a/source/blender/sequencer/intern/sequencer.c
+++ b/source/blender/sequencer/intern/sequencer.c
@@ -488,7 +488,7 @@ static Sequence *seq_dupli(const Scene *scene_src,
}
else {
/* sequence type not handled in duplicate! Expect a crash now... */
- BLI_assert(0);
+ BLI_assert_unreachable();
}
/* When using SEQ_DUPE_UNIQUE_NAME, it is mandatory to add new sequences in relevant container
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
index 1e77ccd7a1c..32b6a6e6b31 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
@@ -324,7 +324,7 @@ bool wm_gizmogroup_is_visible_in_drawstep(const wmGizmoGroup *gzgroup,
case WM_GIZMOMAP_DRAWSTEP_3D:
return (gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D);
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
return false;
}
}
@@ -393,7 +393,7 @@ static int gizmo_select_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE
return OPERATOR_FINISHED;
}
- BLI_assert(0);
+ BLI_assert_unreachable();
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
}
@@ -498,7 +498,7 @@ static int gizmo_tweak_modal(bContext *C, wmOperator *op, const wmEvent *event)
bool clear_modal = true;
if (gz == NULL) {
- BLI_assert(0);
+ BLI_assert_unreachable();
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
}
@@ -585,7 +585,7 @@ static int gizmo_tweak_invoke(bContext *C, wmOperator *op, const wmEvent *event)
if (!gz) {
/* wm_handlers_do_intern shouldn't let this happen */
- BLI_assert(0);
+ BLI_assert_unreachable();
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
}
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c
index a9e24867351..ab5a265547d 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c
@@ -18,6 +18,8 @@
* \ingroup wm
*/
+#include <stdio.h>
+
#include "BLI_ghash.h"
#include "BLI_utildefines.h"
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
index 9b38f010205..45950a32d85 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
@@ -941,7 +941,7 @@ bool WM_gizmomap_select_all(bContext *C, wmGizmoMap *gzmap, const int action)
changed = wm_gizmomap_deselect_all(gzmap);
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
break;
}
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c
index efd7a13d02a..185854b1ca0 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c
@@ -18,6 +18,8 @@
* \ingroup wm
*/
+#include <stdio.h>
+
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index ed1b53e8e20..791aeeb39cd 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -144,7 +144,7 @@ wmEvent *wm_event_add(wmWindow *win, const wmEvent *event_to_add)
wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
{
if ((G.f & G_FLAG_EVENT_SIMULATE) == 0) {
- BLI_assert(0);
+ BLI_assert_unreachable();
return NULL;
}
wmEvent *event = wm_event_add(win, event_to_add);
@@ -1037,7 +1037,7 @@ static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat,
ED_area_type_hud_clear(wm, NULL);
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
}
@@ -2885,7 +2885,7 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers
}
else {
/* Unreachable (handle all types above). */
- BLI_assert(0);
+ BLI_assert_unreachable();
}
if (action & WM_HANDLER_BREAK) {
diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c
index a9b30f91bee..bbcb0669cce 100644
--- a/source/blender/windowmanager/intern/wm_files.c
+++ b/source/blender/windowmanager/intern/wm_files.c
@@ -2298,7 +2298,7 @@ static int operator_state_dispatch(bContext *C, wmOperator *op, OperatorDispatch
return target.run(C, op);
}
}
- BLI_assert(false);
+ BLI_assert_unreachable();
return OPERATOR_CANCELLED;
}
diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c
index bf7cf81f0a9..840debad01b 100644
--- a/source/blender/windowmanager/intern/wm_files_link.c
+++ b/source/blender/windowmanager/intern/wm_files_link.c
@@ -750,7 +750,7 @@ static void lib_relocate_do_remap(Main *bmain,
/* In some cases, new_id might become direct link, remove parent of library in this case. */
if (new_id->lib->parent && (new_id->tag & LIB_TAG_INDIRECT) == 0) {
if (do_reload) {
- BLI_assert(0); /* Should not happen in 'pure' reload case... */
+ BLI_assert_unreachable(); /* Should not happen in 'pure' reload case... */
}
new_id->lib->parent = NULL;
}
diff --git a/source/blender/windowmanager/intern/wm_menu_type.c b/source/blender/windowmanager/intern/wm_menu_type.c
index 0c52e636e4d..bcaa2243f9f 100644
--- a/source/blender/windowmanager/intern/wm_menu_type.c
+++ b/source/blender/windowmanager/intern/wm_menu_type.c
@@ -20,6 +20,8 @@
* Menu Registry.
*/
+#include <stdio.h>
+
#include "BLI_sys_types.h"
#include "DNA_windowmanager_types.h"
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index e8a41b04d84..84c16999c1b 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -1229,7 +1229,7 @@ ID *WM_operator_drop_load_path(struct bContext *C, wmOperator *op, const short i
id = (ID *)BKE_image_load_exists_ex(bmain, path, &exists);
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
if (!id) {
@@ -1248,7 +1248,7 @@ ID *WM_operator_drop_load_path(struct bContext *C, wmOperator *op, const short i
BLI_path_rel(((Image *)id)->filepath, BKE_main_blendfile_path(bmain));
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
}
}
@@ -1687,7 +1687,7 @@ static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdat
UI_but_func_menu_search(but);
}
else {
- BLI_assert(0);
+ BLI_assert_unreachable();
}
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
diff --git a/source/blender/windowmanager/intern/wm_uilist_type.c b/source/blender/windowmanager/intern/wm_uilist_type.c
index ef6d855cb36..45c14c0bbe9 100644
--- a/source/blender/windowmanager/intern/wm_uilist_type.c
+++ b/source/blender/windowmanager/intern/wm_uilist_type.c
@@ -20,6 +20,8 @@
* UI List Registry.
*/
+#include <stdio.h>
+
#include "BLI_sys_types.h"
#include "DNA_windowmanager_types.h"
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index 25232e8e3d5..eb1da15807c 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -2174,7 +2174,7 @@ void WM_window_screen_rect_calc(const wmWindow *win, rcti *r_rect)
screen_rect.ymin += height;
break;
default:
- BLI_assert(0);
+ BLI_assert_unreachable();
break;
}
}