tahoma2d/toonz/sources/tnztools/brushtool.cpp
Campbell Barton 8c6c57f1b4 Enable missing-declarations warning (#643)
Finish ensuring symbols use headers correctly
2016-07-22 13:38:33 +09:00

2167 lines
75 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "brushtool.h"
// TnzTools includes
#include "tools/toolhandle.h"
#include "tools/toolutils.h"
#include "tools/tooloptions.h"
#include "bluredbrush.h"
// TnzQt includes
#include "toonzqt/dvdialog.h"
#include "toonzqt/imageutils.h"
// TnzLib includes
#include "toonz/tobjecthandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txsheet.h"
#include "toonz/tstageobject.h"
#include "toonz/tstageobjectspline.h"
#include "toonz/rasterstrokegenerator.h"
#include "toonz/ttileset.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/toonzimageutils.h"
#include "toonz/palettecontroller.h"
#include "toonz/stage2.h"
// TnzCore includes
#include "tstream.h"
#include "tcolorstyles.h"
#include "tvectorimage.h"
#include "tenv.h"
#include "tregion.h"
#include "tstroke.h"
#include "tgl.h"
#include "trop.h"
// Qt includes
#include <QPainter>
using namespace ToolUtils;
TEnv::DoubleVar VectorBrushMinSize("InknpaintVectorBrushMinSize", 1);
TEnv::DoubleVar VectorBrushMaxSize("InknpaintVectorBrushMaxSize", 5);
TEnv::IntVar VectorCapStyle("InknpaintVectorCapStyle", 0);
TEnv::IntVar VectorJoinStyle("InknpaintVectorJoinStyle", 0);
TEnv::IntVar VectorMiterValue("InknpaintVectorMiterValue", 4);
TEnv::DoubleVar RasterBrushMinSize("InknpaintRasterBrushMinSize", 1);
TEnv::DoubleVar RasterBrushMaxSize("InknpaintRasterBrushMaxSize", 5);
TEnv::DoubleVar BrushAccuracy("InknpaintBrushAccuracy", 20);
TEnv::DoubleVar BrushSmooth("InknpaintBrushSmooth", 0);
TEnv::IntVar BrushSelective("InknpaintBrushSelective", 0);
TEnv::IntVar BrushBreakSharpAngles("InknpaintBrushBreakSharpAngles", 0);
TEnv::IntVar RasterBrushPencilMode("InknpaintRasterBrushPencilMode", 0);
TEnv::IntVar BrushPressureSensitivity("InknpaintBrushPressureSensitivity", 1);
TEnv::DoubleVar RasterBrushHardness("RasterBrushHardness", 100);
//-------------------------------------------------------------------
#define ROUNDC_WSTR L"round_cap"
#define BUTT_WSTR L"butt_cap"
#define PROJECTING_WSTR L"projecting_cap"
#define ROUNDJ_WSTR L"round_join"
#define BEVEL_WSTR L"bevel_join"
#define MITER_WSTR L"miter_join"
#define CUSTOM_WSTR L"<custom>"
//-------------------------------------------------------------------
//
// (Da mettere in libreria) : funzioni che spezzano una stroke
// nei suoi punti angolosi. Lo facciamo specialmente per limitare
// i problemi di fill.
//
//-------------------------------------------------------------------
//
// Split a stroke in n+1 parts, according to n parameter values
// Input:
// stroke = stroke to split
// parameterValues[] = vector of parameters where I want to split the
// stroke
// assert: 0<a[0]<a[1]<...<a[n-1]<1
// Output:
// strokes[] = the split strokes
//
// note: stroke is unchanged
//
static void split(TStroke *stroke, const std::vector<double> &parameterValues,
std::vector<TStroke *> &strokes) {
TThickPoint p2;
std::vector<TThickPoint> points;
TThickPoint lastPoint = stroke->getControlPoint(0);
int n = parameterValues.size();
int chunk;
double t;
int last_chunk = -1, startPoint = 0;
double lastLocT = 0;
for (int i = 0; i < n; i++) {
points.push_back(lastPoint); // Add first point of the stroke
double w =
parameterValues[i]; // Global parameter. along the stroke 0<=w<=1
stroke->getChunkAndT(w, chunk,
t); // t: local parameter in the chunk-th quadratic
if (i == 0)
startPoint = 1;
else {
int indexAfterLastT =
stroke->getControlPointIndexAfterParameter(parameterValues[i - 1]);
startPoint = indexAfterLastT;
if ((indexAfterLastT & 1) && lastLocT != 1) startPoint++;
}
int endPoint = 2 * chunk + 1;
if (lastLocT != 1 && i > 0) {
if (last_chunk != chunk || t == 1)
points.push_back(p2); // If the last local t is not an extreme
// add the point p2
}
for (int j = startPoint; j < endPoint; j++)
points.push_back(stroke->getControlPoint(j));
TThickPoint p, A, B, C;
p = stroke->getPoint(w);
C = stroke->getControlPoint(2 * chunk + 2);
B = stroke->getControlPoint(2 * chunk + 1);
A = stroke->getControlPoint(2 * chunk);
p.thick = A.thick;
if (last_chunk != chunk) {
TThickPoint p1 = (1 - t) * A + t * B;
points.push_back(p1);
p.thick = p1.thick;
} else {
if (t != 1) {
// If the i-th cut point belong to the same chunk of the (i-1)-th cut
// point.
double tInters = lastLocT / t;
TThickPoint p11 = (1 - t) * A + t * B;
TThickPoint p1 = (1 - tInters) * p11 + tInters * p;
points.push_back(p1);
p.thick = p1.thick;
}
}
points.push_back(p);
if (t != 1) p2 = (1 - t) * B + t * C;
assert(points.size() & 1);
// Add new stroke
TStroke *strokeAdd = new TStroke(points);
strokeAdd->setStyle(stroke->getStyle());
strokeAdd->outlineOptions() = stroke->outlineOptions();
strokes.push_back(strokeAdd);
lastPoint = p;
last_chunk = chunk;
lastLocT = t;
points.clear();
}
// Add end stroke
points.push_back(lastPoint);
if (lastLocT != 1) points.push_back(p2);
startPoint =
stroke->getControlPointIndexAfterParameter(parameterValues[n - 1]);
if ((stroke->getControlPointIndexAfterParameter(parameterValues[n - 1]) &
1) &&
lastLocT != 1)
startPoint++;
for (int j = startPoint; j < stroke->getControlPointCount(); j++)
points.push_back(stroke->getControlPoint(j));
assert(points.size() & 1);
TStroke *strokeAdd = new TStroke(points);
strokeAdd->setStyle(stroke->getStyle());
strokeAdd->outlineOptions() = stroke->outlineOptions();
strokes.push_back(strokeAdd);
points.clear();
}
// Compute Parametric Curve Curvature
// By Formula:
// k(t)=(|p'(t) x p''(t)|)/Norm2(p')^3
// p(t) is parametric curve
// Input:
// dp = First Derivate.
// ddp = Second Derivate
// Output:
// return curvature value.
// Note: if the curve is a single point (that's dp=0) or it is a straight
// line (that's ddp=0) return 0
static double curvature(TPointD dp, TPointD ddp) {
if (dp == TPointD(0, 0))
return 0;
else
return fabs(cross(dp, ddp) / pow(norm2(dp), 1.5));
}
// Find the max curvature points of a stroke.
// Input:
// stroke.
// angoloLim = Value (radians) of the Corner between two tangent vector.
// Up this value the two corner can be considered angular.
// curvMaxLim = Value of the max curvature.
// Up this value the point can be considered a max curvature
// point.
// Output:
// parameterValues = vector of max curvature parameter points
static void findMaxCurvPoints(TStroke *stroke, const float &angoloLim,
const float &curvMaxLim,
std::vector<double> &parameterValues) {
TPointD tg1, tg2; // Tangent vectors
TPointD dp, ddp; // First and Second derivate.
parameterValues.clear();
int cpn = stroke ? stroke->getControlPointCount() : 0;
for (int j = 2; j < cpn; j += 2) {
TPointD p0 = stroke->getControlPoint(j - 2);
TPointD p1 = stroke->getControlPoint(j - 1);
TPointD p2 = stroke->getControlPoint(j);
TPointD q = p1 - (p0 + p2) * 0.5;
// Search corner point
if (j > 2) {
tg2 = -p0 + p2 + 2 * q; // Tangent vector to this chunk at t=0
double prod_scal =
tg2 * tg1; // Inner product between tangent vectors at t=0.
assert(tg1 != TPointD(0, 0) || tg2 != TPointD(0, 0));
// Compute corner between two tangent vectors
double angolo =
acos(prod_scal / (pow(norm2(tg2), 0.5) * pow(norm2(tg1), 0.5)));
// Add corner point
if (angolo > angoloLim) {
double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)),
0); // transform lacal t to global t
parameterValues.push_back(w);
}
}
tg1 = -p0 + p2 - 2 * q; // Tangent vector to this chunk at t=1
// End search corner point
// Search max curvature point
// Value of t where the curvature function has got an extreme.
// (Point where first derivate is null)
double estremo_int = 0;
double t = -1;
if (q != TPointD(0, 0)) {
t = 0.25 * (2 * q.x * q.x + 2 * q.y * q.y - q.x * p0.x + q.x * p2.x -
q.y * p0.y + q.y * p2.y) /
(q.x * q.x + q.y * q.y);
dp = -p0 + p2 + 2 * q - 4 * t * q; // First derivate of the curve
ddp = -4 * q; // Second derivate of the curve
estremo_int = curvature(dp, ddp);
double h = 0.01;
dp = -p0 + p2 + 2 * q - 4 * (t + h) * q;
double c_dx = curvature(dp, ddp);
dp = -p0 + p2 + 2 * q - 4 * (t - h) * q;
double c_sx = curvature(dp, ddp);
// Check the point is a max and not a minimum
if (estremo_int < c_dx && estremo_int < c_sx) {
estremo_int = 0;
}
}
double curv_max = estremo_int;
// Compute curvature at the extreme of interval [0,1]
// Compute curvature at t=0 (Left extreme)
dp = -p0 + p2 + 2 * q;
double estremo_sx = curvature(dp, ddp);
// Compute curvature at t=1 (Right extreme)
dp = -p0 + p2 - 2 * q;
double estremo_dx = curvature(dp, ddp);
// Compare curvature at the extreme of interval [0,1] with the internal
// value
double t_ext;
if (estremo_sx >= estremo_dx)
t_ext = 0;
else
t_ext = 1;
double maxEstremi = std::max(estremo_dx, estremo_sx);
if (maxEstremi > estremo_int) {
t = t_ext;
curv_max = maxEstremi;
}
// Add max curvature point
if (t >= 0 && t <= 1 && curv_max > curvMaxLim) {
double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)),
t); // transform local t to global t
parameterValues.push_back(w);
}
// End search max curvature point
}
// Delete duplicate of parameterValues
// Because some max cuvature point can coincide with the corner point
if ((int)parameterValues.size() > 1) {
std::sort(parameterValues.begin(), parameterValues.end());
parameterValues.erase(
std::unique(parameterValues.begin(), parameterValues.end()),
parameterValues.end());
}
}
static void addStroke(TTool::Application *application, const TVectorImageP &vi,
TStroke *stroke, bool breakAngles, bool frameCreated,
bool levelCreated) {
QMutexLocker lock(vi->getMutex());
if (application->getCurrentObject()->isSpline()) {
application->getCurrentXsheet()->notifyXsheetChanged();
return;
}
std::vector<double> corners;
std::vector<TStroke *> strokes;
const float angoloLim =
1; // Value (radians) of the Corner between two tangent vector.
// Up this value the two corner can be considered angular.
const float curvMaxLim = 0.8; // Value of the max curvature.
// Up this value the point can be considered a max curvature point.
findMaxCurvPoints(stroke, angoloLim, curvMaxLim, corners);
TXshSimpleLevel *sl = application->getCurrentLevel()->getSimpleLevel();
TFrameId id = application->getCurrentTool()->getTool()->getCurrentFid();
if (!corners.empty()) {
if (breakAngles)
split(stroke, corners, strokes);
else
strokes.push_back(new TStroke(*stroke));
int n = strokes.size();
TUndoManager::manager()->beginBlock();
for (int i = 0; i < n; i++) {
std::vector<TFilledRegionInf> *fillInformation =
new std::vector<TFilledRegionInf>;
ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation,
stroke->getBBox());
TStroke *str = new TStroke(*strokes[i]);
vi->addStroke(str);
TUndoManager::manager()->add(new UndoPencil(str, fillInformation, sl, id,
frameCreated, levelCreated));
}
TUndoManager::manager()->endBlock();
} else {
std::vector<TFilledRegionInf> *fillInformation =
new std::vector<TFilledRegionInf>;
ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation,
stroke->getBBox());
TStroke *str = new TStroke(*stroke);
vi->addStroke(str);
TUndoManager::manager()->add(new UndoPencil(str, fillInformation, sl, id,
frameCreated, levelCreated));
}
for (int k = 0; k < (int)strokes.size(); k++) delete strokes[k];
strokes.clear();
application->getCurrentTool()->getTool()->notifyImageChanged();
}
//-------------------------------------------------------------------
//
// Gennaro: end
//
//-------------------------------------------------------------------
//===================================================================
//
// Helper functions and classes
//
//-------------------------------------------------------------------
namespace {
//-------------------------------------------------------------------
void addStrokeToImage(TTool::Application *application, const TVectorImageP &vi,
TStroke *stroke, bool breakAngles, bool frameCreated,
bool levelCreated) {
QMutexLocker lock(vi->getMutex());
addStroke(application, vi.getPointer(), stroke, breakAngles, frameCreated,
levelCreated);
// la notifica viene gia fatta da addStroke!
// getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
}
//=========================================================================================================
class RasterBrushUndo final : public TRasterUndo {
std::vector<TThickPoint> m_points;
int m_styleId;
bool m_selective;
bool m_isPencil;
public:
RasterBrushUndo(TTileSetCM32 *tileSet, const std::vector<TThickPoint> &points,
int styleId, bool selective, TXshSimpleLevel *level,
const TFrameId &frameId, bool isPencil, bool isFrameCreated,
bool isLevelCreated)
: TRasterUndo(tileSet, level, frameId, isFrameCreated, isLevelCreated, 0)
, m_points(points)
, m_styleId(styleId)
, m_selective(selective)
, m_isPencil(isPencil) {}
void redo() const override {
insertLevelAndFrameIfNeeded();
TToonzImageP image = getImage();
TRasterCM32P ras = image->getRaster();
RasterStrokeGenerator m_rasterTrack(
ras, BRUSH, NONE, m_styleId, m_points[0], m_selective, 0, !m_isPencil);
m_rasterTrack.setPointsSequence(m_points);
m_rasterTrack.generateStroke(m_isPencil);
image->setSavebox(image->getSavebox() +
m_rasterTrack.getBBox(m_rasterTrack.getPointsSequence()));
ToolUtils::updateSaveBox();
TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
notifyImageChanged();
}
int getSize() const override {
return sizeof(*this) + TRasterUndo::getSize();
}
QString getToolName() override { return QString("Brush Tool"); }
int getHistoryType() override { return HistoryType::BrushTool; }
};
//=========================================================================================================
class RasterBluredBrushUndo final : public TRasterUndo {
std::vector<TThickPoint> m_points;
int m_styleId;
bool m_selective;
int m_maxThick;
double m_hardness;
public:
RasterBluredBrushUndo(TTileSetCM32 *tileSet,
const std::vector<TThickPoint> &points, int styleId,
bool selective, TXshSimpleLevel *level,
const TFrameId &frameId, int maxThick, double hardness,
bool isFrameCreated, bool isLevelCreated)
: TRasterUndo(tileSet, level, frameId, isFrameCreated, isLevelCreated, 0)
, m_points(points)
, m_styleId(styleId)
, m_selective(selective)
, m_maxThick(maxThick)
, m_hardness(hardness) {}
void redo() const override {
if (m_points.size() == 0) return;
insertLevelAndFrameIfNeeded();
TToonzImageP image = getImage();
TRasterCM32P ras = image->getRaster();
TRasterCM32P backupRas = ras->clone();
TRaster32P workRaster(ras->getSize());
QRadialGradient brushPad = ToolUtils::getBrushPad(m_maxThick, m_hardness);
workRaster->clear();
BluredBrush brush(workRaster, m_maxThick, brushPad, false);
std::vector<TThickPoint> points;
points.push_back(m_points[0]);
TRect bbox = brush.getBoundFromPoints(points);
brush.addPoint(m_points[0], 1);
brush.updateDrawing(ras, ras, bbox, m_styleId, m_selective);
if (m_points.size() > 1) {
points.clear();
points.push_back(m_points[0]);
points.push_back(m_points[1]);
bbox = brush.getBoundFromPoints(points);
brush.addArc(m_points[0], (m_points[1] + m_points[0]) * 0.5, m_points[1],
1, 1);
brush.updateDrawing(ras, backupRas, bbox, m_styleId, m_selective);
int i;
for (i = 1; i + 2 < (int)m_points.size(); i = i + 2) {
points.clear();
points.push_back(m_points[i]);
points.push_back(m_points[i + 1]);
points.push_back(m_points[i + 2]);
bbox = brush.getBoundFromPoints(points);
brush.addArc(m_points[i], m_points[i + 1], m_points[i + 2], 1, 1);
brush.updateDrawing(ras, backupRas, bbox, m_styleId, m_selective);
}
}
ToolUtils::updateSaveBox();
TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
notifyImageChanged();
}
int getSize() const override {
return sizeof(*this) + TRasterUndo::getSize();
}
QString getToolName() override { return QString("Brush Tool"); }
int getHistoryType() override { return HistoryType::BrushTool; }
};
//=========================================================================================================
double computeThickness(int pressure, const TDoublePairProperty &property,
bool isPath) {
if (isPath) return 0.0;
double p = pressure / 255.0;
double t = p * p * p;
double thick0 = property.getValue().first;
double thick1 = property.getValue().second;
if (thick1 < 0.0001) thick0 = thick1 = 0.0;
return (thick0 + (thick1 - thick0) * t) * 0.5;
}
//---------------------------------------------------------------------------------------------------------
int computeThickness(int pressure, const TIntPairProperty &property,
bool isPath) {
if (isPath) return 0.0;
double p = pressure / 255.0;
double t = p * p * p;
int thick0 = property.getValue().first;
int thick1 = property.getValue().second;
return tround(thick0 + (thick1 - thick0) * t);
}
} // namespace
//--------------------------------------------------------------------------------------------------
static void CatmullRomInterpolate(const TThickPoint &P0, const TThickPoint &P1,
const TThickPoint &P2, const TThickPoint &P3,
int samples,
std::vector<TThickPoint> &points) {
double x0 = P1.x;
double x1 = (-P0.x + P2.x) * 0.5f;
double x2 = P0.x - 2.5f * P1.x + 2.0f * P2.x - 0.5f * P3.x;
double x3 = -0.5f * P0.x + 1.5f * P1.x - 1.5f * P2.x + 0.5f * P3.x;
double y0 = P1.y;
double y1 = (-P0.y + P2.y) * 0.5f;
double y2 = P0.y - 2.5f * P1.y + 2.0f * P2.y - 0.5f * P3.y;
double y3 = -0.5f * P0.y + 1.5f * P1.y - 1.5f * P2.y + 0.5f * P3.y;
double z0 = P1.thick;
double z1 = (-P0.thick + P2.thick) * 0.5f;
double z2 = P0.thick - 2.5f * P1.thick + 2.0f * P2.thick - 0.5f * P3.thick;
double z3 =
-0.5f * P0.thick + 1.5f * P1.thick - 1.5f * P2.thick + 0.5f * P3.thick;
for (int i = 1; i <= samples; ++i) {
double t = i / (double)(samples + 1);
double t2 = t * t;
double t3 = t2 * t;
TThickPoint p;
p.x = x0 + x1 * t + x2 * t2 + x3 * t3;
p.y = y0 + y1 * t + y2 * t2 + y3 * t3;
p.thick = z0 + z1 * t + z2 * t2 + z3 * t3;
points.push_back(p);
}
}
//--------------------------------------------------------------------------------------------------
static void Smooth(std::vector<TThickPoint> &points, int radius) {
int n = (int)points.size();
if (radius < 1 || n < 3) {
return;
}
std::vector<TThickPoint> result;
float d = 1.0f / (radius * 2 + 1);
for (int i = 1; i < n - 1; ++i) {
int lower = i - radius;
int upper = i + radius;
TThickPoint total;
total.x = 0;
total.y = 0;
total.thick = 0;
for (int j = lower; j <= upper; ++j) {
int idx = j;
if (idx < 0) {
idx = 0;
} else if (idx >= n) {
idx = n - 1;
}
total.x += points[idx].x;
total.y += points[idx].y;
total.thick += points[idx].thick;
}
total.x *= d;
total.y *= d;
total.thick *= d;
result.push_back(total);
}
for (int i = 1; i < n - 1; ++i) {
points[i].x = result[i - 1].x;
points[i].y = result[i - 1].y;
points[i].thick = result[i - 1].thick;
}
if (points.size() >= 3) {
std::vector<TThickPoint> pts;
CatmullRomInterpolate(points[0], points[0], points[1], points[2], 10, pts);
std::vector<TThickPoint>::iterator it = points.begin();
points.insert(it, pts.begin(), pts.end());
pts.clear();
CatmullRomInterpolate(points[n - 3], points[n - 2], points[n - 1],
points[n - 1], 10, pts);
it = points.begin();
it += n - 1;
points.insert(it, pts.begin(), pts.end());
}
}
//--------------------------------------------------------------------------------------------------
void SmoothStroke::beginStroke(int smooth) {
m_smooth = smooth;
m_outputIndex = 0;
m_readIndex = -1;
m_rawPoints.clear();
m_outputPoints.clear();
}
//--------------------------------------------------------------------------------------------------
void SmoothStroke::addPoint(const TThickPoint &point) {
if (m_rawPoints.size() > 0 && m_rawPoints.back().x == point.x &&
m_rawPoints.back().y == point.y) {
return;
}
m_rawPoints.push_back(point);
generatePoints();
}
//--------------------------------------------------------------------------------------------------
void SmoothStroke::endStroke() {
generatePoints();
// force enable the output all segments
m_outputIndex = m_outputPoints.size() - 1;
}
//--------------------------------------------------------------------------------------------------
void SmoothStroke::getSmoothPoints(std::vector<TThickPoint> &smoothPoints) {
int n = m_outputPoints.size();
for (int i = m_readIndex + 1; i <= m_outputIndex && i < n; ++i) {
smoothPoints.push_back(m_outputPoints[i]);
}
m_readIndex = m_outputIndex;
}
//--------------------------------------------------------------------------------------------------
void SmoothStroke::generatePoints() {
int n = (int)m_rawPoints.size();
if (n == 0) {
return;
}
std::vector<TThickPoint> smoothedPoints;
// Add more stroke samples before applying the smoothing
// This is because the raw inputs points are too few to support smooth result,
// especially on stroke ends
smoothedPoints.push_back(m_rawPoints.front());
for (int i = 1; i < n; ++i) {
const TThickPoint &p1 = m_rawPoints[i - 1];
const TThickPoint &p2 = m_rawPoints[i];
const TThickPoint &p0 = i - 2 >= 0 ? m_rawPoints[i - 2] : p1;
const TThickPoint &p3 = i + 1 < n ? m_rawPoints[i + 1] : p2;
int samples = 8;
CatmullRomInterpolate(p0, p1, p2, p3, samples, smoothedPoints);
smoothedPoints.push_back(p2);
}
// Apply the 1D box filter
// Multiple passes result in better quality and fix the stroke ends break
// issue
for (int i = 0; i < 3; ++i) {
Smooth(smoothedPoints, m_smooth);
}
// Compare the new smoothed stroke with old one
// Enable the output for unchanged parts
int outputNum = (int)m_outputPoints.size();
for (int i = m_outputIndex; i < outputNum; ++i) {
if (m_outputPoints[i] != smoothedPoints[i]) {
break;
}
++m_outputIndex;
}
m_outputPoints = smoothedPoints;
}
//===================================================================
//
// BrushTool
//
//-----------------------------------------------------------------------------
BrushTool::BrushTool(std::string name, int targetType)
: TTool(name)
, m_thickness("Size", 0, 100, 0, 5)
, m_rasThickness("Size", 1, 100, 1, 5)
, m_accuracy("Accuracy:", 1, 100, 20)
, m_smooth("Smooth:", 0, 50, 0)
, m_hardness("Hardness:", 0, 100, 100)
, m_preset("Preset:")
, m_selective("Selective", false)
, m_breakAngles("Break", true)
, m_pencil("Pencil", false)
, m_pressure("Pressure", true)
, m_capStyle("Cap")
, m_joinStyle("Join")
, m_miterJoinLimit("Miter:", 0, 100, 4)
, m_rasterTrack(0)
, m_styleId(0)
, m_modifiedRegion()
, m_bluredBrush(0)
, m_active(false)
, m_enabled(false)
, m_isPrompting(false)
, m_firstTime(true)
, m_presetsLoaded(false)
, m_workingFrameId(TFrameId()) {
bind(targetType);
if (targetType & TTool::Vectors) {
m_prop[0].bind(m_thickness);
m_prop[0].bind(m_accuracy);
m_prop[0].bind(m_smooth);
m_prop[0].bind(m_breakAngles);
m_breakAngles.setId("BreakSharpAngles");
}
if (targetType & TTool::ToonzImage) {
m_prop[0].bind(m_rasThickness);
m_prop[0].bind(m_hardness);
m_prop[0].bind(m_smooth);
m_prop[0].bind(m_selective);
m_prop[0].bind(m_pencil);
m_pencil.setId("PencilMode");
m_selective.setId("Selective");
}
m_prop[0].bind(m_pressure);
m_prop[0].bind(m_preset);
m_preset.setId("BrushPreset");
m_preset.addValue(CUSTOM_WSTR);
m_pressure.setId("PressureSensitivity");
m_capStyle.addValue(BUTT_WSTR);
m_capStyle.addValue(ROUNDC_WSTR);
m_capStyle.addValue(PROJECTING_WSTR);
m_capStyle.setId("Cap");
m_joinStyle.addValue(MITER_WSTR);
m_joinStyle.addValue(ROUNDJ_WSTR);
m_joinStyle.addValue(BEVEL_WSTR);
m_joinStyle.setId("Join");
m_miterJoinLimit.setId("Miter");
if (targetType & TTool::Vectors) {
m_prop[1].bind(m_capStyle);
m_prop[1].bind(m_joinStyle);
m_prop[1].bind(m_miterJoinLimit);
}
}
//-------------------------------------------------------------------------------------------------------
ToolOptionsBox *BrushTool::createOptionsBox() {
TPaletteHandle *currPalette =
TTool::getApplication()->getPaletteController()->getCurrentLevelPalette();
ToolHandle *currTool = TTool::getApplication()->getCurrentTool();
return new BrushToolOptionsBox(0, this, currPalette, currTool);
}
//-------------------------------------------------------------------------------------------------------
void BrushTool::drawLine(const TPointD &point, const TPointD &centre,
bool horizontal, bool isDecimal) {
if (!isDecimal) {
if (horizontal) {
tglDrawSegment(TPointD(point.x - 1.5, point.y + 0.5) + centre,
TPointD(point.x - 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, -point.x + 1.5) + centre,
TPointD(point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y + 0.5) + centre,
TPointD(-point.x - 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, point.x + 0.5) + centre,
TPointD(point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, -point.y + 0.5) + centre,
TPointD(point.x - 1.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x + 1.5) + centre);
tglDrawSegment(TPointD(-point.x - 0.5, point.y + 0.5) + centre,
TPointD(-point.x + 0.5, point.y + 0.5) + centre);
} else {
tglDrawSegment(TPointD(point.x - 1.5, point.y + 1.5) + centre,
TPointD(point.x - 1.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 1.5, point.y + 0.5) + centre,
TPointD(point.x - 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, -point.x + 1.5) + centre,
TPointD(point.y - 0.5, -point.x + 1.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, -point.x + 1.5) + centre,
TPointD(point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 0.5) + centre,
TPointD(-point.x + 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y + 0.5) + centre,
TPointD(-point.x - 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
TPointD(point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, point.x - 0.5) + centre,
TPointD(point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 1.5, -point.y - 0.5) + centre,
TPointD(point.x - 1.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 1.5, -point.y + 0.5) + centre,
TPointD(point.x - 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, -point.x + 1.5) + centre,
TPointD(-point.y - 0.5, -point.x + 1.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 1.5) + centre,
TPointD(-point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 1.5) + centre,
TPointD(-point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
TPointD(-point.x - 0.5, point.y + 0.5) + centre);
}
} else {
if (horizontal) {
tglDrawSegment(TPointD(point.x - 0.5, point.y + 0.5) + centre,
TPointD(point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
TPointD(point.y + 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, -point.x + 0.5) + centre,
TPointD(point.y + 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.x + 0.5, -point.y - 0.5) + centre,
TPointD(point.x - 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.x - 0.5, -point.y - 0.5) + centre,
TPointD(-point.x + 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
TPointD(-point.x - 0.5, point.y + 0.5) + centre);
} else {
tglDrawSegment(TPointD(point.x - 0.5, point.y + 1.5) + centre,
TPointD(point.x - 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, point.y + 0.5) + centre,
TPointD(point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 1.5, point.x - 0.5) + centre,
TPointD(point.y + 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
TPointD(point.y + 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 1.5, -point.x + 0.5) + centre,
TPointD(point.y + 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, -point.x + 0.5) + centre,
TPointD(point.y + 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, -point.y - 1.5) + centre,
TPointD(point.x - 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, -point.y - 0.5) + centre,
TPointD(point.x + 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 1.5) + centre,
TPointD(-point.x + 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 0.5) + centre,
TPointD(-point.x - 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 1.5) + centre,
TPointD(-point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
TPointD(-point.x - 0.5, point.y + 0.5) + centre);
}
}
}
//-------------------------------------------------------------------------------------------------------
void BrushTool::drawEmptyCircle(TPointD pos, int thick, bool isLxEven,
bool isLyEven, bool isPencil) {
if (isLxEven) pos.x += 0.5;
if (isLyEven) pos.y += 0.5;
if (!isPencil)
tglDrawCircle(pos, (thick + 1) * 0.5);
else {
int x = 0, y = tround((thick * 0.5) - 0.5);
int d = 3 - 2 * (int)(thick * 0.5);
bool horizontal = true, isDecimal = thick % 2 != 0;
drawLine(TPointD(x, y), pos, horizontal, isDecimal);
while (y > x) {
if (d < 0) {
d = d + 4 * x + 6;
horizontal = true;
} else {
d = d + 4 * (x - y) + 10;
horizontal = false;
y--;
}
x++;
drawLine(TPointD(x, y), pos, horizontal, isDecimal);
}
}
}
//-------------------------------------------------------------------------------------------------------
void BrushTool::updateTranslation() {
m_thickness.setQStringName(tr("Size"));
m_rasThickness.setQStringName(tr("Size"));
m_hardness.setQStringName(tr("Hardness:"));
m_accuracy.setQStringName(tr("Accuracy:"));
m_smooth.setQStringName(tr("Smooth:"));
m_selective.setQStringName(tr("Selective"));
// m_filled.setQStringName(tr("Filled"));
m_preset.setQStringName(tr("Preset:"));
m_breakAngles.setQStringName(tr("Break"));
m_pencil.setQStringName(tr("Pencil"));
m_pressure.setQStringName(tr("Pressure"));
m_capStyle.setQStringName(tr("Cap"));
m_joinStyle.setQStringName(tr("Join"));
m_miterJoinLimit.setQStringName(tr("Miter:"));
}
//---------------------------------------------------------------------------------------------------
void BrushTool::updateWorkAndBackupRasters(const TRect &rect) {
TToonzImageP ti = TImageP(getImage(false, 1));
if (!ti) return;
TRasterCM32P ras = ti->getRaster();
TRect _rect = rect * ras->getBounds();
TRect _lastRect = m_lastRect * ras->getBounds();
if (_rect.isEmpty()) return;
if (m_lastRect.isEmpty()) {
m_workRas->extract(_rect)->clear();
m_backupRas->extract(_rect)->copy(ras->extract(_rect));
return;
}
QList<TRect> rects = ToolUtils::splitRect(_rect, _lastRect);
for (int i = 0; i < rects.size(); i++) {
m_workRas->extract(rects[i])->clear();
m_backupRas->extract(rects[i])->copy(ras->extract(rects[i]));
}
}
//---------------------------------------------------------------------------------------------------
void BrushTool::onActivate() {
if (m_firstTime) {
m_thickness.setValue(
TDoublePairProperty::Value(VectorBrushMinSize, VectorBrushMaxSize));
m_rasThickness.setValue(
TDoublePairProperty::Value(RasterBrushMinSize, RasterBrushMaxSize));
m_capStyle.setIndex(VectorCapStyle);
m_joinStyle.setIndex(VectorJoinStyle);
m_miterJoinLimit.setValue(VectorMiterValue);
m_selective.setValue(BrushSelective ? 1 : 0);
m_breakAngles.setValue(BrushBreakSharpAngles ? 1 : 0);
m_pencil.setValue(RasterBrushPencilMode ? 1 : 0);
m_pressure.setValue(BrushPressureSensitivity ? 1 : 0);
m_firstTime = false;
m_accuracy.setValue(BrushAccuracy);
m_smooth.setValue(BrushSmooth);
m_hardness.setValue(RasterBrushHardness);
}
if (m_targetType & TTool::ToonzImage) {
m_brushPad = ToolUtils::getBrushPad(m_rasThickness.getValue().second,
m_hardness.getValue() * 0.01);
setWorkAndBackupImages();
}
// TODO:app->editImageOrSpline();
}
//--------------------------------------------------------------------------------------------------
void BrushTool::onDeactivate() {
/*---
* ドラッグ中にツールが切り替わった場合に備え、onDeactivateにもMouseReleaseと同じ処理を行う
* ---*/
if (m_tileSaver && !m_isPath) {
bool isValid = m_enabled && m_active;
m_enabled = false;
if (isValid) {
TImageP image = getImage(true);
if (TToonzImageP ti = image)
finishRasterBrush(m_mousePos,
1); /*-- 最後のストロークの筆圧は1とする --*/
}
}
m_workRas = TRaster32P();
m_backupRas = TRasterCM32P();
}
//--------------------------------------------------------------------------------------------------
bool BrushTool::preLeftButtonDown() {
touchImage();
if (m_isFrameCreated) setWorkAndBackupImages();
return true;
}
//--------------------------------------------------------------------------------------------------
void BrushTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
TTool::Application *app = TTool::getApplication();
if (!app) return;
int col = app->getCurrentColumn()->getColumnIndex();
m_isPath = app->getCurrentObject()->isSpline();
m_enabled = col >= 0 || m_isPath;
// todo: gestire autoenable
if (!m_enabled) return;
if (!m_isPath) {
m_currentColor = TPixel32::Black;
m_active = !!getImage(true);
if (!m_active) {
m_active = !!touchImage();
}
if (!m_active) return;
if (m_active) {
// nel caso che il colore corrente sia un cleanup/studiopalette color
// oppure il colore di un colorfield
m_styleId = app->getCurrentLevelStyleIndex();
TColorStyle *cs = app->getCurrentLevelStyle();
if (cs) {
TRasterStyleFx *rfx = cs ? cs->getRasterStyleFx() : 0;
m_active =
cs != 0 && (cs->isStrokeStyle() || (rfx && rfx->isInkStyle()));
m_currentColor = cs->getAverageColor();
m_currentColor.m = 255;
} else {
m_styleId = 1;
m_currentColor = TPixel32::Black;
}
}
} else {
m_currentColor = TPixel32::Red;
m_active = true;
}
// assert(0<=m_styleId && m_styleId<2);
TImageP img = getImage(true);
TToonzImageP ri(img);
if (ri) {
TRasterCM32P ras = ri->getRaster();
if (ras) {
TPointD rasCenter = ras->getCenterD();
m_tileSet = new TTileSetCM32(ras->getSize());
m_tileSaver = new TTileSaverCM32(ras, m_tileSet);
double maxThick = m_rasThickness.getValue().second;
double thickness =
(m_pressure.getValue() || m_isPath)
? computeThickness(e.m_pressure, m_rasThickness, m_isPath) * 2
: maxThick;
/*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する
* ---*/
if (m_pressure.getValue() && e.m_pressure == 255)
thickness = m_rasThickness.getValue().first;
TPointD halfThick(maxThick * 0.5, maxThick * 0.5);
TRectD invalidateRect(pos - halfThick, pos + halfThick);
if (m_hardness.getValue() == 100 || m_pencil.getValue()) {
/*-- Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる
* --*/
if (!m_pencil.getValue()) thickness -= 1.0;
TThickPoint thickPoint(pos + convert(ras->getCenter()), thickness);
m_rasterTrack = new RasterStrokeGenerator(
ras, BRUSH, NONE, m_styleId, thickPoint, m_selective.getValue(), 0,
!m_pencil.getValue());
m_tileSaver->save(m_rasterTrack->getLastRect());
m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue());
m_smoothStroke.beginStroke(m_smooth.getValue());
m_smoothStroke.addPoint(thickPoint);
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(
pts); // skip first point because it has been outputted
} else {
m_points.clear();
TThickPoint point(pos + rasCenter, thickness);
m_points.push_back(point);
m_bluredBrush = new BluredBrush(m_workRas, maxThick, m_brushPad, false);
m_strokeRect = m_bluredBrush->getBoundFromPoints(m_points);
updateWorkAndBackupRasters(m_strokeRect);
m_tileSaver->save(m_strokeRect);
m_bluredBrush->addPoint(point, 1);
m_bluredBrush->updateDrawing(ri->getRaster(), m_backupRas, m_strokeRect,
m_styleId, m_selective.getValue());
m_lastRect = m_strokeRect;
m_smoothStroke.beginStroke(m_smooth.getValue());
m_smoothStroke.addPoint(point);
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(
pts); // skip first point because it has been outputted
}
/*-- 作業中のFidを登録 --*/
m_workingFrameId = getFrameId();
invalidate(invalidateRect);
}
} else {
m_track.clear();
double thickness =
(m_pressure.getValue() || m_isPath)
? computeThickness(e.m_pressure, m_thickness, m_isPath)
: m_thickness.getValue().second * 0.5;
/*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/
if (m_pressure.getValue() && e.m_pressure == 255)
thickness = m_rasThickness.getValue().first;
m_smoothStroke.beginStroke(m_smooth.getValue());
addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
}
}
//-------------------------------------------------------------------------------------------------------------
void BrushTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) {
m_brushPos = m_mousePos = pos;
if (!m_enabled || !m_active) return;
bool isAdded;
if (TToonzImageP ti = TImageP(getImage(true))) {
TPointD rasCenter = ti->getRaster()->getCenterD();
int maxThickness = m_rasThickness.getValue().second;
double thickness =
(m_pressure.getValue() || m_isPath)
? computeThickness(e.m_pressure, m_rasThickness, m_isPath) * 2
: maxThickness;
TRectD invalidateRect;
if (m_rasterTrack &&
(m_hardness.getValue() == 100 || m_pencil.getValue())) {
/*-- Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる
* --*/
if (!m_pencil.getValue()) thickness -= 1.0;
TThickPoint thickPoint(pos + rasCenter, thickness);
m_smoothStroke.addPoint(thickPoint);
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(pts);
for (size_t i = 0; i < pts.size(); ++i) {
const TThickPoint &thickPoint = pts[i];
isAdded = m_rasterTrack->add(thickPoint);
if (isAdded) {
m_tileSaver->save(m_rasterTrack->getLastRect());
m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue());
std::vector<TThickPoint> brushPoints =
m_rasterTrack->getPointsSequence();
int m = (int)brushPoints.size();
std::vector<TThickPoint> points;
if (m == 3) {
points.push_back(brushPoints[0]);
points.push_back(brushPoints[1]);
} else {
points.push_back(brushPoints[m - 4]);
points.push_back(brushPoints[m - 3]);
points.push_back(brushPoints[m - 2]);
}
if (i == 0) {
invalidateRect =
ToolUtils::getBounds(points, maxThickness) - rasCenter;
} else {
invalidateRect +=
ToolUtils::getBounds(points, maxThickness) - rasCenter;
}
}
}
} else {
// antialiased brush
assert(m_workRas.getPointer() && m_backupRas.getPointer());
TThickPoint thickPoint(pos + rasCenter, thickness);
m_smoothStroke.addPoint(thickPoint);
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(pts);
bool rectUpdated = false;
for (size_t i = 0; i < pts.size(); ++i) {
TThickPoint old = m_points.back();
if (norm2(pos - old) < 4) continue;
const TThickPoint &point = pts[i];
TThickPoint mid((old + point) * 0.5, (point.thick + old.thick) * 0.5);
m_points.push_back(mid);
m_points.push_back(point);
TRect bbox;
int m = (int)m_points.size();
std::vector<TThickPoint> points;
if (m == 3) {
// ho appena cominciato. devo disegnare un segmento
TThickPoint pa = m_points.front();
points.push_back(pa);
points.push_back(mid);
bbox = m_bluredBrush->getBoundFromPoints(points);
updateWorkAndBackupRasters(bbox + m_lastRect);
m_tileSaver->save(bbox);
m_bluredBrush->addArc(pa, (mid + pa) * 0.5, mid, 1, 1);
m_lastRect += bbox;
} else {
points.push_back(m_points[m - 4]);
points.push_back(old);
points.push_back(mid);
bbox = m_bluredBrush->getBoundFromPoints(points);
updateWorkAndBackupRasters(bbox + m_lastRect);
m_tileSaver->save(bbox);
m_bluredBrush->addArc(m_points[m - 4], old, mid, 1, 1);
m_lastRect += bbox;
}
if (!rectUpdated) {
invalidateRect =
ToolUtils::getBounds(points, maxThickness) - rasCenter;
rectUpdated = true;
} else {
invalidateRect +=
ToolUtils::getBounds(points, maxThickness) - rasCenter;
}
m_bluredBrush->updateDrawing(ti->getRaster(), m_backupRas, bbox,
m_styleId, m_selective.getValue());
m_strokeRect += bbox;
}
}
invalidate(invalidateRect.enlarge(2));
} else {
double thickness =
(m_pressure.getValue() || m_isPath)
? computeThickness(e.m_pressure, m_thickness, m_isPath)
: m_thickness.getValue().second * 0.5;
addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
invalidate();
}
}
//---------------------------------------------------------------------------------------------------------------
void BrushTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) {
bool isValid = m_enabled && m_active;
m_enabled = false;
if (!isValid) return;
if (m_isPath) {
double error = 20.0 * getPixelSize();
flushTrackPoint();
TStroke *stroke = m_track.makeStroke(error);
int points = stroke->getControlPointCount();
if (TVectorImageP vi = getImage(true)) {
struct Cleanup {
BrushTool *m_this;
~Cleanup() { m_this->m_track.clear(), m_this->invalidate(); }
} cleanup = {this};
if (!isJustCreatedSpline(vi.getPointer())) {
m_isPrompting = true;
QString question("Are you sure you want to replace the motion path?");
int ret =
DVGui::MsgBox(question, QObject::tr("Yes"), QObject::tr("No"), 0);
m_isPrompting = false;
if (ret == 2 || ret == 0) return;
}
QMutexLocker lock(vi->getMutex());
TUndo *undo =
new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline());
while (vi->getStrokeCount() > 0) vi->deleteStroke(0);
vi->addStroke(stroke, false);
notifyImageChanged();
TUndoManager::manager()->add(undo);
}
return;
}
TImageP image = getImage(true);
if (TVectorImageP vi = image) {
if (m_track.isEmpty()) {
m_styleId = 0;
m_track.clear();
return;
}
m_track.filterPoints();
double error = 30.0 / (1 + 0.5 * m_accuracy.getValue());
error *= getPixelSize();
flushTrackPoint();
TStroke *stroke = m_track.makeStroke(error);
stroke->setStyle(m_styleId);
{
TStroke::OutlineOptions &options = stroke->outlineOptions();
options.m_capStyle = m_capStyle.getIndex();
options.m_joinStyle = m_joinStyle.getIndex();
options.m_miterUpper = m_miterJoinLimit.getValue();
}
m_styleId = 0;
QMutexLocker lock(vi->getMutex());
if (stroke->getControlPointCount() == 3 &&
stroke->getControlPoint(0) !=
stroke->getControlPoint(2)) // gli stroke con solo 1 chunk vengono
// fatti dal tape tool...e devono venir
// riconosciuti come speciali di
// autoclose proprio dal fatto che
// hanno 1 solo chunk.
stroke->insertControlPoints(0.5);
addStrokeToImage(getApplication(), vi, stroke, m_breakAngles.getValue(),
m_isFrameCreated, m_isLevelCreated);
TRectD bbox = stroke->getBBox().enlarge(2) + m_track.getModifiedRegion();
invalidate();
assert(stroke);
m_track.clear();
} else if (TToonzImageP ti = image) {
finishRasterBrush(pos, e.m_pressure);
}
}
//--------------------------------------------------------------------------------------------------
void BrushTool::addTrackPoint(const TThickPoint &point, double pixelSize2) {
m_smoothStroke.addPoint(point);
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(pts);
for (size_t i = 0; i < pts.size(); ++i) {
m_track.add(pts[i], pixelSize2);
}
}
//--------------------------------------------------------------------------------------------------
void BrushTool::flushTrackPoint() {
m_smoothStroke.endStroke();
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(pts);
double pixelSize2 = getPixelSize() * getPixelSize();
for (size_t i = 0; i < pts.size(); ++i) {
m_track.add(pts[i], pixelSize2);
}
}
//---------------------------------------------------------------------------------------------------------------
/*!
* ドラッグ中にツールが切り替わった場合に備え、onDeactivate時とMouseRelease時にと同じ終了処理を行う
*/
void BrushTool::finishRasterBrush(const TPointD &pos, int pressureVal) {
TImageP image = getImage(true);
TToonzImageP ti = image;
if (!ti) return;
TPointD rasCenter = ti->getRaster()->getCenterD();
TTool::Application *app = TTool::getApplication();
TXshLevel *level = app->getCurrentLevel()->getLevel();
TXshSimpleLevelP simLevel = level->getSimpleLevel();
/*--
* 描画中にカレントフレームが変わっても、描画開始時のFidに対してUndoを記録する
* --*/
TFrameId frameId =
m_workingFrameId.isEmptyFrame() ? getCurrentFid() : m_workingFrameId;
if (m_rasterTrack && (m_hardness.getValue() == 100 || m_pencil.getValue())) {
double thickness =
m_pressure.getValue()
? computeThickness(pressureVal, m_rasThickness, m_isPath)
: m_rasThickness.getValue().second;
/*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/
if (m_pressure.getValue() && pressureVal == 255)
thickness = m_rasThickness.getValue().first;
/*-- Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる --*/
if (!m_pencil.getValue()) thickness -= 1.0;
TRectD invalidateRect;
TThickPoint thickPoint(pos + rasCenter, thickness);
m_smoothStroke.addPoint(thickPoint);
m_smoothStroke.endStroke();
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(pts);
for (size_t i = 0; i < pts.size(); ++i) {
const TThickPoint &thickPoint = pts[i];
bool isAdded = m_rasterTrack->add(thickPoint);
if (isAdded) {
m_tileSaver->save(m_rasterTrack->getLastRect());
m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue(), true);
std::vector<TThickPoint> brushPoints =
m_rasterTrack->getPointsSequence();
int m = (int)brushPoints.size();
std::vector<TThickPoint> points;
if (m == 3) {
points.push_back(brushPoints[0]);
points.push_back(brushPoints[1]);
} else {
points.push_back(brushPoints[m - 4]);
points.push_back(brushPoints[m - 3]);
points.push_back(brushPoints[m - 2]);
}
int maxThickness = m_rasThickness.getValue().second;
if (i == 0) {
invalidateRect =
ToolUtils::getBounds(points, maxThickness) - rasCenter;
} else {
invalidateRect +=
ToolUtils::getBounds(points, maxThickness) - rasCenter;
}
}
}
invalidate(invalidateRect.enlarge(2));
if (m_tileSet->getTileCount() > 0) {
TUndoManager::manager()->add(new RasterBrushUndo(
m_tileSet, m_rasterTrack->getPointsSequence(),
m_rasterTrack->getStyleId(), m_rasterTrack->isSelective(),
simLevel.getPointer(), frameId, m_pencil.getValue(), m_isFrameCreated,
m_isLevelCreated));
}
delete m_rasterTrack;
m_rasterTrack = 0;
} else {
if (m_points.size() != 1) {
double maxThickness = m_rasThickness.getValue().second;
double thickness =
(m_pressure.getValue() || m_isPath)
? computeThickness(pressureVal, m_rasThickness, m_isPath)
: maxThickness;
TPointD rasCenter = ti->getRaster()->getCenterD();
TRectD invalidateRect;
bool rectUpdated = false;
TThickPoint thickPoint(pos + rasCenter, thickness);
m_smoothStroke.addPoint(thickPoint);
m_smoothStroke.endStroke();
std::vector<TThickPoint> pts;
m_smoothStroke.getSmoothPoints(pts);
for (size_t i = 0; i < pts.size() - 1; ++i) {
TThickPoint old = m_points.back();
if (norm2(pos - old) < 4) continue;
const TThickPoint &point = pts[i];
TThickPoint mid((old + point) * 0.5, (point.thick + old.thick) * 0.5);
m_points.push_back(mid);
m_points.push_back(point);
TRect bbox;
int m = (int)m_points.size();
std::vector<TThickPoint> points;
if (m == 3) {
// ho appena cominciato. devo disegnare un segmento
TThickPoint pa = m_points.front();
points.push_back(pa);
points.push_back(mid);
bbox = m_bluredBrush->getBoundFromPoints(points);
updateWorkAndBackupRasters(bbox + m_lastRect);
m_tileSaver->save(bbox);
m_bluredBrush->addArc(pa, (mid + pa) * 0.5, mid, 1, 1);
m_lastRect += bbox;
} else {
points.push_back(m_points[m - 4]);
points.push_back(old);
points.push_back(mid);
bbox = m_bluredBrush->getBoundFromPoints(points);
updateWorkAndBackupRasters(bbox + m_lastRect);
m_tileSaver->save(bbox);
m_bluredBrush->addArc(m_points[m - 4], old, mid, 1, 1);
m_lastRect += bbox;
}
if (!rectUpdated) {
invalidateRect =
ToolUtils::getBounds(points, maxThickness) - rasCenter;
rectUpdated = true;
} else {
invalidateRect +=
ToolUtils::getBounds(points, maxThickness) - rasCenter;
}
m_bluredBrush->updateDrawing(ti->getRaster(), m_backupRas, bbox,
m_styleId, m_selective.getValue());
m_strokeRect += bbox;
}
if (pts.size() > 0) {
TThickPoint point = pts.back();
m_points.push_back(point);
int m = m_points.size();
std::vector<TThickPoint> points;
points.push_back(m_points[m - 3]);
points.push_back(m_points[m - 2]);
points.push_back(m_points[m - 1]);
TRect bbox = m_bluredBrush->getBoundFromPoints(points);
updateWorkAndBackupRasters(bbox);
m_tileSaver->save(bbox);
m_bluredBrush->addArc(points[0], points[1], points[2], 1, 1);
m_bluredBrush->updateDrawing(ti->getRaster(), m_backupRas, bbox,
m_styleId, m_selective.getValue());
if (!rectUpdated) {
invalidateRect =
ToolUtils::getBounds(points, maxThickness) - rasCenter;
} else {
invalidateRect +=
ToolUtils::getBounds(points, maxThickness) - rasCenter;
}
m_lastRect += bbox;
m_strokeRect += bbox;
}
invalidate(invalidateRect.enlarge(2));
m_lastRect.empty();
}
delete m_bluredBrush;
m_bluredBrush = 0;
if (m_tileSet->getTileCount() > 0) {
TUndoManager::manager()->add(new RasterBluredBrushUndo(
m_tileSet, m_points, m_styleId, m_selective.getValue(),
simLevel.getPointer(), frameId, m_rasThickness.getValue().second,
m_hardness.getValue() * 0.01, m_isFrameCreated, m_isLevelCreated));
}
}
delete m_tileSaver;
m_tileSaver = 0;
/*-- FIdを指定して、描画中にフレームが動いても、
  描画開始時のFidのサムネイルが更新されるようにする。--*/
