#include "tools/tool.h" #include "tools/toolutils.h" #include "tthreadmessage.h" #include "tgl.h" #include "tstroke.h" #include "tvectorimage.h" #include "tmathutil.h" #include "tools/cursors.h" #include "tproperty.h" #include "toonzqt/imageutils.h" #include "toonz/tframehandle.h" #include "toonz/tcolumnhandle.h" #include "toonz/txshlevelhandle.h" #include "toonz/tobjecthandle.h" #include "toonz/txsheethandle.h" #include "toonz/tstageobject.h" #include "tools/toolhandle.h" #include "toonz/stage2.h" #include "tenv.h" // For Qt translation support #include using namespace ToolUtils; #define POINT2POINT L"Endpoint to Endpoint" #define POINT2LINE L"Endpoint to Line" #define LINE2LINE L"Line to Line" #define NORMAL L"Normal" #define RECT L"Rectangular" TEnv::StringVar TapeMode("InknpaintTapeMode1", "Endpoint to Endpoint"); TEnv::IntVar TapeSmooth("InknpaintTapeSmooth", 0); TEnv::IntVar TapeJoinStrokes("InknpaintTapeJoinStrokes", 0); TEnv::StringVar TapeType("InknpaintTapeType1", "Normal"); TEnv::DoubleVar AutocloseFactor("InknpaintAutocloseFactor", 4.0); namespace { class UndoAutoclose : public ToolUtils::TToolUndo { int m_oldStrokeId1; int m_oldStrokeId2; int m_pos1, m_pos2; VIStroke *m_oldStroke1; VIStroke *m_oldStroke2; vector *m_fillInformation; int m_row; int m_column; vector m_changedStrokes; public: VIStroke *m_newStroke; int m_newStrokeId; int m_newStrokePos; UndoAutoclose(TXshSimpleLevel *level, const TFrameId &frameId, int pos1, int pos2, vector *fillInformation, const vector &changedStrokes) : ToolUtils::TToolUndo(level, frameId), m_oldStroke1(0), m_oldStroke2(0), m_pos1(pos1), m_pos2(pos2), m_newStrokePos(-1), m_fillInformation(fillInformation), m_changedStrokes(changedStrokes) { TVectorImageP image = level->getFrame(m_frameId, true); if (pos1 != -1) { m_oldStrokeId1 = image->getStroke(pos1)->getId(); m_oldStroke1 = cloneVIStroke(image->getVIStroke(pos1)); } if (pos2 != -1 && pos1 != pos2 && image) { m_oldStrokeId2 = image->getStroke(pos2)->getId(); m_oldStroke2 = cloneVIStroke(image->getVIStroke(pos2)); } TTool::Application *app = TTool::getApplication(); if (app) { m_row = app->getCurrentFrame()->getFrame(); m_column = app->getCurrentColumn()->getColumnIndex(); } } ~UndoAutoclose() { deleteVIStroke(m_newStroke); if (m_oldStroke1) deleteVIStroke(m_oldStroke1); if (m_oldStroke2) deleteVIStroke(m_oldStroke2); if (m_isLastInBlock) delete m_fillInformation; } void undo() const { TTool::Application *app = TTool::getApplication(); if (!app) return; if (app->getCurrentFrame()->isEditingScene()) { app->getCurrentColumn()->setColumnIndex(m_column); app->getCurrentFrame()->setFrame(m_row); } else app->getCurrentFrame()->setFid(m_frameId); TVectorImageP image = m_level->getFrame(m_frameId, true); assert(!!image); if (!image) return; QMutexLocker lock(image->getMutex()); int strokeIndex = image->getStrokeIndexById(m_newStrokeId); if (strokeIndex != -1) image->removeStroke(strokeIndex); if (m_oldStroke1) image->insertStrokeAt(cloneVIStroke(m_oldStroke1), m_pos1); if (m_oldStroke2) image->insertStrokeAt(cloneVIStroke(m_oldStroke2), m_pos2); image->notifyChangedStrokes(m_changedStrokes, vector()); if (!m_isLastInBlock) return; for (UINT i = 0; i < m_fillInformation->size(); i++) { TRegion *reg = image->getRegion((*m_fillInformation)[i].m_regionId); assert(reg); if (reg) reg->setStyle((*m_fillInformation)[i].m_styleId); } app->getCurrentXsheet()->notifyXsheetChanged(); notifyImageChanged(); } void redo() const { TTool::Application *app = TTool::getApplication(); if (!app) return; if (app->getCurrentFrame()->isEditingScene()) { app->getCurrentColumn()->setColumnIndex(m_column); app->getCurrentFrame()->setFrame(m_row); } else app->getCurrentFrame()->setFid(m_frameId); TVectorImageP image = m_level->getFrame(m_frameId, true); assert(!!image); if (!image) return; QMutexLocker lock(image->getMutex()); if (m_oldStroke1) { int strokeIndex = image->getStrokeIndexById(m_oldStrokeId1); if (strokeIndex != -1) image->removeStroke(strokeIndex); } if (m_oldStroke2) { int strokeIndex = image->getStrokeIndexById(m_oldStrokeId2); if (strokeIndex != -1) image->removeStroke(strokeIndex); } VIStroke *stroke = cloneVIStroke(m_newStroke); image->insertStrokeAt(stroke, m_pos1 == -1 ? m_newStrokePos : m_pos1, false); image->notifyChangedStrokes(m_changedStrokes, vector()); app->getCurrentXsheet()->notifyXsheetChanged(); notifyImageChanged(); } int getSize() const { return sizeof(*this) + m_fillInformation->capacity() * sizeof(TFilledRegionInf) + 500; } virtual QString getToolName() { return QString("Autoclose Tool"); } int getHistoryType() { return HistoryType::AutocloseTool; } }; } // namespace //============================================================================= // Autoclose Tool //----------------------------------------------------------------------------- class VectorTapeTool : public TTool { Q_DECLARE_TR_FUNCTIONS(VectorTapeTool) bool m_draw; bool m_secondPoint; int m_strokeIndex1, m_strokeIndex2; double m_w1, m_w2, m_pixelSize; TPointD m_pos; bool m_firstTime; TRectD m_selectionRect; TPointD m_startRect; TBoolProperty m_smooth; TBoolProperty m_joinStrokes; TEnumProperty m_mode; TPropertyGroup m_prop; TDoubleProperty m_autocloseFactor; TEnumProperty m_type; public: VectorTapeTool() : TTool("T_Tape"), m_secondPoint(false), m_strokeIndex1(-1), m_strokeIndex2(-1), m_w1(-1.0), m_w2(-1.0), m_pixelSize(1), m_draw(false), m_smooth("Smooth", false) // W_ToolOptions_Smooth , m_joinStrokes("JoinStrokes", false), m_mode("Mode"), m_type("Type"), m_autocloseFactor("Distance", 0.1, 100, 0.5), m_firstTime(true), m_selectionRect(), m_startRect() { bind(TTool::Vectors); m_prop.bind(m_type); m_prop.bind(m_mode); m_prop.bind(m_autocloseFactor); m_prop.bind(m_joinStrokes); m_prop.bind(m_smooth); m_mode.addValue(POINT2POINT); m_mode.addValue(POINT2LINE); m_mode.addValue(LINE2LINE); m_smooth.setId("Smooth"); m_type.addValue(NORMAL); m_type.addValue(RECT); m_mode.setId("Mode"); m_type.setId("Type"); m_joinStrokes.setId("JoinVectors"); m_autocloseFactor.setId("Distance"); } //----------------------------------------------------------------------------- ToolType getToolType() const { return TTool::LevelWriteTool; } //----------------------------------------------------------------------------- bool onPropertyChanged(string propertyName) { TapeMode = toString(m_mode.getValue()); TapeSmooth = (int)(m_smooth.getValue()); wstring s = m_type.getValue(); if (!s.empty()) TapeType = toString(s); TapeJoinStrokes = (int)(m_joinStrokes.getValue()); AutocloseFactor = (double)(m_autocloseFactor.getValue()); m_selectionRect = TRectD(); m_startRect = TPointD(); if (propertyName == "Distance" && (ToonzCheck::instance()->getChecks() & ToonzCheck::eAutoclose)) notifyImageChanged(); return true; } //----------------------------------------------------------------------------- void updateTranslation() { m_smooth.setQStringName(tr("Smooth")); m_joinStrokes.setQStringName(tr("Join Vectors")); m_mode.setQStringName(tr("Mode:")); m_type.setQStringName(tr("Type:")); } //----------------------------------------------------------------------------- TPropertyGroup *getProperties(int targetType) { return &m_prop; } void draw() { TVectorImageP vi(getImage(false)); if (!m_draw) return; if (!vi) return; //TAffine viewMatrix = getViewer()->getViewMatrix(); //glPushMatrix(); //tglMultMatrix(viewMatrix); if (m_type.getValue() == RECT) { if (!m_selectionRect.isEmpty()) ToolUtils::drawRect(m_selectionRect, TPixel::Black, 0x3F33, true); return; } if (m_strokeIndex1 == -1 || m_strokeIndex1 >= (int)(vi->getStrokeCount())) return; tglColor(TPixelD(0.1, 0.9, 0.1)); TStroke *stroke1 = vi->getStroke(m_strokeIndex1); TThickPoint point1 = stroke1->getPoint(m_w1); //TThickPoint point1 = stroke1->getControlPoint(m_cpIndex1); m_pixelSize = getPixelSize(); double thick = tmax(6.0 * m_pixelSize, point1.thick); tglDrawCircle(point1, thick); TThickPoint point2; if (m_secondPoint) { if (m_strokeIndex2 != -1) { TStroke *stroke2 = vi->getStroke(m_strokeIndex2); point2 = stroke2->getPoint(m_w2); thick = tmax(6.0 * m_pixelSize, point2.thick); } else { tglColor(TPixelD(0.6, 0.7, 0.4)); thick = 4 * m_pixelSize; point2 = m_pos; } tglDrawCircle(point2, thick); tglDrawSegment(point1, point2); } //glPopMatrix(); } //----------------------------------------------------------------------------- void mouseMove(const TPointD &pos, const TMouseEvent &) { TVectorImageP vi(getImage(false)); if (!vi) return; //BUTTA e rimetti (Dava problemi con la penna) if (!m_draw) return; //Questa riga potrebbe non essere messa //m_draw=true; //Perche'??? Non basta dargli true in onEnter?? if (m_type.getValue() == RECT) return; double minDistance2 = 10000000000.; m_strokeIndex1 = -1; m_secondPoint = false; int i, strokeNumber = vi->getStrokeCount(); TStroke *stroke; double distance2, outW; TPointD point; int cpMax; for (i = 0; i < strokeNumber; i++) { stroke = vi->getStroke(i); if (m_mode.getValue() == LINE2LINE) { if (stroke->getNearestW(pos, outW, distance2) && distance2 < minDistance2) { minDistance2 = distance2; m_strokeIndex1 = i; if (areAlmostEqual(outW, 0.0, 1e-3)) m_w1 = 0.0; else if (areAlmostEqual(outW, 1.0, 1e-3)) m_w1 = 1.0; else m_w1 = outW; } } else if (!stroke->isSelfLoop()) { point = stroke->getControlPoint(0); if ((distance2 = tdistance2(pos, point)) < minDistance2) { minDistance2 = distance2; m_strokeIndex1 = i; m_w1 = 0.0; //m_cpIndex1=0; } cpMax = stroke->getControlPointCount() - 1; point = stroke->getControlPoint(cpMax); if ((distance2 = tdistance2(pos, point)) < minDistance2) { minDistance2 = distance2; m_strokeIndex1 = i; m_w1 = 1.0; //m_cpIndex1=cpMax; } } } invalidate(); } //----------------------------------------------------------------------------- void leftButtonDown(const TPointD &pos, const TMouseEvent &) { if (!(TVectorImageP)getImage(false)) return; if (m_type.getValue() == RECT) { m_startRect = pos; } else if (m_strokeIndex1 != -1) m_secondPoint = true; } //----------------------------------------------------------------------------- void leftButtonDrag(const TPointD &pos, const TMouseEvent &) { TVectorImageP vi(getImage(false)); if (!vi) return; if (m_type.getValue() == RECT) { m_selectionRect = TRectD(tmin(m_startRect.x, pos.x), tmin(m_startRect.y, pos.y), tmax(m_startRect.x, pos.x), tmax(m_startRect.y, pos.y)); invalidate(); return; } if (m_strokeIndex1 == -1 || !m_secondPoint) return; double minDistance2 = 900 * m_pixelSize; int i, strokeNumber = vi->getStrokeCount(); TStroke *stroke; double distance2, outW; TPointD point; int cpMax; m_strokeIndex2 = -1; for (i = 0; i < strokeNumber; i++) { if (!vi->sameGroup(m_strokeIndex1, i)) continue; stroke = vi->getStroke(i); if (m_mode.getValue() != POINT2POINT) { if (stroke->getNearestW(pos, outW, distance2) && distance2 < minDistance2) { minDistance2 = distance2; m_strokeIndex2 = i; if (areAlmostEqual(outW, 0.0, 1e-3)) m_w2 = 0.0; else if (areAlmostEqual(outW, 1.0, 1e-3)) m_w2 = 1.0; else m_w2 = outW; } } if (!stroke->isSelfLoop()) { cpMax = stroke->getControlPointCount() - 1; if (!(m_strokeIndex1 == i && (m_w1 == 0.0 || cpMax < 3))) { point = stroke->getControlPoint(0); if ((distance2 = tdistance2(pos, point)) < minDistance2) { minDistance2 = distance2; m_strokeIndex2 = i; m_w2 = 0.0; } } if (!(m_strokeIndex1 == i && (m_w1 == 1.0 || cpMax < 3))) { point = stroke->getControlPoint(cpMax); if ((distance2 = tdistance2(pos, point)) < minDistance2) { minDistance2 = distance2; m_strokeIndex2 = i; m_w2 = 1.0; } } } } m_pos = pos; invalidate(); } //----------------------------------------------------------------------------- void joinPointToPoint(const TVectorImageP &vi, vector *fillInfo) { int minindex = tmin(m_strokeIndex1, m_strokeIndex2); int maxindex = tmax(m_strokeIndex1, m_strokeIndex2); UndoAutoclose *autoCloseUndo = 0; TUndo *undo = 0; if (TTool::getApplication()->getCurrentObject()->isSpline()) undo = new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline()); else { TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel(); vector v(1); v[0] = minindex; autoCloseUndo = new UndoAutoclose(level, getCurrentFid(), minindex, maxindex, fillInfo, v); } VIStroke *newStroke = vi->joinStroke(m_strokeIndex1, m_strokeIndex2, (m_w1 == 0.0) ? 0 : vi->getStroke(m_strokeIndex1)->getControlPointCount() - 1, (m_w2 == 0.0) ? 0 : vi->getStroke(m_strokeIndex2)->getControlPointCount() - 1, m_smooth.getValue()); if (autoCloseUndo) { autoCloseUndo->m_newStroke = cloneVIStroke(newStroke); autoCloseUndo->m_newStrokeId = vi->getStroke(minindex)->getId(); undo = autoCloseUndo; } vi->notifyChangedStrokes(minindex); notifyImageChanged(); TUndoManager::manager()->add(undo); } //----------------------------------------------------------------------------- void joinPointToLine(const TVectorImageP &vi, vector *fillInfo) { TUndo *undo = 0; UndoAutoclose *autoCloseUndo = 0; if (TTool::getApplication()->getCurrentObject()->isSpline()) undo = new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline()); else { vector v(2); v[0] = m_strokeIndex1; v[1] = m_strokeIndex2; TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel(); autoCloseUndo = new UndoAutoclose(level, getCurrentFid(), m_strokeIndex1, -1, fillInfo, v); } VIStroke *newStroke; newStroke = vi->extendStroke(m_strokeIndex1, vi->getStroke(m_strokeIndex2)->getThickPoint(m_w2), (m_w1 == 0.0) ? 0 : vi->getStroke(m_strokeIndex1)->getControlPointCount() - 1, m_smooth.getValue()); if (autoCloseUndo) { autoCloseUndo->m_newStroke = cloneVIStroke(newStroke); autoCloseUndo->m_newStrokeId = vi->getStroke(m_strokeIndex1)->getId(); undo = autoCloseUndo; } vi->notifyChangedStrokes(m_strokeIndex1); notifyImageChanged(); TUndoManager::manager()->add(undo); } //----------------------------------------------------------------------------- void joinLineToLine(const TVectorImageP &vi, vector *fillInfo) { if (TTool::getApplication()->getCurrentObject()->isSpline()) return; //Caanot add vectros to spline... Spline can be only one vector TThickPoint p1 = vi->getStroke(m_strokeIndex1)->getThickPoint(m_w1); TThickPoint p2 = vi->getStroke(m_strokeIndex2)->getThickPoint(m_w2); UndoAutoclose *autoCloseUndo = 0; vector v(2); v[0] = m_strokeIndex1; v[1] = m_strokeIndex2; TXshSimpleLevel *level = TTool::getApplication()->getCurrentLevel()->getSimpleLevel(); autoCloseUndo = new UndoAutoclose(level, getCurrentFid(), -1, -1, fillInfo, v); vector points(3); points[0] = p1; points[1] = 0.5 * (p1 + p2); points[2] = p2; TStroke *auxStroke = new TStroke(points); auxStroke->setStyle(TTool::getApplication()->getCurrentLevelStyleIndex()); auxStroke->outlineOptions() = vi->getStroke(m_strokeIndex1)->outlineOptions(); int pos = vi->addStrokeToGroup(auxStroke, m_strokeIndex1); if (pos < 0) return; VIStroke *newStroke = vi->getVIStroke(pos); autoCloseUndo->m_newStrokePos = pos; autoCloseUndo->m_newStroke = cloneVIStroke(newStroke); autoCloseUndo->m_newStrokeId = vi->getStroke(pos)->getId(); vi->notifyChangedStrokes(v, vector()); notifyImageChanged(); TUndoManager::manager()->add(autoCloseUndo); } //----------------------------------------------------------------------------- void inline rearrangeClosingPoints(const TVectorImageP &vi, pair &closingPoint, const TPointD &p) { int erasedIndex = tmax(m_strokeIndex1, m_strokeIndex2); int joinedIndex = tmin(m_strokeIndex1, m_strokeIndex2); if (closingPoint.first == joinedIndex) closingPoint.second = vi->getStroke(joinedIndex)->getW(p); else if (closingPoint.first == erasedIndex) { closingPoint.first = joinedIndex; closingPoint.second = vi->getStroke(joinedIndex)->getW(p); } else if (closingPoint.first > erasedIndex) closingPoint.first--; } //------------------------------------------------------------------------------------- #define p2p 1 #define p2l 2 #define l2p 3 #define l2l 4 void tapeRect(const TVectorImageP &vi, const TRectD &rect) { vector *fillInformation = new vector; ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation, rect); bool initUndoBlock = false; vector> startPoints, endPoints; getClosingPoints(rect, m_autocloseFactor.getValue(), vi, startPoints, endPoints); assert(startPoints.size() == endPoints.size()); vector startP(startPoints.size()), endP(startPoints.size()); if (!startPoints.empty()) { TUndoManager::manager()->beginBlock(); for (UINT i = 0; i < startPoints.size(); i++) { startP[i] = vi->getStroke(startPoints[i].first)->getPoint(startPoints[i].second); endP[i] = vi->getStroke(endPoints[i].first)->getPoint(endPoints[i].second); } } for (UINT i = 0; i < startPoints.size(); i++) { m_strokeIndex1 = startPoints[i].first; m_strokeIndex2 = endPoints[i].first; m_w1 = startPoints[i].second; m_w2 = endPoints[i].second; int type = doTape(vi, fillInformation, m_joinStrokes.getValue()); if (type == p2p && m_strokeIndex1 != m_strokeIndex2) { for (UINT j = i + 1; j < startPoints.size(); j++) { rearrangeClosingPoints(vi, startPoints[j], startP[j]); rearrangeClosingPoints(vi, endPoints[j], endP[j]); } } else if (type == p2l || (type == p2p && m_strokeIndex1 == m_strokeIndex2)) { for (UINT j = i + 1; j < startPoints.size(); j++) { if (startPoints[j].first == m_strokeIndex1) startPoints[j].second = vi->getStroke(m_strokeIndex1)->getW(startP[j]); if (endPoints[j].first == m_strokeIndex1) endPoints[j].second = vi->getStroke(m_strokeIndex1)->getW(endP[j]); } } } if (!startPoints.empty()) TUndoManager::manager()->endBlock(); } int doTape(const TVectorImageP &vi, vector *fillInformation, bool joinStrokes) { int type; if (!joinStrokes) type = l2l; else { type = (m_w1 == 0.0 || m_w1 == 1.0) ? ((m_w2 == 0.0 || m_w2 == 1.0) ? p2p : p2l) : ((m_w2 == 0.0 || m_w2 == 1.0) ? l2p : l2l); if (type == l2p) { tswap(m_strokeIndex1, m_strokeIndex2); tswap(m_w1, m_w2); type = p2l; } } switch (type) { case p2p: joinPointToPoint(vi, fillInformation); CASE p2l : joinPointToLine(vi, fillInformation); CASE l2l : joinLineToLine(vi, fillInformation); DEFAULT: assert(false); } return type; } //------------------------------------------------------------------------------- void leftButtonUp(const TPointD &, const TMouseEvent &) { TVectorImageP vi(getImage(true)); if (vi && m_type.getValue() == RECT) { tapeRect(vi, m_selectionRect); m_selectionRect = TRectD(); m_startRect = TPointD(); notifyImageChanged(); invalidate(); return; } if (!vi || m_strokeIndex1 == -1 || !m_secondPoint || m_strokeIndex2 == -1) { m_strokeIndex1 = -1; m_strokeIndex2 = -1; m_w1 = -1.0; m_w2 = -1.0; m_secondPoint = false; return; } QMutexLocker lock(vi->getMutex()); m_secondPoint = false; vector *fillInformation = new vector; ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation, vi->getStroke(m_strokeIndex1)->getBBox() + vi->getStroke(m_strokeIndex2)->getBBox()); doTape(vi, fillInformation, m_joinStrokes.getValue()); invalidate(); m_strokeIndex2 = -1; m_w1 = -1.0; m_w2 = -1.0; } //----------------------------------------------------------------------------- void onEnter() { // getApplication()->editImage(); m_draw = true; m_selectionRect = TRectD(); m_startRect = TPointD(); } void onLeave() { m_draw = false; //m_strokeIndex1=-1; } void onActivate() { if (!m_firstTime) return; wstring s = toWideString(TapeMode.getValue()); if (s != L"") m_mode.setValue(s); s = toWideString(TapeType.getValue()); if (s != L"") m_type.setValue(s); m_autocloseFactor.setValue(AutocloseFactor); m_smooth.setValue(TapeSmooth ? 1 : 0); m_joinStrokes.setValue(TapeJoinStrokes ? 1 : 0); m_firstTime = false; m_selectionRect = TRectD(); m_startRect = TPointD(); } int getCursorId() const { if (ToonzCheck::instance()->getChecks() & ToonzCheck::eBlackBg) return ToolCursor::TapeCursorWhite; else return ToolCursor::TapeCursor; } } vectorTapeTool; //TTool *getAutocloseTool() {return &autocloseTool;}