diff --git a/stuff/config/current.txt b/stuff/config/current.txt
index f6fe58e..97020a9 100644
--- a/stuff/config/current.txt
+++ b/stuff/config/current.txt
@@ -1172,6 +1172,8 @@
- "STD_inoSpinBlurFx.alpha_rendering" "Alpha Rendering"
- "STD_inoSpinBlurFx.anti_alias" "Anti Alias"
- "STD_inoSpinBlurFx.reference" "Reference"
+ - "STD_inoSpinBlurFx.ellipse_aspect_ratio" "Ellipse Aspect Ratio"
+ - "STD_inoSpinBlurFx.ellipse_angle" "Ellipse Angle"
- "STD_inoWarphvFx" "Warp HV Ino"
- "STD_inoWarphvFx.h_maxlen" "H MaxLen"
- "STD_inoWarphvFx.v_maxlen" "V MaxLen"
diff --git a/stuff/profiles/layouts/fxs/STD_inoSpinBlurFx.xml b/stuff/profiles/layouts/fxs/STD_inoSpinBlurFx.xml
index 0340739..6f215c4 100644
--- a/stuff/profiles/layouts/fxs/STD_inoSpinBlurFx.xml
+++ b/stuff/profiles/layouts/fxs/STD_inoSpinBlurFx.xml
@@ -5,6 +5,8 @@
blur
type
alpha_rendering
+ ellipse_aspect_ratio
+ ellipse_angle
anti_alias
reference
diff --git a/toonz/sources/include/tparamuiconcept.h b/toonz/sources/include/tparamuiconcept.h
index 091c39b..dfc6419 100644
--- a/toonz/sources/include/tparamuiconcept.h
+++ b/toonz/sources/include/tparamuiconcept.h
@@ -70,6 +70,8 @@ public:
RAINBOW_WIDTH,
+ ELLIPSE, // used in spin blur ino
+
TYPESCOUNT
};
diff --git a/toonz/sources/stdfx/igs_rotate_blur.cpp b/toonz/sources/stdfx/igs_rotate_blur.cpp
index bffa42e..3a4b51b 100644
--- a/toonz/sources/stdfx/igs_rotate_blur.cpp
+++ b/toonz/sources/stdfx/igs_rotate_blur.cpp
@@ -5,493 +5,367 @@
#include // std::domain_error()
#include "igs_ifx_common.h"
#include "igs_rotate_blur.h"
+
+#include
+#include
namespace {
+
+enum Type { Accelerator = 0, Uniform_Angle, Uniform_Length };
+
//------------------------------------------------------------------
-template
-class rotate_ {
+class Rotator {
+ const float* in_top_;
+ const int hh_;
+ const int ww_;
+ const int cc_;
+ const TPointD center_;
+ const bool antialias_sw_;
+ const bool alpha_rendering_sw_;
+ const double radian_;
+ const double blur_radius_;
+ const double spin_radius_;
+ const int type_;
+ const double ellipse_aspect_ratio_;
+ const double ellipse_angle_;
+ QTransform tr_, tr_inv_;
+
public:
- rotate_(const T *in_top, const int height, const int width,
- const int channels, const double xc, const double yc,
- const double sub_size, const int imax, const double dmax,
- const double radian, const double blur_radius,
- const double spin_radius)
+ Rotator(const float* in_top, const int height, const int width,
+ const int channels, const TPointD center, const bool antialias_sw,
+ const bool alpha_rendering_sw, const double radian,
+ const double blur_radius, const double spin_radius, const int type,
+ const double ellipse_aspect_ratio, const double ellipse_angle)
: in_top_(in_top)
, hh_(height)
, ww_(width)
, cc_(channels)
- , xc_(xc)
- , yc_(yc)
- , sub_size_(sub_size)
- , imax_(imax)
- , dmax_(dmax)
+ , center_(center)
+ , antialias_sw_(antialias_sw)
+ , alpha_rendering_sw_(alpha_rendering_sw)
, radian_(radian)
, blur_radius_(blur_radius)
- , spin_radius_(spin_radius) {}
- void pixel_value(const T *in_current_pixel, const int xx, const int yy,
- const int z1, const int z2, const double ref_increase_val,
- const double ref_decrease_val,
- const double each_pixel_blur_ratio, T *result_pixel) {
+ , spin_radius_(spin_radius)
+ , type_(type)
+ , ellipse_aspect_ratio_(ellipse_aspect_ratio)
+ , ellipse_angle_(ellipse_angle) {
+ if (ellipse_aspect_ratio_ != 1.0) {
+ double axis_x =
+ 2.0 * ellipse_aspect_ratio_ / (ellipse_aspect_ratio_ + 1.0);
+ double axis_y = axis_x / ellipse_aspect_ratio_;
+ tr_ = QTransform()
+ .rotateRadians(this->ellipse_angle_)
+ .scale(axis_x, axis_y);
+ tr_inv_ = QTransform(tr_).inverted();
+ }
+ }
+ void pixel_value(const float* in_current_pixel, const int xx, const int yy,
+ const bool isRGB, const double refVal, float* result_pixel) {
+ auto in_pixel = [&](int x, int y) {
+ /* clamp */
+ x = (x < 0) ? 0 : ((this->ww_ <= x) ? this->ww_ - 1 : x);
+ y = (y < 0) ? 0 : ((this->hh_ <= y) ? this->hh_ - 1 : y);
+ return this->in_top_ + this->cc_ * y * this->ww_ + this->cc_ * x;
+ };
+ auto interp = [&](float v1, float v2, float r) {
+ return v1 * (1.f - r) + v2 * r;
+ };
+ auto accum_interp_in_values = [&](QPointF pos, std::vector& accumP,
+ int z1, int z2, float weight) {
+ int xId = (int)std::floor(pos.x());
+ float rx = pos.x() - (float)xId;
+ int yId = (int)std::floor(pos.y());
+ float ry = pos.y() - (float)yId;
+ const float* p00 = in_pixel(xId, yId);
+ const float* p01 = in_pixel(xId + 1, yId);
+ const float* p10 = in_pixel(xId, yId + 1);
+ const float* p11 = in_pixel(xId + 1, yId + 1);
+ for (int zz = z1; zz <= z2; zz++) {
+ accumP[zz] += weight * interp(interp(p00[zz], p01[zz], rx),
+ interp(p10[zz], p11[zz], rx), ry);
+ }
+ };
+ auto accum_in_values = [&](QPointF pos, std::vector& accumP, int z1,
+ int z2, float weight) {
+ int xId = (int)std::round(pos.x());
+ int yId = (int)std::round(pos.y());
+ const float* p = in_pixel(xId, yId);
+ for (int zz = z1; zz <= z2; zz++) {
+ accumP[zz] += weight * p[zz];
+ }
+ };
+
+ int c1, c2;
+ if (isRGB) {
+#if defined RGBA_ORDER_OF_TOONZ6
+ c1 = igs::image::rgba::blu;
+ c2 = igs::image::rgba::red;
+#elif defined RGBA_ORDER_OF_OPENGL
+ c1 = igs::image::rgba::red;
+ c2 = igs::image::rgba::blu;
+#else
+ Must be define / DRGBA_ORDER_OF_TOONZ6 or / DRGBA_ORDER_OF_OPENGL
+#endif
+ } else {
+ c1 = igs::image::rgba::alp;
+ c2 = igs::image::rgba::alp;
+ }
+
+ const QPointF center(this->center_.x, this->center_.y);
/* Pixel位置(0.5 1.5 2.5 ...) */
- const double xp = static_cast(xx) + 0.5;
- const double yp = static_cast(yy) + 0.5;
+ const QPointF p(static_cast(xx) + 0.5f,
+ static_cast(yy) + 0.5f);
/* 中心からPixel位置へのベクトルと長さ */
- const double xv = xp - this->xc_;
- const double yv = yp - this->yc_;
- const double dist = sqrt(xv * xv + yv * yv);
+ const QVector2D v(p - center);
+ const float dist = v.length();
/* 指定半径の範囲内なら何もしない */
- if (dist <= this->blur_radius_) {
- for (int zz = z1; zz <= z2; ++zz) {
- result_pixel[zz] = in_current_pixel[zz];
+ bool is_in_blur_radius = false;
+ if (this->ellipse_aspect_ratio_ == 1.f) {
+ is_in_blur_radius = (dist <= this->blur_radius_);
+ } else {
+ is_in_blur_radius =
+ QVector2D(this->tr_inv_.map(v.toPointF())).lengthSquared() <=
+ (this->blur_radius_ * this->blur_radius_);
+ }
+ if (is_in_blur_radius) {
+ for (int c = c1; c <= c2; ++c) {
+ result_pixel[c] = in_current_pixel[c];
}
return;
}
- /* 中心からPixel位置への単位ベクトル */
- const double cosval = xv / dist;
- const double sinval = yv / dist;
-
- /* 円周接線方向Sampling1つずらした位置への回転角度 */
- const double xw = xv - this->sub_size_ * sinval;
- const double yw = yv + this->sub_size_ * cosval;
- const double sub_radian =
- acos((xv * xw + yv * yw) /
- (sqrt(xv * xv + yv * yv) * sqrt(xw * xw + yw * yw)));
-
/* 積算値と積算回数 */
- std::vector accum_val(this->cc_);
- int accum_counter = 0;
+ std::vector accum_val(this->cc_);
+ float accum_counter = 0.f; // TODO 重みづけを均一以外も選べるようにしたい
/* 参照画像による強弱付加 */
- double scale = this->radian_;
- if (0.0 <= each_pixel_blur_ratio) {
- scale *= each_pixel_blur_ratio;
+ float radian = this->radian_ * refVal; // TODO: it can be bidirectional..
+
+ if (type_ == Accelerator) { /* 外への強調 */
+ radian *= (dist - this->blur_radius_) / this->spin_radius_;
+ } else if (type_ == Uniform_Length &&
+ dist > 0.) { // decrease radian so that the blur length becomes
+ // the same along radius
+ radian *= (this->spin_radius_ + this->blur_radius_) / dist;
+ radian = std::min(radian, 2.f * (float)M_PI);
}
- /* Radial方向Sampling */
- for (double ss = this->sub_size_ / 2.0 - 0.5; ss < 0.5;
- ss += this->sub_size_) {
- /* Radial方向位置 */
- const double xp2 = xp + ss * cosval;
- const double yp2 = yp + ss * sinval;
-
- /* ぼかす回転角度範囲range */
- double radian = scale;
-
- if (0.0 < this->spin_radius_) { /* 外への強調 */
- const double xv2 = xp2 - this->xc_;
- const double yv2 = yp2 - this->yc_;
- const double dist2 = sqrt(xv2 * xv2 + yv2 * yv2);
- radian *=
- /**(dist2 - this->blur_radius_) /
-(this->spin_radius_ - this->blur_radius_);**/
- (dist2 - this->blur_radius_) / this->spin_radius_;
+ // blur pixel length at the current pos
+ float spin_length_half = dist * radian * 0.5f;
+
+ // sampling in both directions
+ float sample_length = 0.f;
+ while (sample_length < spin_length_half) {
+ // compute weight
+ float weight = 1.f;
+ if (sample_length >= spin_length_half - 1.f) {
+ if (antialias_sw_)
+ weight = spin_length_half - sample_length;
+ else
+ break;
}
-
- /* Sampling回数 */
- int sub_count = static_cast(radian / sub_radian);
-
- /* Sampling開始角度 */
- const double sub_start =
- (radian - sub_count * sub_radian) / 2.0 - radian / 2.0;
-
- /* 円周方向Sampling */
- for (double rr = sub_start; 0 < sub_count--; rr += sub_radian) {
- /* Radial位置(xp2,yp2)から円周位置(xx,yy)へ */
- const double cosval2 = cos(rr);
- const double sinval2 = sin(rr);
-
- const double xd = xp2 - this->xc_;
- const double yd = yp2 - this->yc_;
-
- int xx = static_cast(xd * cosval2 - yd * sinval2 + this->xc_);
- int yy = static_cast(xd * sinval2 + yd * cosval2 + this->yc_);
-
- /* clamp */
- xx = (xx < 0) ? 0 : ((this->ww_ <= xx) ? this->ww_ - 1 : xx);
- yy = (yy < 0) ? 0 : ((this->hh_ <= yy) ? this->hh_ - 1 : yy);
-
- /* 画像のPixel位置 */
- const T *in_current =
- this->in_top_ + this->cc_ * yy * this->ww_ + this->cc_ * xx;
-
- /* 積算 */
- for (int zz = z1; zz <= z2; ++zz) {
- accum_val[zz] += static_cast(in_current[zz]);
- }
- ++accum_counter;
+ // advance to the next sample
+ sample_length += weight;
+
+ // compute for both side
+ float sample_radian = sample_length / dist;
+ QPointF vrot1, vrot2;
+ if (this->ellipse_aspect_ratio_ == 1.f) {
+ float cos = std::cos(sample_radian);
+ float sin = std::sin(sample_radian);
+
+ vrot1 = QPointF(cos * v.x() - sin * v.y(), sin * v.x() + cos * v.y());
+ vrot2 = QPointF(cos * v.x() + sin * v.y(), -sin * v.x() + cos * v.y());
+ } else {
+ vrot1 =
+ (this->tr_inv_ * QTransform(this->tr_).rotateRadians(sample_radian))
+ .map(v.toPointF());
+ vrot2 = (this->tr_inv_ *
+ QTransform(this->tr_).rotateRadians(-sample_radian))
+ .map(v.toPointF());
+ }
+ if (antialias_sw_) {
+ accum_interp_in_values(vrot1 + center, accum_val, c1, c2, weight);
+ accum_interp_in_values(vrot2 + center, accum_val, c1, c2, weight);
+ } else {
+ accum_in_values(vrot1 + center, accum_val, c1, c2, weight);
+ accum_in_values(vrot2 + center, accum_val, c1, c2, weight);
}
+ accum_counter += weight * 2.f;
}
+
+ // sample the original pos
+ if (antialias_sw_)
+ accum_interp_in_values(p, accum_val, c1, c2, 1.f);
+ else
+ accum_in_values(p, accum_val, c1, c2, 1.f);
+ accum_counter += 1.f;
+
+ //}
/* 積算しなかったとき(念のためのCheck) */
- if (accum_counter <= 0) {
- for (int zz = z1; zz <= z2; ++zz) {
- result_pixel[zz] = in_current_pixel[zz];
+ if (accum_counter <= 0.f) {
+ for (int c = c1; c <= c2; ++c) {
+ result_pixel[c] = in_current_pixel[c];
}
return;
}
/* ここで画像Pixelに保存 */
- for (int zz = z1; zz <= z2; ++zz) {
- accum_val[zz] /= static_cast(accum_counter);
+ for (int c = c1; c <= c2; ++c) {
+ accum_val[c] /= accum_counter;
- if ((0 <= ref_increase_val) && (in_current_pixel[zz] < accum_val[zz])) {
+ if (isRGB && !this->alpha_rendering_sw_ &&
+ (in_current_pixel[c] < accum_val[c]) &&
+ result_pixel[igs::image::rgba::alp] < 1.f) {
/* 増分のみMask! */
- accum_val[zz] =
- static_cast(in_current_pixel[zz]) +
- (accum_val[zz] - in_current_pixel[zz]) * ref_increase_val;
- } else if ((0 <= ref_decrease_val) &&
- (accum_val[zz] < in_current_pixel[zz])) {
- /* 減分のみMask! */
- accum_val[zz] =
- static_cast(in_current_pixel[zz]) +
- (accum_val[zz] - in_current_pixel[zz]) * ref_decrease_val;
- }
-
- accum_val[zz] += 0.5; /* 誤差対策 */
- if (this->dmax_ < accum_val[zz]) {
- result_pixel[zz] = static_cast(this->imax_);
- } else if (accum_val[zz] < 0) {
- result_pixel[zz] = 0;
+ result_pixel[c] =
+ in_current_pixel[c] + (accum_val[c] - in_current_pixel[c]) *
+ result_pixel[igs::image::rgba::alp];
} else {
- result_pixel[zz] = static_cast(accum_val[zz]);
+ result_pixel[c] = accum_val[c];
}
}
}
private:
- rotate_() {}
-
- const T *in_top_;
- const int hh_;
- const int ww_;
- const int cc_;
- const double xc_;
- const double yc_;
- const double sub_size_;
- const int imax_;
- const double dmax_;
- const double radian_;
- const double blur_radius_;
- const double spin_radius_;
-
- /* copy constructorを無効化 */
- rotate_(const rotate_ &);
-
- /* 代入演算子を無効化 */
- rotate_ &operator=(const rotate_ &);
+ // disable
+ Rotator();
+ Rotator(const Rotator&);
+ Rotator& operator=(const Rotator&) {}
};
//------------------------------------------------------------------
-const double pi = 3.14159265358979;
-template
-void rotate_convert_template_(
- const IT *in, const int margin /* 参照画像(in)がもつ余白 */
-
- ,
- const RT *ref /* 求める画像(out)と同じ高さ、幅、チャンネル数 */
- ,
- const int ref_bits, const int ref_mode // R,G,B,A,luminance
-
- ,
- IT *out, const int hh /* 求める画像(out)の高さ */
- ,
- const int ww /* 求める画像(out)の幅 */
- ,
- const int cc, const double xc, const double yc, const double degree,
- const double blur_radius /* ぼかしの始まる半径 */
- ,
- const double spin_radius /* ゼロ以上でspin指定となり、
- かつぼかし強弱の一定になる半径となる */
- ,
- const int sub_div /* 1ならJaggy、2以上はAntialias */
- ,
- const bool alpha_rendering_sw) {
- ref_bits; // for warning
-
- /* 強度のないとき、または、サブ分割がないとき、なにもしない */
- if (degree <= 0.0 || sub_div <= 0
- // || spin_radius<=blur_radius
- ) {
- return;
- }
-
- rotate_ cl_rota(in, hh + margin * 2, ww + margin * 2, cc, xc + margin,
- yc + margin, 1.0 / sub_div,
- std::numeric_limits::max(),
- static_cast(std::numeric_limits::max()),
- degree * pi / 180.0, blur_radius, spin_radius);
-
-#if defined RGBA_ORDER_OF_TOONZ6
- const int z1 = igs::image::rgba::blu;
- const int z2 = igs::image::rgba::red;
-#elif defined RGBA_ORDER_OF_OPENGL
- const int z1 = igs::image::rgba::red;
- const int z2 = igs::image::rgba::blu;
-#else
- Must be define / DRGBA_ORDER_OF_TOONZ6 or
- / DRGBA_ORDER_OF_OPENGL
-#endif
-
- const IT *p_in = in + margin * (ww + margin * 2) * cc + margin * cc;
- IT *pout = out;
- if (0 == ref) { /* 参照なし */
- if (igs::image::rgba::siz == cc) {
+void rotate_convert(
+ const float* in, float* out, const int margin, /* 参照画像(in)がもつ余白 */
+ const TDimension out_dim, /* 求める画像(out)のサイズ */
+ const int channels, const float* ref, /* 求める画像(out)と同じ高さ、幅 */
+ const TPointD center, const double degree,
+ const double blur_radius, /* ぼかしの始まる半径 */
+ const double spin_radius, /* ゼロ以上でspin指定となり、
+ かつぼかし強弱の一定になる半径となる */
+ const int type, // 0: Accelerator, 1: Uniform Angle, 2: Uniform Length
+ const bool antialias_sw, /* when true, sampled pixel will be
+ bilinear-interpolated */
+ const bool alpha_rendering_sw, const double ellipse_aspect_ratio,
+ const double ellipse_angle) {
+ assert(degree > 0.0);
+
+ Rotator rotator(in, out_dim.ly + margin * 2, out_dim.lx + margin * 2,
+ channels, center, antialias_sw, alpha_rendering_sw,
+ degree * M_PI_180, blur_radius, spin_radius, type,
+ ellipse_aspect_ratio, ellipse_angle * M_PI_180);
+
+ const float* p_in =
+ in + margin * (out_dim.lx + margin * 2) * channels + margin * channels;
+ float* p_out = out;
+ const float* p_ref = ref; // may be nullptr
+
+ for (int yy = margin; yy < out_dim.ly + margin;
+ ++yy, p_in += 2 * margin * channels) {
+ for (int xx = margin; xx < out_dim.lx + margin;
+ ++xx, p_in += channels, p_out += channels) {
using namespace igs::image::rgba;
- if (alpha_rendering_sw) { /* Alphaも処理する */
- for (int yy = margin; yy < hh + margin; ++yy, p_in += 2 * margin * cc) {
- for (int xx = margin; xx < ww + margin;
- ++xx, p_in += cc, pout += cc) {
- cl_rota.pixel_value(p_in, xx, yy, alp, alp, -1.0, -1.0, -1.0, pout);
- if (0 == pout[alp]) {
- pout[red] = p_in[red];
- pout[gre] = p_in[gre];
- pout[blu] = p_in[blu];
- continue;
- }
- cl_rota.pixel_value(p_in, xx, yy, z1, z2, -1.0, -1.0, -1.0, pout);
- }
- }
- } else { /* Alpha処理しない、RGB増分をAlphaでMaskする */
- const unsigned int val_max = std::numeric_limits::max();
- for (int yy = margin; yy < hh + margin; ++yy, p_in += 2 * margin * cc) {
- for (int xx = margin; xx < ww + margin;
- ++xx, p_in += cc, pout += cc) {
- pout[alp] = p_in[alp];
- if (0 == pout[alp]) {
- pout[red] = p_in[red];
- pout[gre] = p_in[gre];
- pout[blu] = p_in[blu];
- continue;
- }
- cl_rota.pixel_value(p_in, xx, yy, z1, z2,
- static_cast(p_in[alp]) / val_max, -1.0,
- -1.0, pout);
- }
- }
- }
- } else { /* Alphaがない, RGB/Grayscale... */
- for (int yy = margin; yy < hh + margin; ++yy, p_in += 2 * margin * cc) {
- for (int xx = margin; xx < ww + margin; ++xx, p_in += cc, pout += cc) {
- cl_rota.pixel_value(p_in, xx, yy, 0, cc - 1, -1.0, -1.0, -1.0, pout);
- }
+ float refVal = (ref) ? *p_ref : 1.f;
+
+ // if the reference value is zero
+ if (refVal == 0.f) {
+ for (int c = 0; c < channels; ++c) p_out[c] = p_in[c];
+ p_ref++;
+ continue;
}
- }
- } else { /* 参照あり */
- const RT *refe = ref;
- const int r_max = std::numeric_limits