diff --git a/stuff/config/current.txt b/stuff/config/current.txt
index e8ac97b..7aa6923 100644
--- a/stuff/config/current.txt
+++ b/stuff/config/current.txt
@@ -1286,6 +1286,14 @@
- "STD_iwa_CorridorGradientFx.inner_color" "Inner Color"
- "STD_iwa_CorridorGradientFx.outer_color" "Outer Color"
+ - "STD_iwa_SpinGradientFx" "Spin Gradient Iwa"
+ - "STD_iwa_SpinGradientFx.center" "Center"
+ - "STD_iwa_SpinGradientFx.curveType" "Type"
+ - "STD_iwa_SpinGradientFx.startAngle" "Start Angle"
+ - "STD_iwa_SpinGradientFx.startColor" "Start Color"
+ - "STD_iwa_SpinGradientFx.endAngle" "End Angle"
+ - "STD_iwa_SpinGradientFx.endColor" "End Color"
+
- STD_iwa_TiledParticlesFx "Tiled Particles Iwa"
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_SpinGradientFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_SpinGradientFx.xml
new file mode 100644
index 0000000..8ab64c5
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_SpinGradientFx.xml
@@ -0,0 +1,12 @@
+
+
+ center
+ curveType
+
+ startAngle
+ startColor
+
+ endAngle
+ endColor
+
+
diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst
index ca26b7c..f2d13c3 100644
--- a/stuff/profiles/layouts/fxs/fxs.lst
+++ b/stuff/profiles/layouts/fxs/fxs.lst
@@ -45,6 +45,7 @@
STD_spiralFx
STD_squareGradientFx
STD_iwa_CorridorGradientFx
+ STD_iwa_SpinGradientFx
STD_iwa_AdjustExposureFx
diff --git a/toonz/sources/include/tparamuiconcept.h b/toonz/sources/include/tparamuiconcept.h
index b764e03..8a62c37 100644
--- a/toonz/sources/include/tparamuiconcept.h
+++ b/toonz/sources/include/tparamuiconcept.h
@@ -36,12 +36,14 @@ public:
enum Type {
NONE = 0,
- RADIUS, // Distance from a point (radius). Represented by {
- // [TDoubleParamP], TPointParamP }
- WIDTH, // Width, as distance from a line with given angle. {
- // [TDoubleParamP], TDoubleParamP }
- ANGLE, // An angle. {
- // [TDoubleParamP] }
+ RADIUS, // Distance from a point (radius). Represented by {
+ // [TDoubleParamP], TPointParamP }
+ WIDTH, // Width, as distance from a line with given angle. {
+ // [TDoubleParamP], TDoubleParamP }
+ ANGLE, // An angle. {
+ // [TDoubleParamP] }
+ ANGLE_2, // An angle range defined with start and end angles.
+ // { [2 TDoubleParamP], TDoubleParamP }
POINT, // A Point. {
// [TPointParamP] }
diff --git a/toonz/sources/stdfx/CMakeLists.txt b/toonz/sources/stdfx/CMakeLists.txt
index 4aa2aaf..e871986 100644
--- a/toonz/sources/stdfx/CMakeLists.txt
+++ b/toonz/sources/stdfx/CMakeLists.txt
@@ -77,6 +77,7 @@ set(HEADERS
iwa_bokehreffx.h
iwa_textfx.h
iwa_corridorgradientfx.h
+ iwa_spingradientfx.h
)
set(SOURCES
@@ -260,6 +261,7 @@ set(SOURCES
iwa_barreldistortfx.cpp
iwa_textfx.cpp
iwa_corridorgradientfx.cpp
+ iwa_spingradientfx.cpp
)
set(OBJCSOURCES
diff --git a/toonz/sources/stdfx/iwa_spingradientfx.cpp b/toonz/sources/stdfx/iwa_spingradientfx.cpp
new file mode 100644
index 0000000..812be16
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_spingradientfx.cpp
@@ -0,0 +1,174 @@
+#include "iwa_spingradientfx.h"
+
+#include "trop.h"
+#include "tparamuiconcept.h"
+#include "tspectrumparam.h"
+#include "gradients.h"
+
+#include
+
+#include
+#include
+
+//------------------------------------------------------------
+
+Iwa_SpinGradientFx::Iwa_SpinGradientFx()
+ : m_center(TPointD(0.0, 0.0))
+ , m_startAngle(0.0)
+ , m_endAngle(0.0)
+ , m_startColor(TPixel32::Black)
+ , m_endColor(TPixel32::White)
+ , m_curveType(new TIntEnumParam()) {
+ m_center->getX()->setMeasureName("fxLength");
+ m_center->getY()->setMeasureName("fxLength");
+ bindParam(this, "center", m_center);
+
+ m_startAngle->setValueRange(-360, 720);
+ m_endAngle->setValueRange(-360, 720);
+ bindParam(this, "startAngle", m_startAngle);
+ bindParam(this, "endAngle", m_endAngle);
+
+ m_curveType->addItem(EaseInOut, "Ease In-Out");
+ m_curveType->addItem(Linear, "Linear");
+ m_curveType->addItem(EaseIn, "Ease In");
+ m_curveType->addItem(EaseOut, "Ease Out");
+ m_curveType->setDefaultValue(Linear);
+ m_curveType->setValue(Linear);
+ bindParam(this, "curveType", m_curveType);
+
+ bindParam(this, "startColor", m_startColor);
+ bindParam(this, "endColor", m_endColor);
+}
+
+//------------------------------------------------------------
+
+bool Iwa_SpinGradientFx::doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &ri) {
+ bBox = TConsts::infiniteRectD;
+ return true;
+}
+
+//------------------------------------------------------------
+namespace {
+template
+void doSpinGradientT(RASTER ras, TDimensionI dim, TPointD centerPos,
+ double startAngle, double endAngle,
+ const TSpectrumT &spectrum,
+ GradientCurveType type) {
+ auto getFactor = [&](double angle) {
+ double p = angle - startAngle;
+ if (p < 0) p += 2.0 * M_PI;
+ double range = endAngle - startAngle;
+ if (range <= 0) range += 2.0 * M_PI;
+
+ double t;
+ if (range >= p)
+ t = p / range;
+ else if (M_PI + range / 2.0 > p)
+ t = 1.0;
+ else
+ t = 0.0;
+
+ double factor;
+ switch (type) {
+ case Linear:
+ factor = t;
+ break;
+ case EaseIn:
+ factor = t * t;
+ break;
+ case EaseOut:
+ factor = 1.0 - (1.0 - t) * (1.0 - t);
+ break;
+ case EaseInOut:
+ default:
+ factor = (-2 * t + 3) * (t * t);
+ break;
+ }
+ return factor;
+ };
+
+ ras->lock();
+ for (int j = 0; j < ras->getLy(); j++) {
+ PIXEL *pix = ras->pixels(j);
+ PIXEL *endPix = pix + ras->getLx();
+ double dy = (double)j - centerPos.y;
+ double dx = -centerPos.x;
+ while (pix < endPix) {
+ double angle = std::atan2(dy, dx);
+ double factor = getFactor(angle);
+ *pix++ = spectrum.getPremultipliedValue(factor);
+ dx += 1.0;
+ }
+ }
+ ras->unlock();
+}
+} // namespace
+
+//------------------------------------------------------------
+
+void Iwa_SpinGradientFx::doCompute(TTile &tile, double frame,
+ const TRenderSettings &ri) {
+ if (!((TRaster32P)tile.getRaster()) && !((TRaster64P)tile.getRaster())) {
+ throw TRopException("unsupported input pixel type");
+ }
+
+ // convert shape position to render region coordinate
+ TAffine aff = ri.m_affine;
+ TDimensionI dimOut(tile.getRaster()->getLx(), tile.getRaster()->getLy());
+ TPointD dimOffset((float)dimOut.lx / 2.0f, (float)dimOut.ly / 2.0f);
+ TPointD centerPos = aff * m_center->getValue(frame) -
+ (tile.m_pos + tile.getRaster()->getCenterD()) + dimOffset;
+
+ std::vector colors = {
+ TSpectrum::ColorKey(0, m_startColor->getValue(frame)),
+ TSpectrum::ColorKey(1, m_endColor->getValue(frame))};
+ TSpectrumParamP m_colors = TSpectrumParamP(colors);
+
+ auto conv2RadianAndClamp = [](double angle) {
+ double ret = angle * M_PI / 180.0;
+ while (ret < -M_PI) {
+ ret += 2.0 * M_PI;
+ }
+ while (ret >= M_PI) {
+ ret -= 2.0 * M_PI;
+ }
+ return ret;
+ };
+
+ double startAngle = conv2RadianAndClamp(m_startAngle->getValue(frame));
+ double endAngle = conv2RadianAndClamp(m_endAngle->getValue(frame));
+
+ tile.getRaster()->clear();
+ TRaster32P outRas32 = (TRaster32P)tile.getRaster();
+ TRaster64P outRas64 = (TRaster64P)tile.getRaster();
+ if (outRas32)
+ doSpinGradientT(
+ outRas32, dimOut, centerPos, startAngle, endAngle,
+ m_colors->getValue(frame), (GradientCurveType)m_curveType->getValue());
+ else if (outRas64)
+ doSpinGradientT(
+ outRas64, dimOut, centerPos, startAngle, endAngle,
+ m_colors->getValue64(frame),
+ (GradientCurveType)m_curveType->getValue());
+}
+
+//------------------------------------------------------------
+
+void Iwa_SpinGradientFx::getParamUIs(TParamUIConcept *&concepts, int &length) {
+ concepts = new TParamUIConcept[length = 2];
+
+ concepts[0].m_type = TParamUIConcept::ANGLE_2;
+ concepts[0].m_label = "Angle";
+ concepts[0].m_params.push_back(m_startAngle);
+ concepts[0].m_params.push_back(m_endAngle);
+ concepts[0].m_params.push_back(m_center);
+
+ concepts[1].m_type = TParamUIConcept::POINT;
+ concepts[1].m_label = "Center";
+ concepts[1].m_params.push_back(m_center);
+}
+
+//------------------------------------------------------------
+
+FX_PLUGIN_IDENTIFIER(Iwa_SpinGradientFx, "iwa_SpinGradientFx");
diff --git a/toonz/sources/stdfx/iwa_spingradientfx.h b/toonz/sources/stdfx/iwa_spingradientfx.h
new file mode 100644
index 0000000..264f7ce
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_spingradientfx.h
@@ -0,0 +1,29 @@
+#pragma once
+#ifndef IWA_SPINGRADIENTFX_H
+#define IWA_SPINGRADIENTFX_H
+
+#include "tfxparam.h"
+#include "stdfx.h"
+#include "tparamset.h"
+
+class Iwa_SpinGradientFx final : public TStandardZeraryFx {
+ FX_PLUGIN_DECLARATION(Iwa_SpinGradientFx)
+
+ TIntEnumParamP m_curveType;
+ TPointParamP m_center;
+ TDoubleParamP m_startAngle, m_endAngle;
+ TPixelParamP m_startColor, m_endColor;
+
+public:
+ Iwa_SpinGradientFx();
+
+ bool canHandle(const TRenderSettings &info, double frame) override {
+ return true;
+ }
+ bool doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &ri) override;
+ void doCompute(TTile &tile, double frame, const TRenderSettings &ri) override;
+ void getParamUIs(TParamUIConcept *&concepts, int &length) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/toonz/sources/tnztools/edittoolgadgets.cpp b/toonz/sources/tnztools/edittoolgadgets.cpp
index 489c4d8..2cd9d93 100644
--- a/toonz/sources/tnztools/edittoolgadgets.cpp
+++ b/toonz/sources/tnztools/edittoolgadgets.cpp
@@ -553,6 +553,171 @@ void AngleFxGadget::leftButtonUp(const TPointD &pos, const TMouseEvent &) {}
//=============================================================================
+class AngleRangeFxGadget final : public FxGadget {
+ TDoubleParamP m_startAngle, m_endAngle;
+ TPointParamP m_center;
+
+ enum HANDLE { StartAngle = 0, EndAngle, None } m_handle = None;
+
+ double m_clickedAngle;
+ double m_targetAngle, m_anotherAngle;
+
+public:
+ AngleRangeFxGadget(FxGadgetController *controller,
+ const TDoubleParamP &startAngle,
+ const TDoubleParamP &endAngle, const TPointParamP ¢er);
+
+ void draw(bool picking) override;
+
+ void leftButtonDown(const TPointD &pos, const TMouseEvent &) override;
+ void leftButtonDrag(const TPointD &pos, const TMouseEvent &) override;
+ void leftButtonUp(const TPointD &pos, const TMouseEvent &) override;
+};
+
+//---------------------------------------------------------------------------
+
+AngleRangeFxGadget::AngleRangeFxGadget(FxGadgetController *controller,
+ const TDoubleParamP &startAngle,
+ const TDoubleParamP &endAngle,
+ const TPointParamP ¢er)
+ : FxGadget(controller, 2)
+ , m_startAngle(startAngle)
+ , m_endAngle(endAngle)
+ , m_center(center) {
+ addParam(startAngle);
+ addParam(endAngle);
+ addParam(center->getX());
+ addParam(center->getY());
+}
+
+//---------------------------------------------------------------------------
+
+void AngleRangeFxGadget::draw(bool picking) {
+ auto setColorById = [&](int id) {
+ if (isSelected(id))
+ glColor3dv(m_selectedColor);
+ else
+ glColor3d(0, 0, 1);
+ };
+
+ double pixelSize = sqrt(tglGetPixelSize2()) * getDevPixRatio();
+ double r = pixelSize * 200;
+ double a = pixelSize * 30;
+
+ TPointD center = getValue(m_center);
+ double start = getValue(m_startAngle);
+ double end = getValue(m_endAngle);
+
+ glPushMatrix();
+ glTranslated(center.x, center.y, 0);
+
+ setColorById(StartAngle);
+ glPushMatrix();
+ glPushName(getId() + StartAngle);
+ glRotated(start, 0, 0, 1);
+ glBegin(GL_LINE_STRIP);
+ glVertex2d(0, 0);
+ glVertex2d(r, 0);
+ // expand handle while dragging
+ if (m_handle == StartAngle) glVertex2d(r * 5.0, 0);
+ glEnd();
+ glPopName();
+
+ glPushMatrix();
+ glTranslated(r * 1.05, 0, 0.0);
+ glScaled(pixelSize * 1.6, pixelSize * 1.6, 1);
+ glRotated(-start, 0, 0, 1);
+ tglDrawText(TPointD(0, 0), "Start Angle");
+ glPopMatrix();
+
+ glPopMatrix();
+
+ setColorById(EndAngle);
+ glPushMatrix();
+ glPushName(getId() + EndAngle);
+ glRotated(end, 0, 0, 1);
+ glBegin(GL_LINE_STRIP);
+ glVertex2d(0, 0);
+ glVertex2d(r, 0);
+ // expand handle while dragging
+ if (m_handle == EndAngle) glVertex2d(r * 5.0, 0);
+ glEnd();
+
+ glPopName();
+ glPushMatrix();
+ glTranslated(r * 1.05, 0, 0.0);
+ glScaled(pixelSize * 1.6, pixelSize * 1.6, 1);
+ glRotated(-end, 0, 0, 1);
+ tglDrawText(TPointD(0, 0), "End Angle");
+ glPopMatrix();
+
+ glPopMatrix();
+
+ // draw arc
+ while (end <= start) end += 360.0;
+
+ glColor3d(0, 0, 1);
+ glBegin(GL_LINE_STRIP);
+ double angle = start;
+ double dAngle = 5.0;
+ while (angle <= end) {
+ double rad = angle / M_180_PI;
+ glVertex2d(a * std::cos(rad), a * std::sin(rad));
+ angle += dAngle;
+ }
+ if (angle != end)
+ glVertex2d(a * std::cos(end / M_180_PI), a * std::sin(end / M_180_PI));
+ glEnd();
+
+ glPopMatrix();
+}
+
+//---------------------------------------------------------------------------
+
+void AngleRangeFxGadget::leftButtonDown(const TPointD &pos,
+ const TMouseEvent &) {
+ m_handle = (HANDLE)m_selected;
+ if (m_handle == None) return;
+ TPointD d = pos - getValue(m_center);
+ m_clickedAngle = atan2(d.y, d.x) * M_180_PI;
+ TDoubleParamP target = (m_handle == StartAngle) ? m_startAngle : m_endAngle;
+ TDoubleParamP another = (m_handle == StartAngle) ? m_endAngle : m_startAngle;
+ m_targetAngle = getValue(target);
+ m_anotherAngle = getValue(another);
+}
+
+//---------------------------------------------------------------------------
+
+void AngleRangeFxGadget::leftButtonDrag(const TPointD &pos,
+ const TMouseEvent &e) {
+ if (m_handle == None) return;
+ TDoubleParamP target = (m_handle == StartAngle) ? m_startAngle : m_endAngle;
+ TPointD d = pos - getValue(m_center);
+ double angle = atan2(d.y, d.x) * M_180_PI;
+ double targetAngle = m_targetAngle + angle - m_clickedAngle;
+ // move every 10 degrees when pressing Shift key
+ if (e.isShiftPressed()) targetAngle = std::round(targetAngle / 10.0) * 10.0;
+ setValue(target, targetAngle);
+
+ // move both angles when pressing Ctrl key
+ if (e.isCtrlPressed()) {
+ TDoubleParamP another =
+ (m_handle == StartAngle) ? m_endAngle : m_startAngle;
+ double anotherAngle = m_anotherAngle + angle - m_clickedAngle;
+ if (e.isShiftPressed())
+ anotherAngle = std::round(anotherAngle / 10.0) * 10.0;
+ setValue(another, anotherAngle);
+ }
+}
+
+//---------------------------------------------------------------------------
+
+void AngleRangeFxGadget::leftButtonUp(const TPointD &pos, const TMouseEvent &) {
+ m_handle = None;
+}
+
+//=============================================================================
+
class DiamondFxGadget final : public FxGadget {
TDoubleParamP m_param;
@@ -1284,6 +1449,14 @@ FxGadget *FxGadgetController::allocateGadget(const TParamUIConcept &uiConcept) {
break;
}
+ case TParamUIConcept::ANGLE_2: {
+ assert(uiConcept.m_params.size() == 3);
+ gadget =
+ new AngleRangeFxGadget(this, uiConcept.m_params[0],
+ uiConcept.m_params[1], uiConcept.m_params[2]);
+ break;
+ }
+
case TParamUIConcept::POINT: {
assert(uiConcept.m_params.size() == 1);
gadget = new PointFxGadget(this, uiConcept.m_params[0]);