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::max(); - if (igs::image::rgba::siz == cc) { - 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, refe += cc) { - const double refv = - igs::color::ref_value(refe, cc, r_max, ref_mode); - if (0 == refv) { - for (int zz = 0; zz < cc; ++zz) { - pout[zz] = p_in[zz]; - } - continue; - } - - cl_rota.pixel_value(p_in, xx, yy, alp, alp, -1.0, -1.0, refv, 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, refv, 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, refe += cc) { - const double refv = - igs::color::ref_value(refe, cc, r_max, ref_mode); - if (0 == refv) { - for (int zz = 0; zz < cc; ++zz) { - pout[zz] = p_in[zz]; - } - continue; - } - - 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, - refv, pout); - } - } + + if (alpha_rendering_sw) { // blur alpha + rotator.pixel_value(p_in, xx, yy, false, refVal, p_out); + } else { // use the src alpha as-is + p_out[alp] = p_in[alp]; } - } 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, refe += cc) { - const double refv = igs::color::ref_value(refe, cc, r_max, ref_mode); - if (0 == refv) { - for (int zz = 0; zz < cc; ++zz) { - pout[zz] = p_in[zz]; - } - continue; - } - - cl_rota.pixel_value(p_in, xx, yy, 0, cc - 1, -1.0, -1.0, refv, pout); - } + + if (p_out[alp] == 0.f) { + p_out[red] = p_in[red]; + p_out[gre] = p_in[gre]; + p_out[blu] = p_in[blu]; + if (ref) p_ref++; + continue; } + + // blur RGB channels + rotator.pixel_value(p_in, xx, yy, true, refVal, p_out); + + if (ref) p_ref++; } } } + //------------------------------------------------------------------ -} +} // namespace void igs::rotate_blur::convert( - const unsigned char *in, const int margin /* 参照画像(in)がもつ余白 */ - - , - const unsigned char *ref /* outと同じ高さ、幅、チャンネル数 */ - , - const int ref_bits, const int ref_mode // R,G,B,A,luminance - - , - unsigned char *out - - , - const int height /* 求める画像(out)の高さ */ - , - const int width /* 求める画像(out)の幅 */ - , - const int channels, const int bits - - , - const double xc, const double yc, const double degree /* ぼかしの回転角度 */ - , - const double blur_radius /* ぼかしの始まる半径 */ - , - const double spin_radius /* ゼロ以上でspin指定となり、 + const float* in, float* out, const int margin, + 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 sub_div /* 1ならJaggy、2以上はAntialias */ - , - const bool alpha_rendering_sw) { - if ((igs::image::rgba::siz != channels) && - (igs::image::rgb::siz != channels) && (1 != channels) /* bit(monoBW) */ - ) { - throw std::domain_error("Bad channels,Not rgba/rgb/grayscale"); - } - - if ((std::numeric_limits::digits != bits) && - (std::numeric_limits::digits != bits)) { - throw std::domain_error("Bad in bits,Not uchar/ushort"); - } - if ((0 != ref) && (std::numeric_limits::digits != ref_bits) && - (std::numeric_limits::digits != ref_bits)) { - throw std::domain_error("Bad ref bits,Not uchar/ushort"); - } - - /* 強度のないとき、または、サブ分割がないとき */ - if (degree <= 0.0 || sub_div <= 0 - // || spin_radius<=blur_radius - ) { - if (std::numeric_limits::digits == bits) { - igs::image::copy_except_margin(in, margin, out, height, width, channels); - } else if (std::numeric_limits::digits == bits) { - igs::image::copy_except_margin( - reinterpret_cast(in), margin, - reinterpret_cast(out), height, width, channels); - } + 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) { + /* 強度のないとき */ + if (degree <= 0.0) { + igs::image::copy_except_margin(in, margin, out, out_dim.ly, out_dim.lx, + channels); return; } - if (std::numeric_limits::digits == ref_bits) { - if (std::numeric_limits::digits == bits) { - rotate_convert_template_(in, margin, ref, ref_bits, ref_mode, out, height, - width, channels, xc, yc, degree, blur_radius, - spin_radius, sub_div, alpha_rendering_sw); - } else if (std::numeric_limits::digits == bits) { - rotate_convert_template_(reinterpret_cast(in), - margin, ref, ref_bits, ref_mode, - reinterpret_cast(out), height, - width, channels, xc, yc, degree, blur_radius, - spin_radius, sub_div, alpha_rendering_sw); - } - } else { /* ref_bitsがゼロでも(refがなくても)ここにくる */ - if (std::numeric_limits::digits == bits) { - rotate_convert_template_( - in, margin, reinterpret_cast(ref), ref_bits, - ref_mode, out, height, width, channels, xc, yc, degree, blur_radius, - spin_radius, sub_div, alpha_rendering_sw); - } else if (std::numeric_limits::digits == bits) { - rotate_convert_template_( - reinterpret_cast(in), margin, - reinterpret_cast(ref), ref_bits, ref_mode, - reinterpret_cast(out), height, width, channels, xc, - yc, degree, blur_radius, spin_radius, sub_div, alpha_rendering_sw); - } - } + rotate_convert(in, out, margin, out_dim, channels, ref, center, degree, + blur_radius, spin_radius, type, antialias_sw, + alpha_rendering_sw, ellipse_aspect_ratio, ellipse_angle); } //-------------------------------------------------------------------- namespace { -double reference_margin_length_(const double xc, const double yc, - const double xp, const double yp, double radian, +double reference_margin_length_(const TPointD center, const double xp, + const double yp, double radian, const double blur_radius, - const double spin_radius, - const double sub_size) { - sub_size; // for warning + const double spin_radius, const int type) { + const QPointF c(center.x, center.y); + const QPointF p(xp, yp); + const QVector2D v(p - c); + const float dist = v.length(); - const double xv = xp - xc; - const double yv = yp - yc; - - if (0.0 < spin_radius) { /* 外への強調 */ - const double dist = sqrt(xv * xv + yv * yv); - // radian *= (dist-blur_radius)/(spin_radius-blur_radius); + if (type == Accelerator) { /* 外への強調 */ radian *= (dist - blur_radius) / spin_radius; + } else if (type == Uniform_Length && + dist > 0.) { // decrease radian so that the blur length becomes + // the same along radius + radian *= (spin_radius + blur_radius) / dist; + radian = std::min(radian, 2. * M_PI); } - double cosval = cos(radian / 2.0); - double sinval = sin(radian / 2.0); - const double x1 = xv * cosval - yv * sinval + xc; - const double y1 = xv * sinval + yv * cosval + yc; - const double xv1 = x1 - xp; - const double yv1 = y1 - yp; - const double dist1 = sqrt(xv1 * xv1 + yv1 * yv1); /* 必ずプラス値 */ - - cosval = cos(-radian / 2.0); - sinval = sin(-radian / 2.0); - const double xp2 = xv * cosval - yv * sinval + xc; - const double yp2 = xv * sinval + yv * cosval + yc; - const double xv2 = xp2 - xp; - const double yv2 = yp2 - yp; - const double dist2 = sqrt(xv2 * xv2 + yv2 * yv2); /* 必ずプラス値 */ + double cosval = cos(radian / 2.0); + double sinval = sin(radian / 2.0); + QPointF vrot1(v.x() * cosval - v.y() * sinval, + v.x() * sinval + v.y() * cosval); + QPointF vrot2(v.x() * cosval + v.y() * sinval, + -v.x() * sinval + v.y() * cosval); + float dist1 = QVector2D(vrot1 + c - p).length(); + float dist2 = QVector2D(vrot2 + c - p).length(); return (dist1 < dist2) ? dist2 : dist1; } -} +} // namespace int igs::rotate_blur::reference_margin( - const int height /* 求める画像(out)の高さ */ - , - const int width /* 求める画像(out)の幅 */ - , - const double xc, const double yc, const double degree /* ぼかしの回転角度 */ - , - const double blur_radius /* ぼかしの始まる半径 */ - , - const double spin_radius /* ゼロ以上でspin指定となり、 + const int height, /* 求める画像(out)の高さ */ + const int width, /* 求める画像(out)の幅 */ + const TPointD center, const double degree, /* ぼかしの回転角度 */ + const double blur_radius, /* ぼかしの始まる半径 */ + const double spin_radius, /* ゼロ以上でspin指定となり、 かつぼかし強弱の一定になる半径となる */ - , - const int sub_div /* 1ならJaggy、2以上はAntialias */ - ) { - /* 強度のないとき、または、サブ分割がないとき、なにもしない */ - if (degree <= 0.0 || sub_div <= 0 - // || spin_radius<=blur_radius - ) { + const int type // 0: Accelerator, 1: Uniform Angle, 2: Uniform Length +) { + /* 強度のないとき、なにもしない */ + if (degree <= 0.0) { return 0; } @@ -503,27 +377,27 @@ int igs::rotate_blur::reference_margin( deg = 180.0; } - margin1 = reference_margin_length_(xc, yc, -width / 2.0, -height / 2.0, - deg * pi / 180.0, blur_radius, spin_radius, - 1.0 / sub_div); + margin1 = + reference_margin_length_(center, -width / 2.0, -height / 2.0, + deg * M_PI_180, blur_radius, spin_radius, type); - margin2 = reference_margin_length_(xc, yc, -width / 2.0, height / 2.0, - deg * pi / 180.0, blur_radius, spin_radius, - 1.0 / sub_div); + margin2 = + reference_margin_length_(center, -width / 2.0, height / 2.0, + deg * M_PI_180, blur_radius, spin_radius, type); if (margin1 < margin2) { margin1 = margin2; } - margin2 = reference_margin_length_(xc, yc, width / 2.0, -height / 2.0, - deg * pi / 180.0, blur_radius, spin_radius, - 1.0 / sub_div); + margin2 = + reference_margin_length_(center, width / 2.0, -height / 2.0, + deg * M_PI_180, blur_radius, spin_radius, type); if (margin1 < margin2) { margin1 = margin2; } - margin2 = reference_margin_length_(xc, yc, width / 2.0, height / 2.0, - deg * pi / 180.0, blur_radius, spin_radius, - 1.0 / sub_div); + margin2 = + reference_margin_length_(center, width / 2.0, height / 2.0, + deg * M_PI_180, blur_radius, spin_radius, type); if (margin1 < margin2) { margin1 = margin2; } diff --git a/toonz/sources/stdfx/igs_rotate_blur.h b/toonz/sources/stdfx/igs_rotate_blur.h index 8340edb..4bb1d1b 100644 --- a/toonz/sources/stdfx/igs_rotate_blur.h +++ b/toonz/sources/stdfx/igs_rotate_blur.h @@ -7,38 +7,23 @@ #define IGS_ROTATE_BLUR_EXPORT #endif +#include "tgeometry.h" + namespace igs { namespace rotate_blur { IGS_ROTATE_BLUR_EXPORT void convert( - const unsigned char *in, const int margin /* 参照画像(in)がもつ余白 */ - - , - const unsigned char *ref /* outと同じ高さ、幅、チャンネル数 */ - , - const int ref_bits, const int ref_mode // R,G,B,A,luminance - - , - unsigned char *out - - , - const int height /* 求める画像(out)の高さ */ - , - const int width /* 求める画像(out)の幅 */ - , - const int channels, const int bits - - , - const double cx, const double cy, - const double degree = 30.0 /* ぼかしの回転角度 */ - , - const double blur_radius = 0.0 /* ぼかしの始まる半径 */ - , - const double spin_radius = 0.0 /* ゼロ以上でspin指定となり、 + const float* in, float* out, const int margin, + const TDimension out_dim, /* 求める画像(out)のサイズ */ + const int channels, const float* ref, /* outと同じ高さ、幅 */ + const TPointD center, const double degree = 30.0, /* ぼかしの回転角度 */ + const double blur_radius = 0.0, /* ぼかしの始まる半径 */ + const double spin_radius = 0.0, /* ゼロ以上でspin指定となり、 かつぼかし強弱の一定になる半径となる */ - , - const int sub_div = 4 /* 1ならJaggy、2以上はAntialias */ - , - const bool alpha_rendering_sw = true); + const int type = 0, // 0: Accelerator, 1: Uniform Angle, 2: Uniform Length + const bool antialias_sw = + true, /* when true, sampled pixel will be bilinear-interpolated */ + const bool alpha_rendering_sw = true, + const double ellipse_aspect_ratio = 1.0, const double ellipse_angle = 0.0); #if 0 //------------------- comment out start ------------------------ IGS_ROTATE_BLUR_EXPORT int enlarge_margin( const int height /* 求める画像(out)の高さ */ @@ -53,20 +38,15 @@ IGS_ROTATE_BLUR_EXPORT void convert( ); #endif //------------------- comment out end ------------------------- IGS_ROTATE_BLUR_EXPORT int reference_margin( - const int height /* 求める画像(out)の高さ */ - , - const int width /* 求める画像(out)の幅 */ - , - const double xc, const double yc, const double degree /* ぼかしの回転角度 */ - , - const double blur_radius /* ぼかしの始まる半径 */ - , - const double spin_radius /* ゼロ以上でspin指定となり、 + const int height, /* 求める画像(out)の高さ */ + const int width, /* 求める画像(out)の幅 */ + const TPointD center, const double degree, /* ぼかしの回転角度 */ + const double blur_radius, /* ぼかしの始まる半径 */ + const double spin_radius, /* ゼロ以上でspin指定となり、 かつぼかし強弱の一定になる半径となる */ - , - const int sub_div /* 1ならJaggy、2以上はAntialias */ - ); -} -} + const int type // 0: Accelerator, 1: Uniform Angle, 2: Uniform Length +); +} // namespace rotate_blur +} // namespace igs #endif /* !igs_rotate_blur_h */ diff --git a/toonz/sources/stdfx/ino_common.cpp b/toonz/sources/stdfx/ino_common.cpp index 867299e..7136dbf 100644 --- a/toonz/sources/stdfx/ino_common.cpp +++ b/toonz/sources/stdfx/ino_common.cpp @@ -41,6 +41,32 @@ void ras_to_arr_(const TRasterPT ras, U* arr, const int channels) { } } } + +// T is TPixel32 or TPixel64 +// normalize to 0.0 - 1.0 +template +void ras_to_float_arr_(const TRasterPT ras, float* arr, const int channels) { + using namespace igs::image::rgba; + float fac = 1.f / (float)T::maxChannelValue; + for (int yy = 0; yy < ras->getLy(); ++yy) { + const T* ras_sl = ras->pixels(yy); + for (int xx = 0; xx < ras->getLx(); ++xx, arr += channels) { + if (red < channels) { + arr[red] = (float)ras_sl[xx].r * fac; + } + if (gre < channels) { + arr[gre] = (float)ras_sl[xx].g * fac; + } + if (blu < channels) { + arr[blu] = (float)ras_sl[xx].b * fac; + } + if (alp < channels) { + arr[alp] = (float)ras_sl[xx].m * fac; + } + } + } +} + template void arr_to_ras_(const U* arr, const int channels, TRasterPT ras, const int margin // default is 0 @@ -70,6 +96,96 @@ void arr_to_ras_(const U* arr, const int channels, TRasterPT ras, } } } + +template +void float_arr_to_ras_(const float* arr, const int channels, TRasterPT ras, + const int margin // default is 0 +) { + arr += + (ras->getLx() + margin + margin) * margin * channels + margin * channels; + + using namespace igs::image::rgba; + float fac = (float)T::maxChannelValue; + + for (int yy = 0; yy < ras->getLy(); + ++yy, arr += (ras->getLx() + margin + margin) * channels) { + const float* arrx = arr; + T* ras_sl = ras->pixels(yy); + for (int xx = 0; xx < ras->getLx(); ++xx, arrx += channels) { + if (red < channels) { + ras_sl[xx].r = (arrx[red] >= 1.f) + ? T::maxChannelValue + : (arrx[red] <= 0.f) + ? (typename T::Channel)0 + : (typename T::Channel)( + std::round(arrx[red] * fac + 0.5f)); + } + if (gre < channels) { + ras_sl[xx].g = (arrx[gre] >= 1.f) + ? T::maxChannelValue + : (arrx[gre] <= 0.f) + ? (typename T::Channel)0 + : (typename T::Channel)( + std::round(arrx[gre] * fac + 0.5f)); + } + if (blu < channels) { + ras_sl[xx].b = (arrx[blu] >= 1.f) + ? T::maxChannelValue + : (arrx[blu] <= 0.f) + ? (typename T::Channel)0 + : (typename T::Channel)( + std::round(arrx[blu] * fac + 0.5f)); + } + if (alp < channels) { + ras_sl[xx].m = (arrx[alp] >= 1.f) + ? T::maxChannelValue + : (arrx[alp] <= 0.f) + ? (typename T::Channel)0 + : (typename T::Channel)( + std::round(arrx[alp] * fac + 0.5f)); + } + } + } +} + +template +float getFactor() { + return 1.f / (float)T::maxChannelValue; +} + +// T is either TPixel32, TPixel64 +template +void ras_to_ref_float_arr_(const TRasterPT ras, float* arr, + const int refer_mode) { + float fac = getFactor(); + for (int yy = 0; yy < ras->getLy(); ++yy) { + const T* ras_sl = ras->pixels(yy); + for (int xx = 0; xx < ras->getLx(); ++xx, arr++, ras_sl++) { + switch (refer_mode) { + case 0: + *arr = static_cast(ras_sl->r) * fac; + break; + case 1: + *arr = static_cast(ras_sl->g) * fac; + break; + case 2: + *arr = static_cast(ras_sl->b) * fac; + break; + case 3: + *arr = static_cast(ras_sl->m) * fac; + break; + case 4: + *arr = /* 輝度(Luminance)(CCIR Rec.601) */ + (0.298912f * static_cast(ras_sl->r) + + 0.586611f * static_cast(ras_sl->g) + + 0.114478f * static_cast(ras_sl->b)) * + fac; + break; + } + } + } +} + } // namespace //-------------------- void ino::ras_to_arr(const TRasterP in_ras, const int channels, @@ -81,6 +197,14 @@ void ino::ras_to_arr(const TRasterP in_ras, const int channels, in_ras, reinterpret_cast(out_arr), channels); } } +void ino::ras_to_float_arr(const TRasterP in_ras, const int channels, + float* out_arr) { + if ((TRaster32P)in_ras) { + ras_to_float_arr_(in_ras, out_arr, channels); + } else if ((TRaster64P)in_ras) { + ras_to_float_arr_(in_ras, out_arr, channels); + } +} void ino::arr_to_ras(const unsigned char* in_arr, const int channels, TRasterP out_ras, const int margin) { if ((TRaster32P)out_ras) { @@ -91,6 +215,16 @@ void ino::arr_to_ras(const unsigned char* in_arr, const int channels, margin); } } +void ino::float_arr_to_ras(const unsigned char* in_arr, const int channels, + TRasterP out_ras, const int margin) { + if ((TRaster32P)out_ras) { + float_arr_to_ras_(reinterpret_cast(in_arr), + channels, out_ras, margin); + } else if ((TRaster64P)out_ras) { + float_arr_to_ras_(reinterpret_cast(in_arr), + channels, out_ras, margin); + } +} //-------------------- void ino::ras_to_vec(const TRasterP in_ras, const int channels, std::vector& out_vec) { @@ -106,6 +240,17 @@ void ino::vec_to_ras(std::vector& in_vec, const int channels, in_vec.clear(); } //-------------------- + +void ino::ras_to_ref_float_arr(const TRasterP in_ras, float* out_arr, + const int refer_mode) { + if ((TRaster32P)in_ras) { + ras_to_ref_float_arr_(in_ras, out_arr, refer_mode); + } else if ((TRaster64P)in_ras) { + ras_to_ref_float_arr_(in_ras, out_arr, refer_mode); + } +} + +//-------------------- #if 0 //--- void ino::Lx_to_wrap( TRasterP ras ) { /* diff --git a/toonz/sources/stdfx/ino_common.h b/toonz/sources/stdfx/ino_common.h index 8247691..6696460 100644 --- a/toonz/sources/stdfx/ino_common.h +++ b/toonz/sources/stdfx/ino_common.h @@ -11,12 +11,19 @@ namespace ino { /* 一時バッファとの変換機能 */ void ras_to_arr(const TRasterP in_ras, const int channels, unsigned char* out_arr); +void ras_to_float_arr(const TRasterP in_ras, const int channels, + float* out_arr); void arr_to_ras(const unsigned char* in_arr, const int channels, TRasterP out_ras, const int margin); +void float_arr_to_ras(const unsigned char* in_arr, const int channels, + TRasterP out_ras, const int margin); void ras_to_vec(const TRasterP ras, const int channels, std::vector& vec); void vec_to_ras(std::vector& vec, const int channels, TRasterP ras, const int margin = 0); +void ras_to_ref_float_arr(const TRasterP in_ras, float* out_arr, + const int refer_mode); + // void Lx_to_wrap( TRasterP ras ); /* logのserverアクセスON/OFF,install時設定をするための機能 */ diff --git a/toonz/sources/stdfx/ino_spin_blur.cpp b/toonz/sources/stdfx/ino_spin_blur.cpp index 51fda1f..dc59acd 100644 --- a/toonz/sources/stdfx/ino_spin_blur.cpp +++ b/toonz/sources/stdfx/ino_spin_blur.cpp @@ -20,6 +20,9 @@ class ino_spin_blur final : public TStandardRasterFx { TBoolParamP m_alpha_rendering; TBoolParamP m_anti_alias; TIntEnumParamP m_ref_mode; + // elliptical shape + TDoubleParamP m_ellipse_aspect_ratio; + TDoubleParamP m_ellipse_angle; public: ino_spin_blur() @@ -29,7 +32,9 @@ public: , m_type(new TIntEnumParam(0, "Accelerator")) , m_alpha_rendering(true) , m_anti_alias(false) - , m_ref_mode(new TIntEnumParam(0, "Red")) { + , m_ref_mode(new TIntEnumParam(0, "Red")) + , m_ellipse_aspect_ratio(1.0) + , m_ellipse_angle(0.0) { this->m_center->getX()->setMeasureName("fxLength"); this->m_center->getY()->setMeasureName("fxLength"); this->m_radius->setMeasureName("fxLength"); @@ -44,11 +49,16 @@ public: bindParam(this, "alpha_rendering", this->m_alpha_rendering); bindParam(this, "anti_alias", this->m_anti_alias); bindParam(this, "reference", this->m_ref_mode); + bindParam(this, "ellipse_aspect_ratio", this->m_ellipse_aspect_ratio); + bindParam(this, "ellipse_angle", this->m_ellipse_angle); this->m_radius->setValueRange(0, (std::numeric_limits::max)()); /* 拡大のしすぎを防ぐためにMaxを制限する */ this->m_blur->setValueRange(0.0, 180.0); - this->m_type->addItem(1, "Uniform"); + this->m_ellipse_aspect_ratio->setValueRange(0.1, 10.0); + this->m_ellipse_angle->setValueRange(-180.0, 180.0); + this->m_type->addItem(1, "Uniform Angle"); + this->m_type->addItem(2, "Uniform Length"); this->m_ref_mode->addItem(1, "Green"); this->m_ref_mode->addItem(2, "Blue"); this->m_ref_mode->addItem(3, "Alpha"); @@ -74,10 +84,10 @@ public: /*--- margin計算...Twist時正確でない ---*/ return igs::rotate_blur::reference_margin( static_cast(ceil(bBox.getLy())), - static_cast(ceil(bBox.getLx())), center.x, center.y, + static_cast(ceil(bBox.getLx())), center, this->m_blur->getValue(frame), this->m_radius->getValue(frame) * scale, ((0 < this->m_type->getValue()) ? 0.0 : (bBox.getLy() / 2.0)), - (this->m_anti_alias->getValue() ? 4 : 1)); + this->m_type->getValue()); } void get_render_enlarge(const double frame, const TAffine affine, TRectD &bBox) { @@ -127,77 +137,67 @@ public: // add 20140130 void getParamUIs(TParamUIConcept *&concepts, int &length) override { - concepts = new TParamUIConcept[length = 3]; + concepts = new TParamUIConcept[length = 2]; - concepts[0].m_type = TParamUIConcept::POINT; - concepts[0].m_label = "Center"; + concepts[0].m_type = TParamUIConcept::ELLIPSE; + concepts[0].m_label = "Radius"; + concepts[0].m_params.push_back(m_radius); concepts[0].m_params.push_back(m_center); + concepts[0].m_params.push_back(m_ellipse_aspect_ratio); + concepts[0].m_params.push_back(m_ellipse_angle); - concepts[1].m_type = TParamUIConcept::RADIUS; - concepts[1].m_label = "Radius"; - concepts[1].m_params.push_back(m_radius); + concepts[1].m_type = TParamUIConcept::COMPASS_SPIN; concepts[1].m_params.push_back(m_center); - - concepts[2].m_type = TParamUIConcept::COMPASS_SPIN; - concepts[2].m_params.push_back(m_center); + concepts[1].m_params.push_back(m_ellipse_aspect_ratio); + concepts[1].m_params.push_back(m_ellipse_angle); } // add 20140130 }; FX_PLUGIN_IDENTIFIER(ino_spin_blur, "inoSpinBlurFx"); //-------------------------------------------------------------------- namespace { -void fx_(const TRasterP in_ras // with margin - , - const int margin - - /* ここではinとrefは同サイズで使用している */ - , - const TRasterP refer_ras // no margin - , - const int refer_mode - - , - TRasterP out_ras // no margin - - , - const double xp, const double yp, const int type, const double blur, +void fx_(const TRasterP in_ras, // with margin + const int margin, /* ここではinとrefは同サイズで使用している */ + const TRasterP refer_ras, // no margin + const int refer_mode, + TRasterP out_ras, // no margin + const TPointD center, const int type, const double blur, const double radius, const bool alpha_rendering_sw, - const bool anti_alias_sw) { + const bool anti_alias_sw, const double ellipse_aspect_ratio, + const double ellipse_angle) { + TRasterGR8P ref_gr8; + if ((refer_ras != nullptr) && (0 <= refer_mode)) { + ref_gr8 = + TRasterGR8P(refer_ras->getLy(), refer_ras->getLx() * sizeof(float)); + ref_gr8->lock(); + ino::ras_to_ref_float_arr(refer_ras, + reinterpret_cast(ref_gr8->getRawData()), + refer_mode); + } + TRasterGR8P in_gr8(in_ras->getLy(), - in_ras->getLx() * ino::channels() * - ((TRaster64P)in_ras ? sizeof(unsigned short) - : sizeof(unsigned char))); + in_ras->getLx() * ino::channels() * sizeof(float)); in_gr8->lock(); + ino::ras_to_float_arr(in_ras, ino::channels(), + reinterpret_cast(in_gr8->getRawData())); - igs::rotate_blur::convert( - in_ras->getRawData() // BGRA - , - 0 /* margin機能は使っていない、のでinとref画像は同サイズ */ - - , - (((refer_ras != nullptr) && (0 <= refer_mode)) ? refer_ras->getRawData() - : nullptr) // BGRA - , - (((refer_ras != nullptr) && (0 <= refer_mode)) ? ino::bits(refer_ras) - : 0), - refer_mode + TRasterGR8P out_buffer(out_ras->getLy(), + out_ras->getLx() * ino::channels() * sizeof(float)); + out_buffer->lock(); - , - in_gr8->getRawData() // BGRA - - , - in_ras->getLy(), in_ras->getLx(), ino::channels(), ino::bits(out_ras) - - , - xp + margin, yp + margin, blur /*degree*/ - , - radius, ((0 < type) ? 0.0 : (out_ras->getLy() / 2.0)) - - , - (anti_alias_sw ? 4 : 1), alpha_rendering_sw); + igs::rotate_blur::convert( + reinterpret_cast(in_gr8->getRawData()), + reinterpret_cast(out_buffer->getRawData()), margin, + out_ras->getSize(), ino::channels(), + (ref_gr8) ? reinterpret_cast(ref_gr8->getRawData()) : nullptr, + center + TPointD(margin, margin), blur, /*degree*/ + radius, (out_ras->getLy() / 2.0), type, anti_alias_sw, alpha_rendering_sw, + ellipse_aspect_ratio, ellipse_angle); - ino::arr_to_ras(in_gr8->getRawData(), ino::channels(), out_ras, margin); in_gr8->unlock(); + ino::float_arr_to_ras(out_buffer->getRawData(), ino::channels(), out_ras, 0); + out_buffer->unlock(); + if (ref_gr8) ref_gr8->unlock(); } } // namespace //------------------------------------------------------------ @@ -220,6 +220,9 @@ void ino_spin_blur::doCompute(TTile &tile, double frame, const bool alpha_rend_sw = this->m_alpha_rendering->getValue(); const bool anti_alias_sw = this->m_anti_alias->getValue(); const int refer_mode = this->m_ref_mode->getValue(); + const double ellipse_aspect_ratio = + this->m_ellipse_aspect_ratio->getValue(frame); + const double ellipse_angle = this->m_ellipse_angle->getValue(frame); TPointD center = this->m_center->getValue(frame); TPointD render_center( @@ -253,12 +256,9 @@ void ino_spin_blur::doCompute(TTile &tile, double frame, bool refer_sw = false; if (this->m_refer.isConnected()) { refer_sw = true; - this->m_refer->allocateAndCompute( - refer_tile, bBox.getP00(), - TDimensionI(/* Pixel単位で四捨五入 */ - static_cast(bBox.getLx() + 0.5), - static_cast(bBox.getLy() + 0.5)), - tile.getRaster(), frame, ri); + this->m_refer->allocateAndCompute(refer_tile, tile.m_pos, + tile.getRaster()->getSize(), + tile.getRaster(), frame, ri); } /* ------ 保存すべき画像メモリを塗りつぶしクリア ---------- */ tile.getRaster()->clear(); /* 塗りつぶしクリア */ @@ -295,17 +295,9 @@ void ino_spin_blur::doCompute(TTile &tile, double frame, if (refer_tile.getRaster() != nullptr) { refer_tile.getRaster()->lock(); } - fx_(enlarge_tile.getRaster(), margin - - , - refer_tile.getRaster(), refer_mode - - , - tile.getRaster() - - , - render_center.x, render_center.y, type, blur, radius, alpha_rend_sw, - anti_alias_sw); + fx_(enlarge_tile.getRaster(), margin, refer_tile.getRaster(), refer_mode, + tile.getRaster(), render_center, type, blur, radius, alpha_rend_sw, + anti_alias_sw, ellipse_aspect_ratio, ellipse_angle); if (refer_tile.getRaster() != nullptr) { refer_tile.getRaster()->unlock(); } diff --git a/toonz/sources/tnztools/edittoolgadgets.cpp b/toonz/sources/tnztools/edittoolgadgets.cpp index 4f4d426..b9b9fc1 100644 --- a/toonz/sources/tnztools/edittoolgadgets.cpp +++ b/toonz/sources/tnztools/edittoolgadgets.cpp @@ -21,6 +21,7 @@ #include #include +#include using namespace EditToolGadgets; @@ -31,6 +32,85 @@ TPointD hadamard(const TPointD &v1, const TPointD &v2) { return TPointD(v1.x * v2.x, v1.y * v2.y); } +#define SPIN_NUMVERTS 72 + +void drawSpinField(const TRectD geom, const TPointD center, + const double lineInterval, const double e_aspect_ratio, + const double e_angle) { + static GLdouble vertices[SPIN_NUMVERTS * 2]; + static bool isInitialized = false; + if (!isInitialized) { + isInitialized = true; + for (int r = 0; r < SPIN_NUMVERTS; r++) { + double theta = 2.0 * M_PI * (double)r / (double)SPIN_NUMVERTS; + vertices[r * 2] = std::cos(theta); + vertices[r * 2 + 1] = std::sin(theta); + } + } + // obtain the nearest and the furthest pos inside the geom + TPointD nearestPos; + nearestPos.x = (center.x <= geom.x0) + ? geom.x0 + : (center.x >= geom.x1) ? geom.x1 : center.x; + nearestPos.y = (center.y <= geom.y0) + ? geom.y0 + : (center.y >= geom.y1) ? geom.y1 : center.y; + double minDist = norm(nearestPos - center); + TPointD farthestPos; + farthestPos.x = (center.x <= geom.x0) + ? geom.x1 + : (center.x >= geom.x1) + ? geom.x0 + : ((center.x - geom.x0) >= (geom.x1 - center.x)) + ? geom.x0 + : geom.x1; + farthestPos.y = (center.y <= geom.y0) + ? geom.y1 + : (center.y >= geom.y1) + ? geom.y0 + : ((center.y - geom.y0) >= (geom.y1 - center.y)) + ? geom.y0 + : geom.y1; + double maxDist = norm(farthestPos - center); + double scale[2] = {1.0, 1.0}; + // adjust size for ellipse + if (e_aspect_ratio != 1.0) { + scale[0] = 2.0 * e_aspect_ratio / (e_aspect_ratio + 1); + scale[1] = scale[0] / e_aspect_ratio; + minDist *= std::min(scale[0], scale[1]); + maxDist *= std::max(scale[0], scale[1]); + } + // obtain id range + int minId = (int)std::ceil(minDist / lineInterval); + int maxId = (int)std::floor(maxDist / lineInterval); + + glColor3d(0, 0, 1); + glEnableClientState(GL_VERTEX_ARRAY); + glLineStipple(1, 0x00FF); + glEnable(GL_LINE_STIPPLE); + + glVertexPointer(2, GL_DOUBLE, 0, vertices); + + glPushMatrix(); + + glTranslated(center.x, center.y, 0.0); + glRotated(e_angle, 0., 0., 1.); + glScaled(scale[0] * lineInterval, scale[1] * lineInterval, 1.); + + for (int id = minId; id <= maxId; id++) { + if (id == 0) continue; + glPushMatrix(); + glScaled((double)id, (double)id, 1.); + // draw using vertex array + glDrawArrays(GL_LINE_LOOP, 0, SPIN_NUMVERTS); + glPopMatrix(); + } + + glDisable(GL_LINE_STIPPLE); + glDisableClientState(GL_VERTEX_ARRAY); + glPopMatrix(); +} + } // namespace //************************************************************************************* @@ -1497,6 +1577,8 @@ void LinearRangeFxGadget::leftButtonUp() { m_handle = None; } class CompassFxGadget final : public FxGadget { TPointParamP m_center; + TDoubleParamP m_ellipse_aspect_ratio; + TDoubleParamP m_ellipse_angle; enum HANDLE { Body = 0, Near, Far, None } m_handle = None; @@ -1507,7 +1589,9 @@ class CompassFxGadget final : public FxGadget { public: CompassFxGadget(FxGadgetController *controller, - const TPointParamP ¢erPoint, bool isSpin = false); + const TPointParamP ¢erPoint, bool isSpin = false, + const TDoubleParamP &ellipse_aspect_ratio = TDoubleParamP(), + const TDoubleParamP &ellipse_angle = TDoubleParamP()); void draw(bool picking) override; @@ -1519,10 +1603,18 @@ public: //--------------------------------------------------------------------------- CompassFxGadget::CompassFxGadget(FxGadgetController *controller, - const TPointParamP ¢erPoint, bool isSpin) - : FxGadget(controller, 3), m_center(centerPoint), m_isSpin(isSpin) { + const TPointParamP ¢erPoint, bool isSpin, + const TDoubleParamP &ellipse_aspect_ratio, + const TDoubleParamP &ellipse_angle) + : FxGadget(controller, 3) + , m_center(centerPoint) + , m_isSpin(isSpin) + , m_ellipse_aspect_ratio(ellipse_aspect_ratio) + , m_ellipse_angle(ellipse_angle) { addParam(centerPoint->getX()); addParam(centerPoint->getY()); + if (ellipse_aspect_ratio) addParam(ellipse_aspect_ratio); + if (ellipse_angle) addParam(ellipse_angle); } //--------------------------------------------------------------------------- @@ -1566,6 +1658,10 @@ void CompassFxGadget::draw(bool picking) { TPointD center = getValue(m_center); double dCenter = norm(center); + double e_aspect_ratio = + (m_ellipse_aspect_ratio) ? getValue(m_ellipse_aspect_ratio) : 1.0; + double e_angle = (m_ellipse_angle) ? getValue(m_ellipse_angle) : 0.0; + TPointD handleVec; if (dCenter > lineHalf) { handleVec = normalize(center) * lineHalf; @@ -1580,33 +1676,73 @@ void CompassFxGadget::draw(bool picking) { double angle = std::atan2(-center.y, -center.x) * M_180_PI; double theta = M_180_PI * lineInterval / dCenter; - // draw guides - glColor3d(0, 0, 1); - glLineStipple(1, 0x00FF); - glEnable(GL_LINE_STIPPLE); - glPushMatrix(); - glTranslated(center.x, center.y, 0); - glRotated(angle, 0, 0, 1); - for (int i = -3; i <= 3; i++) { + // draw spin lines field + if (isSelected() && !isSelected(None) && m_isSpin && + m_ellipse_aspect_ratio && m_ellipse_angle) { + TRectD geom = m_controller->getGeometry(); + + drawSpinField(geom, center, lineInterval, e_aspect_ratio, e_angle); + } else { + // draw guides + glColor3d(0, 0, 1); + glLineStipple(1, 0x00FF); + glEnable(GL_LINE_STIPPLE); + glPushMatrix(); + glTranslated(center.x, center.y, 0); if (!m_isSpin) { // radial direction - if (i == 0) continue; - glPushMatrix(); - glRotated(theta * (double)i, 0, 0, 1); - glBegin(GL_LINES); - glVertex2d(dCenter - lineHalf, 0.0); - glVertex2d(dCenter + lineHalf, 0.0); - glEnd(); - glPopMatrix(); + for (int i = -3; i <= 3; i++) { + if (i == 0) continue; + glPushMatrix(); + glRotated(theta * (double)i + angle, 0, 0, 1); + glBegin(GL_LINES); + glVertex2d(dCenter - lineHalf, 0.0); + glVertex2d(dCenter + lineHalf, 0.0); + glEnd(); + glPopMatrix(); + } } else { // rotational direction - if (i == 3 || i == -3) continue; - double tmpRad = dCenter + (double)i * lineInterval; - double d_angle = (lineInterval / dCenter) * 6.0 / 10.0; - glBegin(GL_LINE_STRIP); - for (int r = -5; r <= 5; r++) { - double tmpAngle = (double)r * d_angle; - glVertex2d(tmpRad * std::cos(tmpAngle), tmpRad * std::sin(tmpAngle)); + if (areAlmostEqual(e_aspect_ratio, 1.0)) { + for (int i = -2; i <= 2; i++) { + double tmpRad = dCenter + (double)i * lineInterval; + double d_angle = (lineInterval / dCenter) * 6.0 / 10.0; + glBegin(GL_LINE_STRIP); + for (int r = -5; r <= 5; r++) { + double tmpAngle = (double)r * d_angle + angle * M_PI_180; + glVertex2d(tmpRad * std::cos(tmpAngle), + tmpRad * std::sin(tmpAngle)); + } + glEnd(); + } + } else { + double scale[2]; + scale[0] = 2.0 * e_aspect_ratio / (e_aspect_ratio + 1); + scale[1] = scale[0] / e_aspect_ratio; + glRotated(e_angle, 0., 0., 1.); + glScaled(scale[0], scale[1], 1.); + + QTransform tr = QTransform() + .translate(center.x, center.y) + .rotate(e_angle) + .scale(scale[0], scale[1]) + .inverted(); + QPointF begin = tr.map(QPointF(handleVec.x, handleVec.y)); + QPointF end = tr.map(QPointF(-handleVec.x, -handleVec.y)); + + angle = std::atan2(begin.y(), begin.x()); + double distBegin = QVector2D(begin).length(); + double distEnd = QVector2D(end).length(); + for (int i = 0; i <= 4; i++) { + double tmpRad = distBegin + (double)i * (distEnd - distBegin) / 4.; + double d_angle = (lineInterval / dCenter) * 6.0 / 10.0; + glBegin(GL_LINE_STRIP); + for (int r = -5; r <= 5; r++) { + double tmpAngle = (double)r * d_angle + angle; + glVertex2d(tmpRad * std::cos(tmpAngle), + tmpRad * std::sin(tmpAngle)); + } + glEnd(); + } } - glEnd(); } } @@ -1764,6 +1900,227 @@ void RainbowWidthFxGadget::leftButtonDrag(const TPointD &pos, setValue(m_widthScale, std::min(max, std::max(min, scale))); } +//============================================================================= + +class EllipseFxGadget final : public FxGadget { + TDoubleParamP m_radius; + TDoubleParamP m_xParam, m_yParam; + TDoubleParamP m_aspect_ratio; + TDoubleParamP m_angle; + + TPointD m_pos; + + enum HANDLE { Radius = 0, Center, AngleAndAR, None } m_handle = None; + +public: + EllipseFxGadget(FxGadgetController *controller, const TDoubleParamP &radius, + const TPointParamP ¢er, const TDoubleParamP &aspect_ratio, + const TDoubleParamP &angle) + : FxGadget(controller, 4) + , m_radius(radius) + , m_xParam(center->getX()) + , m_yParam(center->getY()) + , m_aspect_ratio(aspect_ratio) + , m_angle(angle) { + addParam(radius); + addParam(m_xParam); + addParam(m_yParam); + addParam(m_aspect_ratio); + addParam(m_angle); + } + + TPointD getCenter() const; + + void draw(bool picking) override; + + void leftButtonDown(const TPointD &pos, const TMouseEvent &) override; + void leftButtonDrag(const TPointD &pos, const TMouseEvent &) override; + void leftButtonUp() override; +}; + +//--------------------------------------------------------------------------- + +TPointD EllipseFxGadget::getCenter() const { + return TPointD(getValue(m_xParam), getValue(m_yParam)); +} + +//--------------------------------------------------------------------------- + +void EllipseFxGadget::draw(bool picking) { + int idBase = getId(); + + auto setColorById = [&](int id) { + if (isSelected(id)) + glColor3dv(m_selectedColor); + else + glColor3d(0, 0, 1); + }; + + setPixelSize(); + glPushMatrix(); + + TPointD center = getCenter(); + double aspect_ratio = getValue(m_aspect_ratio); + double angle = getValue(m_angle); + + // draw spin lines field + if (isSelected() && !isSelected(None)) { + double lineInterval = getPixelSize() * 50; + TRectD geom = m_controller->getGeometry(); + drawSpinField(geom, center, lineInterval, aspect_ratio, angle); + } + + double unit = getPixelSize(); + glTranslated(center.x, center.y, 0); + + //--- radius --- + setColorById(Radius); + glPushName(idBase + Radius); + double radius = getValue(m_radius); + + double scale[2] = {1.0, 1.0}; + if (!areAlmostEqual(aspect_ratio, 1.0)) { + scale[0] = 2.0 * aspect_ratio / (aspect_ratio + 1.0); + scale[1] = scale[0] / aspect_ratio; + } + glPushMatrix(); + + glRotated(angle, 0., 0., 1.); + glScaled(scale[0], scale[1], 1.0); + + glLineStipple(1, 0xAAAA); + glEnable(GL_LINE_STIPPLE); + tglDrawCircle(TPointD(), radius); + glDisable(GL_LINE_STIPPLE); + + glPopMatrix(); + + QTransform transform = QTransform().rotate(angle).scale(scale[0], scale[1]); + QPointF radiusHandlePos = transform.map(QPointF(0.0, radius)); + drawDot(TPointD(radiusHandlePos.x(), radiusHandlePos.y())); + glPopName(); + + if (isSelected(Radius)) { + QPointF namePos = transform.map(QPointF(0.707, 0.707) * radius); + drawTooltip(TPointD(namePos.x(), namePos.y()), getLabel()); + } + + //--- center --- + setColorById(Center); + glPushName(idBase + Center); + double d = unit * 8; + tglDrawCircle(TPointD(), d); + + if (radius > d) { + glBegin(GL_LINES); + glVertex2d(-d, 0); + glVertex2d(d, 0); + glVertex2d(0, -d); + glVertex2d(0, d); + glEnd(); + } + + glPopName(); + if (isSelected(Center)) { + drawTooltip(TPointD(), "Center"); + } + + //---- AR and rotate + double handleLength = unit * 100; + radius = std::max(radius, unit * 10); + setColorById(AngleAndAR); + QPointF qHandleRoot = transform.map(QPointF(radius, 0.0)); + glPushMatrix(); + glPushName(idBase + AngleAndAR); + glTranslated(qHandleRoot.x(), qHandleRoot.y(), 0.); + glRotated(angle, 0., 0., 1.); + glBegin(GL_LINES); + glVertex2d(0., 0.); + glVertex2d(handleLength, 0.); + glEnd(); + drawDot(TPointD(handleLength, 0.)); + + glPopMatrix(); + glPopName(); + + if (isSelected(AngleAndAR)) { + double angle_radian = angle * M_PI_180; + TPointD namePos(qHandleRoot.x() + std::cos(angle_radian) * handleLength, + qHandleRoot.y() + std::sin(angle_radian) * handleLength); + drawTooltip(namePos, "Angle and Aspect"); + } + + glPopMatrix(); // cancel translation to center +} + +//--------------------------------------------------------------------------- + +void EllipseFxGadget::leftButtonDown(const TPointD &pos, const TMouseEvent &) { + m_handle = (HANDLE)m_selected; + m_pos = pos; +} + +//--------------------------------------------------------------------------- + +void EllipseFxGadget::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) { + if (m_handle == None) return; + if (m_handle == Radius) { + double aspect_ratio = getValue(m_aspect_ratio); + double angle = getValue(m_angle); + double scale[2] = {1.0, 1.0}; + if (!areAlmostEqual(aspect_ratio, 1.0)) { + scale[0] = 2.0 * aspect_ratio / (aspect_ratio + 1.0); + scale[1] = scale[0] / aspect_ratio; + } + TPointD center = getCenter(); + QTransform transform = QTransform() + .translate(center.x, center.y) + .rotate(angle) + .scale(scale[0], scale[1]) + .inverted(); + QPointF transformedP = transform.map(QPointF(pos.x, pos.y)); + setValue(m_radius, QVector2D(transformedP).length()); + } else if (m_handle == Center) { + setValue(m_xParam, pos.x); + setValue(m_yParam, pos.y); + } else if (m_handle == AngleAndAR) { + // ��]�ƐL�k�ɕ����� + TPointD center = getCenter(); + TPointD old_v = m_pos - center; + TPointD new_v = pos - center; + if (old_v == TPointD() || new_v == TPointD()) return; + // AR + double aspect_ratio = getValue(m_aspect_ratio); + double pre_axisLength = 2.0 * aspect_ratio / (aspect_ratio + 1.0); + double ratio = norm(new_v) / norm(old_v); + if (ratio == 0.) return; + double new_axisLength = pre_axisLength * ratio; + double new_ar = new_axisLength / (2.0 - new_axisLength); + if (new_ar < 0.1) + new_ar = 0.1; + else if (new_ar > 10.0) + new_ar = 10.0; + setValue(m_aspect_ratio, new_ar); + + // angle + double angle = getValue(m_angle); + double d_angle = + std::atan2(new_v.y, new_v.x) - std::atan2(old_v.y, old_v.x); + double new_angle = angle + d_angle * M_180_PI; + if (new_angle < -180.0) + new_angle += 360.0; + else if (new_angle > 180.0) + new_angle -= 360.0; + setValue(m_angle, new_angle); + + m_pos = pos; + } +} + +//--------------------------------------------------------------------------- + +void EllipseFxGadget::leftButtonUp() { m_handle = None; } + //************************************************************************************* // FxGadgetController implementation //************************************************************************************* @@ -1957,8 +2314,15 @@ FxGadget *FxGadgetController::allocateGadget(const TParamUIConcept &uiConcept) { } case TParamUIConcept::COMPASS_SPIN: { - assert(uiConcept.m_params.size() == 1); - gadget = new CompassFxGadget(this, uiConcept.m_params[0], true); + assert(uiConcept.m_params.size() == 1 || uiConcept.m_params.size() == 3); + + if (uiConcept.m_params.size() == 3) + gadget = + new CompassFxGadget(this, uiConcept.m_params[0], true, + uiConcept.m_params[1], uiConcept.m_params[2]); + else + gadget = new CompassFxGadget(this, uiConcept.m_params[0], true); + break; } @@ -1969,6 +2333,15 @@ FxGadget *FxGadgetController::allocateGadget(const TParamUIConcept &uiConcept) { uiConcept.m_params[1], uiConcept.m_params[2]); break; } + + case TParamUIConcept::ELLIPSE: { + assert(uiConcept.m_params.size() == 4); + gadget = + new EllipseFxGadget(this, uiConcept.m_params[0], uiConcept.m_params[1], + uiConcept.m_params[2], uiConcept.m_params[3]); + break; + } + default: break; } @@ -2060,6 +2433,14 @@ int FxGadgetController::getCurrentFrame() const { return m_tool->getFrame(); } void FxGadgetController::invalidateViewer() { m_tool->invalidate(); } +//--------------------------------------------------------------------------- + int FxGadgetController::getDevPixRatio() { return getDevicePixelRatio(m_tool->getViewer()->viewerWidget()); } + +//--------------------------------------------------------------------------- + +TRectD FxGadgetController::getGeometry() { + return (m_tool->getViewer()) ? m_tool->getViewer()->getGeometry() : TRectD(); +} diff --git a/toonz/sources/tnztools/edittoolgadgets.h b/toonz/sources/tnztools/edittoolgadgets.h index ceb1564..a15dbc4 100644 --- a/toonz/sources/tnztools/edittoolgadgets.h +++ b/toonz/sources/tnztools/edittoolgadgets.h @@ -153,6 +153,9 @@ public: int getDevPixRatio(); + // get the current viewer geometry + TRectD getGeometry(); + public slots: void onFxSwitched();