diff options
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d.py | 5 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_gpencil.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil.c | 21 | ||||
-rw-r--r-- | source/blender/editors/gpencil/CMakeLists.txt | 16 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_intern.h | 1 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_ops.c | 4 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_trace.h | 79 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_trace_ops.c | 313 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_trace_utils.c | 373 | ||||
-rw-r--r-- | source/blender/python/intern/CMakeLists.txt | 3 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_app_build_options.c | 7 |
11 files changed, 823 insertions, 1 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 351654f61e4..888b4a62139 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2260,6 +2260,11 @@ class VIEW3D_MT_object(Menu): else: layout.operator_menu_enum("object.convert", "target") + # Potrace lib dependency + if bpy.app.build_options.potrace: + layout.separator() + layout.operator("gpencil.trace_image") + layout.separator() layout.menu("VIEW3D_MT_object_showhide") diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index f1912b14e8c..c484f0753a3 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -282,6 +282,8 @@ void BKE_gpencil_parent_matrix_get(const struct Depsgraph *depsgraph, void BKE_gpencil_update_layer_parent(const struct Depsgraph *depsgraph, struct Object *ob); +int BKE_gpencil_material_find_index_by_name_prefix(struct Object *ob, const char *name_prefix); + void BKE_gpencil_blend_read_data(struct BlendDataReader *reader, struct bGPdata *gpd); #ifdef __cplusplus diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 03ac7e622e1..7bc3daf037f 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -2745,4 +2745,25 @@ void BKE_gpencil_update_layer_parent(const Depsgraph *depsgraph, Object *ob) } } } + +/** + * Find material by name prefix. + * \param ob: Object pointer + * \param name_prefix: Prefix name of the material + * \return Index + */ +int BKE_gpencil_material_find_index_by_name_prefix(Object *ob, const char *name_prefix) +{ + const int name_prefix_len = strlen(name_prefix); + for (int i = 0; i < ob->totcol; i++) { + Material *ma = BKE_object_material_get(ob, i + 1); + if ((ma != NULL) && (ma->gp_style != NULL) && + (STREQLEN(ma->id.name + 2, name_prefix, name_prefix_len))) { + return i; + } + } + + return -1; +} + /** \} */ diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 20408327105..7bf8a93a97c 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -29,6 +29,7 @@ set(INC ../../windowmanager ../../../../intern/glew-mx ../../../../intern/guardedalloc + ../../../../extern/potrace/src ) set(SRC @@ -60,6 +61,7 @@ set(SRC gpencil_weight_paint.c gpencil_intern.h + gpencil_trace.h ) set(LIB @@ -67,6 +69,20 @@ set(LIB bf_blenlib ) +if(WITH_POTRACE) + list(APPEND SRC + gpencil_trace_ops.c + gpencil_trace_utils.c + ) + list(APPEND INC + ${POTRACE_INCLUDE_DIRS} + ) + list(APPEND LIB + ${POTRACE_LIBRARIES} + ) + add_definitions(-DWITH_POTRACE) +endif() + if(WITH_INTERNATIONAL) add_definitions(-DWITH_INTERNATIONAL) endif() diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index f45321b6b20..e3e2199f8a3 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -492,6 +492,7 @@ void GPENCIL_OT_convert(struct wmOperatorType *ot); void GPENCIL_OT_bake_mesh_animation(struct wmOperatorType *ot); void GPENCIL_OT_image_to_grease_pencil(struct wmOperatorType *ot); +void GPENCIL_OT_trace_image(struct wmOperatorType *ot); enum { GP_STROKE_JOIN = -1, diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 211ff895e67..9f7725e01f5 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -606,7 +606,9 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_bake_mesh_animation); WM_operatortype_append(GPENCIL_OT_image_to_grease_pencil); - +#ifdef WITH_POTRACE + WM_operatortype_append(GPENCIL_OT_trace_image); +#endif WM_operatortype_append(GPENCIL_OT_stroke_arrange); WM_operatortype_append(GPENCIL_OT_stroke_change_color); WM_operatortype_append(GPENCIL_OT_material_lock_unused); diff --git a/source/blender/editors/gpencil/gpencil_trace.h b/source/blender/editors/gpencil/gpencil_trace.h new file mode 100644 index 00000000000..7ac35ee5e04 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_trace.h @@ -0,0 +1,79 @@ +/* + * 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 edgpencil + */ + +#ifndef __GPENCIL_TRACE_H__ +#define __GPENCIL_TRACE_H__ + +/* internal exports only */ +struct bGPDframe; +struct FILE; +struct ImBuf; +struct Main; +struct Object; + +#include "potracelib.h" + +/* Potrace macros for writing individual bitmap pixels. */ +#define BM_WORDSIZE ((int)sizeof(potrace_word)) +#define BM_WORDBITS (8 * BM_WORDSIZE) +#define BM_HIBIT (((potrace_word)1) << (BM_WORDBITS - 1)) +#define BM_ALLBITS (~(potrace_word)0) + +#define bm_scanline(bm, y) ((bm)->map + (y) * (bm)->dy) +#define bm_index(bm, x, y) (&bm_scanline(bm, y)[(x) / BM_WORDBITS]) +#define bm_mask(x) (BM_HIBIT >> ((x) & (BM_WORDBITS - 1))) +#define bm_range(x, a) ((int)(x) >= 0 && (int)(x) < (a)) +#define bm_safe(bm, x, y) (bm_range(x, (bm)->w) && bm_range(y, (bm)->h)) + +#define BM_UGET(bm, x, y) ((*bm_index(bm, x, y) & bm_mask(x)) != 0) +#define BM_USET(bm, x, y) (*bm_index(bm, x, y) |= bm_mask(x)) +#define BM_UCLR(bm, x, y) (*bm_index(bm, x, y) &= ~bm_mask(x)) +#define BM_UINV(bm, x, y) (*bm_index(bm, x, y) ^= bm_mask(x)) +#define BM_UPUT(bm, x, y, b) ((b) ? BM_USET(bm, x, y) : BM_UCLR(bm, x, y)) +#define BM_GET(bm, x, y) (bm_safe(bm, x, y) ? BM_UGET(bm, x, y) : 0) +#define BM_SET(bm, x, y) (bm_safe(bm, x, y) ? BM_USET(bm, x, y) : 0) +#define BM_CLR(bm, x, y) (bm_safe(bm, x, y) ? BM_UCLR(bm, x, y) : 0) +#define BM_INV(bm, x, y) (bm_safe(bm, x, y) ? BM_UINV(bm, x, y) : 0) +#define BM_PUT(bm, x, y, b) (bm_safe(bm, x, y) ? BM_UPUT(bm, x, y, b) : 0) + +void ED_gpencil_trace_bitmap_print(FILE *f, const potrace_bitmap_t *bm); + +potrace_bitmap_t *ED_gpencil_trace_bitmap_new(int32_t w, int32_t h); +void ED_gpencil_trace_bitmap_free(const potrace_bitmap_t *bm); +void ED_gpencil_trace_bitmap_invert(const potrace_bitmap_t *bm); + +void ED_gpencil_trace_image_to_bitmap(struct ImBuf *ibuf, + const potrace_bitmap_t *bm, + const float threshold); + +void ED_gpencil_trace_data_to_strokes(struct Main *bmain, + potrace_state_t *st, + struct Object *ob, + struct bGPDframe *gpf, + int32_t offset[2], + const float scale, + const float sample, + const int32_t resolution, + const int32_t thickness); + +#endif /* __GPENCIL_TRACE_H__ */ diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c new file mode 100644 index 00000000000..4391abee5a1 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_trace_ops.c @@ -0,0 +1,313 @@ +/* + * 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 edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "BLT_translation.h" + +#include "DNA_gpencil_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" +#include "BKE_duplilist.h" +#include "BKE_gpencil.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_report.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "ED_gpencil.h" +#include "ED_object.h" + +#include "gpencil_intern.h" +#include "gpencil_trace.h" +#include "potracelib.h" + +/** + * Trace a image. + * \param C: Context + * \param op: Operator + * \param ob: Grease pencil object, can be NULL + * \param ima: Image + * \param gpf: Destination frame + */ +static bool gpencil_trace_image( + bContext *C, wmOperator *op, Object *ob, Image *ima, bGPDframe *gpf) +{ + Main *bmain = CTX_data_main(C); + + potrace_bitmap_t *bm = NULL; + potrace_param_t *param = NULL; + potrace_state_t *st = NULL; + + const float threshold = RNA_float_get(op->ptr, "threshold"); + const float scale = RNA_float_get(op->ptr, "scale"); + const float sample = RNA_float_get(op->ptr, "sample"); + const int32_t resolution = RNA_int_get(op->ptr, "resolution"); + const int32_t thickness = RNA_int_get(op->ptr, "thickness"); + const int32_t turnpolicy = RNA_enum_get(op->ptr, "turnpolicy"); + + ImBuf *ibuf; + void *lock; + ibuf = BKE_image_acquire_ibuf(ima, NULL, &lock); + + /* Create an empty BW bitmap. */ + bm = ED_gpencil_trace_bitmap_new(ibuf->x, ibuf->y); + if (!bm) { + return false; + } + + /* Set tracing parameters, starting from defaults */ + param = potrace_param_default(); + if (!param) { + return false; + } + param->turdsize = 0; + param->turnpolicy = turnpolicy; + + /* Load BW bitmap with image. */ + ED_gpencil_trace_image_to_bitmap(ibuf, bm, threshold); + + /* Trace the bitmap. */ + st = potrace_trace(param, bm); + if (!st || st->status != POTRACE_STATUS_OK) { + ED_gpencil_trace_bitmap_free(bm); + if (st) { + potrace_state_free(st); + } + potrace_param_free(param); + return false; + } + /* Free BW bitmap. */ + ED_gpencil_trace_bitmap_free(bm); + + /* Convert the trace to strokes. */ + int32_t offset[2]; + offset[0] = ibuf->x / 2; + offset[1] = ibuf->y / 2; + + /* Scale correction for Potrace. + * Really, there isn't documented in Potrace about how the scale is calculated, + * but after doing a lot of tests, it looks is using a VGA resolution (640) as a base. + * Maybe there are others ways to get the right scale conversion, but this solution works. */ + float scale_potrace = scale * (640.0f / (float)ibuf->x) * ((float)ibuf->x / (float)ibuf->y); + if (ibuf->x > ibuf->y) { + scale_potrace *= (float)ibuf->y / (float)ibuf->x; + } + + ED_gpencil_trace_data_to_strokes( + bmain, st, ob, gpf, offset, scale_potrace, sample, resolution, thickness); + + /* Free memory. */ + potrace_state_free(st); + potrace_param_free(param); + + /* Release ibuf. */ + if (ibuf) { + BKE_image_release_ibuf(ima, ibuf, lock); + } + + return true; +} + +/* Trace Image to Grease Pencil. */ +static bool gpencil_trace_image_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_EMPTY) || (ob->data == NULL)) { + CTX_wm_operator_poll_msg_set(C, "No image empty selected"); + return false; + } + + return true; +} + +static int gpencil_trace_image_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + Base *base_active = CTX_data_active_base(C); + Object *ob_active = base_active->object; + Image *image = (Image *)ob_active->data; + bool ob_created = false; + + const int32_t frame_target = CFRA; + Object *ob_gpencil = (Object *)RNA_pointer_get(op->ptr, "target").data; + + /* Create a new grease pencil object. */ + if (ob_gpencil == NULL) { + ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; + ob_gpencil = ED_gpencil_add_object(C, ob_active->loc, local_view_bits); + /* Apply image rotation. */ + copy_v3_v3(ob_gpencil->rot, ob_active->rot); + /* Grease pencil is rotated 90 degrees in X axis by default. */ + ob_gpencil->rot[0] -= DEG2RADF(90.0f); + ob_created = true; + /* Apply image Scale. */ + copy_v3_v3(ob_gpencil->scale, ob_active->scale); + } + + if ((ob_gpencil == NULL) || (ob_gpencil->type != OB_GPENCIL)) { + BKE_report(op->reports, RPT_ERROR, "Target grease pencil object not valid"); + return OPERATOR_CANCELLED; + } + + /* Create Layer. */ + bGPdata *gpd = (bGPdata *)ob_gpencil->data; + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + if (gpl == NULL) { + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("Trace"), true); + } + + /* Create frame. */ + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, frame_target, GP_GETFRAME_ADD_NEW); + gpencil_trace_image(C, op, ob_gpencil, image, gpf); + + /* Back to active base. */ + ED_object_base_activate(C, base_active); + + /* notifiers */ + if (ob_created) { + DEG_relations_tag_update(bmain); + } + + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); + + return OPERATOR_FINISHED; +} + +static bool rna_GPencil_object_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + return ((Object *)value.owner_id)->type == OB_GPENCIL; +} + +void GPENCIL_OT_trace_image(wmOperatorType *ot) +{ + PropertyRNA *prop; + + static const EnumPropertyItem turnpolicy_type[] = { + {POTRACE_TURNPOLICY_BLACK, + "BLACK", + 0, + "Black", + "Prefers to connect black (foreground) components"}, + {POTRACE_TURNPOLICY_WHITE, + "WHITE", + 0, + "White", + "Prefers to connect white (background) components"}, + {POTRACE_TURNPOLICY_LEFT, "LEFT", 0, "Left", "Always take a left turn"}, + {POTRACE_TURNPOLICY_RIGHT, "RIGHT", 0, "Right", "Always take a right turn"}, + {POTRACE_TURNPOLICY_MINORITY, + "MINORITY", + 0, + "Minority", + "Prefers to connect the color (black or white) that occurs least frequently in the local " + "neighborhood of the current position"}, + {POTRACE_TURNPOLICY_MAJORITY, + "MAJORITY", + 0, + "Majority", + "Prefers to connect the color (black or white) that occurs most frequently in the local " + "neighborhood of the current position"}, + {POTRACE_TURNPOLICY_RANDOM, "RANDOM", 0, "Random", "Choose pseudo-randomly"}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Trace Image to Grease Pencil"; + ot->idname = "GPENCIL_OT_trace_image"; + ot->description = "Extract Grease Pencil strokes from image"; + + /* callbacks */ + ot->exec = gpencil_trace_image_exec; + ot->poll = gpencil_trace_image_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_pointer_runtime(ot->srna, "target", &RNA_Object, "Target", ""); + RNA_def_property_poll_runtime(prop, rna_GPencil_object_poll); + + RNA_def_int(ot->srna, "thickness", 10, 1, 1000, "Thickness", "", 1, 1000); + RNA_def_int( + ot->srna, "resolution", 5, 1, 20, "Resolution", "Resolution of the generated curves", 1, 20); + + RNA_def_float(ot->srna, + "scale", + 1.0f, + 0.001f, + 100.0f, + "Scale", + "Scale of the final stroke", + 0.001f, + 100.0f); + RNA_def_float(ot->srna, + "sample", + 0.0f, + 0.0f, + 100.0f, + "Sample", + "Distance to sample points, zero to disable", + 0.0f, + 100.0f); + RNA_def_float_factor(ot->srna, + "threshold", + 0.5f, + 0.0f, + 1.0f, + "Color Threshold", + "Determine what is considered white and what black", + 0.0f, + 1.0f); + RNA_def_enum(ot->srna, + "turnpolicy", + turnpolicy_type, + POTRACE_TURNPOLICY_MINORITY, + "Turn Policy", + "Determines how to resolve ambiguities during decomposition of bitmaps into paths"); +} diff --git a/source/blender/editors/gpencil/gpencil_trace_utils.c b/source/blender/editors/gpencil/gpencil_trace_utils.c new file mode 100644 index 00000000000..f7776da03b7 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_trace_utils.c @@ -0,0 +1,373 @@ +/* + * 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 edgpencil + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" + +#include "DNA_gpencil_types.h" +#include "DNA_image_types.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "gpencil_trace.h" + +/** + * Print trace bitmap for debuging + * \param f: Output handle. Use stderr for printing + * \param bm: Trace bitmap + */ +void ED_gpencil_trace_bitmap_print(FILE *f, const potrace_bitmap_t *bm) +{ + int32_t x, y; + int32_t xx, yy; + int32_t d; + int32_t sw, sh; + + sw = bm->w < 79 ? bm->w : 79; + sh = bm->w < 79 ? bm->h : bm->h * sw * 44 / (79 * bm->w); + + for (yy = sh - 1; yy >= 0; yy--) { + for (xx = 0; xx < sw; xx++) { + d = 0; + for (x = xx * bm->w / sw; x < (xx + 1) * bm->w / sw; x++) { + for (y = yy * bm->h / sh; y < (yy + 1) * bm->h / sh; y++) { + if (BM_GET(bm, x, y)) { + d++; + } + } + } + fputc(d ? '*' : ' ', f); + } + fputc('\n', f); + } +} + +/** + * Return new un-initialized trace bitmap + * \param w: Width in pixels + * \param h: Height in pixels + * \return: Trace bitmap + */ +potrace_bitmap_t *ED_gpencil_trace_bitmap_new(int32_t w, int32_t h) +{ + potrace_bitmap_t *bm; + int32_t dy = (w + BM_WORDBITS - 1) / BM_WORDBITS; + + bm = (potrace_bitmap_t *)MEM_mallocN(sizeof(potrace_bitmap_t), __func__); + if (!bm) { + return NULL; + } + bm->w = w; + bm->h = h; + bm->dy = dy; + bm->map = (potrace_word *)calloc(h, dy * BM_WORDSIZE); + if (!bm->map) { + free(bm); + return NULL; + } + + return bm; +} + +/** + * Free a trace bitmap + * \param bm: Trace bitmap + */ +void ED_gpencil_trace_bitmap_free(const potrace_bitmap_t *bm) +{ + if (bm != NULL) { + free(bm->map); + } + MEM_SAFE_FREE(bm); +} + +/** + * Invert the given bitmap (Black to White) + * \param bm: Trace bitmap + */ +void ED_gpencil_trace_bitmap_invert(const potrace_bitmap_t *bm) +{ + int32_t dy = bm->dy; + int32_t y; + int32_t i; + potrace_word *p; + + if (dy < 0) { + dy = -dy; + } + + for (y = 0; y < bm->h; y++) { + p = bm_scanline(bm, y); + for (i = 0; i < dy; i++) { + p[i] ^= BM_ALLBITS; + } + } +} + +/** + * Return pixel data (rgba) at index + * \param ibuf: ImBuf of the image + * \param idx: Index of the pixel + * \return: RGBA value + */ +static void pixel_at_index(const ImBuf *ibuf, const int32_t idx, float r_col[4]) +{ + BLI_assert(idx < (ibuf->x * ibuf->y)); + + if (ibuf->rect_float) { + const float *frgba = &ibuf->rect_float[idx * 4]; + copy_v4_v4(r_col, frgba); + } + else { + unsigned char *cp = (unsigned char *)(ibuf->rect + idx); + r_col[0] = (float)cp[0] / 255.0f; + r_col[1] = (float)cp[1] / 255.0f; + r_col[2] = (float)cp[2] / 255.0f; + r_col[3] = (float)cp[3] / 255.0f; + } +} + +/** + * Convert image to BW bitmap for tracing + * \param ibuf: ImBuf of the image + * \param bm: Trace bitmap + */ +void ED_gpencil_trace_image_to_bitmap(ImBuf *ibuf, + const potrace_bitmap_t *bm, + const float threshold) +{ + float rgba[4]; + int32_t pixel = 0; + + for (uint32_t y = 0; y < ibuf->y; y++) { + for (uint32_t x = 0; x < ibuf->x; x++) { + pixel = (ibuf->x * y) + x; + pixel_at_index(ibuf, pixel, rgba); + /* Get a BW color. */ + mul_v3_fl(rgba, rgba[3]); + float color = (rgba[0] + rgba[1] + rgba[2]) / 3.0f; + int32_t bw = (color > threshold) ? 0 : 1; + BM_PUT(bm, x, y, bw); + } + } +} + +/* Helper to add point to stroke. */ +static void add_point(bGPDstroke *gps, float scale, const int32_t offset[2], float x, float y) +{ + int32_t idx = gps->totpoints; + if (gps->totpoints == 0) { + gps->points = MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points"); + } + else { + gps->points = MEM_recallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); + } + bGPDspoint *pt = &gps->points[idx]; + pt->x = (x - offset[0]) * scale; + pt->y = 0; + pt->z = (y - offset[1]) * scale; + pt->pressure = 1.0f; + pt->strength = 1.0f; + + gps->totpoints++; +} + +/* helper to generate all points of curve. */ +static void add_bezier(bGPDstroke *gps, + float scale, + int32_t offset[2], + int32_t resolution, + float bcp1[2], + float bcp2[2], + float bcp3[2], + float bcp4[2], + const bool skip) +{ + const float step = 1.0f / (float)(resolution - 1); + float a = 0.0f; + + for (int32_t i = 0; i < resolution; i++) { + if ((!skip) || (i > 0)) { + float fpt[3]; + interp_v2_v2v2v2v2_cubic(fpt, bcp1, bcp2, bcp3, bcp4, a); + add_point(gps, scale, offset, fpt[0], fpt[1]); + } + a += step; + } +} + +/** + * Convert Potrace Bitmap to Grease Pencil strokes + * \param st: Data with traced data + * \param ob: Target grease pencil object + * \param offset: Offset to center + * \param scale: Scale of the output + * \param sample: Sample distance to distribute points + */ +void ED_gpencil_trace_data_to_strokes(Main *bmain, + potrace_state_t *st, + Object *ob, + bGPDframe *gpf, + int32_t offset[2], + const float scale, + const float sample, + const int32_t resolution, + const int32_t thickness) +{ +#define MAX_LENGTH 100.0f + /* Find materials and create them if not found. */ + int32_t mat_fill_idx = BKE_gpencil_material_find_index_by_name_prefix(ob, "Stroke"); + int32_t mat_mask_idx = BKE_gpencil_material_find_index_by_name_prefix(ob, "Holdout"); + + const float default_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + /* Stroke and Fill material. */ + if (mat_fill_idx == -1) { + int32_t new_idx; + Material *mat_gp = BKE_gpencil_object_material_new(bmain, ob, "Stroke", &new_idx); + MaterialGPencilStyle *gp_style = mat_gp->gp_style; + + copy_v4_v4(gp_style->stroke_rgba, default_color); + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + mat_fill_idx = ob->totcol - 1; + } + /* Holdout material. */ + if (mat_mask_idx == -1) { + int32_t new_idx; + Material *mat_gp = BKE_gpencil_object_material_new(bmain, ob, "Holdout", &new_idx); + MaterialGPencilStyle *gp_style = mat_gp->gp_style; + + copy_v4_v4(gp_style->stroke_rgba, default_color); + copy_v4_v4(gp_style->fill_rgba, default_color); + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + gp_style->flag |= GP_MATERIAL_IS_STROKE_HOLDOUT; + gp_style->flag |= GP_MATERIAL_IS_FILL_HOLDOUT; + mat_mask_idx = ob->totcol - 1; + } + + potrace_path_t *path = st->plist; + int n, *tag; + potrace_dpoint_t(*c)[3]; + + /* There isn't any rule here, only the result of lots of testing to get a value that gets + * good results using the Potrace data. */ + const float scalef = 0.008f * scale; + /* Draw each curve. */ + path = st->plist; + while (path != NULL) { + n = path->curve.n; + tag = path->curve.tag; + c = path->curve.c; + int mat_idx = path->sign == '+' ? 0 : 1; + /* Create a new stroke. */ + bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, mat_idx, 0, thickness, false); + /* Last point that is equals to start point. */ + float start_point[2], last[2]; + start_point[0] = c[n - 1][2].x; + start_point[1] = c[n - 1][2].y; + + for (int32_t i = 0; i < n; i++) { + switch (tag[i]) { + case POTRACE_CORNER: { + if (gps->totpoints == 0) { + add_point(gps, scalef, offset, c[n - 1][2].x, c[n - 1][2].y); + } + add_point(gps, scalef, offset, c[i][1].x, c[i][1].y); + + add_point(gps, scalef, offset, c[i][2].x, c[i][2].y); + break; + } + case POTRACE_CURVETO: { + float cp1[2], cp2[2], cp3[2], cp4[2]; + if (gps->totpoints == 0) { + cp1[0] = start_point[0]; + cp1[1] = start_point[1]; + } + else { + copy_v2_v2(cp1, last); + } + + cp2[0] = c[i][0].x; + cp2[1] = c[i][0].y; + + cp3[0] = c[i][1].x; + cp3[1] = c[i][1].y; + + cp4[0] = c[i][2].x; + cp4[1] = c[i][2].y; + + add_bezier(gps, + scalef, + offset, + resolution, + cp1, + cp2, + cp3, + cp4, + (gps->totpoints == 0) ? false : true); + copy_v2_v2(last, cp4); + break; + } + default: + break; + } + } + /* In some situations, Potrace can produce a wrong data and generate a very + * long stroke. Here the length is checked and removed if the length is too big. */ + float length = BKE_gpencil_stroke_length(gps, true); + if (length <= MAX_LENGTH) { + if (sample > 0.0f) { + /* Resample stroke. Don't need to call to BKE_gpencil_stroke_geometry_update() because + * the sample function already call that. */ + BKE_gpencil_stroke_sample(gps, sample, false); + } + else { + BKE_gpencil_stroke_geometry_update(gps); + } + } + else { + /* Remove too long strokes. */ + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + + path = path->next; + } +#undef MAX_LENGTH +} diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index febb0d1cad5..772b31fd9d8 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -341,5 +341,8 @@ if(WITH_XR_OPENXR) add_definitions(-DWITH_XR_OPENXR) endif() +if(WITH_POTRACE) + add_definitions(-DWITH_POTRACE) +endif() blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/python/intern/bpy_app_build_options.c b/source/blender/python/intern/bpy_app_build_options.c index cc97f6dec94..e3bd7d25806 100644 --- a/source/blender/python/intern/bpy_app_build_options.c +++ b/source/blender/python/intern/bpy_app_build_options.c @@ -61,6 +61,7 @@ static PyStructSequence_Field app_builtopts_info_fields[] = { {"usd", NULL}, {"fluid", NULL}, {"xr_openxr", NULL}, + {"potrace", NULL}, {NULL}, }; @@ -282,6 +283,12 @@ static PyObject *make_builtopts_info(void) SetObjIncref(Py_False); #endif +#ifdef WITH_POTRACE + SetObjIncref(Py_True); +#else + SetObjIncref(Py_False); +#endif + #undef SetObjIncref return builtopts_info; |