| |
| |
| #include "brushtool.h" |
| |
| |
| #include "tools/toolhandle.h" |
| #include "tools/toolutils.h" |
| #include "tools/tooloptions.h" |
| #include "bluredbrush.h" |
| |
| |
| #include "toonzqt/dvdialog.h" |
| #include "toonzqt/imageutils.h" |
| |
| |
| #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" |
| |
| |
| #include "tstream.h" |
| #include "tcolorstyles.h" |
| #include "tvectorimage.h" |
| #include "tenv.h" |
| #include "tregion.h" |
| #include "tstroke.h" |
| #include "tgl.h" |
| #include "trop.h" |
| |
| |
| #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::IntVar BrushSelective("InknpaintBrushSelective", 0); |
| TEnv::IntVar BrushBreakSharpAngles("InknpaintBrushBreakSharpAngles", 0); |
| TEnv::IntVar RasterBrushPencilMode("InknpaintRasterBrushPencilMode", 0); |
| TEnv::IntVar BrushPressureSensibility("InknpaintBrushPressureSensibility", 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>" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void split( |
| TStroke *stroke, |
| const std::vector<double> ¶meterValues, |
| 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); |
| double w = parameterValues[i]; |
| stroke->getChunkAndT(w, chunk, t); |
| |
| 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); |
| |
| } |
| |
| 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) { |
| |
| 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); |
| |
| |
| 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(); |
| } |
| |
| 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(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| double curvature(TPointD dp, TPointD ddp) |
| { |
| if (dp == TPointD(0, 0)) |
| return 0; |
| else |
| return fabs(cross(dp, ddp) / pow(norm2(dp), 1.5)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void findMaxCurvPoints( |
| TStroke *stroke, |
| const float &angoloLim, |
| const float &curvMaxLim, |
| std::vector<double> ¶meterValues) |
| { |
| TPointD tg1, tg2; |
| |
| TPointD dp, ddp; |
| |
| 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; |
| |
| |
| if (j > 2) { |
| tg2 = -p0 + p2 + 2 * q; |
| double prod_scal = tg2 * tg1; |
| assert(tg1 != TPointD(0, 0) || tg2 != TPointD(0, 0)); |
| |
| double angolo = acos(prod_scal / (pow(norm2(tg2), 0.5) * pow(norm2(tg1), 0.5))); |
| |
| |
| if (angolo > angoloLim) { |
| double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)), 0); |
| parameterValues.push_back(w); |
| } |
| } |
| tg1 = -p0 + p2 - 2 * q; |
| |
| |
| |
| |
| |
| |
| 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; |
| ddp = -4 * q; |
| 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); |
| |
| if (estremo_int < c_dx && estremo_int < c_sx) { |
| estremo_int = 0; |
| } |
| } |
| double curv_max = estremo_int; |
| |
| |
| |
| dp = -p0 + p2 + 2 * q; |
| double estremo_sx = curvature(dp, ddp); |
| |
| |
| dp = -p0 + p2 - 2 * q; |
| double estremo_dx = curvature(dp, ddp); |
| |
| |
| 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; |
| } |
| |
| |
| if (t >= 0 && t <= 1 && curv_max > curvMaxLim) { |
| double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)), t); |
| parameterValues.push_back(w); |
| } |
| |
| } |
| |
| |
| if ((int)parameterValues.size() > 1) { |
| std::sort(parameterValues.begin(), parameterValues.end()); |
| parameterValues.erase(std::unique(parameterValues.begin(), parameterValues.end()), parameterValues.end()); |
| } |
| } |
| |
| 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; |
| |
| const float curvMaxLim = 0.8; |
| |
| |
| 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(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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); |
| |
| |
| } |
| |
| |
| |
| class RasterBrushUndo : 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 |
| { |
| 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 |
| { |
| return sizeof(*this) + TRasterUndo::getSize(); |
| } |
| QString getToolName() |
| { |
| return QString("Brush Tool"); |
| } |
| int getHistoryType() |
| { |
| return HistoryType::BrushTool; |
| } |
| }; |
| |
| |
| |
| class RasterBluredBrushUndo : 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 |
| { |
| 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 |
| { |
| return sizeof(*this) + TRasterUndo::getSize(); |
| } |
| |
| virtual QString getToolName() |
| { |
| return QString("Brush Tool"); |
| } |
| int getHistoryType() |
| { |
| 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); |
| } |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| 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_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_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_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("PressureSensibility"); |
| |
| 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 ¢re, 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_selective.setQStringName(tr("Selective")); |
| |
| 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(BrushPressureSensibility ? 1 : 0); |
| m_firstTime = false; |
| m_accuracy.setValue(BrushAccuracy); |
| m_hardness.setValue(RasterBrushHardness); |
| } |
| if (m_targetType & TTool::ToonzImage) { |
| m_brushPad = ToolUtils::getBrushPad(m_rasThickness.getValue().second, m_hardness.getValue() * 0.01); |
| setWorkAndBackupImages(); |
| } |
| |
| } |
| |
| |
| |
| void BrushTool::onDeactivate() |
| { |
| |
| 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); |
| } |
| } |
| 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; |
| |
| 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) { |
| |
| |
| 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; |
| } |
| |
| 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; |
| |
| |
| 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()) { |
| |
| if (!m_pencil.getValue()) |
| thickness -= 1.0; |
| |
| m_rasterTrack = new RasterStrokeGenerator(ras, BRUSH, NONE, m_styleId, |
| TThickPoint(pos + convert(ras->getCenter()), thickness), |
| m_selective.getValue(), 0, !m_pencil.getValue()); |
| m_tileSaver->save(m_rasterTrack->getLastRect()); |
| m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue()); |
| } 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_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; |
| |
| |
| if (m_pressure.getValue() && e.m_pressure == 255) |
| thickness = m_rasThickness.getValue().first; |
| |
| m_track.add(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())) { |
| |
| if (!m_pencil.getValue()) |
| thickness -= 1.0; |
| |
| isAdded = m_rasterTrack->add(TThickPoint(pos + rasCenter, thickness)); |
| 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]); |
| } |
| invalidateRect = ToolUtils::getBounds(points, maxThickness) - rasCenter; |
| } |
| } else { |
| |
| assert(m_workRas.getPointer() && m_backupRas.getPointer()); |
| |
| TThickPoint old = m_points.back(); |
| if (norm2(pos - old) < 4) |
| return; |
| |
| TThickPoint point(pos + rasCenter, thickness); |
| 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) { |
| |
| 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; |
| } |
| 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; |
| m_track.add(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(); |
| |
| 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(); |
| |
| 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)) |
| 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::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(); |
| |
| |
| 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; |
| |
| |
| if (m_pressure.getValue() && pressureVal == 255) |
| thickness = m_rasThickness.getValue().first; |
| |
| |
| if (!m_pencil.getValue()) |
| thickness -= 1.0; |
| |
| bool isAdded = m_rasterTrack->add(TThickPoint(pos + convert(ti->getRaster()->getCenter()), thickness)); |
| 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; |
| invalidate(ToolUtils::getBounds(points, maxThickness).enlarge(2) - rasCenter); |
| } |
| |
| 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(); |
| TThickPoint point(pos + rasCenter, thickness); |
| 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()); |
| TRectD invalidateRect = ToolUtils::getBounds(points, maxThickness); |
| invalidate(invalidateRect.enlarge(2) - rasCenter); |
| m_strokeRect += bbox; |
| 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; |
| |
| |
| |
| 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()) { |
| |
| case TMouseEvent::ALT_KEY: { |
| |
| 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); |
| |
| |
| tglColor(m_isPrompting ? TPixel32::Green : m_currentColor); |
| m_track.drawAllFragments(); |
| |
| if (getApplication()->getCurrentObject()->isSpline()) |
| return; |
| |
| |
| if ((ToonzCheck::instance()->getChecks() & ToonzCheck::eInk) || (ToonzCheck::instance()->getChecks() & ToonzCheck::ePaint) || (ToonzCheck::instance()->getChecks() & ToonzCheck::eInk1)) |
| glColor3d(0.5, 0.8, 0.8); |
| |
| 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) |
| { |
| |
| |
| bool notifyTool = false; |
| |
| |
| |
| |
| |
| if (propertyName == m_thickness.getName()) { |
| TImageP img = getImage(false); |
| if (TToonzImageP(img)) |
| { |
| RasterBrushMinSize = m_rasThickness.getValue().first; |
| RasterBrushMaxSize = m_rasThickness.getValue().second; |
| |
| m_minThick = m_rasThickness.getValue().first; |
| m_maxThick = m_rasThickness.getValue().second; |
| } else |
| { |
| 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_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()) { |
| BrushPressureSensibility = 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) { |
| |
| m_presetsLoaded = true; |
| if (getTargetType() & TTool::Vectors) |
| m_presetsManager.load(TEnv::getConfigDir() + "brush_vector.txt"); |
| else |
| m_presetsManager.load(TEnv::getConfigDir() + "brush_toonzraster.txt"); |
| } |
| |
| |
| 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 |
| { |
| if (getTargetType() & TTool::Vectors) { |
| m_thickness.setValue(TDoublePairProperty::Value(preset.m_min, preset.m_max)); |
| m_accuracy.setValue(preset.m_acc, 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_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) |
| { |
| |
| 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_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(); |
| |
| |
| m_presetsManager.addPreset(preset); |
| |
| |
| initPresets(); |
| |
| |
| 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(); |
| |
| |
| m_preset.setValue(CUSTOM_WSTR); |
| } |
| |
| |
| |
| |
| bool BrushTool::isPencilModeActive() |
| { |
| return getTargetType() == TTool::ToonzImage && m_pencil.getValue(); |
| } |
| |
| |
| |
| |
| |
| BrushTool vectorPencil("T_Brush", TTool::Vectors | TTool::EmptyTarget); |
| BrushTool toonzPencil("T_Brush", TTool::ToonzImage | TTool::EmptyTarget); |
| |
| |
| |
| |
| |
| BrushData::BrushData() |
| : m_name(), m_min(0.0), m_max(0.0), m_acc(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_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("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 == "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"); |
| |
| |
| |
| |
| |
| 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); |
| m_presets.insert(data); |
| save(); |
| } |
| |
| |
| |
| void BrushPresetManager::removePreset(const std::wstring &name) |
| { |
| m_presets.erase(BrushData(name)); |
| save(); |
| } |
| |