notifyImageChanged(frameId);
m_strokeRect.empty();
ToolUtils::updateSaveBox();
/*-- 作業中のフレームをリセット --*/
m_workingFrameId = TFrameId();
}
//---------------------------------------------------------------------------------------------------------------
void BrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
struct Locals {
BrushTool *m_this;
void setValue(TDoublePairProperty &prop,
const TDoublePairProperty::Value &value) {
prop.setValue(value);
m_this->onPropertyChanged(prop.getName());
TTool::getApplication()->getCurrentTool()->notifyToolChanged();
}
void addMinMax(TDoublePairProperty &prop, double add) {
if (add == 0.0) return;
const TDoublePairProperty::Range &range = prop.getRange();
TDoublePairProperty::Value value = prop.getValue();
value.first = tcrop(value.first + add, range.first, range.second);
value.second = tcrop(value.second + add, range.first, range.second);
setValue(prop, value);
}
} locals = {this};
switch (e.getModifiersMask()) {
/*--
* Altキー+マウス移動で、ブラシサイズMin/Maxともを変えるCtrlやShiftでは誤操作の恐れがある
* --*/
case TMouseEvent::ALT_KEY: {
// User wants to alter the minimum brush size
const TPointD &diff = pos - m_mousePos;
double add = (fabs(diff.x) > fabs(diff.y)) ? diff.x : diff.y;
locals.addMinMax(
TToonzImageP(getImage(false, 1)) ? m_rasThickness : m_thickness, add);
break;
}
default:
m_brushPos = pos;
break;
}
m_mousePos = pos;
invalidate();
if (m_minThick == 0 && m_maxThick == 0) {
if (m_targetType & TTool::ToonzImage) {
m_minThick = m_rasThickness.getValue().first;
m_maxThick = m_rasThickness.getValue().second;
} else {
m_minThick = m_thickness.getValue().first;
m_maxThick = m_thickness.getValue().second;
}
}
}
//-------------------------------------------------------------------------------------------------------------
void BrushTool::draw() {
/*--ショートカットでのツール切り替え時に赤点が描かれるのを防止する--*/
if (m_minThick == 0 && m_maxThick == 0) return;
TImageP img = getImage(false, 1);
// Draw track
tglColor(m_isPrompting ? TPixel32::Green : m_currentColor);
m_track.drawAllFragments();
if (getApplication()->getCurrentObject()->isSpline()) return;
// Draw the brush outline - change color when the Ink / Paint check is
// activated
if ((ToonzCheck::instance()->getChecks() & ToonzCheck::eInk) ||
(ToonzCheck::instance()->getChecks() & ToonzCheck::ePaint) ||
(ToonzCheck::instance()->getChecks() & ToonzCheck::eInk1))
glColor3d(0.5, 0.8, 0.8);
// normally draw in red
else
glColor3d(1.0, 0.0, 0.0);
if (TToonzImageP ti = img) {
TRasterP ras = ti->getRaster();
int lx = ras->getLx();
int ly = ras->getLy();
drawEmptyCircle(m_brushPos, tround(m_minThick), lx % 2 == 0, ly % 2 == 0,
m_pencil.getValue());
drawEmptyCircle(m_brushPos, tround(m_maxThick), lx % 2 == 0, ly % 2 == 0,
m_pencil.getValue());
} else {
tglDrawCircle(m_brushPos, 0.5 * m_minThick);
tglDrawCircle(m_brushPos, 0.5 * m_maxThick);
}
}
//--------------------------------------------------------------------------------------------------------------
void BrushTool::onEnter() {
TImageP img = getImage(false);
if (TToonzImageP(img)) {
m_minThick = m_rasThickness.getValue().first;
m_maxThick = m_rasThickness.getValue().second;
} else {
m_minThick = m_thickness.getValue().first;
m_maxThick = m_thickness.getValue().second;
}
Application *app = getApplication();
m_styleId = app->getCurrentLevelStyleIndex();
TColorStyle *cs = app->getCurrentLevelStyle();
if (cs) {
TRasterStyleFx *rfx = cs->getRasterStyleFx();
m_active = cs->isStrokeStyle() || (rfx && rfx->isInkStyle());
m_currentColor = cs->getAverageColor();
m_currentColor.m = 255;
} else {
m_currentColor = TPixel32::Black;
}
m_active = img;
}
//----------------------------------------------------------------------------------------------------------
void BrushTool::onLeave() {
m_minThick = 0;
m_maxThick = 0;
}
//----------------------------------------------------------------------------------------------------------
TPropertyGroup *BrushTool::getProperties(int idx) {
if (!m_presetsLoaded) initPresets();
return &m_prop[idx];
}
//----------------------------------------------------------------------------------------------------------
void BrushTool::onImageChanged() {
TToonzImageP ti = (TToonzImageP)getImage(false, 1);
if (!ti || !isEnabled()) return;
setWorkAndBackupImages();
}
//----------------------------------------------------------------------------------------------------------
void BrushTool::setWorkAndBackupImages() {
TToonzImageP ti = (TToonzImageP)getImage(false, 1);
if (!ti) return;
TRasterP ras = ti->getRaster();
TDimension dim = ras->getSize();
double hardness = m_hardness.getValue() * 0.01;
if (hardness == 1.0 && ras->getPixelSize() == 4) {
m_workRas = TRaster32P();
m_backupRas = TRasterCM32P();
} else {
if (!m_workRas || m_workRas->getLx() > dim.lx ||
m_workRas->getLy() > dim.ly)
m_workRas = TRaster32P(dim);
if (!m_backupRas || m_backupRas->getLx() > dim.lx ||
m_backupRas->getLy() > dim.ly)
m_backupRas = TRasterCM32P(dim);
m_strokeRect.empty();
m_lastRect.empty();
}
}
//------------------------------------------------------------------
bool BrushTool::onPropertyChanged(std::string propertyName) {
// Set the following to true whenever a different piece of interface must
// be refreshed - done once at the end.
bool notifyTool = false;
/*--- 変更されたPropertyに合わせて処理を分ける ---*/
/*--- m_thicknessとm_rasThicknessは同じName(="Size:")なので、
扱っている画像がラスタかどうかで区別する---*/
if (propertyName == m_thickness.getName()) {
TImageP img = getImage(false);
if (TToonzImageP(img)) // raster
{
RasterBrushMinSize = m_rasThickness.getValue().first;
RasterBrushMaxSize = m_rasThickness.getValue().second;
m_minThick = m_rasThickness.getValue().first;
m_maxThick = m_rasThickness.getValue().second;
} else // vector
{
VectorBrushMinSize = m_thickness.getValue().first;
VectorBrushMaxSize = m_thickness.getValue().second;
m_minThick = m_thickness.getValue().first;
m_maxThick = m_thickness.getValue().second;
}
} else if (propertyName == m_accuracy.getName()) {
BrushAccuracy = m_accuracy.getValue();
} else if (propertyName == m_smooth.getName()) {
BrushSmooth = m_smooth.getValue();
} else if (propertyName == m_preset.getName()) {
loadPreset();
notifyTool = true;
} else if (propertyName == m_selective.getName()) {
BrushSelective = m_selective.getValue();
} else if (propertyName == m_breakAngles.getName()) {
BrushBreakSharpAngles = m_breakAngles.getValue();
} else if (propertyName == m_pencil.getName()) {
RasterBrushPencilMode = m_pencil.getValue();
} else if (propertyName == m_pressure.getName()) {
BrushPressureSensitivity = m_pressure.getValue();
} else if (propertyName == m_capStyle.getName()) {
VectorCapStyle = m_capStyle.getIndex();
} else if (propertyName == m_joinStyle.getName()) {
VectorJoinStyle = m_joinStyle.getIndex();
} else if (propertyName == m_miterJoinLimit.getName()) {
VectorMiterValue = m_miterJoinLimit.getValue();
}
if (m_targetType & TTool::Vectors) {
if (propertyName == m_joinStyle.getName()) notifyTool = true;
}
if (m_targetType & TTool::ToonzImage) {
if (propertyName == m_hardness.getName()) setWorkAndBackupImages();
if (propertyName == m_hardness.getName() ||
propertyName == m_thickness.getName()) {
m_brushPad = getBrushPad(m_rasThickness.getValue().second,
m_hardness.getValue() * 0.01);
TRectD rect(m_mousePos - TPointD(m_maxThick + 2, m_maxThick + 2),
m_mousePos + TPointD(m_maxThick + 2, m_maxThick + 2));
invalidate(rect);
}
}
if (propertyName != m_preset.getName() &&
m_preset.getValue() != CUSTOM_WSTR) {
m_preset.setValue(CUSTOM_WSTR);
notifyTool = true;
}
if (notifyTool) getApplication()->getCurrentTool()->notifyToolChanged();
return true;
}
//------------------------------------------------------------------
void BrushTool::initPresets() {
if (!m_presetsLoaded) {
// If necessary, load the presets from file
m_presetsLoaded = true;
if (getTargetType() & TTool::Vectors)
m_presetsManager.load(TEnv::getConfigDir() + "brush_vector.txt");
else
m_presetsManager.load(TEnv::getConfigDir() + "brush_toonzraster.txt");
}
// Rebuild the presets property entries
const std::set<BrushData> &presets = m_presetsManager.presets();
m_preset.deleteAllValues();
m_preset.addValue(CUSTOM_WSTR);
std::set<BrushData>::const_iterator it, end = presets.end();
for (it = presets.begin(); it != end; ++it) m_preset.addValue(it->m_name);
}
//----------------------------------------------------------------------------------------------------------
void BrushTool::loadPreset() {
const std::set<BrushData> &presets = m_presetsManager.presets();
std::set<BrushData>::const_iterator it;
it = presets.find(BrushData(m_preset.getValue()));
if (it == presets.end()) return;
const BrushData &preset = *it;
try // Don't bother with RangeErrors
{
if (getTargetType() & TTool::Vectors) {
m_thickness.setValue(
TDoublePairProperty::Value(preset.m_min, preset.m_max));
m_accuracy.setValue(preset.m_acc, true);
m_smooth.setValue(preset.m_smooth, true);
m_breakAngles.setValue(preset.m_breakAngles);
m_pressure.setValue(preset.m_pressure);
m_capStyle.setIndex(preset.m_cap);
m_joinStyle.setIndex(preset.m_join);
m_miterJoinLimit.setValue(preset.m_miter);
} else {
m_rasThickness.setValue(TDoublePairProperty::Value(
std::max(preset.m_min, 1.0), preset.m_max));
m_brushPad =
ToolUtils::getBrushPad(preset.m_max, preset.m_hardness * 0.01);
m_smooth.setValue(preset.m_smooth, true);
m_hardness.setValue(preset.m_hardness, true);
m_selective.setValue(preset.m_selective);
m_pencil.setValue(preset.m_pencil);
m_pressure.setValue(preset.m_pressure);
}
} catch (...) {
}
}
//------------------------------------------------------------------
void BrushTool::addPreset(QString name) {
// Build the preset
BrushData preset(name.toStdWString());
if (getTargetType() & TTool::Vectors) {
preset.m_min = m_thickness.getValue().first;
preset.m_max = m_thickness.getValue().second;
} else {
preset.m_min = m_rasThickness.getValue().first;
preset.m_max = m_rasThickness.getValue().second;
}
preset.m_acc = m_accuracy.getValue();
preset.m_smooth = m_smooth.getValue();
preset.m_hardness = m_hardness.getValue();
preset.m_selective = m_selective.getValue();
preset.m_pencil = m_pencil.getValue();
preset.m_breakAngles = m_breakAngles.getValue();
preset.m_pressure = m_pressure.getValue();
preset.m_cap = m_capStyle.getIndex();
preset.m_join = m_joinStyle.getIndex();
preset.m_miter = m_miterJoinLimit.getValue();
// Pass the preset to the manager
m_presetsManager.addPreset(preset);
// Reinitialize the associated preset enum
initPresets();
// Set the value to the specified one
m_preset.setValue(preset.m_name);
}
//------------------------------------------------------------------
void BrushTool::removePreset() {
std::wstring name(m_preset.getValue());
if (name == CUSTOM_WSTR) return;
m_presetsManager.removePreset(name);
initPresets();
// No parameter change, and set the preset value to custom
m_preset.setValue(CUSTOM_WSTR);
}
//------------------------------------------------------------------
/*! Brush、PaintBrush、EraserToolがPencilModeのときにTrueを返す
*/
bool BrushTool::isPencilModeActive() {
return getTargetType() == TTool::ToonzImage && m_pencil.getValue();
}
//==========================================================================================================
// Tools instantiation
BrushTool vectorPencil("T_Brush", TTool::Vectors | TTool::EmptyTarget);
BrushTool toonzPencil("T_Brush", TTool::ToonzImage | TTool::EmptyTarget);
//*******************************************************************************
// Brush Data implementation
//*******************************************************************************
BrushData::BrushData()
: m_name()
, m_min(0.0)
, m_max(0.0)
, m_acc(0.0)
, m_smooth(0.0)
, m_hardness(0.0)
, m_opacityMin(0.0)
, m_opacityMax(0.0)
, m_selective(false)
, m_pencil(false)
, m_breakAngles(false)
, m_pressure(false)
, m_cap(0)
, m_join(0)
, m_miter(0) {}
//----------------------------------------------------------------------------------------------------------
BrushData::BrushData(const std::wstring &name)
: m_name(name)
, m_min(0.0)
, m_max(0.0)
, m_acc(0.0)
, m_smooth(0.0)
, m_hardness(0.0)
, m_opacityMin(0.0)
, m_opacityMax(0.0)
, m_selective(false)
, m_pencil(false)
, m_breakAngles(false)
, m_pressure(false)
, m_cap(0)
, m_join(0)
, m_miter(0) {}
//----------------------------------------------------------------------------------------------------------
void BrushData::saveData(TOStream &os) {
os.openChild("Name");
os << m_name;
os.closeChild();
os.openChild("Thickness");
os << m_min << m_max;
os.closeChild();
os.openChild("Accuracy");
os << m_acc;
os.closeChild();
os.openChild("Smooth");
os << m_smooth;
os.closeChild();
os.openChild("Hardness");
os << m_hardness;
os.closeChild();
os.openChild("Opacity");
os << m_opacityMin << m_opacityMax;
os.closeChild();
os.openChild("Selective");
os << (int)m_selective;
os.closeChild();
os.openChild("Pencil");
os << (int)m_pencil;
os.closeChild();
os.openChild("Break_Sharp_Angles");
os << (int)m_breakAngles;
os.closeChild();
os.openChild("Pressure_Sensitivity");
os << (int)m_pressure;
os.closeChild();
os.openChild("Cap");
os << m_cap;
os.closeChild();
os.openChild("Join");
os << m_join;
os.closeChild();
os.openChild("Miter");
os << m_miter;
os.closeChild();
}
//----------------------------------------------------------------------------------------------------------
void BrushData::loadData(TIStream &is) {
std::string tagName;
int val;
while (is.matchTag(tagName)) {
if (tagName == "Name")
is >> m_name, is.matchEndTag();
else if (tagName == "Thickness")
is >> m_min >> m_max, is.matchEndTag();
else if (tagName == "Accuracy")
is >> m_acc, is.matchEndTag();
else if (tagName == "Smooth")
is >> m_smooth, is.matchEndTag();
else if (tagName == "Hardness")
is >> m_hardness, is.matchEndTag();
else if (tagName == "Opacity")
is >> m_opacityMin >> m_opacityMax, is.matchEndTag();
else if (tagName == "Selective")
is >> val, m_selective = val, is.matchEndTag();
else if (tagName == "Pencil")
is >> val, m_pencil = val, is.matchEndTag();
else if (tagName == "Break_Sharp_Angles")
is >> val, m_breakAngles = val, is.matchEndTag();
else if (tagName == "Pressure_Sensitivity")
is >> val, m_pressure = val, is.matchEndTag();
else if (tagName == "Cap")
is >> m_cap, is.matchEndTag();
else if (tagName == "Join")
is >> m_join, is.matchEndTag();
else if (tagName == "Miter")
is >> m_miter, is.matchEndTag();
else
is.skipCurrentTag();
}
}
//----------------------------------------------------------------------------------------------------------
PERSIST_IDENTIFIER(BrushData, "BrushData");
//*******************************************************************************
// Brush Preset Manager implementation
//*******************************************************************************
void BrushPresetManager::load(const TFilePath &fp) {
m_fp = fp;
std::string tagName;
BrushData data;
TIStream is(m_fp);
try {
while (is.matchTag(tagName)) {
if (tagName == "version") {
VersionNumber version;
is >> version.first >> version.second;
is.setVersion(version);
is.matchEndTag();
} else if (tagName == "brushes") {
while (is.matchTag(tagName)) {
if (tagName == "brush") {
is >> data, m_presets.insert(data);
is.matchEndTag();
} else
is.skipCurrentTag();
}
is.matchEndTag();
} else
is.skipCurrentTag();
}
} catch (...) {
}
}
//------------------------------------------------------------------
void BrushPresetManager::save() {
TOStream os(m_fp);
os.openChild("version");
os << 1 << 19;
os.closeChild();
os.openChild("brushes");
std::set<BrushData>::iterator it, end = m_presets.end();
for (it = m_presets.begin(); it != end; ++it) {
os.openChild("brush");
os << (TPersist &)*it;
os.closeChild();
}
os.closeChild();
}
//------------------------------------------------------------------
void BrushPresetManager::addPreset(const BrushData &data) {
m_presets.erase(data); // Overwriting insertion
m_presets.insert(data);
save();
}
//------------------------------------------------------------------
void BrushPresetManager::removePreset(const std::wstring &name) {
m_presets.erase(BrushData(name));
save();
}