diff options
author | Julian Eisel <julian@blender.org> | 2021-03-31 23:53:03 +0300 |
---|---|---|
committer | Julian Eisel <julian@blender.org> | 2021-03-31 23:53:03 +0300 |
commit | c7904d2398fd4d43d15268c792aa15ea8ee538fe (patch) | |
tree | 484fd7f66ac59c82abd4aca18894f991b4935483 /source/blender/io/gpencil/intern | |
parent | 91b87d8d7fb8aec62faf00e5e0c3619d2753a92e (diff) | |
parent | 1a100d2d78c75c9a6ac015606cc16698e7775682 (diff) |
Merge branch 'master' into ui-asset-view-templateui-asset-view-template
Diffstat (limited to 'source/blender/io/gpencil/intern')
12 files changed, 2121 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..8da1ec27b9c --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc @@ -0,0 +1,396 @@ + + +/* + * 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.hh" + +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); + is_ortho_ = params.is_ortho; + } + else { + unit_m4(persmat_); + is_ortho_ = false; + } + + 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; + is_ortho_ = 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, const bool is_ortho) +{ + float3 parent_co = diff_mat_ * co; + mul_m4_v3(persmat_, parent_co); + + if (!is_ortho) { + parent_co.x = parent_co.x / max_ff(FLT_MIN, parent_co.z); + parent_co.y = parent_co.y / max_ff(FLT_MIN, parent_co.z); + } + + 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, is_orthographic()); + } + 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_; +} + +bool GpencilIO::is_orthographic() +{ + return is_ortho_; +} + +/* 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.hh b/source/blender/io/gpencil/intern/gpencil_io_base.hh new file mode 100644 index 00000000000..cbcd35e470d --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_base.hh @@ -0,0 +1,118 @@ +/* + * 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, const bool is_ortho); + 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(); + bool is_orthographic(); + + 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_; + bool is_ortho_; + 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..a710c175a77 --- /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 <cstdio> + +#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.hh" +#endif + +#ifdef WITH_PUGIXML +# include "gpencil_io_export_svg.hh" +#endif + +#include "gpencil_io_import_svg.hh" + +#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.hh b/source/blender/io/gpencil/intern/gpencil_io_export_base.hh new file mode 100644 index 00000000000..ffb1c6ce262 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_base.hh @@ -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.hh" + +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..9b2dc6d12a3 --- /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.hh" + +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.hh b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.hh new file mode 100644 index 00000000000..20970151344 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.hh @@ -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.hh" +#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..c62764cca06 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc @@ -0,0 +1,465 @@ +/* + * 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.hh" + +#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"); + main_node_.append_attribute("xmlns").set_value("http://www.w3.org/2000/svg"); + + 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 rectangle + * \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 the text + * \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(const 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.hh b/source/blender/io/gpencil/intern/gpencil_io_export_svg.hh new file mode 100644 index 00000000000..3bff31f20bf --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.hh @@ -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.hh" +#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 do_fill); + + void export_stroke_to_polyline(struct bGPDlayer *gpl, + struct bGPDstroke *gps, + pugi::xml_node node_gpl, + const bool is_stroke, + const bool do_fill); + + void color_string_set(struct bGPDlayer *gpl, + struct bGPDstroke *gps, + pugi::xml_node node_gps, + const bool do_fill); + + std::string rgb_to_hexstr(const 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..d0b6e20bda2 --- /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.hh" + +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.hh b/source/blender/io/gpencil/intern/gpencil_io_import_base.hh new file mode 100644 index 00000000000..7d6fad2340b --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.hh @@ -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.hh" + +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..4894cfc1ada --- /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.hh" + +/* 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 millimeters. */ + 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.hh b/source/blender/io/gpencil/intern/gpencil_io_import_svg.hh new file mode 100644 index 00000000000..0e9271dd2c6 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.hh @@ -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.hh" + +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 |