GPencil: New Trace images using Potrace
This patch adds a new operator to convert a black and white image into grease pencil strokes. If the image is not B/W, an internal conversion is done. This is the first operator using Potrace, but we expect to add more features in next Blender versions. Reviewed By: HooglyBoogly Maniphest Tasks: T79877 Differential Revision: https://developer.blender.org/D8951
This commit is contained in:
parent
d93db4f30c
commit
4d62bb8fe5
Notes:
blender-bot
2023-02-14 06:00:47 +01:00
Referenced by issue #82597, Crash running with --debug-wm and pressing F3 Referenced by issue #79877, GPencil: Trace images using Potrace lib
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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__ */
|
|
@ -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");
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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}")
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue