align boundary strokes direction option

Signed-off-by: Jeremy Bullock <jcbullock@gmail.com>
This commit is contained in:
shun-iwasawa 2020-11-20 15:17:37 +09:00 committed by Jeremy Bullock
parent 5563373272
commit 779ffe1342
7 changed files with 226 additions and 53 deletions

View file

@ -95,8 +95,8 @@ Returns image converted.*/
const VectorizerConfiguration &c);
void applyFillColors(TRegion *r, const TRasterP &ras, TPalette *palette,
const CenterlineConfiguration &c, int regionCount);
void applyFillColors(TRegion *r, const TRasterP &ras, TPalette *palette,
const OutlineConfiguration &c, int regionCount);
// void applyFillColors(TRegion *r, const TRasterP &ras, TPalette *palette,
// const OutlineConfiguration &c, int regionCount);
//! Traduces the input VectorizerConfiguration into an edible form.
VectorizerConfiguration traduceConfiguration(

View file

@ -61,13 +61,18 @@ public:
TAffine m_affine; //!< Affine transform applied to the vectorization results
double m_thickScale; //!< Impose a thickness reduction by this ratio
// Align stroke directions to be the same (clockwise, i.e. left to right as
// viewed from inside of the shape).
bool m_alignBoundaryStrokesDirection;
public:
VectorizerConfiguration(bool outline)
: m_outline(outline)
, m_threshold(200)
, m_leaveUnpainted(true)
, m_affine()
, m_thickScale(1.0) {}
, m_thickScale(1.0)
, m_alignBoundaryStrokesDirection(false) {}
};
//*****************************************************************************
@ -231,6 +236,7 @@ public:
bool m_cMakeFrame;
bool m_cPaintFill;
bool m_cAlignBoundaryStrokesDirection;
bool m_cNaaSource;
// Outline options
@ -246,6 +252,7 @@ public:
TPixel32 m_oTransparentColor;
bool m_oPaintFill;
bool m_oAlignBoundaryStrokesDirection;
// Generic data

View file

