diff --git a/toonz/sources/include/tools/tooloptions.h b/toonz/sources/include/tools/tooloptions.h index 428c5fb..d1ee09a 100644 --- a/toonz/sources/include/tools/tooloptions.h +++ b/toonz/sources/include/tools/tooloptions.h @@ -476,6 +476,21 @@ protected slots: //============================================================================= // +// FullColorFillToolOptionsBox +// +//============================================================================= + +class FullColorFillToolOptionsBox final : public ToolOptionsBox { + Q_OBJECT + +public: + FullColorFillToolOptionsBox(QWidget *parent, TTool *tool, + TPaletteHandle *pltHandle, + ToolHandle *toolHandle); +}; + +//============================================================================= +// // FillToolOptionsBox // //============================================================================= diff --git a/toonz/sources/include/toonz/fill.h b/toonz/sources/include/toonz/fill.h index 5a4eece..2d62faf 100644 --- a/toonz/sources/include/toonz/fill.h +++ b/toonz/sources/include/toonz/fill.h @@ -81,6 +81,9 @@ void DVAPI rectFillInk(const TRasterCM32P &ras, const TRect &r, int color); void DVAPI fillautoInks(TRasterCM32P &r, TRect &rect, const TRasterCM32P &rbefore, TPalette *plt); +void DVAPI fullColorFill(const TRaster32P &ras, const FillParameters ¶ms, + TTileSaverFullColor *saver = 0); + //============================================================================= //! The class AreaFiller allows to fill a raster area, delimited by rect or //! spline. diff --git a/toonz/sources/include/toonz/ttileset.h b/toonz/sources/include/toonz/ttileset.h index e5dfac9..c2cefcd 100644 --- a/toonz/sources/include/toonz/ttileset.h +++ b/toonz/sources/include/toonz/ttileset.h @@ -146,6 +146,7 @@ public: void add(const TRasterP &ras, TRect rect) override; const Tile *getTile(int index) const; + Tile *editTile(int index) const; TTileSetFullColor *clone() const override; }; diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt index bfef22e..62ae7a2 100644 --- a/toonz/sources/tnztools/CMakeLists.txt +++ b/toonz/sources/tnztools/CMakeLists.txt @@ -3,6 +3,7 @@ set(MOC_HEADERS edittoolgadgets.h filltool.h fullcolorbrushtool.h + fullcolorfilltool.h plastictool.h skeletonsubtools.h tooloptionscontrols.h @@ -58,6 +59,7 @@ set(SOURCES filltool.cpp fullcolorbrushtool.cpp fullcolorerasertool.cpp + fullcolorfilltool.cpp geometrictool.cpp hooktool.cpp hookselection.cpp diff --git a/toonz/sources/tnztools/fullcolorfilltool.cpp b/toonz/sources/tnztools/fullcolorfilltool.cpp new file mode 100644 index 0000000..5c53980 --- /dev/null +++ b/toonz/sources/tnztools/fullcolorfilltool.cpp @@ -0,0 +1,223 @@ +#include "fullcolorfilltool.h" + +#include "toonz/stage2.h" +#include "tools/cursors.h" +#include "toonz/txshlevelhandle.h" +#include "toonz/trasterimageutils.h" +#include "toonz/ttileset.h" +#include "toonz/ttilesaver.h" +#include "toonz/levelproperties.h" +#include "toonz/preferences.h" +#include "toonz/txsheethandle.h" + +#include "tools/toolhandle.h" +#include "tools/toolutils.h" + +#include "tenv.h" +#include "tpalette.h" +#include "tsystem.h" + +using namespace ToolUtils; + +TEnv::IntVar FullColorMinFillDepth("InknpaintFullColorMinFillDepth", 4); +TEnv::IntVar FullColorMaxFillDepth("InknpaintFullColorMaxFillDepth", 12); + +namespace { + +//============================================================================= +// FullColorFillUndo +//----------------------------------------------------------------------------- + +class FullColorFillUndo final : public TFullColorRasterUndo { + FillParameters m_params; + bool m_saveboxOnly; + +public: + FullColorFillUndo(TTileSetFullColor *tileSet, const FillParameters ¶ms, + TXshSimpleLevel *sl, const TFrameId &fid, bool saveboxOnly) + : TFullColorRasterUndo(tileSet, sl, fid, false, false, 0) + , m_params(params) + , m_saveboxOnly(saveboxOnly) {} + + void redo() const override { + TRasterImageP image = getImage(); + if (!image) return; + TRaster32P r; + if (m_saveboxOnly) { + TRectD temp = image->getBBox(); + TRect ttemp = convert(temp); + r = image->getRaster()->extract(ttemp); + } else + r = image->getRaster(); + + fullColorFill(r, m_params); + + TTool::Application *app = TTool::getApplication(); + if (app) { + app->getCurrentXsheet()->notifyXsheetChanged(); + notifyImageChanged(); + } + } + + int getSize() const override { + return sizeof(*this) + TFullColorRasterUndo::getSize(); + } + + QString getToolName() override { + return QString("Fill Tool : %1") + .arg(QString::fromStdWString(m_params.m_fillType)); + } + int getHistoryType() override { return HistoryType::FillTool; } +}; + +//============================================================================= +// doFill +//----------------------------------------------------------------------------- + +void doFill(const TImageP &img, const TPointD &pos, FillParameters ¶ms, + bool isShiftFill, TXshSimpleLevel *sl, const TFrameId &fid) { + TTool::Application *app = TTool::getApplication(); + if (!app || !sl) return; + + if (TRasterImageP ri = TRasterImageP(img)) { + TPoint offs(0, 0); + TRaster32P ras = ri->getRaster(); + // only accept 32bpp images for now + if (!ras.getPointer() || ras->isEmpty()) return; + + ras->lock(); + + TTileSetFullColor *tileSet = new TTileSetFullColor(ras->getSize()); + TTileSaverFullColor tileSaver(ras, tileSet); + TDimension imageSize = ras->getSize(); + TPointD p(imageSize.lx % 2 ? 0.0 : 0.5, imageSize.ly % 2 ? 0.0 : 0.5); + + /*-- params.m_p = convert(pos-p)�ł́A�}�C�i�X���W�ł��ꂪ������ --*/ + TPointD tmp_p = pos - p; + params.m_p = TPoint((int)floor(tmp_p.x + 0.5), (int)floor(tmp_p.y + 0.5)); + + params.m_p += ras->getCenter(); + params.m_p -= offs; + params.m_shiftFill = isShiftFill; + + TRect rasRect(ras->getSize()); + if (!rasRect.contains(params.m_p)) { + ras->unlock(); + return; + } + + fullColorFill(ras, params, &tileSaver); + + if (tileSaver.getTileSet()->getTileCount() != 0) { + static int count = 0; + TSystem::outputDebug("RASTERFILL" + std::to_string(count++) + "\n"); + if (offs != TPoint()) + for (int i = 0; i < tileSet->getTileCount(); i++) { + TTileSet::Tile *t = tileSet->editTile(i); + t->m_rasterBounds = t->m_rasterBounds + offs; + } + TUndoManager::manager()->add( + new FullColorFillUndo(tileSet, params, sl, fid, + Preferences::instance()->getFillOnlySavebox())); + } + + sl->getProperties()->setDirtyFlag(true); + + ras->unlock(); + } + + TTool *t = app->getCurrentTool()->getTool(); + if (t) t->notifyImageChanged(); +} +}; + +//============================================================================= +// FullColorFillTool +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +FullColorFillTool::FullColorFillTool() + : TTool("T_Fill"), m_fillDepth("Fill Depth", 0, 15, 4, 12) { + bind(TTool::RasterImage); + m_prop.bind(m_fillDepth); +} + +void FullColorFillTool::updateTranslation() { + m_fillDepth.setQStringName(tr("Fill Depth")); +} + +FillParameters FullColorFillTool::getFillParameters() const { + FillParameters params; + int styleId = TTool::getApplication()->getCurrentLevelStyleIndex(); + params.m_styleId = styleId; + params.m_minFillDepth = (int)m_fillDepth.getValue().first; + params.m_maxFillDepth = (int)m_fillDepth.getValue().second; + + if (m_level) params.m_palette = m_level->getPalette(); + return params; +} + +void FullColorFillTool::leftButtonDown(const TPointD &pos, + const TMouseEvent &e) { + m_clickPoint = pos; + TXshLevel *xl = TTool::getApplication()->getCurrentLevel()->getLevel(); + m_level = xl ? xl->getSimpleLevel() : 0; + FillParameters params = getFillParameters(); + doFill(getImage(true), pos, params, e.isShiftPressed(), m_level.getPointer(), + getCurrentFid()); + invalidate(); +} + +void FullColorFillTool::leftButtonDrag(const TPointD &pos, + const TMouseEvent &e) { + FillParameters params = getFillParameters(); + if (m_clickPoint == pos) return; + if (!m_level || !params.m_palette) return; + TImageP img = getImage(true); + TPixel32 fillColor = + params.m_palette->getStyle(params.m_styleId)->getMainColor(); + if (TRasterImageP ri = img) { + TRaster32P ras = ri->getRaster(); + if (!ras) return; + TPointD center = ras->getCenterD(); + TPoint ipos = convert(pos + center); + if (!ras->getBounds().contains(ipos)) return; + TPixel32 pix = ras->pixels(ipos.y)[ipos.x]; + if (pix == fillColor) { + invalidate(); + return; + } + } else + return; + doFill(img, pos, params, e.isShiftPressed(), m_level.getPointer(), + getCurrentFid()); + invalidate(); +} + +bool FullColorFillTool::onPropertyChanged(std::string propertyName) { + // Fill Depth + if (propertyName == m_fillDepth.getName()) { + FullColorMinFillDepth = (int)m_fillDepth.getValue().first; + FullColorMaxFillDepth = (int)m_fillDepth.getValue().second; + } + return true; +} + +void FullColorFillTool::onActivate() { + static bool firstTime = true; + if (firstTime) { + m_fillDepth.setValue(TDoublePairProperty::Value(FullColorMinFillDepth, + FullColorMaxFillDepth)); + firstTime = false; + } +} + +int FullColorFillTool::getCursorId() const { + int ret = ToolCursor::FillCursor; + if (ToonzCheck::instance()->getChecks() & ToonzCheck::eBlackBg) + ret = ret | ToolCursor::Ex_Negate; + return ret; +} + +FullColorFillTool FullColorRasterFillTool; \ No newline at end of file diff --git a/toonz/sources/tnztools/fullcolorfilltool.h b/toonz/sources/tnztools/fullcolorfilltool.h new file mode 100644 index 0000000..1dd979c --- /dev/null +++ b/toonz/sources/tnztools/fullcolorfilltool.h @@ -0,0 +1,45 @@ +#pragma once + +#ifndef FULLCOLORFILLTOOL_H +#define FULLCOLORFILLTOOL_H + +// TnzCore includes +#include "tproperty.h" + +// TnzTools includes +#include "tools/tool.h" +#include "toonz/fill.h" +#include "toonz/txshsimplelevel.h" + +#include +#include + +class FullColorFillTool final : public QObject, public TTool { + Q_DECLARE_TR_FUNCTIONS(FullColorFillTool) + + TXshSimpleLevelP m_level; + TDoublePairProperty m_fillDepth; + TPropertyGroup m_prop; + TPointD m_clickPoint; + +public: + FullColorFillTool(); + + ToolType getToolType() const override { return TTool::LevelWriteTool; } + + void updateTranslation() override; + + TPropertyGroup *getProperties(int targetType) override { return &m_prop; } + + FillParameters getFillParameters() const; + + void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override; + void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) override; + + bool onPropertyChanged(std::string propertyName) override; + + void onActivate() override; + int getCursorId() const override; +}; + +#endif // FULLCOLORFILLTOOL_H \ No newline at end of file diff --git a/toonz/sources/tnztools/tooloptions.cpp b/toonz/sources/tnztools/tooloptions.cpp index d814c5b..871c9f0 100644 --- a/toonz/sources/tnztools/tooloptions.cpp +++ b/toonz/sources/tnztools/tooloptions.cpp @@ -1647,6 +1647,25 @@ void PaintbrushToolOptionsBox::onColorModeChanged(int index) { //============================================================================= // +// FullColorFillToolOptionsBox +// +//============================================================================= + +FullColorFillToolOptionsBox::FullColorFillToolOptionsBox( + QWidget *parent, TTool *tool, TPaletteHandle *pltHandle, + ToolHandle *toolHandle) + : ToolOptionsBox(parent) { + TPropertyGroup *props = tool->getProperties(0); + assert(props->getPropertyCount() > 0); + + ToolOptionControlBuilder builder(this, tool, pltHandle, toolHandle); + if (tool && tool->getProperties(0)) tool->getProperties(0)->accept(builder); + + m_layout->addStretch(0); +} + +//============================================================================= +// // FillToolOptionsBox // //============================================================================= @@ -2808,9 +2827,13 @@ void ToolOptions::onToolSwitched() { panel = new TypeToolOptionsBox(0, tool, currPalette, currTool); else if (tool->getName() == T_PaintBrush) panel = new PaintbrushToolOptionsBox(0, tool, currPalette, currTool); - else if (tool->getName() == T_Fill) - panel = new FillToolOptionsBox(0, tool, currPalette, currTool); - else if (tool->getName() == T_Eraser) + else if (tool->getName() == T_Fill) { + if (tool->getTargetType() & TTool::RasterImage) + panel = + new FullColorFillToolOptionsBox(0, tool, currPalette, currTool); + else + panel = new FillToolOptionsBox(0, tool, currPalette, currTool); + } else if (tool->getName() == T_Eraser) panel = new EraserToolOptionsBox(0, tool, currPalette, currTool); else if (tool->getName() == T_Tape) panel = new TapeToolOptionsBox(0, tool, currPalette, currTool); diff --git a/toonz/sources/toonzlib/fill.cpp b/toonz/sources/toonzlib/fill.cpp index 902a853..7072667 100644 --- a/toonz/sources/toonzlib/fill.cpp +++ b/toonz/sources/toonzlib/fill.cpp @@ -170,7 +170,7 @@ void fillRow(const TRasterCM32P &r, const TPoint &p, int &xa, int &xb, //----------------------------------------------------------------------------- void findSegment(const TRaster32P &r, const TPoint &p, int &xa, int &xb, - const TPixel32 &color) { + const TPixel32 &color, const int fillDepth = 254) { int matte, oldmatte; TPixel32 *pix, *pix0, *limit, *tmp_limit; @@ -185,7 +185,7 @@ void findSegment(const TRaster32P &r, const TPoint &p, int &xa, int &xb, for (; pix <= limit; pix++) { if (*pix == color) break; matte = pix->m; - if (matte < oldmatte || matte == 255) break; + if (matte < oldmatte || matte > fillDepth) break; oldmatte = matte; } if (matte == 0) { @@ -206,7 +206,7 @@ void findSegment(const TRaster32P &r, const TPoint &p, int &xa, int &xb, for (; pix >= limit; pix--) { if (*pix == color) break; matte = pix->m; - if (matte < oldmatte || matte == 255) break; + if (matte < oldmatte || matte > fillDepth) break; oldmatte = matte; } if (matte == 0) { @@ -221,6 +221,77 @@ void findSegment(const TRaster32P &r, const TPoint &p, int &xa, int &xb, } //----------------------------------------------------------------------------- +// Used when the clicked pixel is solid or semi-transparent. +// Check if the fill is stemmed at the target pixel. +// Note that RGB values are used for checking the difference, not Alpha value. + +bool doesStemFill(const TPixel32 &clickColor, const TPixel32 *targetPix, + const int fillDepth2) { + // stop if the target pixel is transparent + if (targetPix->m == 0) return true; + // check difference of RGB values is larger than fillDepth + int dr = (int)clickColor.r - (int)targetPix->r; + int dg = (int)clickColor.g - (int)targetPix->g; + int db = (int)clickColor.b - (int)targetPix->b; + return (dr * dr + dg * dg + db * db) > + fillDepth2; // condition for "stem" the fill +} + +//----------------------------------------------------------------------------- + +void fullColorFindSegment(const TRaster32P &r, const TPoint &p, int &xa, + int &xb, const TPixel32 &color, + const TPixel32 &clickedPosColor, + const int fillDepth) { + if (clickedPosColor.m == 0) { + findSegment(r, p, xa, xb, color, fillDepth); + return; + } + + TPixel32 *pix, *pix0, *limit; + // check to the right + TPixel32 *line = r->pixels(p.y); + + pix0 = line + p.x; // seed pixel + pix = pix0; + limit = line + r->getBounds().x1; // right end + + TPixel32 oldPix = *pix; + + int fillDepth2 = fillDepth * fillDepth; + + for (; pix <= limit; pix++) { + // break if the target pixel is with the same as filling color + if (*pix == color) break; + // continue if the target pixel is the same as the previous one + if (*pix == oldPix) continue; + + if (doesStemFill(clickedPosColor, pix, fillDepth2)) break; + + // store pixel color in case if the next pixel is with the same color + oldPix = *pix; + } + xb = p.x + pix - pix0 - 1; + + // check to the left + pix = pix0; // seed pixel + limit = line + r->getBounds().x0; // left end + oldPix = *pix; + for (; pix >= limit; pix--) { + // break if the target pixel is with the same as filling color + if (*pix == color) break; + // continue if the target pixel is the same as the previous one + if (*pix == oldPix) continue; + + if (doesStemFill(clickedPosColor, pix, fillDepth2)) break; + + // store pixel color in case if the next pixel is with the same color + oldPix = *pix; + } + xa = p.x + pix - pix0 + 1; +} + +//----------------------------------------------------------------------------- class FillSeed { public: @@ -272,6 +343,23 @@ void insertSegment(std::vector> &segments, } //----------------------------------------------------------------------------- + +bool floodCheck(const TPixel32 &clickColor, const TPixel32 *targetPix, + const TPixel32 *oldPix, const int fillDepth) { + auto fullColorThreshMatte = [](int matte, int fillDepth) -> int { + return (matte <= fillDepth) ? matte : 255; + }; + + if (clickColor.m == 0) { + int oldMatte = fullColorThreshMatte(oldPix->m, fillDepth); + int matte = fullColorThreshMatte(targetPix->m, fillDepth); + return matte >= oldMatte && matte != 255; + } + int fillDepth2 = fillDepth * fillDepth; + return !doesStemFill(clickColor, targetPix, fillDepth2); +} + +//----------------------------------------------------------------------------- } // namespace //----------------------------------------------------------------------------- /*-- 戻り値はsaveBoxが更新されたかどうか --*/ @@ -588,3 +676,129 @@ void inkFill(const TRasterCM32P &r, const TPoint &pin, int ink, int searchRay, } r->unlock(); } + +//----------------------------------------------------------------------------- + +void fullColorFill(const TRaster32P &ras, const FillParameters ¶ms, + TTileSaverFullColor *saver) { + int oldy, xa, xb, xc, xd, dy, oldxd, oldxc; + TPixel32 *pix, *limit, *pix0, *oldpix; + int x = params.m_p.x, y = params.m_p.y; + + TRect bbbox = ras->getBounds(); + if (!bbbox.contains(params.m_p)) return; + + TPixel32 clickedPosColor = *(ras->pixels(y) + x); + + TPaletteP plt = params.m_palette; + TPixel32 color = plt->getStyle(params.m_styleId)->getMainColor(); + + if (clickedPosColor == color) return; + + int fillDepth = + params.m_shiftFill ? params.m_maxFillDepth : params.m_minFillDepth; + + assert(fillDepth >= 0 && fillDepth < 16); + TPointD m_firstPoint, m_clickPoint; + + // convert fillDepth range from [0 - 15] to [0 - 255] + fillDepth = (fillDepth << 4) | fillDepth; + + std::stack seeds; + std::map>> segments; + + fullColorFindSegment(ras, params.m_p, xa, xb, color, clickedPosColor, + fillDepth); + + segments[y].push_back(std::pair(xa, xb)); + seeds.push(FillSeed(xa, xb, y, 1)); + seeds.push(FillSeed(xa, xb, y, -1)); + + while (!seeds.empty()) { + FillSeed fs = seeds.top(); + seeds.pop(); + + xa = fs.m_xa; + xb = fs.m_xb; + oldy = fs.m_y; + dy = fs.m_dy; + y = oldy + dy; + // continue if the fill runs over image bounding + if (y > bbbox.y1 || y < bbbox.y0) continue; + // left end of the pixels to be filled + pix = pix0 = ras->pixels(y) + xa; + // right end of the pixels to be filled + limit = ras->pixels(y) + xb; + // left end of the fill seed pixels + oldpix = ras->pixels(oldy) + xa; + + x = xa; + oldxd = (std::numeric_limits::min)(); + oldxc = (std::numeric_limits::max)(); + + // check pixels to right + while (pix <= limit) { + bool test = false; + // check if the target is already in the range to be filled + if (segments.find(y) != segments.end()) + test = isPixelInSegment(segments[y], x); + + if (*pix != color && !test && + floodCheck(clickedPosColor, pix, oldpix, fillDepth)) { + // compute horizontal range to be filled + fullColorFindSegment(ras, TPoint(x, y), xc, xd, color, clickedPosColor, + fillDepth); + // insert segment to be filled + insertSegment(segments[y], std::pair(xc, xd)); + // create new fillSeed to invert direction, if needed + if (xc < xa) seeds.push(FillSeed(xc, xa - 1, y, -dy)); + if (xd > xb) seeds.push(FillSeed(xb + 1, xd, y, -dy)); + if (oldxd >= xc - 1) + oldxd = xd; + else { + if (oldxd >= 0) seeds.push(FillSeed(oldxc, oldxd, y, dy)); + oldxc = xc; + oldxd = xd; + } + // jump to the next pixel to the right end of the range + pix += xd - x + 1; + oldpix += xd - x + 1; + x += xd - x + 1; + } else { + pix++; + oldpix++, x++; + } + } + // insert filled range as new fill seed + if (oldxd > 0) seeds.push(FillSeed(oldxc, oldxd, y, dy)); + } + + // pixels are actually filled here + TPixel32 premultiColor = premultiply(color); + + std::map>>::iterator it; + for (it = segments.begin(); it != segments.end(); it++) { + TPixel32 *line = ras->pixels(it->first); + TPixel32 *refLine = 0; + std::vector> segmentVector = it->second; + for (int i = 0; i < (int)segmentVector.size(); i++) { + std::pair segment = segmentVector[i]; + if (segment.second >= segment.first) { + pix = line + segment.first; + if (saver) { + saver->save( + TRect(segment.first, it->first, segment.second, it->first)); + } + int n; + for (n = 0; n < segment.second - segment.first + 1; n++, pix++) { + if (clickedPosColor.m == 0) + *pix = pix->m == 0 ? color : overPix(color, *pix); + else if (color.m == 0 || color.m == 255) // used for erasing area + *pix = color; + else + *pix = overPix(*pix, premultiColor); + } + } + } + } +} diff --git a/toonz/sources/toonzlib/ttileset.cpp b/toonz/sources/toonzlib/ttileset.cpp index c12288d..fa5e135 100644 --- a/toonz/sources/toonzlib/ttileset.cpp +++ b/toonz/sources/toonzlib/ttileset.cpp @@ -197,6 +197,16 @@ const TTileSetFullColor::Tile *TTileSetFullColor::getTile(int index) const { //------------------------------------------------------------------------------------------ +TTileSetFullColor::Tile *TTileSetFullColor::editTile(int index) const { + assert(0 <= index && index < getTileCount()); + TTileSetFullColor::Tile *tile = + dynamic_cast(m_tiles[index]); + assert(tile); + return tile; +} + +//------------------------------------------------------------------------------------------ + TTileSetFullColor *TTileSetFullColor::clone() const { TTileSetFullColor *tileSet = new TTileSetFullColor(m_srcImageSize); Tiles::const_iterator it = m_tiles.begin();