diff --git a/stuff/config/current.txt b/stuff/config/current.txt index 0841a8a..1bf1c92 100644 --- a/stuff/config/current.txt +++ b/stuff/config/current.txt @@ -1310,9 +1310,16 @@ "STD_iwa_GlareFx" "Glare Iwa" "STD_iwa_GlareFx.renderMode" "Render Mode" + "STD_iwa_GlareFx.irisMode" "Iris Shape" + "STD_iwa_GlareFx.irisScale" "Iris Scale" + "STD_iwa_GlareFx.irisGearEdgeCount" "Edges" + "STD_iwa_GlareFx.irisRandomSeed" "Random Seed" + "STD_iwa_GlareFx.irisSymmetry" "Symmetry" + "STD_iwa_GlareFx.irisAppearance" "Appearance" "STD_iwa_GlareFx.intensity" "Intensity" "STD_iwa_GlareFx.size" "Filter Size" "STD_iwa_GlareFx.rotation" "Filter Rotation" + "STD_iwa_GlareFx.aberration" "Chromatic Aberration" "STD_iwa_GlareFx.noise_factor" "Noise Factor" "STD_iwa_GlareFx.noise_size" "Noise Size" "STD_iwa_GlareFx.noise_octave" "Noise Octave" diff --git a/stuff/profiles/layouts/fxs/STD_iwa_GlareFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_GlareFx.xml index 35823f4..9fe69f1 100644 --- a/stuff/profiles/layouts/fxs/STD_iwa_GlareFx.xml +++ b/stuff/profiles/layouts/fxs/STD_iwa_GlareFx.xml @@ -1,9 +1,24 @@ renderMode + irisMode + + + irisScale + irisSymmetry + irisAppearance + + + irisGearEdgeCount + irisRandomSeed + + + + intensity size rotation + aberration noise_factor noise_size noise_octave diff --git a/toonz/sources/common/tfx/tfx.cpp b/toonz/sources/common/tfx/tfx.cpp index d5bdd5e..90a0f36 100644 --- a/toonz/sources/common/tfx/tfx.cpp +++ b/toonz/sources/common/tfx/tfx.cpp @@ -701,6 +701,9 @@ void TFx::setNewIdentifier() { m_imp->m_id = ++m_imp->m_nextId; } //-------------------------------------------------- void TFx::loadData(TIStream &is) { + // default version of fx is 1 + setFxVersion(1); + std::string tagName; VersionNumber tnzVersion = is.getVersion(); // Prevent to load "params" tag under "super" tag on saving macro fx. @@ -818,6 +821,10 @@ void TFx::loadData(TIStream &is) { is >> groupName; groupNames.append(groupName); } + } else if (tagName == "fxVersion") { + int version = 1; + is >> version; + setFxVersion(version); } else { throw TException("Unknown tag!"); } @@ -907,6 +914,7 @@ void TFx::saveData(TOStream &os) { for (i = 0; i < groupNameStack.size(); i++) os << groupNameStack[i]; os.closeChild(); } + if (getFxVersion() != 1) os.child("fxVersion") << getFxVersion(); } //-------------------------------------------------- @@ -992,6 +1000,14 @@ TFx *TFx::getLinkedFx() const { return m_imp->m_next->m_fx; } +//-------------------------------------------------- + +void TFx::setFxVersion(int v) { m_imp->m_attributes.setFxVersion(v); } + +//-------------------------------------------------- + +int TFx::getFxVersion() const { return m_imp->m_attributes.getFxVersion(); } + //=================================================== // // TFxTimeRegion diff --git a/toonz/sources/include/tfx.h b/toonz/sources/include/tfx.h index 8149fd1..a462ead 100644 --- a/toonz/sources/include/tfx.h +++ b/toonz/sources/include/tfx.h @@ -503,6 +503,9 @@ public: // parameter is loaded. Do nothing by default. virtual void onObsoleteParamLoaded(const std::string ¶mName) {} + void setFxVersion(int); + int getFxVersion() const; + public: // Id-related functions diff --git a/toonz/sources/include/tfxattributes.h b/toonz/sources/include/tfxattributes.h index 92245cb..90946d5 100644 --- a/toonz/sources/include/tfxattributes.h +++ b/toonz/sources/include/tfxattributes.h @@ -33,6 +33,8 @@ class DVAPI TFxAttributes { /*-- MotionBlurなどのFxのために、オブジェクトの軌跡のデータを取得する --*/ QList m_motionPoints; + // to maintain backward compatibility in the fx + int m_fxVersion; public: TFxAttributes(); @@ -62,6 +64,8 @@ public: m_motionPoints = motionPoints; } QList getMotionPoints() { return m_motionPoints; } + void setFxVersion(int version) { m_fxVersion = version; } + int getFxVersion() const { return m_fxVersion; }; // Group management diff --git a/toonz/sources/stdfx/iwa_glarefx.cpp b/toonz/sources/stdfx/iwa_glarefx.cpp index f5d9400..6e558da 100644 --- a/toonz/sources/stdfx/iwa_glarefx.cpp +++ b/toonz/sources/stdfx/iwa_glarefx.cpp @@ -12,11 +12,15 @@ #include "iwa_xyz.h" #include "iwa_simplexnoise.h" +#include + #include #include #include #include #include +#include +#include namespace { // FFT coordinate -> Normal corrdinate @@ -38,25 +42,55 @@ inline int getCoord(int i, int j, int lx, int ly) { Iwa_GlareFx::Iwa_GlareFx() : m_renderMode(new TIntEnumParam(RendeMode_FilterPreview, "Filter Preview")) + , m_irisMode(new TIntEnumParam(Iris_InputImage, "Input Image")) + , m_irisScale(0.2) + , m_irisGearEdgeCount(10) + , m_irisRandomSeed(0) + , m_irisSymmetry(1.0) + , m_irisAppearance(new TIntEnumParam()) , m_intensity(0.0) , m_size(100.0) , m_rotation(0.0) + , m_aberration(1.0) , m_noise_factor(0.0) , m_noise_size(0.5) , m_noise_octave(new TIntEnumParam(1, "1")) , m_noise_evolution(0.0) , m_noise_offset(TPointD(0, 0)) { + // Version 1 : lights had been constantly summed in all wavelength + // Version 2 : intensities are weighted proportional to 1/(rambda^2) + setFxVersion(2); + // Bind the common parameters addInputPort("Source", m_source); addInputPort("Iris", m_iris); bindParam(this, "renderMode", m_renderMode); m_renderMode->addItem(RendeMode_Render, "Render"); + m_renderMode->addItem(RenderMode_Iris, "Iris"); + + bindParam(this, "irisMode", m_irisMode); + m_irisMode->addItem(Iris_Square, "4 Streaks"); + m_irisMode->addItem(Iris_Hexagon, "6 Streaks"); + m_irisMode->addItem(Iris_Octagon, "8 Streaks"); + m_irisMode->addItem(Iris_GearShape, "Multiple Streaks"); + + bindParam(this, "irisScale", m_irisScale); + bindParam(this, "irisGearEdgeCount", m_irisGearEdgeCount); + bindParam(this, "irisRandomSeed", m_irisRandomSeed); + bindParam(this, "irisSymmetry", m_irisSymmetry); + bindParam(this, "irisAppearance", m_irisAppearance); + m_irisAppearance->addItem(Appearance_ThinLine, "Thin Line"); + m_irisAppearance->addItem(Appearance_MediumLine, "Medium Line"); + m_irisAppearance->addItem(Appearance_ThickLine, "Thick Line"); + m_irisAppearance->addItem(Appearance_Fill, "Filled"); + m_irisAppearance->setValue(Appearance_MediumLine); bindParam(this, "intensity", m_intensity, false); bindParam(this, "size", m_size, false); m_size->setMeasureName("fxLength"); bindParam(this, "rotation", m_rotation, false); + bindParam(this, "aberration", m_aberration, false); bindParam(this, "noise_factor", m_noise_factor, false); bindParam(this, "noise_size", m_noise_size, false); @@ -69,9 +103,15 @@ Iwa_GlareFx::Iwa_GlareFx() m_noise_offset->getX()->setMeasureName("fxLength"); m_noise_offset->getY()->setMeasureName("fxLength"); + m_irisScale->setValueRange(0.1, 0.8); + m_irisGearEdgeCount->setValueRange(3, 50); + m_irisSymmetry->setValueRange(0.1, 1.0); + m_irisRandomSeed->setValueRange(0, (std::numeric_limits::max)()); + m_intensity->setValueRange(-5.0, 5.0); - m_size->setValueRange(10.0, 500.0); + m_size->setValueRange(10.0, 1500.0); m_rotation->setValueRange(-1800, 1800); + m_aberration->setValueRange(-2.0, 2.0); m_noise_factor->setValueRange(0.0, 1.0); m_noise_size->setValueRange(0.01, 3.0); } @@ -91,12 +131,149 @@ double Iwa_GlareFx::getSizePixelAmount(const double val, const TAffine affine) { /*--- return the length of the vector ---*/ return sqrt(vect.x * vect.x + vect.y * vect.y); } + +//-------------------------------------------------------------- + +void Iwa_GlareFx::drawPresetIris(TRaster32P irisRas, double irisSize, + const double frame) { + QImage img(irisRas->getLx(), irisRas->getLy(), + QImage::Format_ARGB32_Premultiplied); + img.fill(Qt::black); + QPainter painter(&img); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.translate( + QPointF((float)irisRas->getLx() / 2.0, (float)irisRas->getLy() / 2.0)); + painter.scale(irisSize, irisSize); + // shrink a bit + painter.scale(0.9, 0.9); + + QPen pen(Qt::white); + double lineWidthRatio; + int appearance = m_irisAppearance->getValue(); + if (appearance == Appearance_Fill) { + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::white); + } else { + assert(appearance >= 0 && appearance <= 2); + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::MiterJoin); + double ratio[3] = {0.05, 0.1, 0.2}; + lineWidthRatio = ratio[appearance]; + pen.setWidthF(lineWidthRatio); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + } + + double symmetry = m_irisSymmetry->getValue(frame); + + switch (m_irisMode->getValue()) { + case Iris_Square: + painter.scale(1.0, symmetry); + painter.drawRect(QRectF(-1.0, -1.0, 2.0, 2.0)); + break; + case Iris_Hexagon: { + QPointF p(1.0 - 0.5 * symmetry, symmetry * std::sqrt(3) * 0.5); + const QPointF points[6] = {-p, QPointF(-1.0, 0.0), QPointF(-p.x(), p.y()), + p, QPointF(1.0, 0.0), QPointF(p.x(), -p.y())}; + if (appearance != Appearance_Fill) { + if (symmetry < 1.0) { + painter.drawPolyline(points, 3); + painter.drawPolyline(&points[3], 3); + } + pen.setWidthF(lineWidthRatio * symmetry); + painter.setPen(pen); + } + painter.drawPolygon(points, 6); + if (appearance != Appearance_Fill && symmetry < 1.0) { + pen.setColor(Qt::black); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawRect( + QRectF(-1.2, -p.y() - symmetry, 2.4, (p.y() + symmetry) * 2.0)); + } + } break; + case Iris_Octagon: { + double u = (2 + std::sqrt(2) * (1 - symmetry)) / (2 * std::sqrt(2) + 2); + double v = (2 + std::sqrt(2) * (1 + symmetry)) / (2 * std::sqrt(2) + 2); + const QPointF points[8] = {QPointF(u, v), QPointF(v, u), QPointF(v, -u), + QPointF(u, -v), QPointF(-u, -v), QPointF(-v, -u), + QPointF(-v, u), QPointF(-u, v)}; + if (appearance != Appearance_Fill) { + if (symmetry < 1.0) { + painter.drawLine(points[0], points[1]); + painter.drawLine(points[2], points[3]); + painter.drawLine(points[4], points[5]); + painter.drawLine(points[6], points[7]); + } + pen.setWidthF(lineWidthRatio * symmetry); + painter.setPen(pen); + } + painter.drawPolygon(points, 8); + if (appearance != Appearance_Fill && symmetry < 1.0) { + pen.setColor(Qt::black); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawRect(QRectF(-v - symmetry, -v - symmetry, + (v + symmetry) * 2.0, (v + symmetry) * 2.0)); + } + } break; + case Iris_GearShape: { + int edgeCount = (int)std::round(m_irisGearEdgeCount->getValue(frame)); + QPointF* points = new QPointF[edgeCount * 2]; + QList thickness; + double angleUnit = M_PI / (double)edgeCount; + std::mt19937_64 mt; + mt.seed(m_irisRandomSeed->getValue()); + std::uniform_real_distribution<> random_plusminus1(-1.0, 1.0); + std::uniform_real_distribution<> random_thickness(symmetry * lineWidthRatio, + lineWidthRatio); + for (int e = 0; e < edgeCount; e++) { + double baseAngle = angleUnit * e * 2.0; + double theta = + baseAngle + random_plusminus1(mt) * angleUnit * (1.0 - symmetry); + points[e * 2] = QPointF(std::cos(theta), std::sin(theta)); + thickness.append(random_thickness(mt)); + baseAngle += angleUnit; + theta = baseAngle + random_plusminus1(mt) * angleUnit * (1.0 - symmetry); + points[e * 2 + 1] = QPointF(0.5 * std::cos(theta), 0.5 * std::sin(theta)); + thickness.append(random_thickness(mt)); + } + + if (appearance != Appearance_Fill) { + for (int v = 0; v < edgeCount * 2; v++) { + int next_v = (v == edgeCount * 2 - 1) ? 0 : v + 1; + pen.setWidthF(thickness.at(v)); + painter.setPen(pen); + painter.drawLine(points[v], points[next_v]); + } + } else { + painter.drawPolygon(points, edgeCount * 2); + } + delete[] points; + } break; + default: + break; + } + + for (int j = 0; j < img.height(); j++) { + TPixel32* pix = irisRas->pixels(j); + QRgb* img_p = (QRgb*)img.scanLine(img.height() - j - 1); + for (int i = 0; i < img.width(); i++, img_p++, pix++) { + pix->r = (unsigned char)(qRed(*img_p)); + pix->g = (unsigned char)(qGreen(*img_p)); + pix->b = (unsigned char)(qBlue(*img_p)); + pix->m = (unsigned char)(qAlpha(*img_p)); + } + } +} + //-------------------------------------------------------------- void Iwa_GlareFx::doCompute(TTile& tile, double frame, const TRenderSettings& settings) { + int irisMode = m_irisMode->getValue(); // If the iris is not connected, then do nothing - if (!m_iris.isConnected()) { + if (irisMode == Iris_InputImage && !m_iris.isConnected()) { tile.getRaster()->clear(); return; } @@ -110,16 +287,35 @@ void Iwa_GlareFx::doCompute(TTile& tile, double frame, // Get the original size of Iris image TRectD irisBBox; - m_iris->getBBox(frame, irisBBox, settings); - // Compute the iris tile. TTile irisTile; - m_iris->allocateAndCompute( - irisTile, irisBBox.getP00(), - TDimension(static_cast(irisBBox.getLx() + 0.5), - static_cast(irisBBox.getLy() + 0.5)), - tile.getRaster(), frame, settings); - + TRasterP irisRas; double size = getSizePixelAmount(m_size->getValue(frame), settings.m_affine); + if (irisMode == Iris_InputImage) { + m_iris->getBBox(frame, irisBBox, settings); + // Compute the iris tile. + m_iris->allocateAndCompute( + irisTile, irisBBox.getP00(), + TDimension(static_cast(irisBBox.getLx() + 0.5), + static_cast(irisBBox.getLy() + 0.5)), + tile.getRaster(), frame, settings); + irisRas = irisTile.getRaster(); + } else { + // obtain iris bbox based on the glare pattern size + double irisSize = size * m_irisScale->getValue(frame); + irisBBox = TRectD(-irisSize, -irisSize, irisSize, irisSize); + int dimIrisRas = int(std::ceil(irisSize) * 2.0); + irisRas = TRaster32P(dimIrisRas, dimIrisRas); + drawPresetIris(irisRas, irisSize, frame); + } + + if (renderMode == RenderMode_Iris) { + TTranslation aff( + (double)(tile.getRaster()->getLx() - irisRas->getLx()) * 0.5, + (double)(tile.getRaster()->getLy() - irisRas->getLy()) * 0.5); + TRop::quickPut(tile.getRaster(), irisRas, aff); + return; + } + int dimIris = int(std::ceil(size) * 2.0); dimIris = kiss_fft_next_fast_size(dimIris); while ((tile.getRaster()->getSize().lx - dimIris) % 2 != 0) @@ -140,7 +336,8 @@ void Iwa_GlareFx::doCompute(TTile& tile, double frame, kissfft_comp_iris_before_ras->lock(); kissfft_comp_iris_before = (kiss_fft_cpx*)kissfft_comp_iris_before_ras->getRawData(); - convertIris(kissfft_comp_iris_before, dimIris, irisBBox, irisTile); + + convertIris(kissfft_comp_iris_before, dimIris, irisBBox, irisRas); // Create the FFT plan for the iris image. kiss_fftnd_cfg iris_kissfft_plan; @@ -349,10 +546,20 @@ void Iwa_GlareFx::powerSpectrum2GlarePattern( glare_xyz_ras->clear(); double irisRadius = double(dimIris / 2); + double aberration = m_aberration->getValue(frame); + + // old version had summed each wavelength constantly. + // it was not physically-collect but keep it in order to maintain backward + // compatibility. + bool isOldVersion = getFxVersion() < 2; + // accumurate xyz values for each optical wavelength for (int ram = 0; ram < 34; ram++) { double rambda = 0.38 + 0.01 * (double)ram; - double scale = 0.55 / rambda; + // double scale = 0.55 / rambda; + double scale = std::pow(0.55 / rambda, aberration); + double intensity_scale = + (isOldVersion) ? 1.0 : std::pow(0.55 / rambda, 2.0 * aberration); scale *= irisResizeFactor; for (int j = 0; j < dimIris; j++) { double j_scaled = (double(j) - irisRadius) * scale + irisRadius; @@ -369,7 +576,8 @@ void Iwa_GlareFx::powerSpectrum2GlarePattern( else if (i_scaled > double(dimIris - 1)) break; - double gl = lerpGlarePtn(i_scaled, j_scaled, glarePattern_p); + double gl = + lerpGlarePtn(i_scaled, j_scaled, glarePattern_p) * intensity_scale; g_xyz_p->x += gl * cie_d65[ram] * xyz[ram * 3 + 0]; g_xyz_p->y += gl * cie_d65[ram] * xyz[ram * 3 + 1]; g_xyz_p->z += gl * cie_d65[ram] * xyz[ram * 3 + 2]; @@ -634,7 +842,7 @@ bool Iwa_GlareFx::canHandle(const TRenderSettings& info, double frame) { // Enlarge the iris to the output size. void Iwa_GlareFx::convertIris(kiss_fft_cpx* kissfft_comp_iris_before, const int& dimIris, const TRectD& irisBBox, - const TTile& irisTile) { + const TRasterP irisRaster) { // the original size of iris image double2 irisOrgSize = {irisBBox.getLx(), irisBBox.getLy()}; @@ -655,10 +863,10 @@ void Iwa_GlareFx::convertIris(kiss_fft_cpx* kissfft_comp_iris_before, if (dimIris % 2 == 1) affOffset += TPointD(0.5, 0.5); aff = TTranslation(resizedIris->getCenterD() + affOffset); - aff *= TTranslation(-(irisTile.getRaster()->getCenterD() + affOffset)); + aff *= TTranslation(-(irisRaster->getCenterD() + affOffset)); // resample the iris - TRop::resample(resizedIris, irisTile.getRaster(), aff); + TRop::resample(resizedIris, irisRaster, aff); // accumulated value float irisValAmount = 0.0; diff --git a/toonz/sources/stdfx/iwa_glarefx.h b/toonz/sources/stdfx/iwa_glarefx.h index 2ac6419..5758b48 100644 --- a/toonz/sources/stdfx/iwa_glarefx.h +++ b/toonz/sources/stdfx/iwa_glarefx.h @@ -35,12 +35,21 @@ class Iwa_GlareFx : public TStandardRasterFx { protected: TRasterFxPort m_source; TRasterFxPort m_iris; - // rendering mode (filter preview / render) + // rendering mode (filter preview / render / iris) TIntEnumParamP m_renderMode; + TIntEnumParamP m_irisMode; + + TDoubleParamP m_irisScale; + TDoubleParamP m_irisGearEdgeCount; + TIntParamP m_irisRandomSeed; + TDoubleParamP m_irisSymmetry; + TIntEnumParamP m_irisAppearance; + TDoubleParamP m_intensity; TDoubleParamP m_size; TDoubleParamP m_rotation; + TDoubleParamP m_aberration; TDoubleParamP m_noise_factor; TDoubleParamP m_noise_size; @@ -48,15 +57,32 @@ protected: TDoubleParamP m_noise_evolution; TPointParamP m_noise_offset; - enum { RendeMode_FilterPreview = 0, RendeMode_Render }; + enum { RendeMode_FilterPreview = 0, RendeMode_Render, RenderMode_Iris }; + + enum { + Iris_InputImage = 0, + Iris_Square, + Iris_Hexagon, + Iris_Octagon, + Iris_GearShape + }; + + enum { + Appearance_ThinLine, + Appearance_MediumLine, + Appearance_ThickLine, + Appearance_Fill, + }; double getSizePixelAmount(const double val, const TAffine affine); + void drawPresetIris(TRaster32P irisRas, double irisSize, const double frame); + // Resize / flip the iris image according to the size ratio. // Normalize the brightness of the iris image. // Enlarge the iris to the output size. void convertIris(kiss_fft_cpx *kissfft_comp_iris_before, const int &dimIris, - const TRectD &irisBBox, const TTile &irisTile); + const TRectD &irisBBox, const TRasterP irisRaster); void powerSpectrum2GlarePattern(const double frame, const TAffine affine, kiss_fft_cpx *spectrum, double3 *glare, diff --git a/toonz/sources/tnzbase/tfxattributes.cpp b/toonz/sources/tnzbase/tfxattributes.cpp index 0c92cf1..9f1a9f6 100644 --- a/toonz/sources/tnzbase/tfxattributes.cpp +++ b/toonz/sources/tnzbase/tfxattributes.cpp @@ -13,7 +13,8 @@ TFxAttributes::TFxAttributes() , m_isOpened(false) , m_speed() , m_groupSelector(-1) - , m_passiveCacheDataIdx(-1) {} + , m_passiveCacheDataIdx(-1) + , m_fxVersion(1) {} //----------------------------------------------------------------------