diff --git a/toonz/sources/common/tfx/binaryFx.cpp b/toonz/sources/common/tfx/binaryFx.cpp index 7c3b3685..99189a1a 100644 --- a/toonz/sources/common/tfx/binaryFx.cpp +++ b/toonz/sources/common/tfx/binaryFx.cpp @@ -684,6 +684,80 @@ public: } }; +//****************************************************************************************** +// ColumnMaskFx definition +// +// This is similar to InFx/OutFx but is not visible to the user. It is meant to +// work with and use the original Mask column logic of the normal viewer so +// that the results are the same. +//****************************************************************************************** + +class ColumnMaskFx final : public TBaseRasterFx { + FX_DECLARATION(ColumnMaskFx) + + TRasterFxPort m_source, m_mask; + +public: + ColumnMaskFx() { + addInputPort("Source", m_source); + addInputPort("Mask", m_mask); + setName(L"ColumnMaskFx"); + enableComputeInFloat(true); + } + + ~ColumnMaskFx() {} + + bool doGetBBox(double frame, TRectD &bbox, + const TRenderSettings &info) override { + if (m_source.isConnected()) { + TRenderSettings maskInfo(info); + maskInfo.m_useMaskBox = true; + + return m_source->doGetBBox(frame, bbox, maskInfo); + } + + bbox = TRectD(); + return false; + } + + bool canHandle(const TRenderSettings &info, double frame) override { + return true; + } + + void doCompute(TTile &tile, double frame, + const TRenderSettings &ri) override { + if (!m_source.isConnected()) return; + + TRenderSettings maskRi(ri); + maskRi.m_useMaskBox = true; + + TTile srcTile; + m_source->allocateAndCompute(srcTile, tile.m_pos, + tile.getRaster()->getSize(), tile.getRaster(), + frame, maskRi); + + maskRi.m_applyMask = true; + m_mask->compute(srcTile, frame, maskRi); + + // Replace original tile with masked tile + TRop::copy(tile.getRaster(), srcTile.getRaster()); + } + + void doDryCompute(TRectD &rect, double frame, + const TRenderSettings &info) override { + if (!m_source.isConnected()) return; + + m_source->dryCompute(rect, frame, info); + + if (m_mask.isConnected()) m_mask->dryCompute(rect, frame, info); + } + + int getMemoryRequirement(const TRectD &rect, double frame, + const TRenderSettings &info) override { + return TRasterFx::memorySize(rect, info.m_bpp); + } +}; + //================================================================== //======================= @@ -706,3 +780,5 @@ FX_IDENTIFIER(BlendFx, "blendFx") FX_IDENTIFIER(ColorDodgeFx, "colorDodgeFx") FX_IDENTIFIER(ColorBurnFx, "colorBurnFx") FX_IDENTIFIER(ScreenFx, "screenFx") +//-------- +FX_IDENTIFIER(ColumnMaskFx, "columnMaskFx") diff --git a/toonz/sources/common/tgl/tstencilcontrol.cpp b/toonz/sources/common/tgl/tstencilcontrol.cpp index 784f6d1d..bde2ea7a 100644 --- a/toonz/sources/common/tgl/tstencilcontrol.cpp +++ b/toonz/sources/common/tgl/tstencilcontrol.cpp @@ -70,6 +70,9 @@ public: Imp(); + void copy(Imp *src); + void resetMask(); + void updateOpenGlState(); void pushMask(); @@ -121,6 +124,34 @@ TStencilControl::Imp::Imp() //--------------------------------------------------------- +void TStencilControl::Imp::copy(Imp *src) { + m_stencilBitCount = src->m_stencilBitCount; + m_pushCount = src->m_pushCount; + m_currentWriting = src->m_currentWriting; + m_enabledMask = src->m_enabledMask; + m_writingMask = src->m_writingMask; + m_inOrOutMask = src->m_inOrOutMask; + m_drawOnScreenMask = src->m_drawOnScreenMask; + m_drawOnlyOnceMask = src->m_drawOnlyOnceMask; + m_virtualState = src->m_virtualState; +} + +//--------------------------------------------------------- + +void TStencilControl::Imp::resetMask() { + m_stencilBitCount = 0; + m_pushCount = 1; + m_currentWriting = -1; + m_enabledMask = 0; + m_writingMask = 0; + m_inOrOutMask = 0; + m_drawOnScreenMask = 0; + m_drawOnlyOnceMask = 0; + m_virtualState = 0; +} + +//--------------------------------------------------------- + TStencilControl *TStencilControl::instance() { StencilControlManager *instance = StencilControlManager::instance(); return instance->getCurrentStencilControl(); @@ -128,7 +159,7 @@ TStencilControl *TStencilControl::instance() { //--------------------------------------------------------- -TStencilControl::TStencilControl() : m_imp(new Imp) {} +TStencilControl::TStencilControl() : m_imp(new Imp) { m_impStack.empty(); } //--------------------------------------------------------- @@ -364,3 +395,46 @@ void TStencilControl::disableMask() { } //--------------------------------------------------------- + +bool TStencilControl::isMaskEnabled() { return m_imp->m_virtualState > 0; } + +//--------------------------------------------------------- + +void TStencilControl::stashMask() { + Imp *currentImp = new Imp; + currentImp->copy(m_imp.get()); + m_impStack.push(currentImp); + + int maskCount = m_imp->m_pushCount; + if (m_imp->m_virtualState == 2) + for (int i = 0; i < maskCount; i++) endMask(); + else if (m_imp->m_virtualState == 1) { + for (int i = 0; i < maskCount; i++) disableMask(); + } +} + +//--------------------------------------------------------- + +void TStencilControl::restoreMask() { + int maskCount = m_impStack.top()->m_pushCount; + if (m_impStack.top()->m_virtualState == 2) { + for (int i = 0; i < maskCount; i++) { + if (i > 0) endMask(); + beginMask(); + } + } else if (m_impStack.top()->m_virtualState == 1) { + for (int i = 1; i <= maskCount; i++) { + beginMask(); + endMask(); + unsigned char currentMask = 1 << (i - 1); + if ((m_impStack.top()->m_inOrOutMask & currentMask) != 0) + enableMask(SHOW_INSIDE); + else + enableMask(SHOW_OUTSIDE); + } + } + + m_imp->copy(m_impStack.top()); + + m_impStack.pop(); +} diff --git a/toonz/sources/common/tvrender/tofflinegl.cpp b/toonz/sources/common/tvrender/tofflinegl.cpp index 0908fe82..bfdd3a04 100644 --- a/toonz/sources/common/tvrender/tofflinegl.cpp +++ b/toonz/sources/common/tvrender/tofflinegl.cpp @@ -685,6 +685,30 @@ void TOfflineGL::draw(TVectorImageP image, const TVectorRenderData &rd, //----------------------------------------------------------------------------- +void TOfflineGL::drawMask(TVectorImageP image, const TVectorRenderData &rd, + bool doInitMatrix) { + checkErrorsByGL; + makeCurrent(); + checkErrorsByGL; + + if (doInitMatrix) { + initMatrix(); + checkErrorsByGL; + } + + if (image) { + checkErrorsByGL; + tglDrawMask(rd, image.getPointer()); + checkErrorsByGL; + } + + checkErrorsByGL; + glFlush(); + checkErrorsByGL; +} + +//----------------------------------------------------------------------------- + void TOfflineGL::draw(TRasterImageP ri, const TAffine &aff, bool doInitMatrix) { makeCurrent(); diff --git a/toonz/sources/common/tvrender/tregionprop.cpp b/toonz/sources/common/tvrender/tregionprop.cpp index c7925ca0..d02f66a6 100644 --- a/toonz/sources/common/tvrender/tregionprop.cpp +++ b/toonz/sources/common/tvrender/tregionprop.cpp @@ -123,6 +123,14 @@ bool computeOutline(const TRegion *region, //------------------------------------------------------------------- void OutlineRegionProp::computeRegionOutline() { + // Avoid overlapping calculations + if (!m_outline.setCalculating()) return; + + // Drawing in progress. Hold calculation until it's done + while (m_outline.isInUse()) { + // Wait + } + int subRegionNumber = getRegion()->getSubregionCount(); TRegionOutline::PointVector app; @@ -142,6 +150,7 @@ void OutlineRegionProp::computeRegionOutline() { } m_outline.m_bbox = getRegion()->getBBox(); + m_outline.unsetCalculating(); } //------------------------------------------------------------------- diff --git a/toonz/sources/common/tvrender/ttessellator.cpp b/toonz/sources/common/tvrender/ttessellator.cpp index 4362b97d..67928cb4 100644 --- a/toonz/sources/common/tvrender/ttessellator.cpp +++ b/toonz/sources/common/tvrender/ttessellator.cpp @@ -199,6 +199,13 @@ void TglTessellator::doTessellate(GLTess &glTess, const TColorFunction *cf, void TglTessellator::doTessellate(GLTess &glTess, const TColorFunction *cf, const bool antiAliasing, TRegionOutline &outline) { + // Calculation in progress. Hold drawing until it's done + while (outline.isCalculating()) { + // Wait + } + + outline.setInUse(); + QMutexLocker sl(&CombineDataGuard); Combine_data.clear(); @@ -221,8 +228,8 @@ void TglTessellator::doTessellate(GLTess &glTess, const TColorFunction *cf, #endif #endif - for (TRegionOutline::Boundary::iterator poly_it = outline.m_exterior.begin(); - poly_it != outline.m_exterior.end(); ++poly_it) { + for (int y = 0; y < outline.m_exterior.size(); y++) { + TRegionOutline::PointVector *poly_it = &outline.m_exterior[y]; #ifdef GLU_VERSION_1_2 gluTessBeginContour(glTess.m_tess); #else @@ -233,9 +240,10 @@ void TglTessellator::doTessellate(GLTess &glTess, const TColorFunction *cf, #endif #endif - for (TRegionOutline::PointVector::iterator it = poly_it->begin(); - it != poly_it->end(); ++it) + for (int z = 0; z < poly_it->size(); z++) { + T3DPointD *it = &poly_it->at(z); gluTessVertex(glTess.m_tess, &(it->x), &(it->x)); + } #ifdef GLU_VERSION_1_2 gluTessEndContour(glTess.m_tess); @@ -282,6 +290,8 @@ void TglTessellator::doTessellate(GLTess &glTess, const TColorFunction *cf, endIt = Combine_data.end(); beginIt = Combine_data.begin(); for (; beginIt != endIt; ++beginIt) delete[](*beginIt); + + outline.unsetInUse(); } //------------------------------------------------------------------ diff --git a/toonz/sources/include/orientation.h b/toonz/sources/include/orientation.h index 5381d7c0..1f7331e4 100644 --- a/toonz/sources/include/orientation.h +++ b/toonz/sources/include/orientation.h @@ -140,7 +140,8 @@ enum class PredefinedRect { // ADD_LEVEL, FOOTER_NOTE_OBJ_AREA, FOOTER_NOTE_AREA, - NAVIGATION_TAG_AREA + NAVIGATION_TAG_AREA, + CLIPPING_MASK_AREA }; enum class PredefinedLine { LOCKED, //! dotted vertical line when cell is locked diff --git a/toonz/sources/include/tfxutil.h b/toonz/sources/include/tfxutil.h index 039d4e2c..39ed29ac 100644 --- a/toonz/sources/include/tfxutil.h +++ b/toonz/sources/include/tfxutil.h @@ -39,6 +39,9 @@ DVAPI void deleteKeyframes(const TFxP &fx, int frame); DVAPI void setKeyframe(const TFxP &dstFx, int dstFrame, const TFxP &srcFx, int srcFrame, bool changedOnly = false); + +DVAPI TFxP makeMask(const TFxP &source, const TFxP &mask); + } // namespace TFxUtil #endif diff --git a/toonz/sources/include/tofflinegl.h b/toonz/sources/include/tofflinegl.h index 66012367..df12171d 100644 --- a/toonz/sources/include/tofflinegl.h +++ b/toonz/sources/include/tofflinegl.h @@ -70,6 +70,9 @@ public: void draw(TVectorImageP image, const TVectorRenderData &rd, bool doInitMatrix = false); + void drawMask(TVectorImageP image, const TVectorRenderData &rd, + bool doInitMatrix = false); + void draw(TRasterImageP image, const TAffine &aff, bool doInitMatrix = false); void flush(); diff --git a/toonz/sources/include/toonz/stage.h b/toonz/sources/include/toonz/stage.h index 96b59652..0414fb61 100644 --- a/toonz/sources/include/toonz/stage.h +++ b/toonz/sources/include/toonz/stage.h @@ -44,6 +44,20 @@ namespace Stage { DVVAR extern const double inch; DVVAR extern const double standardDpi; + +//============================================================================= +/*! The ZPlacement class preserve camera position information. + */ +//============================================================================= + +class ZPlacement { +public: + TAffine m_aff; + double m_z; + ZPlacement() : m_aff(), m_z(0) {} + ZPlacement(const TAffine &aff, double z) : m_aff(aff), m_z(z) {} +}; + class Visitor; struct VisitArgs; diff --git a/toonz/sources/include/toonz/stageplayer.h b/toonz/sources/include/toonz/stageplayer.h index 74d3073a..a0eb08f6 100644 --- a/toonz/sources/include/toonz/stageplayer.h +++ b/toonz/sources/include/toonz/stageplayer.h @@ -118,6 +118,10 @@ public: bool m_currentDrawingOnTop; + bool m_isMask; + bool m_isInvertedMask; + bool m_canRenderMask; + public: Player(); diff --git a/toonz/sources/include/toonz/stagevisitor.h b/toonz/sources/include/toonz/stagevisitor.h index 8ec4f66d..3215493d 100644 --- a/toonz/sources/include/toonz/stagevisitor.h +++ b/toonz/sources/include/toonz/stagevisitor.h @@ -7,6 +7,7 @@ #include "timage.h" #include "trastercm.h" #include "tgl.h" +#include "tstencilcontrol.h" // TnzExt includes #include "ext/plasticvisualsettings.h" @@ -106,7 +107,8 @@ public: // used in Toonz derivative works such as Tab or LineTest. They deal with // OpenGL stencil buffer. - virtual void enableMask() = 0; + virtual void enableMask( + TStencilControl::MaskType maskType = TStencilControl::SHOW_INSIDE) = 0; virtual void disableMask() = 0; virtual void beginMask() = 0; @@ -278,7 +280,8 @@ public: void beginMask() override; void endMask() override; - void enableMask() override; + void enableMask(TStencilControl::MaskType maskType = + TStencilControl::SHOW_INSIDE) override; void disableMask() override; int getNodesCount(); @@ -319,7 +322,8 @@ public: void onRasterImage(TRasterImage *ri, const Stage::Player &data) override{}; void beginMask() override; void endMask() override; - void enableMask() override; + void enableMask(TStencilControl::MaskType maskType = + TStencilControl::SHOW_INSIDE) override; void disableMask() override; int getColumnIndex() const; @@ -343,6 +347,8 @@ class DVAPI OpenGlPainter final : public Visitor // Yep, the name sucks... bool m_isViewer, m_alphaEnabled, m_paletteHasChanged; double m_minZ; + bool m_singleColumnEnabled; + public: OpenGlPainter(const TAffine &viewAff, const TRect &rect, const ImagePainter::VisualSettings &vs, bool isViewer, @@ -360,10 +366,14 @@ public: void beginMask() override; void endMask() override; - void enableMask() override; + void enableMask(TStencilControl::MaskType maskType = + TStencilControl::SHOW_INSIDE) override; void disableMask() override; double getMinZ() const { return m_minZ; } + + void enableSingleColumn(bool on) { m_singleColumnEnabled = on; } + bool isSingleColumnEnabled() const { return m_singleColumnEnabled; } }; } // namespace Stage diff --git a/toonz/sources/include/toonz/txshcolumn.h b/toonz/sources/include/toonz/txshcolumn.h index 38d6c390..e9f8355e 100644 --- a/toonz/sources/include/toonz/txshcolumn.h +++ b/toonz/sources/include/toonz/txshcolumn.h @@ -78,7 +78,9 @@ protected: ePreviewVisible = 0x2, eLocked = 0x8, eMasked = 0x10, - eCamstandTransparent43 = 0x20 // obsoleto, solo per retrocompatibilita' + eCamstandTransparent43 = 0x20, // obsoleto, solo per retrocompatibilita' + eInvertedMask = 0x80, + eRenderMask = 0x100 }; TRaster32P m_icon; @@ -192,11 +194,16 @@ Return true if column is a mask. \sa setMask() */ bool isMask() const; + bool isInvertedMask() const; + bool canRenderMask() const; + /*! Set column status mask to \b on. \sa isMask() */ void setIsMask(bool on); + void setInvertedMask(bool on); + void setCanRenderMask(bool on); virtual bool isEmpty() const { return true; } diff --git a/toonz/sources/include/toonz/txshlevelcolumn.h b/toonz/sources/include/toonz/txshlevelcolumn.h index e5192646..a59355cb 100644 --- a/toonz/sources/include/toonz/txshlevelcolumn.h +++ b/toonz/sources/include/toonz/txshlevelcolumn.h @@ -88,6 +88,8 @@ Return \b TFx. // Used in TCellData::getNumbers bool setNumbers(int row, int rowCount, const TXshCell cells[]); + std::vector getColumnMasks(); + private: // not implemented TXshLevelColumn(const TXshLevelColumn &); diff --git a/toonz/sources/include/toonzqt/gutil.h b/toonz/sources/include/toonzqt/gutil.h index 10a36061..17220964 100644 --- a/toonz/sources/include/toonzqt/gutil.h +++ b/toonz/sources/include/toonzqt/gutil.h @@ -144,14 +144,16 @@ QPixmap DVAPI convertImageToPixmap(const QImage &image); QImage DVAPI generateIconImage(const QString &iconSVGName, qreal opacity = qreal(1.0), QSize newSize = QSize(), - Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio); + Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, + bool useThemeColor = true); //----------------------------------------------------------------------------- QPixmap DVAPI generateIconPixmap(const QString &iconSVGName, qreal opacity = qreal(1.0), QSize newSize = QSize(), - Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio); + Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, + bool useThemeColor = true); //----------------------------------------------------------------------------- diff --git a/toonz/sources/include/trasterfx.h b/toonz/sources/include/trasterfx.h index 2a6125a4..238a636c 100644 --- a/toonz/sources/include/trasterfx.h +++ b/toonz/sources/include/trasterfx.h @@ -167,6 +167,11 @@ public: double m_colorSpaceGamma; + bool m_applyMask; + bool m_invertedMask; + bool m_useMaskBox; + bool m_plasticMask; + public: TRenderSettings(); ~TRenderSettings(); diff --git a/toonz/sources/include/tregionoutline.h b/toonz/sources/include/tregionoutline.h index 43ccaea9..f20ad134 100644 --- a/toonz/sources/include/tregionoutline.h +++ b/toonz/sources/include/tregionoutline.h @@ -13,12 +13,31 @@ public: TRectD m_bbox; - TRegionOutline() : m_doAntialiasing(false) {} + bool m_calculating; + int m_inUse; + + TRegionOutline() + : m_doAntialiasing(false), m_calculating(false), m_inUse(0) {} void clear() { m_exterior.clear(); m_interior.clear(); } + + bool setCalculating() { + if (m_calculating) return false; + m_calculating = true; + return true; + } + void unsetCalculating() { m_calculating = false; } + int isCalculating() { return m_calculating; } + + void setInUse() { m_inUse++; } + void unsetInUse() { + m_inUse--; + if (m_inUse < 0) m_inUse = 0; + } + int isInUse() { return m_inUse > 0; } }; #endif diff --git a/toonz/sources/include/tstencilcontrol.h b/toonz/sources/include/tstencilcontrol.h index faf43fb7..78b55c8e 100644 --- a/toonz/sources/include/tstencilcontrol.h +++ b/toonz/sources/include/tstencilcontrol.h @@ -4,6 +4,7 @@ #define TSTENCILCONTROL_H #include +#include #include "tcommon.h" @@ -34,6 +35,8 @@ private: class Imp; std::unique_ptr m_imp; + std::stack m_impStack; + public: static TStencilControl *instance(); @@ -45,6 +48,11 @@ public: void enableMask(MaskType maskType); void disableMask(); + + bool isMaskEnabled(); + + void stashMask(); + void restoreMask(); }; //------------------------------------------------------ diff --git a/toonz/sources/tnzbase/tfxutil.cpp b/toonz/sources/tnzbase/tfxutil.cpp index 8b78efd0..a9b3b1c5 100644 --- a/toonz/sources/tnzbase/tfxutil.cpp +++ b/toonz/sources/tnzbase/tfxutil.cpp @@ -170,3 +170,26 @@ void TFxUtil::setKeyframe(const TFxP &dstFx, int dstFrame, const TFxP &srcFx, dstParam->assignKeyframe(dstFrame, srcParam, srcFrame, changedOnly); } } + +//------------------------------------------------------------------- + +TFxP TFxUtil::makeMask(const TFxP &source, const TFxP &mask) { + if (!source) + return 0; + else if (!mask) + return source; + + assert(source && mask); + + TFxP columnMaskFx = TFx::create("columnMaskFx"); + if (!columnMaskFx) { + assert(columnMaskFx); + return 0; + } + + if (!columnMaskFx->connect("Source", source.getPointer()) || + !columnMaskFx->connect("Mask", mask.getPointer())) + assert(!"Could not connect ports!"); + + return columnMaskFx; +} diff --git a/toonz/sources/tnzbase/trasterfx.cpp b/toonz/sources/tnzbase/trasterfx.cpp index f051c69e..b50fb2b7 100644 --- a/toonz/sources/tnzbase/trasterfx.cpp +++ b/toonz/sources/tnzbase/trasterfx.cpp @@ -1172,7 +1172,11 @@ TRenderSettings::TRenderSettings() , m_isCanceled(NULL) , m_getFullSizeBBox(false) , m_linearColorSpace(false) - , m_colorSpaceGamma(2.2) {} + , m_colorSpaceGamma(2.2) + , m_applyMask(false) + , m_invertedMask(false) + , m_useMaskBox(false) + , m_plasticMask(false) {} //------------------------------------------------------------------------------ @@ -1217,7 +1221,9 @@ bool TRenderSettings::operator==(const TRenderSettings &rhs) const { m_mark != rhs.m_mark || m_isSwatch != rhs.m_isSwatch || m_userCachable != rhs.m_userCachable || m_linearColorSpace != rhs.m_linearColorSpace || - m_colorSpaceGamma != rhs.m_colorSpaceGamma) + m_colorSpaceGamma != rhs.m_colorSpaceGamma || + m_applyMask != rhs.m_applyMask || m_invertedMask != rhs.m_invertedMask || + m_useMaskBox != rhs.m_useMaskBox || m_plasticMask != rhs.m_plasticMask) return false; return std::equal(m_data.begin(), m_data.end(), rhs.m_data.begin(), areEqual); diff --git a/toonz/sources/toonz/icons/dark/actions/16/clipping_mask.svg b/toonz/sources/toonz/icons/dark/actions/16/clipping_mask.svg new file mode 100644 index 00000000..087a300f --- /dev/null +++ b/toonz/sources/toonz/icons/dark/actions/16/clipping_mask.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff --git a/toonz/sources/toonz/toonz.qrc b/toonz/sources/toonz/toonz.qrc index 79b96bdd..a789b105 100644 --- a/toonz/sources/toonz/toonz.qrc +++ b/toonz/sources/toonz/toonz.qrc @@ -598,6 +598,8 @@ icons/dark/actions/16/table.svg icons/dark/actions/16/motion_path.svg + icons/dark/actions/16/clipping_mask.svg + icons/dark/actions/20/key_off.svg icons/dark/actions/20/key_on.svg diff --git a/toonz/sources/toonz/xshcolumnviewer.cpp b/toonz/sources/toonz/xshcolumnviewer.cpp index ee9210df..70520dac 100644 --- a/toonz/sources/toonz/xshcolumnviewer.cpp +++ b/toonz/sources/toonz/xshcolumnviewer.cpp @@ -66,6 +66,7 @@ #include #include #include +#include #include @@ -186,8 +187,7 @@ class ColumnMaskUndo final : public TUndo { std::string m_name; public: - ColumnMaskUndo(int column, bool isMask, std::string name) - : m_col(column), m_isMask(isMask), m_name(name) {} + ColumnMaskUndo(int column, bool isMask) : m_col(column), m_isMask(isMask) {} ~ColumnMaskUndo() {} void undo() const override { @@ -196,20 +196,6 @@ public: TXshColumn::ColumnType type = column->getColumnType(); if (type != TXshColumn::eLevelType) return; - if (containsVectorLevel(m_col)) { - column->setIsMask(m_isMask); - TApp::instance()->getCurrentScene()->notifySceneChanged(); - TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); - TApp::instance()->getCurrentScene()->setDirtyFlag(true); - } - } - - void redo() const override { - TXshColumn *column = - TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(m_col); - TXshColumn::ColumnType type = column->getColumnType(); - if (type != TXshColumn::eLevelType) return; - if (containsVectorLevel(m_col)) { column->setIsMask(!m_isMask); TApp::instance()->getCurrentScene()->notifySceneChanged(); @@ -218,10 +204,116 @@ public: } } + void redo() const override { + TXshColumn *column = + TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(m_col); + TXshColumn::ColumnType type = column->getColumnType(); + if (type != TXshColumn::eLevelType) return; + + if (containsVectorLevel(m_col)) { + column->setIsMask(m_isMask); + TApp::instance()->getCurrentScene()->notifySceneChanged(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + } + } + int getSize() const override { return sizeof(*this); } QString getHistoryString() override { - QString str = QObject::tr("Toggle vector column as mask. "); + QString str = QObject::tr("Toggle column as mask. "); + return str; + } + int getHistoryType() override { return HistoryType::Xsheet; } +}; + +class ColumnMaskInvertUndo final : public TUndo { + int m_col; + bool m_invertMask; + +public: + ColumnMaskInvertUndo(int column, bool invertMask) + : m_col(column), m_invertMask(invertMask) {} + ~ColumnMaskInvertUndo() {} + + void undo() const override { + TXshColumn *column = + TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(m_col); + TXshColumn::ColumnType type = column->getColumnType(); + if (type != TXshColumn::eLevelType) return; + + if (containsVectorLevel(m_col)) { + column->setInvertedMask(!m_invertMask); + TApp::instance()->getCurrentScene()->notifySceneChanged(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + } + } + + void redo() const override { + TXshColumn *column = + TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(m_col); + TXshColumn::ColumnType type = column->getColumnType(); + if (type != TXshColumn::eLevelType) return; + + if (containsVectorLevel(m_col)) { + column->setInvertedMask(m_invertMask); + TApp::instance()->getCurrentScene()->notifySceneChanged(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + } + } + + int getSize() const override { return sizeof(*this); } + + QString getHistoryString() override { + QString str = QObject::tr("Toggle invert column mask. "); + return str; + } + int getHistoryType() override { return HistoryType::Xsheet; } +}; + +class ColumnMaskRenderUndo final : public TUndo { + int m_col; + bool m_renderMask; + +public: + ColumnMaskRenderUndo(int column, bool renderMask) + : m_col(column), m_renderMask(renderMask) {} + ~ColumnMaskRenderUndo() {} + + void undo() const override { + TXshColumn *column = + TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(m_col); + TXshColumn::ColumnType type = column->getColumnType(); + if (type != TXshColumn::eLevelType) return; + + if (containsVectorLevel(m_col)) { + column->setCanRenderMask(!m_renderMask); + TApp::instance()->getCurrentScene()->notifySceneChanged(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + } + } + + void redo() const override { + TXshColumn *column = + TApp::instance()->getCurrentXsheet()->getXsheet()->getColumn(m_col); + TXshColumn::ColumnType type = column->getColumnType(); + if (type != TXshColumn::eLevelType) return; + + if (containsVectorLevel(m_col)) { + column->setCanRenderMask(m_renderMask); + TApp::instance()->getCurrentScene()->notifySceneChanged(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + } + } + + int getSize() const override { return sizeof(*this); } + + QString getHistoryString() override { + QString str = QObject::tr("Toggle render column mask. "); return str; } int getHistoryType() override { return HistoryType::Xsheet; } @@ -820,8 +912,13 @@ void ColumnArea::DrawHeader::levelColors(QColor &columnColor, if (usage == Reference) { columnColor = m_viewer->getReferenceColumnColor(); dragColor = m_viewer->getReferenceColumnBorderColor(); - } else + } else { m_viewer->getColumnColor(columnColor, dragColor, col, xsh); + + if (column->isMask() && m_viewer->orientation()->isVerticalTimeline() && + m_viewer->getXsheetLayout() == "Minimum") + columnColor = columnColor.lighter(175); + } } void ColumnArea::DrawHeader::soundColors(QColor &columnColor, QColor &dragColor) const { @@ -1360,7 +1457,7 @@ void ColumnArea::DrawHeader::drawParentHandleName() const { void ColumnArea::DrawHeader::drawFilterColor() const { if (col < 0 || isEmpty || column->getColorFilterId() == 0 || column->getSoundColumn() || column->getSoundTextColumn() || - column->getPaletteColumn()) + column->getPaletteColumn() || column->isMask()) return; TPixel32 filterColor = TApp::instance() @@ -1374,6 +1471,35 @@ void ColumnArea::DrawHeader::drawFilterColor() const { p.drawPixmap(filterColorRect, getColorChipIcon(filterColor).pixmap(12, 12)); } +void ColumnArea::DrawHeader::drawClippingMask() const { + if (col < 0 || isEmpty || !column->isMask() || + !o->flag(PredefinedFlag::THUMBNAIL_AREA_VISIBLE)) + return; + + QColor maskColor = QColor(75, 75, 75); + + if (column->getColorFilterId()) { + TPixel32 filterColor = + TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getColorFilterColor(column->getColorFilterId()); + maskColor.setRgb(filterColor.r, filterColor.g, filterColor.b, + filterColor.m); + } + + static QPixmap basePixmap = generateIconPixmap( + "clipping_mask", qreal(1.0), QSize(), Qt::KeepAspectRatio, false); + + ThemeManager &themeManager = ThemeManager::getInstance(); + QPixmap maskPixmap = themeManager.recolorBlackPixels(basePixmap, maskColor); + + QRect clippingMaskArea = + o->rect(PredefinedRect::CLIPPING_MASK_AREA).translated(orig); + p.drawPixmap(clippingMaskArea, maskPixmap); +} + void ColumnArea::DrawHeader::drawSoundIcon(bool isPlaying) const { QRect rect = m_viewer->orientation() ->rect(PredefinedRect::SOUND_ICON) @@ -1670,6 +1796,7 @@ void ColumnArea::drawLevelColumnHead(QPainter &p, int col) { QPixmap iconPixmap = getColumnIcon(col); drawHeader.drawThumbnail(iconPixmap); drawHeader.drawFilterColor(); + drawHeader.drawClippingMask(); drawHeader.drawConfig(); drawHeader.drawPegbarName(); drawHeader.drawParentHandleName(); @@ -1973,7 +2100,8 @@ ColumnTransparencyPopup::ColumnTransparencyPopup(XsheetViewer *viewer, : QWidget(parent, Qt::Popup) , m_viewer(viewer) , m_lockBtn(nullptr) - , m_keepClosed(false) { + , m_keepClosed(false) + , m_keepClosedTimer(0) { setFixedWidth(8 + 78 + 8 + 100 + 8 + 8 + 8 + 7); m_keepClosedTimer = new QTimer(this); @@ -1994,6 +2122,25 @@ m_value->setFont(font);*/ // contents of the combo box will be updated in setColumn m_filterColorCombo = new QComboBox(this); + m_invertMask = new QCheckBox(tr("Invert Mask"), this); + m_invertMask->setCheckable(true); + + m_renderMask = new QCheckBox(tr("Render Mask"), this); + m_renderMask->setCheckable(true); + + m_maskGroupBox = new QGroupBox("Clipping Mask", this); + m_maskGroupBox->setCheckable(true); + QGridLayout *maskLay = new QGridLayout(); + maskLay->setMargin(5); + maskLay->setHorizontalSpacing(6); + maskLay->setVerticalSpacing(6); + maskLay->setColumnStretch(2, 1); + { + maskLay->addWidget(m_invertMask, 0, 0, 1, 2); + maskLay->addWidget(m_renderMask, 1, 0, 1, 2); + } + m_maskGroupBox->setLayout(maskLay); + // Lock button is moved in the popup for Minimum layout QPushButton *lockExtraBtn = nullptr; if (m_viewer->getXsheetLayout() == "Minimum") { @@ -2036,6 +2183,8 @@ m_value->setFont(font);*/ mainLayout->addWidget(m_filterColorCombo, 1, 1, Qt::AlignLeft | Qt::AlignVCenter); + mainLayout->addWidget(m_maskGroupBox, 2, 0, 1, 2); + if (m_lockBtn) { QHBoxLayout *lockLay = new QHBoxLayout(); lockLay->setMargin(0); @@ -2044,7 +2193,7 @@ m_value->setFont(font);*/ lockLay->addWidget(m_lockBtn, 0); lockLay->addWidget(lockExtraBtn, 0); } - mainLayout->addLayout(lockLay, 2, 1, Qt::AlignLeft | Qt::AlignVCenter); + mainLayout->addLayout(lockLay, 3, 1, Qt::AlignLeft | Qt::AlignVCenter); } } setLayout(mainLayout); @@ -2060,6 +2209,14 @@ m_value->setFont(font);*/ ret = ret && connect(m_filterColorCombo, SIGNAL(activated(int)), this, SLOT(onFilterColorChanged())); + + ret = ret && connect(m_maskGroupBox, SIGNAL(clicked(bool)), this, + SLOT(onMaskGroupBoxChanged(bool))); + ret = ret && connect(m_invertMask, SIGNAL(stateChanged(int)), this, + SLOT(onInvertMaskCBChanged(int))); + ret = ret && connect(m_renderMask, SIGNAL(stateChanged(int)), this, + SLOT(onRenderMaskCBChanged(int))); + if (m_lockBtn) ret = ret && connect(m_lockBtn, SIGNAL(clicked(bool)), this, SLOT(onLockButtonClicked(bool))); @@ -2128,6 +2285,44 @@ void ColumnTransparencyPopup::onLockButtonClicked(bool on) { ((ColumnArea *)parent())->update(); } + +//----------------------------------------------------------------------------- + +void ColumnTransparencyPopup::onMaskGroupBoxChanged(bool clicked) { + int col = m_column->getIndex(); + + ColumnMaskUndo *undo = new ColumnMaskUndo(col, m_maskGroupBox->isChecked()); + undo->redo(); + TUndoManager::manager()->add(undo); + update(); +} + +//----------------------------------------------------------------------------- + +void ColumnTransparencyPopup::onInvertMaskCBChanged(int checkedState) { + bool checked = checkedState == Qt::Checked; + + int col = m_column->getIndex(); + + ColumnMaskInvertUndo *undo = new ColumnMaskInvertUndo(col, checked); + undo->redo(); + TUndoManager::manager()->add(undo); + update(); +} + +//----------------------------------------------------------------------------- + +void ColumnTransparencyPopup::onRenderMaskCBChanged(int checkedState) { + bool checked = checkedState == Qt::Checked; + + int col = m_column->getIndex(); + + ColumnMaskRenderUndo *undo = new ColumnMaskRenderUndo(col, checked); + undo->redo(); + TUndoManager::manager()->add(undo); + update(); +} + //---------------------------------------------------------------- void ColumnTransparencyPopup::setColumn(TXshColumn *column) { @@ -2160,6 +2355,24 @@ void ColumnTransparencyPopup::setColumn(TXshColumn *column) { m_filterColorCombo->setCurrentIndex( m_filterColorCombo->findData(m_column->getColorFilterId())); + m_maskGroupBox->blockSignals(true); + m_invertMask->blockSignals(true); + m_renderMask->blockSignals(true); + if (containsVectorLevel(m_column->getIndex())) { + m_maskGroupBox->setChecked(m_column->isMask()); + m_maskGroupBox->setEnabled(true); + m_invertMask->setChecked(m_column->isInvertedMask()); + m_renderMask->setChecked(m_column->canRenderMask()); + } else { + m_maskGroupBox->setChecked(false); + m_maskGroupBox->setEnabled(false); + m_invertMask->setChecked(false); + m_renderMask->setChecked(false); + } + m_maskGroupBox->blockSignals(false); + m_invertMask->blockSignals(false); + m_renderMask->blockSignals(false); + if (m_lockBtn) m_lockBtn->setChecked(m_column->isLocked()); } @@ -3057,24 +3270,8 @@ void ColumnArea::contextMenuEvent(QContextMenuEvent *event) { if (!xsh->isColumnEmpty(col)) { menu.addAction(cmdManager->getAction(MI_ReplaceLevel)); menu.addAction(cmdManager->getAction(MI_ReplaceParentDirectory)); - - // if (containsVectorLevel(col)) { - // menu.addSeparator(); - // QAction *setMask = - // new QAction(tr("Temporary Mask (Not in final render)"), this); - // setMask->setCheckable(true); - // setMask->setChecked(xsh->getColumn(col)->isMask()); - // setMask->setToolTip( - // tr("Only Toonz Vector levels can be used as masks. \n Masks don't - // " - // "show up in final renders.")); - // bool ret = true; - // ret = ret && - // connect(setMask, &QAction::toggled, [=]() { onSetMask(col); }); - // assert(ret); - // menu.addAction(setMask); - //} } + if (o->isVerticalTimeline()) { menu.addSeparator(); QAction *showParentColors = @@ -3122,20 +3319,6 @@ void ColumnArea::contextMenuEvent(QContextMenuEvent *event) { //----------------------------------------------------------------------------- -void ColumnArea::onSetMask(int col) { - TXshColumn *column = m_viewer->getXsheet()->getColumn(m_col); - - std::string name = m_viewer->getXsheet() - ->getStageObject(TStageObjectId::ColumnId(col)) - ->getName(); - ColumnMaskUndo *undo = new ColumnMaskUndo(col, column->isMask(), name); - undo->redo(); - TUndoManager::manager()->add(undo); - update(); -} - -//----------------------------------------------------------------------------- - void ColumnArea::onSubSampling(QAction *action) { int subsampling; if (action == m_subsampling1) diff --git a/toonz/sources/toonz/xshcolumnviewer.h b/toonz/sources/toonz/xshcolumnviewer.h index a184df71..48eabd49 100644 --- a/toonz/sources/toonz/xshcolumnviewer.h +++ b/toonz/sources/toonz/xshcolumnviewer.h @@ -31,6 +31,8 @@ class QPushButton; class Orientation; class TApp; class TXsheet; +class QCheckBox; +class QGroupBox; //============================================================================= namespace XsheetGUI { @@ -222,6 +224,10 @@ class ColumnTransparencyPopup final : public QWidget { XsheetViewer *m_viewer; QPushButton *m_lockBtn; + QGroupBox *m_maskGroupBox; + QCheckBox *m_invertMask; + QCheckBox *m_renderMask; + QTimer *m_keepClosedTimer; bool m_keepClosed; @@ -247,6 +253,10 @@ protected slots: void onFilterColorChanged(); void onLockButtonClicked(bool on); + void onMaskGroupBoxChanged(bool clicked); + void onInvertMaskCBChanged(int checkedState); + void onRenderMaskCBChanged(int checkedState); + void resetKeepClosed(); }; @@ -352,6 +362,7 @@ class ColumnArea final : public QWidget { void drawPegbarName() const; void drawParentHandleName() const; void drawFilterColor() const; + void drawClippingMask() const; void drawSoundIcon(bool isPlaying) const; void drawVolumeControl(double volume) const; @@ -401,7 +412,6 @@ protected slots: void onCameraColumnChangedTriggered(); void onCameraColumnLockToggled(bool); void onXsheetCameraChange(int); - void onSetMask(int); }; //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/xsheetviewer.cpp b/toonz/sources/toonz/xsheetviewer.cpp index 23fb07d0..ea2e15fd 100644 --- a/toonz/sources/toonz/xsheetviewer.cpp +++ b/toonz/sources/toonz/xsheetviewer.cpp @@ -151,7 +151,7 @@ void XsheetViewer::getColumnColor(QColor &color, QColor &sideColor, int index, getCellTypeAndColors(ltype, color, sideColor, cell); } } - if (xsh->getColumn(index)->isMask()) color = QColor(255, 0, 255); +// if (xsh->getColumn(index)->isMask()) color = QColor(255, 0, 255); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzlib/orientation.cpp b/toonz/sources/toonzlib/orientation.cpp index 358e3aa1..fe30dbae 100644 --- a/toonz/sources/toonzlib/orientation.cpp +++ b/toonz/sources/toonzlib/orientation.cpp @@ -500,6 +500,9 @@ TopToBottomOrientation::TopToBottomOrientation() { iconRect(cameraIconArea, ICON_WIDTH, ICON_HEIGHT)); addRect(PredefinedRect::FILTER_COLOR, rect(PredefinedRect::CONFIG)); + + addRect(PredefinedRect::CLIPPING_MASK_AREA, QRect(0, 0, -1, -1)); + addRect(PredefinedRect::SOUND_ICON, QRect(0, 0, -1, -1)); addRect(PredefinedRect::VOLUME_AREA, QRect(0, 0, -1, -1)); addRect(PredefinedRect::PEGBAR_NAME, QRect(0, 0, -1, -1)); @@ -596,6 +599,10 @@ TopToBottomOrientation::TopToBottomOrientation() { addRect(PredefinedRect::FILTER_COLOR, QRect(thumbnail.right() - 14, thumbnail.top() + 3, 12, 12)); + addRect(PredefinedRect::CLIPPING_MASK_AREA, + QRect(thumbnail.right() - ICON_WIDTH, thumbnail.top() + 2, ICON_WIDTH, + ICON_HEIGHT)); + addRect(PredefinedRect::SOUND_ICON, QRect(thumbnailArea.topLeft(), QSize(40, 30)) .adjusted((thumbnailArea.width() / 2) - (40 / 2), @@ -708,6 +715,10 @@ TopToBottomOrientation::TopToBottomOrientation() { addRect(PredefinedRect::FILTER_COLOR, QRect(thumbnail.right() - 14, thumbnail.top() + 3, 12, 12)); + addRect(PredefinedRect::CLIPPING_MASK_AREA, + QRect(thumbnail.right() - ICON_WIDTH, thumbnail.top() + 2, + ICON_WIDTH, ICON_HEIGHT)); + addRect(PredefinedRect::SOUND_ICON, QRect(thumbnailArea.topLeft(), QSize(40, 30)) .adjusted((thumbnailArea.width() / 2) - (40 / 2), @@ -816,6 +827,10 @@ TopToBottomOrientation::TopToBottomOrientation() { addRect(PredefinedRect::FILTER_COLOR, QRect(thumbnail.right() - 14, thumbnail.top() + 3, 12, 12)); + addRect(PredefinedRect::CLIPPING_MASK_AREA, + QRect(thumbnail.right() - ICON_WIDTH, thumbnail.top() + 2, + ICON_WIDTH, ICON_HEIGHT)); + addRect( PredefinedRect::SOUND_ICON, QRect(thumbnailArea.topLeft(), QSize(40, 30)).adjusted(21, 19, 21, 19)); @@ -1306,6 +1321,10 @@ LeftToRightOrientation::LeftToRightOrientation() { addRect(PredefinedRect::FILTER_COLOR, QRect(thumbnail.right() - 14, thumbnail.top() + 3, 12, 12)); + + addRect(PredefinedRect::CLIPPING_MASK_AREA, + QRect(thumbnail.right() - 14, thumbnail.top() + 3, 12, 12)); + addRect(PredefinedRect::PEGBAR_NAME, QRect(0, 0, -1, -1)); // hide addRect(PredefinedRect::PARENT_HANDLE_NAME, QRect(0, 0, -1, -1)); // hide diff --git a/toonz/sources/toonzlib/plasticdeformerfx.cpp b/toonz/sources/toonzlib/plasticdeformerfx.cpp index da0ca5d4..e2524530 100644 --- a/toonz/sources/toonzlib/plasticdeformerfx.cpp +++ b/toonz/sources/toonzlib/plasticdeformerfx.cpp @@ -244,6 +244,8 @@ bool PlasticDeformerFx::buildTextureDataSl(double frame, TRenderSettings &info, TScale(handledAff.a11 / worldLevelToLevelAff.a11) * info.m_affine; } + info.m_invertedMask = texColumn->isInvertedMask(); + return true; } @@ -272,6 +274,10 @@ void PlasticDeformerFx::doCompute(TTile &tile, double frame, // Build texture data TRenderSettings texInfo(info); + texInfo.m_applyMask = false; + texInfo.m_useMaskBox = false; + texInfo.m_plasticMask = info.m_applyMask; + TAffine worldTexLevelToTexLevelAff; if (dynamic_cast(m_port.getFx())) { @@ -355,6 +361,9 @@ void PlasticDeformerFx::doCompute(TTile &tile, double frame, TTile inTile; m_port->allocateAndCompute(inTile, bbox.getP00(), tileSize, TRasterP(), frame, texInfo); + + TTile origTile(tile.getRaster()->clone()); + QOpenGLContext *context; // Draw the textured mesh { @@ -448,6 +457,13 @@ void PlasticDeformerFx::doCompute(TTile &tile, double frame, context->moveToThread(0); context->doneCurrent(); delete context; + + if (info.m_applyMask) { + if (texInfo.m_invertedMask) + TRop::ropout(origTile.getRaster(), tile.getRaster(), tile.getRaster()); + else + TRop::ropin(origTile.getRaster(), tile.getRaster(), tile.getRaster()); + } } assert(glGetError() == GL_NO_ERROR); } diff --git a/toonz/sources/toonzlib/scenefx.cpp b/toonz/sources/toonzlib/scenefx.cpp index 9e4bbc13..86df76a9 100644 --- a/toonz/sources/toonzlib/scenefx.cpp +++ b/toonz/sources/toonzlib/scenefx.cpp @@ -618,6 +618,8 @@ public: // prevent infinite loop. QMap> m_globalControlledFx; + bool m_applyMasks; + public: FxBuilder(ToonzScene *scene, TXsheet *xsh, double frame, int whichLevels, bool isPreview = false, bool expandXSheet = true); @@ -648,7 +650,8 @@ FxBuilder::FxBuilder(ToonzScene *scene, TXsheet *xsh, double frame, , m_whichLevels(whichLevels) , m_isPreview(isPreview) , m_expandXSheet(expandXSheet) - , m_particleDescendentCount(0) { + , m_particleDescendentCount(0) + , m_applyMasks(true) { TStageObjectId cameraId; if (m_isPreview) cameraId = m_xsh->getStageObjectTree()->getCurrentPreviewCameraId(); @@ -918,8 +921,9 @@ PlacedFx FxBuilder::makePF(TLevelColumnFx *lcfx) { pf.m_fx = lcfx; /*-- subXsheetのとき、その中身もBuildFxを実行 --*/ - if (!cell.isEmpty() && !cell.getFrameId().isStopFrame() && - cell.m_level->getChildLevel()) { + bool isSubXsheet = !cell.isEmpty() && !cell.getFrameId().isStopFrame() && + cell.m_level->getChildLevel(); + if (isSubXsheet) { // Treat the sub-xsheet case - build the sub-render-tree and reassign stuff // to pf TXsheet *xsh = cell.m_level->getChildLevel()->getXsheet(); @@ -1001,6 +1005,27 @@ PlacedFx FxBuilder::makePF(TLevelColumnFx *lcfx) { } } + // Add check for/create all ClippingMaskFx here + if (m_applyMasks && (sl || isSubXsheet) && + (!column->isMask() || column->canRenderMask())) { + m_applyMasks = false; + std::vector masks = column->getColumnMasks(); + for (int i = 0; i < masks.size(); i++) { + TXshLevelColumn *mask = masks[i]->getLevelColumn(); + if (!mask) break; + TXshCell maskCell = mask->getCell(m_frame); + if (maskCell.isEmpty() || maskCell.getFrameId() == TFrameId::STOP_FRAME) + continue; + PlacedFx maskPf = makePF(mask->getFx()); + + maskPf.m_fx = getFxWithColumnMovements(maskPf); + maskPf.m_fx = TFxUtil::makeAffine(maskPf.m_fx, pf.m_aff.inv()); + + pf.m_fx = TFxUtil::makeMask(pf.m_fx, maskPf.m_fx); + } + m_applyMasks = true; + } + return pf; } else return PlacedFx(); diff --git a/toonz/sources/toonzlib/stage.cpp b/toonz/sources/toonzlib/stage.cpp index a64609e6..13174445 100644 --- a/toonz/sources/toonzlib/stage.cpp +++ b/toonz/sources/toonzlib/stage.cpp @@ -38,6 +38,7 @@ #include "imagebuilders.h" #include "toonz/toonzscene.h" #include "toonz/sceneproperties.h" +#include "tstencilcontrol.h" // Qt includes #include @@ -119,19 +120,6 @@ bool descending(int i, int j) { return (i > j); } //---------------------------------------------------------------- } // namespace -//============================================================================= -/*! The ZPlacement class preserve camera position information. - */ -//============================================================================= - -class ZPlacement { -public: - TAffine m_aff; - double m_z; - ZPlacement() : m_aff(), m_z(0) {} - ZPlacement(const TAffine &aff, double z) : m_aff(aff), m_z(z) {} -}; - //============================================================================= /*! The class PlayerLt allows a priority operator for player. \n Compare zdepth of two players. @@ -373,7 +361,7 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, TXshLevel *xl = cell.m_level.getPointer(); TXshSimpleLevel *sl = xl ? xl->getSimpleLevel() : 0; // check the previous row for a stop motion layer - if (!xl) { + if (!xl && !column->isMask()) { // Get the last populated cell cell = xsh->getCell(row - 1, col); xl = cell.m_level.getPointer(); @@ -400,10 +388,10 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, if (!columnBehindCamera) return; - bool storePlayer = - sl || (xl->getChildLevel() && - locals::applyPlasticDeform(column.getPointer(), m_vs) && - locals::isMeshDeformed(xsh, pegbar, m_vs)); + bool storePlayer = sl || (column->isMask() && column->isInvertedMask()) || + (xl && xl->getChildLevel() && + locals::applyPlasticDeform(column.getPointer(), m_vs) && + locals::isMeshDeformed(xsh, pegbar, m_vs)); if (storePlayer) { // Build and store a player @@ -430,6 +418,9 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, player.m_opacity = column->getOpacity(); player.m_filterColor = scene->getProperties()->getColorFilterColor(column->getColorFilterId()); + player.m_isMask = column->isMask(); + player.m_isInvertedMask = column->isInvertedMask(); + player.m_canRenderMask = column->canRenderMask(); if (m_subXSheetStack.empty()) { player.m_z = columnZ; @@ -491,7 +482,7 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, player.m_bingoOrder = 10; players.push_back(player); - } else if (TXshChildLevel *cl = xl->getChildLevel()) { + } else if (TXshChildLevel *cl = xl ? xl->getChildLevel() : 0) { int childRow = cell.m_frameId.getNumber() - 1; TXsheet *childXsheet = cl->getXsheet(); TStageObjectId childCameraId = @@ -687,12 +678,20 @@ void StageBuilder::addFrame(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, TXshColumn *column = xsh->getColumn(c); bool isMask = false; if (column && !column->isEmpty() && !column->getSoundColumn()) { - if (!column->isPreviewVisible() && checkPreviewVisibility) continue; + if (!column->isPreviewVisible() && checkPreviewVisibility) { + if (!isMask && column->getColumnType() != TXshColumn::eMeshType) { + while (m_masks.size() > maskCount) m_masks.pop_back(); + } + continue; + } if (column->isCamstandVisible() || includeUnvisible) // se l'"occhietto" non e' chiuso { if (column->isMask()) // se e' una maschera (usate solo in tab pro) { + if (column->canRenderMask()) + addCellWithOnionSkin(players, scene, xsh, row, c, level, + subSheetColIndex); isMask = true; std::vector saveMasks; saveMasks.swap(m_masks); @@ -708,7 +707,7 @@ void StageBuilder::addFrame(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, subSheetColIndex); } } - if (!isMask) { + if (!isMask && column->getColumnType() != TXshColumn::eMeshType) { while (m_masks.size() > maskCount) m_masks.pop_back(); } } @@ -863,7 +862,13 @@ void StageBuilder::visit(PlayerSet &players, Visitor &visitor, bool isPlaying) { visit(*m_maskPool[maskIndex], visitor, isPlaying); visitor.endMask(); masks.push_back(maskIndex); - visitor.enableMask(); + TStencilControl::MaskType maskType = TStencilControl::SHOW_INSIDE; + if (m_maskPool[maskIndex]->size()) { + Player playerMask = m_maskPool[maskIndex]->at(0); + if (playerMask.m_isInvertedMask) + maskType = TStencilControl::SHOW_OUTSIDE; + } + visitor.enableMask(maskType); i++; } } diff --git a/toonz/sources/toonzlib/stageplayer.cpp b/toonz/sources/toonzlib/stageplayer.cpp index f4c446d1..4360ede6 100644 --- a/toonz/sources/toonzlib/stageplayer.cpp +++ b/toonz/sources/toonzlib/stageplayer.cpp @@ -46,7 +46,10 @@ Stage::Player::Player() , m_isPlaying(false) , m_opacity(255) , m_bingoOrder(0) - , m_currentDrawingOnTop(false) {} + , m_currentDrawingOnTop(false) + , m_isMask(false) + , m_isInvertedMask(false) + , m_canRenderMask(false) {} //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzlib/stagevisitor.cpp b/toonz/sources/toonzlib/stagevisitor.cpp index 95881609..e3fc3501 100644 --- a/toonz/sources/toonzlib/stagevisitor.cpp +++ b/toonz/sources/toonzlib/stagevisitor.cpp @@ -327,7 +327,7 @@ void Picker::endMask() {} //----------------------------------------------------------------------------- -void Picker::enableMask() {} +void Picker::enableMask(TStencilControl::MaskType maskType) {} //----------------------------------------------------------------------------- @@ -389,8 +389,8 @@ void RasterPainter::endMask() { TStencilControl::instance()->endMask(); } //! Utilizzato solo per TAB Pro -void RasterPainter::enableMask() { - TStencilControl::instance()->enableMask(TStencilControl::SHOW_INSIDE); +void RasterPainter::enableMask(TStencilControl::MaskType maskType) { + TStencilControl::instance()->enableMask(maskType); } //! Utilizzato solo per TAB Pro void RasterPainter::disableMask() { @@ -785,7 +785,8 @@ static void drawAutocloses(TVectorImage *vi, TVectorRenderData &rd) { onToonzImage(). */ void RasterPainter::onImage(const Stage::Player &player) { - if (m_singleColumnEnabled && !player.m_isCurrentColumn) return; + if (m_singleColumnEnabled && !player.m_isCurrentColumn && !player.m_isMask) + return; // Attempt Plastic-deformed drawing // For now generating icons of plastic-deformed image causes crash as @@ -1141,11 +1142,15 @@ OpenGlPainter::OpenGlPainter(const TAffine &viewAff, const TRect &rect, , m_isViewer(isViewer) , m_alphaEnabled(alphaEnabled) , m_paletteHasChanged(false) - , m_minZ(0) {} + , m_minZ(0) + , m_singleColumnEnabled(false) {} //----------------------------------------------------------------------------- void OpenGlPainter::onImage(const Stage::Player &player) { + if (m_singleColumnEnabled && !player.m_isCurrentColumn && !player.m_isMask) + return; + if (player.m_z < m_minZ) m_minZ = player.m_z; glPushAttrib(GL_ALL_ATTRIB_BITS); @@ -1330,8 +1335,8 @@ void OpenGlPainter::endMask() { --m_maskLevel; TStencilControl::instance()->endMask(); } -void OpenGlPainter::enableMask() { - TStencilControl::instance()->enableMask(TStencilControl::SHOW_INSIDE); +void OpenGlPainter::enableMask(TStencilControl::MaskType maskType) { + TStencilControl::instance()->enableMask(maskType); } void OpenGlPainter::disableMask() { TStencilControl::instance()->disableMask(); diff --git a/toonz/sources/toonzlib/tcolumnfx.cpp b/toonz/sources/toonzlib/tcolumnfx.cpp index 20dba525..51383f31 100644 --- a/toonz/sources/toonzlib/tcolumnfx.cpp +++ b/toonz/sources/toonzlib/tcolumnfx.cpp @@ -42,14 +42,20 @@ #include "toonz/fill.h" #include "toonz/tstageobjectid.h" #include "toonz/tstageobject.h" +#include "toonz/tstageobjecttree.h" #include "toonz/levelproperties.h" #include "toonz/imagemanager.h" #include "toonz/toonzimageutils.h" #include "toonz/tvectorimageutils.h" #include "toonz/preferences.h" #include "toonz/dpiscale.h" +#include "toonz/tcamera.h" #include "imagebuilders.h" +#include "tstencilcontrol.h" +#include "tvectorgl.h" +#include "toonz/glrasterpainter.h" + // 4.6 compatibility - sandor fxs #include "toonz4.6/raster.h" #include "sandor_fxs/blend.h" @@ -985,6 +991,10 @@ void TLevelColumnFx::doCompute(TTile &tile, double frame, const TRenderSettings &info) { if (!m_levelColumn) return; + if (m_levelColumn->isMask() && !info.m_applyMask && + !m_levelColumn->canRenderMask() && !info.m_plasticMask) + return; + // Ensure that a corresponding cell and level exists int row = (int)frame; TXshCell cell = m_levelColumn->getCell(row); @@ -1075,7 +1085,36 @@ void TLevelColumnFx::doCompute(TTile &tile, double frame, if (!m_isCachable) vpalette->mutex()->lock(); vpalette->setFrame((int)frame); - m_offlineContext->draw(vectorImage, rd, true); + + if (info.m_applyMask) { + bool initMatrix = true; + + rd.m_alphaChannel = false; + + TStencilControl *stencil = TStencilControl::instance(); + + glPushAttrib(GL_ALL_ATTRIB_BITS); + tglEnableBlending(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + stencil->beginMask(); + m_offlineContext->drawMask(vectorImage, rd, initMatrix); + stencil->endMask(); + TStencilControl::MaskType maskType = m_levelColumn->isInvertedMask() + ? TStencilControl::SHOW_OUTSIDE + : TStencilControl::SHOW_INSIDE; + stencil->enableMask(maskType); + TRasterP ras = tile.getRaster(); + + TAffine tileAff = TTranslation((ras->getLx() / 2), (ras->getLy() / 2)) * + TScale(1.0 / info.m_shrinkX, 1.0 / info.m_shrinkY); + GLRasterPainter::drawRaster(tileAff, tile.getRaster(), true); + + glPopAttrib(); + + stencil->disableMask(); + } else + m_offlineContext->draw(vectorImage, rd, true); + vpalette->setFrame(oldFrame); if (!m_isCachable) vpalette->mutex()->unlock(); @@ -1464,6 +1503,17 @@ bool TLevelColumnFx::doGetBBox(double frame, TRectD &bBox, } } + if (info.m_useMaskBox) { + TXsheet *xsh = m_levelColumn->getXsheet(); + TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId(); + TStageObject *cameraPegbar = xsh->getStageObject(cameraId); + TCamera *camera = cameraPegbar->getCamera(); + + TRectD imageBBox(bBox); + double enlargement = camera->getRes().lx-imageBBox.x1; + bBox += imageBBox.enlarge(enlargement * dpi); + } + return true; } @@ -1565,7 +1615,21 @@ std::string TLevelColumnFx::getAlias(double frame, rdata += "column_0"; } - return getFxType() + "[" + ::to_string(fp.getWideString()) + "," + rdata + + std::vector masks = m_levelColumn->getColumnMasks(); + if (masks.size()) { + std::string maskAlias = "masked"; + for (int i = 0; i < masks.size(); i++) { + TXshLevelColumn *mask = masks[i]->getLevelColumn(); + if (!mask) break; + + if (mask->isInvertedMask()) maskAlias += "inv"; + if (mask->canRenderMask()) maskAlias += "render"; + break; + } + rdata += maskAlias; + } + + return getFxType() + "[" + ::to_string(fp.getWideString()) + "," + rdata + "]"; } diff --git a/toonz/sources/toonzlib/textureutils.cpp b/toonz/sources/toonzlib/textureutils.cpp index 98f3466f..f678defd 100644 --- a/toonz/sources/toonzlib/textureutils.cpp +++ b/toonz/sources/toonzlib/textureutils.cpp @@ -14,6 +14,7 @@ #include "toonz/toonzscene.h" #include "toonz/imagemanager.h" #include "imagebuilders.h" +#include "tstencilcontrol.h" // TnzCore includes #include "tpalette.h" @@ -191,15 +192,22 @@ DrawableTextureDataP texture_utils::getTextureData(const TXsheet *xsh, xsh->getPlacement(xsh->getStageObjectTree()->getCurrentCameraId(), frame); bbox = (cameraAff.inv() * bbox).enlarge(1.0); -// Render the xsheet on the specified bbox + // Render the xsheet on the specified bbox + bool masked = TStencilControl::instance()->isMaskEnabled(); #ifdef MACOSX + // Must move masks aside when building texture + if (masked) TStencilControl::instance()->stashMask(); xsh->getScene()->renderFrame(tex, frame, xsh, bbox, TAffine()); + if (masked) TStencilControl::instance()->restoreMask(); #else // The call below will change context (I know, it's a shame :( ) TGlContext currentContext = tglGetCurrentContext(); { tglDoneCurrent(currentContext); + // Must move masks aside when building texture + if (masked) TStencilControl::instance()->stashMask(); xsh->getScene()->renderFrame(tex, frame, xsh, bbox, TAffine()); + if (masked) TStencilControl::instance()->restoreMask(); tglMakeCurrent(currentContext); } #endif diff --git a/toonz/sources/toonzlib/toonzscene.cpp b/toonz/sources/toonzlib/toonzscene.cpp index 89f01e00..3d16d9e4 100644 --- a/toonz/sources/toonzlib/toonzscene.cpp +++ b/toonz/sources/toonzlib/toonzscene.cpp @@ -812,7 +812,7 @@ void ToonzScene::renderFrame(const TRaster32P &ras, int row, const TXsheet *xsh, #ifdef MACOSX std::unique_ptr fb( new QOpenGLFramebufferObject(ras->getLx(), ras->getLy())); - + fb->setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); fb->bind(); assert(glGetError() == GL_NO_ERROR); diff --git a/toonz/sources/toonzlib/txshcolumn.cpp b/toonz/sources/toonzlib/txshcolumn.cpp index b3dc3199..ce1bd05a 100644 --- a/toonz/sources/toonzlib/txshcolumn.cpp +++ b/toonz/sources/toonzlib/txshcolumn.cpp @@ -743,6 +743,18 @@ bool TXshColumn::isMask() const { return (m_status & eMasked) != 0; } //----------------------------------------------------------------------------- +bool TXshColumn::isInvertedMask() const { + return (m_status & eInvertedMask) != 0; +} + +//----------------------------------------------------------------------------- + +bool TXshColumn::canRenderMask() const { + return (m_status & eRenderMask) != 0; +} + +//----------------------------------------------------------------------------- + void TXshColumn::setIsMask(bool on) { const int mask = eMasked; if (on) @@ -753,6 +765,26 @@ void TXshColumn::setIsMask(bool on) { //----------------------------------------------------------------------------- +void TXshColumn::setInvertedMask(bool on) { + const int mask = eInvertedMask; + if (on) + m_status |= mask; + else + m_status &= ~mask; +} + +//----------------------------------------------------------------------------- + +void TXshColumn::setCanRenderMask(bool on) { + const int mask = eRenderMask; + if (on) + m_status |= mask; + else + m_status &= ~mask; +} + +//----------------------------------------------------------------------------- + void TXshColumn::resetColumnProperties() { setStatusWord(0); setOpacity(255); diff --git a/toonz/sources/toonzlib/txshlevelcolumn.cpp b/toonz/sources/toonzlib/txshlevelcolumn.cpp index ac2ef6a7..052840a6 100644 --- a/toonz/sources/toonzlib/txshlevelcolumn.cpp +++ b/toonz/sources/toonzlib/txshlevelcolumn.cpp @@ -6,6 +6,7 @@ #include "toonz/tcolumnfxset.h" #include "toonz/tcolumnfx.h" #include "toonz/txshleveltypes.h" +#include "toonz/txsheet.h" #include "tstream.h" @@ -309,4 +310,25 @@ bool TXshLevelColumn::setNumbers(int row, int rowCount, //----------------------------------------------------------------------------- +std::vector TXshLevelColumn::getColumnMasks() { + std::vector masks; + + if (m_index <= 0) return masks; + + TXsheet *xsh = getXsheet(); + for (int i = m_index - 1; i >= 0; i--) { + TXshColumn *mcol = xsh->getColumn(i); + + if (!mcol || mcol->isEmpty()) break; + if (mcol->getColumnType() == TXshColumn::eMeshType) + continue; // ignore mesh levels + if (!mcol->isMask() || !mcol->isPreviewVisible()) break; + masks.push_back(mcol); + } + + return masks; +} + +//----------------------------------------------------------------------------- + PERSIST_IDENTIFIER(TXshLevelColumn, "levelColumn") diff --git a/toonz/sources/toonzqt/gutil.cpp b/toonz/sources/toonzqt/gutil.cpp index 06dbe822..7479355a 100644 --- a/toonz/sources/toonzqt/gutil.cpp +++ b/toonz/sources/toonzqt/gutil.cpp @@ -436,7 +436,8 @@ QPixmap convertImageToPixmap(const QImage &image) { // Load, theme colorize and change opacity of an icon image QImage generateIconImage(const QString &iconSVGName, qreal opacity, - QSize newSize, Qt::AspectRatioMode aspectRatioMode) { + QSize newSize, Qt::AspectRatioMode aspectRatioMode, + bool useThemeColor) { static ThemeManager &themeManager = ThemeManager::getInstance(); if (iconSVGName.isEmpty() || !themeManager.hasIcon(iconSVGName)) { @@ -453,7 +454,7 @@ QImage generateIconImage(const QString &iconSVGName, qreal opacity, QImage image(svgToImage(imgPath, newSize, aspectRatioMode)); // Colorize QImage - image = themeManager.recolorBlackPixels(image); + if (useThemeColor) image = themeManager.recolorBlackPixels(image); // Change opacity if required if (opacity != qreal(1.0)) image = adjustImageOpacity(image, opacity); @@ -465,9 +466,10 @@ QImage generateIconImage(const QString &iconSVGName, qreal opacity, // Load, theme colorize and change opacity of an icon image file QPixmap generateIconPixmap(const QString &iconSVGName, qreal opacity, - QSize newSize, Qt::AspectRatioMode aspectRatioMode) { - QImage image = - generateIconImage(iconSVGName, opacity, newSize, aspectRatioMode); + QSize newSize, Qt::AspectRatioMode aspectRatioMode, + bool useThemeColor) { + QImage image = generateIconImage(iconSVGName, opacity, newSize, + aspectRatioMode, useThemeColor); return convertImageToPixmap(image); }