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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/io/gpencil/intern')
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_base.cc386
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_base.h116
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_capi.cc202
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_base.h38
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc311
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_pdf.h67
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_svg.cc464
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_svg.h89
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_base.cc85
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_base.h41
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_svg.cc253
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_svg.h56
12 files changed, 2108 insertions, 0 deletions
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc
new file mode 100644
index 00000000000..855252e648c
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc
@@ -0,0 +1,386 @@
+
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_path_util.h"
+#include "BLI_span.hh"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_layer_types.h"
+#include "DNA_material_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_camera.h"
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "UI_view2d.h"
+
+#include "ED_view3d.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "gpencil_io_base.h"
+
+using blender::Span;
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilIO::GpencilIO(const GpencilIOParams *iparams)
+{
+ params_ = *iparams;
+
+ /* Easy access data. */
+ bmain_ = CTX_data_main(params_.C);
+ depsgraph_ = CTX_data_depsgraph_pointer(params_.C);
+ scene_ = CTX_data_scene(params_.C);
+ rv3d_ = (RegionView3D *)params_.region->regiondata;
+ gpd_ = (params_.ob != nullptr) ? (bGPdata *)params_.ob->data : nullptr;
+ cfra_ = iparams->frame_cur;
+
+ /* Calculate camera matrix. */
+ Object *cam_ob = params_.v3d->camera;
+ if (cam_ob != nullptr) {
+ /* Set up parameters. */
+ CameraParams params;
+ BKE_camera_params_init(&params);
+ BKE_camera_params_from_object(&params, cam_ob);
+
+ /* Compute matrix, viewplane, .. */
+ RenderData *rd = &scene_->r;
+ BKE_camera_params_compute_viewplane(&params, rd->xsch, rd->ysch, rd->xasp, rd->yasp);
+ BKE_camera_params_compute_matrix(&params);
+
+ float viewmat[4][4];
+ invert_m4_m4(viewmat, cam_ob->obmat);
+
+ mul_m4_m4m4(persmat_, params.winmat, viewmat);
+ }
+ else {
+ unit_m4(persmat_);
+ }
+
+ winx_ = params_.region->winx;
+ winy_ = params_.region->winy;
+
+ /* Camera rectangle. */
+ if (rv3d_->persp == RV3D_CAMOB) {
+ render_x_ = (scene_->r.xsch * scene_->r.size) / 100;
+ render_y_ = (scene_->r.ysch * scene_->r.size) / 100;
+
+ ED_view3d_calc_camera_border(CTX_data_scene(params_.C),
+ depsgraph_,
+ params_.region,
+ params_.v3d,
+ rv3d_,
+ &camera_rect_,
+ true);
+ is_camera_ = true;
+ camera_ratio_ = render_x_ / (camera_rect_.xmax - camera_rect_.xmin);
+ offset_.x = camera_rect_.xmin;
+ offset_.y = camera_rect_.ymin;
+ }
+ else {
+ is_camera_ = false;
+ /* Calc selected object boundbox. Need set initial value to some variables. */
+ camera_ratio_ = 1.0f;
+ offset_.x = 0.0f;
+ offset_.y = 0.0f;
+
+ selected_objects_boundbox_calc();
+ rctf boundbox;
+ selected_objects_boundbox_get(&boundbox);
+
+ render_x_ = boundbox.xmax - boundbox.xmin;
+ render_y_ = boundbox.ymax - boundbox.ymin;
+ offset_.x = boundbox.xmin;
+ offset_.y = boundbox.ymin;
+ }
+}
+
+/** Create a list of selected objects sorted from back to front */
+void GpencilIO::create_object_list()
+{
+ ViewLayer *view_layer = CTX_data_view_layer(params_.C);
+
+ float3 camera_z_axis;
+ copy_v3_v3(camera_z_axis, rv3d_->viewinv[2]);
+ ob_list_.clear();
+
+ LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
+ Object *object = base->object;
+
+ if (object->type != OB_GPENCIL) {
+ continue;
+ }
+ if ((params_.select_mode == GP_EXPORT_ACTIVE) && (params_.ob != object)) {
+ continue;
+ }
+
+ if ((params_.select_mode == GP_EXPORT_SELECTED) && ((base->flag & BASE_SELECTED) == 0)) {
+ continue;
+ }
+
+ /* Save z-depth from view to sort from back to front. */
+ if (is_camera_) {
+ float camera_z = dot_v3v3(camera_z_axis, object->obmat[3]);
+ ObjectZ obz = {camera_z, object};
+ ob_list_.append(obz);
+ }
+ else {
+ float zdepth = 0;
+ if (rv3d_) {
+ if (rv3d_->is_persp) {
+ zdepth = ED_view3d_calc_zfac(rv3d_, object->obmat[3], nullptr);
+ }
+ else {
+ zdepth = -dot_v3v3(rv3d_->viewinv[2], object->obmat[3]);
+ }
+ ObjectZ obz = {zdepth * -1.0f, object};
+ ob_list_.append(obz);
+ }
+ }
+ }
+ /* Sort list of objects from point of view. */
+ std::sort(ob_list_.begin(), ob_list_.end(), [](const ObjectZ &obz1, const ObjectZ &obz2) {
+ return obz1.zdepth < obz2.zdepth;
+ });
+}
+
+/**
+ * Set file input_text full path.
+ * \param filename: Path of the file provided by save dialog.
+ */
+void GpencilIO::filename_set(const char *filename)
+{
+ BLI_strncpy(filename_, filename, FILE_MAX);
+ BLI_path_abs(filename_, BKE_main_blendfile_path(bmain_));
+}
+
+/** Convert to screenspace. */
+bool GpencilIO::gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co)
+{
+ float3 parent_co = diff_mat_ * co;
+ float2 screen_co;
+ eV3DProjTest test = (eV3DProjTest)(V3D_PROJ_RET_OK);
+ if (ED_view3d_project_float_global(params_.region, parent_co, screen_co, test) ==
+ V3D_PROJ_RET_OK) {
+ if (!ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1])) {
+ copy_v2_v2(r_co, screen_co);
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co[0] = winx_ - r_co[0];
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co[1] = winy_ - r_co[1];
+ }
+ /* Apply offset and scale. */
+ sub_v2_v2(r_co, &offset_.x);
+ mul_v2_fl(r_co, camera_ratio_);
+
+ return true;
+ }
+ }
+ r_co[0] = V2D_IS_CLIPPED;
+ r_co[1] = V2D_IS_CLIPPED;
+
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co[0] = winx_ - r_co[0];
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co[1] = winy_ - r_co[1];
+ }
+
+ return false;
+}
+
+/** Convert to render space. */
+float2 GpencilIO::gpencil_3D_point_to_render_space(const float3 co)
+{
+ float3 parent_co = diff_mat_ * co;
+ mul_m4_v3(persmat_, parent_co);
+
+ parent_co.x = parent_co.x / max_ff(FLT_MIN, parent_co[2]);
+ parent_co.y = parent_co.y / max_ff(FLT_MIN, parent_co[2]);
+
+ float2 r_co;
+ r_co.x = (parent_co.x + 1.0f) / 2.0f * (float)render_x_;
+ r_co.y = (parent_co.y + 1.0f) / 2.0f * (float)render_y_;
+
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co.x = (float)render_x_ - r_co.x;
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co.y = (float)render_y_ - r_co.y;
+ }
+
+ return r_co;
+}
+
+/** Convert to 2D. */
+float2 GpencilIO::gpencil_3D_point_to_2D(const float3 co)
+{
+ const bool is_camera = (bool)(rv3d_->persp == RV3D_CAMOB);
+ if (is_camera) {
+ return gpencil_3D_point_to_render_space(co);
+ }
+ float2 result;
+ gpencil_3D_point_to_screen_space(co, result);
+ return result;
+}
+
+/** Get radius of point. */
+float GpencilIO::stroke_point_radius_get(bGPDlayer *gpl, bGPDstroke *gps)
+{
+ bGPDspoint *pt = &gps->points[0];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+
+ /* Radius. */
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps, 3, diff_mat_.values);
+
+ pt = &gps_perimeter->points[0];
+ const float2 screen_ex = gpencil_3D_point_to_2D(&pt->x);
+
+ const float2 v1 = screen_co - screen_ex;
+ float radius = v1.length();
+ BKE_gpencil_free_stroke(gps_perimeter);
+
+ return MAX2(radius, 1.0f);
+}
+
+void GpencilIO::prepare_layer_export_matrix(Object *ob, bGPDlayer *gpl)
+{
+ BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob, gpl, diff_mat_.values);
+ diff_mat_ = diff_mat_ * float4x4(gpl->layer_invmat);
+}
+
+void GpencilIO::prepare_stroke_export_colors(Object *ob, bGPDstroke *gps)
+{
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
+
+ /* Stroke color. */
+ copy_v4_v4(stroke_color_, gp_style->stroke_rgba);
+ avg_opacity_ = 0;
+ /* Get average vertex color and apply. */
+ float avg_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
+ add_v4_v4(avg_color, pt.vert_color);
+ avg_opacity_ += pt.strength;
+ }
+
+ mul_v4_v4fl(avg_color, avg_color, 1.0f / (float)gps->totpoints);
+ interp_v3_v3v3(stroke_color_, stroke_color_, avg_color, avg_color[3]);
+ avg_opacity_ /= (float)gps->totpoints;
+
+ /* Fill color. */
+ copy_v4_v4(fill_color_, gp_style->fill_rgba);
+ /* Apply vertex color for fill. */
+ interp_v3_v3v3(fill_color_, fill_color_, gps->vert_color_fill, gps->vert_color_fill[3]);
+}
+
+float GpencilIO::stroke_average_opacity_get()
+{
+ return avg_opacity_;
+}
+
+bool GpencilIO::is_camera_mode()
+{
+ return is_camera_;
+}
+
+/* Calculate selected strokes boundbox. */
+void GpencilIO::selected_objects_boundbox_calc()
+{
+ const float gap = 10.0f;
+
+ float2 min, max;
+ INIT_MINMAX2(min, max);
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob_eval, gpl, diff_mat_.values);
+
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == nullptr) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints == 0) {
+ continue;
+ }
+ for (const bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
+ minmax_v2v2_v2(min, max, screen_co);
+ }
+ }
+ }
+ }
+ /* Add small gap. */
+ add_v2_fl(min, gap * -1.0f);
+ add_v2_fl(max, gap);
+
+ select_boundbox_.xmin = min[0];
+ select_boundbox_.ymin = min[1];
+ select_boundbox_.xmax = max[0];
+ select_boundbox_.ymax = max[1];
+}
+
+void GpencilIO::selected_objects_boundbox_get(rctf *boundbox)
+{
+ boundbox->xmin = select_boundbox_.xmin;
+ boundbox->xmax = select_boundbox_.xmax;
+ boundbox->ymin = select_boundbox_.ymin;
+ boundbox->ymax = select_boundbox_.ymax;
+}
+
+void GpencilIO::frame_number_set(const int value)
+{
+ cfra_ = value;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.h b/source/blender/io/gpencil/intern/gpencil_io_base.h
new file mode 100644
index 00000000000..986221618b7
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.h
@@ -0,0 +1,116 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_vector.hh"
+
+#include "DNA_space_types.h" /* for FILE_MAX */
+
+#include "gpencil_io.h"
+
+struct Depsgraph;
+struct Main;
+struct Object;
+struct RegionView3D;
+struct Scene;
+
+struct bGPdata;
+struct bGPDlayer;
+struct bGPDstroke;
+
+using blender::Vector;
+
+namespace blender::io::gpencil {
+
+class GpencilIO {
+ public:
+ GpencilIO(const GpencilIOParams *iparams);
+
+ void frame_number_set(const int value);
+
+ protected:
+ GpencilIOParams params_;
+
+ bool invert_axis_[2];
+ float4x4 diff_mat_;
+ char filename_[FILE_MAX];
+
+ /* Used for sorting objects. */
+ struct ObjectZ {
+ float zdepth;
+ struct Object *ob;
+ };
+
+ /** List of included objects. */
+ blender::Vector<ObjectZ> ob_list_;
+
+ /* Data for easy access. */
+ struct Depsgraph *depsgraph_;
+ struct bGPdata *gpd_;
+ struct Main *bmain_;
+ struct Scene *scene_;
+ struct RegionView3D *rv3d_;
+
+ int16_t winx_, winy_;
+ int16_t render_x_, render_y_;
+ float camera_ratio_;
+ rctf camera_rect_;
+
+ float2 offset_;
+
+ int cfra_;
+
+ float stroke_color_[4], fill_color_[4];
+
+ /* Geometry functions. */
+ bool gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co);
+ float2 gpencil_3D_point_to_render_space(const float3 co);
+ float2 gpencil_3D_point_to_2D(const float3 co);
+
+ float stroke_point_radius_get(struct bGPDlayer *gpl, struct bGPDstroke *gps);
+ void create_object_list();
+
+ bool is_camera_mode();
+
+ float stroke_average_opacity_get();
+
+ void prepare_layer_export_matrix(struct Object *ob, struct bGPDlayer *gpl);
+ void prepare_stroke_export_colors(struct Object *ob, struct bGPDstroke *gps);
+
+ void selected_objects_boundbox_calc();
+ void selected_objects_boundbox_get(rctf *boundbox);
+ void filename_set(const char *filename);
+
+ private:
+ float avg_opacity_;
+ bool is_camera_;
+ rctf select_boundbox_;
+
+ /* Camera matrix. */
+ float persmat_[4][4];
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
new file mode 100644
index 00000000000..34539a66fe8
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
@@ -0,0 +1,202 @@
+/*
+ * 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. */
+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;
+}
+
+/* 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(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..b49b4c969ad
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc
@@ -0,0 +1,85 @@
+
+
+/*
+ * 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"
+
+#include "pugixml.hpp"
+
+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