@ -618,6 +618,18 @@ paramsLayout->addWidget(m_cThicknessRatio, row++, 1);*/
m_paramsLayout->addWidget(m_cPaintFill, row++, 1);
}
{
static const QString name = tr("Align Boundary Strokes Direction");
locals.addParameter(l_centerlineParamGroups, name);
m_cAlignBoundaryStrokesDirection = new CheckBox(name, this);
m_cAlignBoundaryStrokesDirection->setFixedHeight(WidgetHeight);
m_cAlignBoundaryStrokesDirection->setToolTip(
tr("Align boundary strokes direction to be the same.\n(clockwise, i.e. "
"left to right as viewed from inside of the shape)"));
m_paramsLayout->addWidget(m_cAlignBoundaryStrokesDirection, row++, 1);
}
{
static const QString name = tr("Add Border");
locals.addParameter(l_centerlineParamGroups, name);
@ -674,6 +686,17 @@ paramsLayout->addWidget(m_cThicknessRatio, row++, 1);*/
m_paramsLayout->addWidget(m_oPaintFill, row++, 1);
}
{
static const QString name = tr("Align Boundary Strokes Direction");
locals.addParameter(l_outlineParamGroups, name);
m_oAlignBoundaryStrokesDirection = new CheckBox(name, this);
m_oAlignBoundaryStrokesDirection->setFixedHeight(WidgetHeight);
m_oAlignBoundaryStrokesDirection->setToolTip(
tr("Align boundary strokes direction to be the same.\n(clockwise, i.e. "
"left to right as viewed from inside of the shape)"));
m_paramsLayout->addWidget(m_oAlignBoundaryStrokesDirection, row++, 1);
}
locals.addParameterGroup(l_outlineParamGroups, group++, row + 1, row);
m_oCornersSeparator = new Separator(tr("Corners"));
@ -830,6 +853,8 @@ paramsLayout->addWidget(m_cThicknessRatio, row++, 1);*/
SLOT(onValueEdited()));
connect(m_cMakeFrame, SIGNAL(stateChanged(int)), this, SLOT(onValueEdited()));
connect(m_cPaintFill, SIGNAL(stateChanged(int)), this, SLOT(onValueEdited()));
connect(m_cAlignBoundaryStrokesDirection, SIGNAL(stateChanged(int)), this,
SLOT(onValueEdited()));
connect(m_cNaaSource, SIGNAL(stateChanged(int)), this, SLOT(onValueEdited()));
connect(m_oAccuracy, SIGNAL(valueChanged(bool)), this,
@ -837,6 +862,8 @@ paramsLayout->addWidget(m_cThicknessRatio, row++, 1);*/
connect(m_oDespeckling, SIGNAL(valueChanged(bool)), this,
SLOT(onValueEdited(bool)));
connect(m_oPaintFill, SIGNAL(stateChanged(int)), this, SLOT(onValueEdited()));
connect(m_oAlignBoundaryStrokesDirection, SIGNAL(stateChanged(int)), this,
SLOT(onValueEdited()));
connect(m_oAdherence, SIGNAL(valueChanged(bool)), this,
SLOT(onValueEdited(bool)));
connect(m_oAngle, SIGNAL(valueChanged(bool)), this,
@ -1117,6 +1144,8 @@ void VectorizerPopup::updateSceneSettings() {
vParams->m_oToneThreshold = m_oToneThreshold->getValue();
vParams->m_oTransparentColor = m_oTransparentColor->getColor();
vParams->m_oPaintFill = m_oPaintFill->isChecked();
vParams->m_oAlignBoundaryStrokesDirection =
m_oAlignBoundaryStrokesDirection->isChecked();
} else {
vParams->m_cThreshold = m_cThreshold->getValue();
vParams->m_cAccuracy = m_cAccuracy->getValue();
@ -1127,6 +1156,8 @@ void VectorizerPopup::updateSceneSettings() {
vParams->m_cThicknessRatioLast = m_cThicknessRatioLast->getValue() * 100.0;
vParams->m_cMakeFrame = m_cMakeFrame->isChecked();
vParams->m_cPaintFill = m_cPaintFill->isChecked();
vParams->m_cAlignBoundaryStrokesDirection =
m_cAlignBoundaryStrokesDirection->isChecked();
vParams->m_cNaaSource = m_cNaaSource->isChecked();
}
}
@ -1239,6 +1270,7 @@ void VectorizerPopup::setType(bool outline) {
m_cThicknessRatioLast->setVisible(centerline);
m_cPaintFill->setVisible(centerline);
m_cAlignBoundaryStrokesDirection->setVisible(centerline);
m_cMakeFrame->setVisible(centerline);
m_cNaaSourceSeparator->setVisible(centerline);
m_cNaaSource->setVisible(centerline);
@ -1248,6 +1280,7 @@ void VectorizerPopup::setType(bool outline) {
m_oDespecklingLabel->setVisible(outline);
m_oDespeckling->setVisible(outline);
m_oPaintFill->setVisible(outline);
m_oAlignBoundaryStrokesDirection->setVisible(outline);
m_oCornersSeparator->setVisible(outline);
m_oAngleLabel->setVisible(outline);
m_oAngle->setVisible(outline);
@ -1298,6 +1331,8 @@ void VectorizerPopup::loadConfiguration(bool isOutline) {
m_oRelative->setValue(vParams->m_oRelative);
m_oAccuracy->setValue(vParams->m_oAccuracy);
m_oPaintFill->setChecked(vParams->m_oPaintFill);
m_oAlignBoundaryStrokesDirection->setChecked(
vParams->m_oAlignBoundaryStrokesDirection);
m_oMaxColors->setValue(vParams->m_oMaxColors);
m_oTransparentColor->setColor(vParams->m_oTransparentColor);
m_oToneThreshold->setValue(vParams->m_oToneThreshold);
@ -1305,6 +1340,8 @@ void VectorizerPopup::loadConfiguration(bool isOutline) {
m_cThreshold->setValue(vParams->m_cThreshold);
m_cDespeckling->setValue(vParams->m_cDespeckling);
m_cPaintFill->setChecked(vParams->m_cPaintFill);
m_cAlignBoundaryStrokesDirection->setChecked(
vParams->m_cAlignBoundaryStrokesDirection);
m_cMakeFrame->setChecked(vParams->m_cMakeFrame);
m_cNaaSource->setChecked(vParams->m_cNaaSource);
m_cMaxThickness->setValue(vParams->m_cMaxThickness);
@ -1393,8 +1430,9 @@ void VectorizerPopup::populateVisibilityMenu() {
menu->clear();
VectorizerParameters *vParams = getParameters();
locals.addActions(menu, vParams->m_isOutline ? l_outlineParamGroups
: l_centerlineParamGroups,
locals.addActions(
menu,
vParams->m_isOutline ? l_outlineParamGroups : l_centerlineParamGroups,
vParams->m_visibilityBits);
}

View file

@ -41,7 +41,7 @@ class IntField;
class MeasuredDoubleLineEdit;
class ColorField;
class CheckBox;
}
} // namespace DVGui
//====================================================
@ -141,6 +141,8 @@ private:
DVGui::CheckBox *m_cPaintFill;
DVGui::CheckBox *m_cAlignBoundaryStrokesDirection;
DVGui::Separator *m_cNaaSourceSeparator;
DVGui::CheckBox *m_cNaaSource;
@ -156,6 +158,8 @@ private:
QLabel *m_oPaintFillLabel;
DVGui::CheckBox *m_oPaintFill;
DVGui::CheckBox *m_oAlignBoundaryStrokesDirection;
DVGui::Separator *m_oCornersSeparator;
QLabel *m_oAdherenceLabel;

View file

@ -772,7 +772,7 @@ void outlineVectorize(TVectorImageP &vi, const TRasterImageP &ri,
vi->transform(conf.m_affine);
vi->findRegions();
if (!conf.m_leaveUnpainted)
if (!conf.m_leaveUnpainted || conf.m_alignBoundaryStrokesDirection)
// Finally, build region colors.
buildColorsRGBM(vi, reader.scHash());
}
@ -798,7 +798,8 @@ void outlineVectorize(TVectorImageP &vi, const TToonzImageP &ti,
vi->transform(conf.m_affine);
vi->findRegions();
if (!conf.m_leaveUnpainted) buildColorsCM(vi, reader.scHash());
if (!conf.m_leaveUnpainted || conf.m_alignBoundaryStrokesDirection)
buildColorsCM(vi, reader.scHash());
}
} // namespace

View file

@ -595,8 +595,7 @@ void OutlineVectorizer::makeDataRaster(const TRasterP &src) {
if (srcRGBM) {
assert(m_palette);
int inkId = m_palette->getClosestStyle(m_configuration.m_inkColor);
if (!inkId ||
m_configuration.m_inkColor !=
if (!inkId || m_configuration.m_inkColor !=
m_palette->getStyle(inkId)->getMainColor()) {
inkId = m_palette->getStyleCount();
m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
@ -635,8 +634,7 @@ void OutlineVectorizer::makeDataRaster(const TRasterP &src) {
} else if (srcGR) {
assert(m_palette);
int inkId = m_palette->getClosestStyle(m_configuration.m_inkColor);
if (!inkId ||
m_configuration.m_inkColor !=
if (!inkId || m_configuration.m_inkColor !=
m_palette->getStyle(inkId)->getMainColor()) {
inkId = m_palette->getStyleCount();
m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
@ -1387,7 +1385,7 @@ void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
}
//-----------------------------------------------------------------
/*
void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
TPalette *palette,
const OutlineConfiguration &c,
@ -1473,7 +1471,7 @@ void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
for (int i = 0; i < (int)r->getSubregionCount(); ++i)
applyFillColors(r->getSubregion(i), ras, palette, c, regionCount);
}
*/
//-----------------------------------------------------------------
void VectorizerCore::applyFillColors(TVectorImageP vi, const TImageP &img,
@ -1481,11 +1479,14 @@ void VectorizerCore::applyFillColors(TVectorImageP vi, const TImageP &img,
const VectorizerConfiguration &c) {
const CenterlineConfiguration &centConf =
static_cast<const CenterlineConfiguration &>(c);
const OutlineConfiguration &outConf =
static_cast<const OutlineConfiguration &>(c);
// const OutlineConfiguration &outConf =
// static_cast<const OutlineConfiguration &>(c);
// If configuration is not set for color fill at all, quit.
if (c.m_leaveUnpainted && (!c.m_outline || outConf.m_ignoreInkColors)) return;
if (c.m_leaveUnpainted && !c.m_outline && !c.m_alignBoundaryStrokesDirection)
return;
// if (c.m_leaveUnpainted && (!c.m_outline || outConf.m_ignoreInkColors))
// return;
TToonzImageP ti = img;
TRasterImageP ri = img;
@ -1496,18 +1497,116 @@ void VectorizerCore::applyFillColors(TVectorImageP vi, const TImageP &img,
vi->findRegions();
int r, regionsCount = vi->getRegionCount();
if (c.m_outline) {
for (r = 0; r < regionsCount; ++r)
applyFillColors(vi->getRegion(r), ras, palette, outConf, 1);
} else {
// filling colors in outline mode is done in tnewoutlinevectorize.cpp
// if (c.m_outline) {
// for (r = 0; r < regionsCount; ++r)
// applyFillColors(vi->getRegion(r), ras, palette, outConf, 1);
//} else {
for (r = 0; r < regionsCount; ++r)
applyFillColors(vi->getRegion(r), ras, palette, centConf,
1); // 1 - c.m_makeFrame;
clearInkRegionFlags(vi);
//}
}
namespace {
struct StrokeData {
UCHAR m_hasColor, m_hasRegion;
};
// found the shape boundaries and recognize their stroke direction.
// based on the function getBoundaries() in levelselection.cpp
void alignBoundariesDirection(TVectorImageP vi) {
enum { FORWARD = 0x1, BACKWARD = 0x2, INTERNAL = FORWARD | BACKWARD };
struct locals {
static void markEdges(const TRegion &region, std::vector<StrokeData> &sData,
bool parentRegionHasColor) {
bool regionHasColor = (region.getStyle() != 0);
// Traverse region edges, marking associated strokes accordingly
UINT e, eCount = region.getEdgeCount();
for (e = 0; e != eCount; ++e) {
const TEdge &ed = *region.getEdge(e);
assert(ed.m_s);
int strokeIdx = ed.m_index;
if (strokeIdx >= 0) // Could be <0 in case the corresponding
{ // stroke is a region 'closure' (autoclose)
assert(0 <= strokeIdx && strokeIdx < sData.size());
StrokeData &sd = sData[strokeIdx];
UCHAR side = (ed.m_w1 > ed.m_w0) ? FORWARD : BACKWARD;
sd.m_hasRegion |= side;
if (regionHasColor) sd.m_hasColor |= side;
}
}
if (parentRegionHasColor) {
// Mark non-region edge sides with color
for (e = 0; e != eCount; ++e) {
const TEdge &ed = *region.getEdge(e);
assert(ed.m_s);
int strokeIdx = ed.m_index;
if (strokeIdx >= 0) {
StrokeData &sd = sData[strokeIdx];
sd.m_hasColor |= (INTERNAL & ~sd.m_hasRegion);
}
}
}
// Mark recursively on sub-regions
UINT sr, srCount = region.getSubregionCount();
for (sr = 0; sr != srCount; ++sr)
markEdges(*region.getSubregion(sr), sData, regionHasColor);
}
}; // locals
std::vector<StrokeData> sData(vi->getStrokeCount());
// Traverse regions, mark each stroke edge with the side a COLORED region is
// on
UINT r, rCount = vi->getRegionCount();
for (r = 0; r != rCount; ++r)
locals::markEdges(*vi->getRegion(r), sData, false);
UINT s, sCount = vi->getStrokeCount();
for (s = 0; s != sCount; ++s) {
// Strokes not appearing as region edges must be checked for region
// inclusion separately
if (!sData[s].m_hasRegion) {
TRegion *parentRegion = vi->getRegion(vi->getStroke(s)->getPoint(0.5));
if (parentRegion && parentRegion->getStyle())
sData[s].m_hasColor = INTERNAL;
}
// flip the stroke here
if (sData[s].m_hasColor == FORWARD) vi->getStroke(s)->changeDirection();
}
}
void removeFillColors(TRegion *r) {
UINT i, edgeCount = r->getEdgeCount();
for (i = 0; i < edgeCount; ++i) {
r->getEdge(i)->setStyle(0);
}
// Build the color for its sub-regions
int j, rCount = r->getSubregionCount();
for (j = 0; j < rCount; ++j) removeFillColors(r->getSubregion(j));
}
void removeFillColors(TVectorImageP vi) {
int i, rCount = vi->getRegionCount();
for (i = 0; i < rCount; ++i) {
removeFillColors(vi->getRegion(i));
}
}
} // namespace
//=================================================================
TVectorImageP VectorizerCore::vectorize(const TImageP &img,
@ -1538,6 +1637,13 @@ TVectorImageP VectorizerCore::vectorize(const TImageP &img,
applyFillColors(vi, img2, plt, c);
}
}
// align boundary strokes direction
if (c.m_alignBoundaryStrokesDirection) {
alignBoundariesDirection(vi);
vi->validateRegions(false);
vi->findRegions();
if (c.m_leaveUnpainted) removeFillColors(vi);
}
return vi;
}

View file

@ -29,6 +29,7 @@ const char *s_version = "version",
*s_thicknessRatioFirst = "thicknessRatioFirst",
*s_thicknessRatioLast = "thicknessRatioLast",
*s_paintFill = "paintFill",
*s_alignBoundaryStrokesDirection = "alignBoundaryStrokesDirection",
*s_Outline = "Outline", *s_adherenceTol = "adherenceTol",
*s_angleTol = "angleTol", *s_relativeTol = "relativeTol",
@ -180,6 +181,8 @@ void convert(const CenterlineConfiguration &conf,
params.m_cThicknessRatioLast = conf.m_thicknessRatio;
params.m_cMakeFrame = conf.m_makeFrame;
params.m_cPaintFill = !conf.m_leaveUnpainted;
params.m_cAlignBoundaryStrokesDirection =
conf.m_alignBoundaryStrokesDirection;
params.m_cNaaSource = conf.m_naaSource;
}
@ -196,6 +199,8 @@ void convert(const NewOutlineConfiguration &conf,
params.m_oToneThreshold = conf.m_toneTol;
params.m_oTransparentColor = conf.m_transparentColor;
params.m_oPaintFill = !conf.m_leaveUnpainted;
params.m_oAlignBoundaryStrokesDirection =
conf.m_alignBoundaryStrokesDirection;
}
} // namespace
@ -233,6 +238,7 @@ CenterlineConfiguration VectorizerParameters::getCenterlineConfiguration(
conf.m_thicknessRatio =
(1 - frame) * m_cThicknessRatioFirst + frame * m_cThicknessRatioLast;
conf.m_leaveUnpainted = !m_cPaintFill;
conf.m_alignBoundaryStrokesDirection = m_cAlignBoundaryStrokesDirection;
conf.m_makeFrame = m_cMakeFrame;
conf.m_naaSource = m_cNaaSource;
@ -252,6 +258,7 @@ NewOutlineConfiguration VectorizerParameters::getOutlineConfiguration(
conf.m_relativeTol = m_oRelative * 0.01;
conf.m_mergeTol = 5.0 - m_oAccuracy * 0.5;
conf.m_leaveUnpainted = !m_oPaintFill;
conf.m_alignBoundaryStrokesDirection = m_oAlignBoundaryStrokesDirection;
conf.m_maxColors = m_oMaxColors;
conf.m_transparentColor = m_oTransparentColor;
conf.m_toneTol = m_oToneThreshold;
@ -276,6 +283,8 @@ void VectorizerParameters::saveData(TOStream &os) {
os.child(s_thicknessRatioLast) << m_cThicknessRatioLast;
os.child(s_makeFrame) << (m_cMakeFrame ? 1 : 0);
os.child(s_paintFill) << (m_cPaintFill ? 1 : 0);
os.child(s_alignBoundaryStrokesDirection)
<< (m_cAlignBoundaryStrokesDirection ? 1 : 0);
os.child(s_naaSource) << (m_cNaaSource ? 1 : 0);
}
os.closeChild();
@ -291,6 +300,8 @@ void VectorizerParameters::saveData(TOStream &os) {
os.child(s_toneThreshold) << m_oToneThreshold;
os.child(s_transparentColor) << m_oTransparentColor;
os.child(s_paintFill) << (m_oPaintFill ? 1 : 0);
os.child(s_alignBoundaryStrokesDirection)
<< (m_oAlignBoundaryStrokesDirection ? 1 : 0);
}
os.closeChild();
}
@ -333,6 +344,9 @@ void VectorizerParameters::loadData(TIStream &is) {
is >> val, m_cMakeFrame = (val != 0), is.matchEndTag();
else if (tagName == s_paintFill)
is >> val, m_cPaintFill = (val != 0), is.matchEndTag();
else if (tagName == s_alignBoundaryStrokesDirection)
is >> val, m_cAlignBoundaryStrokesDirection = (val != 0),
is.matchEndTag();
else if (tagName == s_naaSource)
is >> val, m_cNaaSource = (val != 0), is.matchEndTag();
else
@ -360,6 +374,9 @@ void VectorizerParameters::loadData(TIStream &is) {
is >> m_oTransparentColor, is.matchEndTag();
else if (tagName == s_paintFill)
is >> val, m_oPaintFill = (val != 0), is.matchEndTag();
else if (tagName == s_alignBoundaryStrokesDirection)
is >> val, m_oAlignBoundaryStrokesDirection = (val != 0),
is.matchEndTag();
else
is.skipCurrentTag();
}