diff options
author | Hans Goudey <h.goudey@me.com> | 2021-03-25 00:46:39 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2021-03-25 00:46:39 +0300 |
commit | 4ea3d249301003e5d823bddce2962c81274556d8 (patch) | |
tree | f11e69df04d82d6e7477c38447d425205bf1f755 | |
parent | 2495c0c53929d0480cfb6ae81502d79636a656b8 (diff) | |
parent | 9ad3d1d36b64f336fabdd9b3d9f02b13740068b0 (diff) |
Merge branch 'master' into temp-geometry-nodes-processor-prototype
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, ¶ms); + 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, ¶ms); + 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, ¶ms); + 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(¶ms); + BKE_camera_params_from_object(¶ms, cam_ob); + + /* Compute matrix, viewplane, .. */ + RenderData *rd = &scene_->r; + BKE_camera_params_compute_viewplane(¶ms, rd->xsch, rd->ysch, rd->xasp, rd->yasp); + BKE_camera_params_compute_matrix(¶ms); + + 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, ¶ms); 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, ¶ms); 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, ¶ms); 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, ¶ms); 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; } } |