Merge pull request #3194 from pojienie/add-segment-mode-in-eraser-tool
add segment mode in eraser tool
This commit is contained in:
commit
ee895f62a9
1 changed files with 229 additions and 8 deletions
|
@ -54,6 +54,7 @@ namespace {
|
||||||
#define RECT_ERASE L"Rectangular"
|
#define RECT_ERASE L"Rectangular"
|
||||||
#define FREEHAND_ERASE L"Freehand"
|
#define FREEHAND_ERASE L"Freehand"
|
||||||
#define POLYLINE_ERASE L"Polyline"
|
#define POLYLINE_ERASE L"Polyline"
|
||||||
|
#define SEGMENT_ERASE L"Segment"
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -341,6 +342,8 @@ private:
|
||||||
|
|
||||||
void eraseRegion(const TVectorImageP vi, TStroke *stroke);
|
void eraseRegion(const TVectorImageP vi, TStroke *stroke);
|
||||||
|
|
||||||
|
bool eraseSegments(const TVectorImageP vi, TStroke *eraseStroke);
|
||||||
|
|
||||||
void multiEraseRect(TFrameId firstFrameId, TFrameId lastFrameId,
|
void multiEraseRect(TFrameId firstFrameId, TFrameId lastFrameId,
|
||||||
TRectD firstRect, TRectD lastRect, bool invert);
|
TRectD firstRect, TRectD lastRect, bool invert);
|
||||||
void doMultiErase(TFrameId &firstFrameId, TFrameId &lastFrameId,
|
void doMultiErase(TFrameId &firstFrameId, TFrameId &lastFrameId,
|
||||||
|
@ -379,6 +382,7 @@ EraserTool::EraserTool()
|
||||||
m_eraseType.addValue(RECT_ERASE);
|
m_eraseType.addValue(RECT_ERASE);
|
||||||
m_eraseType.addValue(FREEHAND_ERASE);
|
m_eraseType.addValue(FREEHAND_ERASE);
|
||||||
m_eraseType.addValue(POLYLINE_ERASE);
|
m_eraseType.addValue(POLYLINE_ERASE);
|
||||||
|
m_eraseType.addValue(SEGMENT_ERASE);
|
||||||
m_prop.bind(m_selective);
|
m_prop.bind(m_selective);
|
||||||
m_prop.bind(m_invertOption);
|
m_prop.bind(m_invertOption);
|
||||||
m_prop.bind(m_multi);
|
m_prop.bind(m_multi);
|
||||||
|
@ -409,6 +413,7 @@ void EraserTool::updateTranslation() {
|
||||||
m_eraseType.setItemUIName(RECT_ERASE, tr("Rectangular"));
|
m_eraseType.setItemUIName(RECT_ERASE, tr("Rectangular"));
|
||||||
m_eraseType.setItemUIName(FREEHAND_ERASE, tr("Freehand"));
|
m_eraseType.setItemUIName(FREEHAND_ERASE, tr("Freehand"));
|
||||||
m_eraseType.setItemUIName(POLYLINE_ERASE, tr("Polyline"));
|
m_eraseType.setItemUIName(POLYLINE_ERASE, tr("Polyline"));
|
||||||
|
m_eraseType.setItemUIName(SEGMENT_ERASE, tr("Segment"));
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -439,7 +444,8 @@ void EraserTool::draw() {
|
||||||
tglDrawCircle(m_brushPos, m_pointSize);
|
tglDrawCircle(m_brushPos, m_pointSize);
|
||||||
}
|
}
|
||||||
if ((m_eraseType.getValue() == FREEHAND_ERASE ||
|
if ((m_eraseType.getValue() == FREEHAND_ERASE ||
|
||||||
m_eraseType.getValue() == POLYLINE_ERASE) &&
|
m_eraseType.getValue() == POLYLINE_ERASE ||
|
||||||
|
m_eraseType.getValue() == SEGMENT_ERASE) &&
|
||||||
m_multi.getValue() && m_firstStroke) {
|
m_multi.getValue() && m_firstStroke) {
|
||||||
TPixel color = blackBg ? TPixel32::White : TPixel32::Red;
|
TPixel color = blackBg ? TPixel32::White : TPixel32::Red;
|
||||||
tglColor(color);
|
tglColor(color);
|
||||||
|
@ -461,7 +467,9 @@ void EraserTool::draw() {
|
||||||
for (UINT i = 0; i < m_polyline.size(); i++) tglVertex(m_polyline[i]);
|
for (UINT i = 0; i < m_polyline.size(); i++) tglVertex(m_polyline[i]);
|
||||||
tglVertex(m_mousePos);
|
tglVertex(m_mousePos);
|
||||||
glEnd();
|
glEnd();
|
||||||
} else if (m_eraseType.getValue() == FREEHAND_ERASE && !m_track.isEmpty()) {
|
} else if ((m_eraseType.getValue() == FREEHAND_ERASE ||
|
||||||
|
m_eraseType.getValue() == SEGMENT_ERASE) &&
|
||||||
|
!m_track.isEmpty()) {
|
||||||
TPixel color = blackBg ? TPixel32::White : TPixel32::Black;
|
TPixel color = blackBg ? TPixel32::White : TPixel32::Black;
|
||||||
tglColor(color);
|
tglColor(color);
|
||||||
glPushMatrix();
|
glPushMatrix();
|
||||||
|
@ -815,7 +823,8 @@ void EraserTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
|
||||||
m_selectingRect.x1 = pos.x + 1;
|
m_selectingRect.x1 = pos.x + 1;
|
||||||
m_selectingRect.y1 = pos.y + 1;
|
m_selectingRect.y1 = pos.y + 1;
|
||||||
invalidate();
|
invalidate();
|
||||||
} else if (m_eraseType.getValue() == FREEHAND_ERASE) {
|
} else if (m_eraseType.getValue() == FREEHAND_ERASE ||
|
||||||
|
m_eraseType.getValue() == SEGMENT_ERASE) {
|
||||||
startFreehand(pos);
|
startFreehand(pos);
|
||||||
} else if (m_eraseType.getValue() == POLYLINE_ERASE) {
|
} else if (m_eraseType.getValue() == POLYLINE_ERASE) {
|
||||||
addPointPolyline(pos);
|
addPointPolyline(pos);
|
||||||
|
@ -838,7 +847,8 @@ void EraserTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) {
|
||||||
} else if (m_eraseType.getValue() == NORMAL_ERASE) {
|
} else if (m_eraseType.getValue() == NORMAL_ERASE) {
|
||||||
if (!m_undo) leftButtonDown(pos, e);
|
if (!m_undo) leftButtonDown(pos, e);
|
||||||
if (TVectorImageP vi = image) erase(vi, pos);
|
if (TVectorImageP vi = image) erase(vi, pos);
|
||||||
} else if (m_eraseType.getValue() == FREEHAND_ERASE) {
|
} else if (m_eraseType.getValue() == FREEHAND_ERASE ||
|
||||||
|
m_eraseType.getValue() == SEGMENT_ERASE) {
|
||||||
freehandDrag(pos);
|
freehandDrag(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -914,7 +924,8 @@ void EraserTool::onImageChanged() {
|
||||||
if (!xshl || m_level.getPointer() != xshl ||
|
if (!xshl || m_level.getPointer() != xshl ||
|
||||||
(m_eraseType.getValue() == RECT_ERASE && m_selectingRect.isEmpty()) ||
|
(m_eraseType.getValue() == RECT_ERASE && m_selectingRect.isEmpty()) ||
|
||||||
((m_eraseType.getValue() == FREEHAND_ERASE ||
|
((m_eraseType.getValue() == FREEHAND_ERASE ||
|
||||||
m_eraseType.getValue() == POLYLINE_ERASE) &&
|
m_eraseType.getValue() == POLYLINE_ERASE ||
|
||||||
|
m_eraseType.getValue() == SEGMENT_ERASE) &&
|
||||||
!m_firstStroke))
|
!m_firstStroke))
|
||||||
resetMulti();
|
resetMulti();
|
||||||
else if (m_firstFrameId == getCurrentFid())
|
else if (m_firstFrameId == getCurrentFid())
|
||||||
|
@ -988,6 +999,18 @@ void EraserTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) {
|
||||||
notifyImageChanged();
|
notifyImageChanged();
|
||||||
}
|
}
|
||||||
m_track.clear();
|
m_track.clear();
|
||||||
|
} else if (m_eraseType.getValue() == SEGMENT_ERASE) {
|
||||||
|
double error = (30.0 / 11) * sqrt(getPixelSize() * getPixelSize());
|
||||||
|
m_stroke = m_track.makeStroke(error);
|
||||||
|
m_stroke->setStyle(1);
|
||||||
|
bool erasedSomething = eraseSegments(vi, m_stroke);
|
||||||
|
if (erasedSomething) {
|
||||||
|
invalidate();
|
||||||
|
notifyImageChanged();
|
||||||
|
}
|
||||||
|
m_track.clear();
|
||||||
|
delete m_stroke;
|
||||||
|
m_stroke = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1191,6 +1214,203 @@ void EraserTool::closePolyline(const TPointD &pos) {
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool doublePairCompare(DoublePair p1, DoublePair p2) {
|
||||||
|
return p1.first < p2.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EraserTool::eraseSegments(const TVectorImageP vi, TStroke *eraseStroke) {
|
||||||
|
if (!vi || !eraseStroke) return false;
|
||||||
|
|
||||||
|
int strokeNumber = vi->getStrokeCount();
|
||||||
|
std::vector<int> touchedStrokeIndex;
|
||||||
|
std::vector<std::vector<double>> touchedStrokeW;
|
||||||
|
std::vector<std::vector<DoublePair>> touchedStrokeRanges;
|
||||||
|
|
||||||
|
// find all touched strokes and where it is touched
|
||||||
|
for (int i = 0; i < strokeNumber; ++i) {
|
||||||
|
std::vector<DoublePair> intersections;
|
||||||
|
std::vector<double> ws;
|
||||||
|
TStroke *stroke = vi->getStroke(i);
|
||||||
|
bool touched = false;
|
||||||
|
|
||||||
|
intersect(eraseStroke, stroke, intersections, false);
|
||||||
|
|
||||||
|
for (auto &intersection : intersections) {
|
||||||
|
touched = true;
|
||||||
|
ws.push_back(intersection.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (touched) {
|
||||||
|
touchedStrokeIndex.push_back(i);
|
||||||
|
touchedStrokeW.push_back(ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the eraser did not touch any strokes, return
|
||||||
|
if (touchedStrokeIndex.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find closest intersections of each end of the touched place of each stroke
|
||||||
|
for (int i = 0; i < touchedStrokeIndex.size(); ++i) {
|
||||||
|
std::vector<DoublePair> range;
|
||||||
|
for (auto w : touchedStrokeW[i]) {
|
||||||
|
std::vector<DoublePair> intersections;
|
||||||
|
double lowerW = 0.0, higherW = 1.0;
|
||||||
|
double higherW0 = 1.0,
|
||||||
|
lowerW1 = 0.0; // these two value are used when the stroke is
|
||||||
|
// self-loop-ed, it assumes touched W is 0 or 1 to
|
||||||
|
// find closet intersection
|
||||||
|
|
||||||
|
int strokeIndex = touchedStrokeIndex[i];
|
||||||
|
TStroke *stroke = vi->getStroke(strokeIndex);
|
||||||
|
|
||||||
|
// check self intersection first
|
||||||
|
intersect(stroke, stroke, intersections, false);
|
||||||
|
for (auto &intersection : intersections) {
|
||||||
|
if (areAlmostEqual(intersection.first, 0, 1e-6)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (areAlmostEqual(intersection.second, 1, 1e-6)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersection.first < w) {
|
||||||
|
lowerW = max(lowerW, intersection.first);
|
||||||
|
} else {
|
||||||
|
higherW = min(higherW, intersection.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersection.second < w) {
|
||||||
|
lowerW = max(lowerW, intersection.second);
|
||||||
|
} else {
|
||||||
|
higherW = min(higherW, intersection.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerW1 = max(lowerW1, intersection.first);
|
||||||
|
higherW0 = min(higherW0, intersection.first);
|
||||||
|
lowerW1 = max(lowerW1, intersection.second);
|
||||||
|
higherW0 = min(higherW0, intersection.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then check intersection with other strokes
|
||||||
|
for (int j = 0; j < strokeNumber; ++j) {
|
||||||
|
if (j == strokeIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TStroke *intersectedStroke = vi->getStroke(j);
|
||||||
|
intersect(stroke, intersectedStroke, intersections, false);
|
||||||
|
for (auto &intersection : intersections) {
|
||||||
|
if (intersection.first < w) {
|
||||||
|
lowerW = max(lowerW, intersection.first);
|
||||||
|
} else {
|
||||||
|
higherW = min(higherW, intersection.first);
|
||||||
|
}
|
||||||
|
lowerW1 = max(lowerW1, intersection.first);
|
||||||
|
higherW0 = min(higherW0, intersection.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
range.push_back(std::make_pair(lowerW, higherW));
|
||||||
|
if (stroke->isSelfLoop()) {
|
||||||
|
if (lowerW == 0.0) {
|
||||||
|
range.push_back(std::make_pair(lowerW1, 1.0));
|
||||||
|
} else if (higherW == 1.0) {
|
||||||
|
range.push_back(std::make_pair(0.0, higherW0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
touchedStrokeRanges.push_back(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge all ranges of the same stroke by using interval merging algorithm
|
||||||
|
for (auto &ranges : touchedStrokeRanges) {
|
||||||
|
std::vector<DoublePair> merged;
|
||||||
|
|
||||||
|
std::sort(ranges.begin(), ranges.end(), doublePairCompare);
|
||||||
|
|
||||||
|
merged.push_back(ranges[0]);
|
||||||
|
for (auto &range : ranges) {
|
||||||
|
if (merged.back().second < range.first &&
|
||||||
|
!areAlmostEqual(merged.back().second, range.first, 1e-3)) {
|
||||||
|
merged.push_back(range);
|
||||||
|
} else if (merged.back().second < range.second) {
|
||||||
|
merged.back().second = range.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges = merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create complement range
|
||||||
|
for (auto &ranges : touchedStrokeRanges) {
|
||||||
|
std::vector<DoublePair> complement;
|
||||||
|
|
||||||
|
double last = 0.0;
|
||||||
|
for (auto &range : ranges) {
|
||||||
|
if (!areAlmostEqual(last, range.first, 1e-3)) {
|
||||||
|
complement.push_back(std::make_pair(last, range.first));
|
||||||
|
}
|
||||||
|
last = range.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!areAlmostEqual(last, 1.0, 1e-3)) {
|
||||||
|
complement.push_back(std::make_pair(last, 1.0));
|
||||||
|
}
|
||||||
|
ranges = complement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate how many lines are added for caculating the final index of added
|
||||||
|
// strokes
|
||||||
|
int added = 0;
|
||||||
|
for (int i = touchedStrokeIndex.size() - 1; i >= 0; --i) {
|
||||||
|
bool willbeJoined = vi->getStroke(touchedStrokeIndex[i])->isSelfLoop() &&
|
||||||
|
touchedStrokeRanges[i][0].first == 0.0 &&
|
||||||
|
touchedStrokeRanges[i].back().second == 1.0;
|
||||||
|
|
||||||
|
added += touchedStrokeRanges[i].size();
|
||||||
|
if (willbeJoined) {
|
||||||
|
--added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the erasing and construct the undo action
|
||||||
|
TXshSimpleLevel *level =
|
||||||
|
TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
|
||||||
|
UndoEraser *undo = new UndoEraser(level, getCurrentFid());
|
||||||
|
for (int i = touchedStrokeIndex.size() - 1; i >= 0; --i) {
|
||||||
|
undo->addOldStroke(touchedStrokeIndex[i],
|
||||||
|
vi->getVIStroke(touchedStrokeIndex[i]));
|
||||||
|
|
||||||
|
if (touchedStrokeRanges[i].size() == 0) {
|
||||||
|
vi->deleteStroke(touchedStrokeIndex[i]);
|
||||||
|
} else {
|
||||||
|
bool willbeJoined = vi->getStroke(touchedStrokeIndex[i])->isSelfLoop() &&
|
||||||
|
touchedStrokeRanges[i][0].first == 0.0 &&
|
||||||
|
touchedStrokeRanges[i].back().second == 1.0;
|
||||||
|
|
||||||
|
vi->splitStroke(touchedStrokeIndex[i], touchedStrokeRanges[i]);
|
||||||
|
|
||||||
|
int size = touchedStrokeRanges[i].size();
|
||||||
|
if (willbeJoined) {
|
||||||
|
--size;
|
||||||
|
}
|
||||||
|
added -= size;
|
||||||
|
for (int j = 0; j < size; ++j) {
|
||||||
|
int finalIndex = touchedStrokeIndex[i] + j - i + added;
|
||||||
|
int currentIndex = touchedStrokeIndex[i] + j;
|
||||||
|
undo->addNewStroke(finalIndex, vi->getVIStroke(currentIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TUndoManager::manager()->add(undo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
//! Cancella stroke presenti in \b vi e contenuti nella regione delimitata da \b
|
//! Cancella stroke presenti in \b vi e contenuti nella regione delimitata da \b
|
||||||
//! stroke.
|
//! stroke.
|
||||||
/*!Vengono cercati gli stroke da cancellare facendo i dovuti controlli sui
|
/*!Vengono cercati gli stroke da cancellare facendo i dovuti controlli sui
|
||||||
|
@ -1317,7 +1537,8 @@ void EraserTool::doMultiErase(TFrameId &firstFrameId, TFrameId &lastFrameId,
|
||||||
// Setto il fid come corrente per notificare il cambiamento dell'immagine
|
// Setto il fid come corrente per notificare il cambiamento dell'immagine
|
||||||
if (app) {
|
if (app) {
|
||||||
if (app->getCurrentFrame()->isEditingScene())
|
if (app->getCurrentFrame()->isEditingScene())
|
||||||
app->getCurrentFrame()->setFrame(startRowInXSheet + fid.getNumber() - 1);
|
app->getCurrentFrame()->setFrame(startRowInXSheet + fid.getNumber() -
|
||||||
|
1);
|
||||||
else
|
else
|
||||||
app->getCurrentFrame()->setFid(fid);
|
app->getCurrentFrame()->setFid(fid);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue