diff --git a/stuff/config/current.txt b/stuff/config/current.txt
index c0f90d1..fc53318 100644
--- a/stuff/config/current.txt
+++ b/stuff/config/current.txt
@@ -1514,6 +1514,52 @@
- "STD_iwa_RainbowFx.secondary_rainbow" "Secondary Rainbow Intensity"
- "STD_iwa_RainbowFx.alpha_rendering" "Alpha Rendering"
+ - "STD_iwa_TangentFlowFx" "Tangent Flow Iwa"
+ - "STD_iwa_TangentFlowFx.iteration" "Iteration"
+ - "STD_iwa_TangentFlowFx.kernelRadius" "Kernel Radius"
+ - "STD_iwa_TangentFlowFx.threshold" "Threshold"
+ - "STD_iwa_TangentFlowFx.alignDirection" "Align Direction"
+ - "STD_iwa_TangentFlowFx.pivotAngle" "Pivot Angle"
+
+ - "STD_iwa_FlowBlurFx" "Flow Blur Iwa"
+ - "STD_iwa_FlowBlurFx.length" "Length"
+ - "STD_iwa_FlowBlurFx.filterType" "Filter Type"
+ - "STD_iwa_FlowBlurFx.linear" "Linear Color Space"
+ - "STD_iwa_FlowBlurFx.gamma" "Gamma"
+ - "STD_iwa_FlowBlurFx.referenceMode" "Reference"
+
+ - "STD_iwa_FlowPaintBrushFx" "Flow Paint Brush Iwa"
+ - "STD_iwa_FlowPaintBrushFx.h_density" "Horizontal Density"
+ - "STD_iwa_FlowPaintBrushFx.v_density" "Vertical Density"
+ - "STD_iwa_FlowPaintBrushFx.pos_randomness" "Position Randomness"
+ - "STD_iwa_FlowPaintBrushFx.pos_wobble" "Position Wobble by Frame"
+ - "STD_iwa_FlowPaintBrushFx.tip_width" "Tip Width"
+ - "STD_iwa_FlowPaintBrushFx.tip_length" "Tip Length"
+ - "STD_iwa_FlowPaintBrushFx.tip_alpha" "Tip Alpha"
+ - "STD_iwa_FlowPaintBrushFx.tip_joints" "Tip Joints"
+ - "STD_iwa_FlowPaintBrushFx.bidirectional" "Bidirectional"
+ - "STD_iwa_FlowPaintBrushFx.width_randomness" "Width Randomness"
+ - "STD_iwa_FlowPaintBrushFx.length_randomness" "Length Randomness"
+ - "STD_iwa_FlowPaintBrushFx.angle_randomness" "Angle Randomness"
+ - "STD_iwa_FlowPaintBrushFx.sustain_width_to_skew" "Sustain Width to Skew"
+ - "STD_iwa_FlowPaintBrushFx.anti_jaggy" "Prevent Jaggies by Texture Margin"
+ - "STD_iwa_FlowPaintBrushFx.origin_pos" "Origin"
+ - "STD_iwa_FlowPaintBrushFx.horizontal_pos" "Horizontal"
+ - "STD_iwa_FlowPaintBrushFx.vertical_pos" "Vertical"
+ - "STD_iwa_FlowPaintBrushFx.curve_point" "Curve Anchor"
+ - "STD_iwa_FlowPaintBrushFx.fill_gap_size" "Fill Gap Size"
+ - "STD_iwa_FlowPaintBrushFx.reference_frame" "Reference Frame"
+ - "STD_iwa_FlowPaintBrushFx.reference_prevalence" "Reference Prevalence"
+ - "STD_iwa_FlowPaintBrushFx.random_seed" "Random Seed"
+ - "STD_iwa_FlowPaintBrushFx.sort_by" "Sort By"
+
+ - "STD_iwa_MotionFlowFx" "Motion Flow Iwa"
+ - "STD_iwa_MotionFlowFx.shutterLength" "Shutter Length"
+ - "STD_iwa_MotionFlowFx.motionObjectType" "Reference Object"
+ - "STD_iwa_MotionFlowFx.motionObjectIndex" "Index"
+ - "STD_iwa_MotionFlowFx.normalizeType" "Normalize"
+ - "STD_iwa_MotionFlowFx.normalizeRange" "Maximum Length"
+
- STD_iwa_TiledParticlesFx "Tiled Particles Iwa"
diff --git a/stuff/library/textures/brush tips/dry_brush.0001.png b/stuff/library/textures/brush tips/dry_brush.0001.png
new file mode 100644
index 0000000..2f37ed5
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0001.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0002.png b/stuff/library/textures/brush tips/dry_brush.0002.png
new file mode 100644
index 0000000..e41dd2b
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0002.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0003.png b/stuff/library/textures/brush tips/dry_brush.0003.png
new file mode 100644
index 0000000..6471cba
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0003.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0004.png b/stuff/library/textures/brush tips/dry_brush.0004.png
new file mode 100644
index 0000000..9325baa
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0004.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0005.png b/stuff/library/textures/brush tips/dry_brush.0005.png
new file mode 100644
index 0000000..4527155
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0005.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0006.png b/stuff/library/textures/brush tips/dry_brush.0006.png
new file mode 100644
index 0000000..9cfc115
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0006.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0007.png b/stuff/library/textures/brush tips/dry_brush.0007.png
new file mode 100644
index 0000000..b7e60b1
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0007.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0008.png b/stuff/library/textures/brush tips/dry_brush.0008.png
new file mode 100644
index 0000000..b9d732e
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0008.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0009.png b/stuff/library/textures/brush tips/dry_brush.0009.png
new file mode 100644
index 0000000..2c8e93e
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0009.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0010.png b/stuff/library/textures/brush tips/dry_brush.0010.png
new file mode 100644
index 0000000..54b5822
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0010.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0011.png b/stuff/library/textures/brush tips/dry_brush.0011.png
new file mode 100644
index 0000000..972ba97
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0011.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0012.png b/stuff/library/textures/brush tips/dry_brush.0012.png
new file mode 100644
index 0000000..424e915
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0012.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0013.png b/stuff/library/textures/brush tips/dry_brush.0013.png
new file mode 100644
index 0000000..cd4e38f
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0013.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0014.png b/stuff/library/textures/brush tips/dry_brush.0014.png
new file mode 100644
index 0000000..efaacba
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0014.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0015.png b/stuff/library/textures/brush tips/dry_brush.0015.png
new file mode 100644
index 0000000..be012dd
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0015.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0016.png b/stuff/library/textures/brush tips/dry_brush.0016.png
new file mode 100644
index 0000000..bf91dab
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0016.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0017.png b/stuff/library/textures/brush tips/dry_brush.0017.png
new file mode 100644
index 0000000..41f41aa
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0017.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0018.png b/stuff/library/textures/brush tips/dry_brush.0018.png
new file mode 100644
index 0000000..3edce29
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0018.png differ
diff --git a/stuff/library/textures/brush tips/dry_brush.0019.png b/stuff/library/textures/brush tips/dry_brush.0019.png
new file mode 100644
index 0000000..a6ab20f
Binary files /dev/null and b/stuff/library/textures/brush tips/dry_brush.0019.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0001.png b/stuff/library/textures/brush tips/fluffy.0001.png
new file mode 100644
index 0000000..e05568d
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0001.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0002.png b/stuff/library/textures/brush tips/fluffy.0002.png
new file mode 100644
index 0000000..8e7f62e
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0002.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0003.png b/stuff/library/textures/brush tips/fluffy.0003.png
new file mode 100644
index 0000000..9706411
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0003.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0004.png b/stuff/library/textures/brush tips/fluffy.0004.png
new file mode 100644
index 0000000..3c8f19a
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0004.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0005.png b/stuff/library/textures/brush tips/fluffy.0005.png
new file mode 100644
index 0000000..a151346
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0005.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0006.png b/stuff/library/textures/brush tips/fluffy.0006.png
new file mode 100644
index 0000000..076e227
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0006.png differ
diff --git a/stuff/library/textures/brush tips/fluffy.0007.png b/stuff/library/textures/brush tips/fluffy.0007.png
new file mode 100644
index 0000000..cc00c7a
Binary files /dev/null and b/stuff/library/textures/brush tips/fluffy.0007.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0001.png b/stuff/library/textures/brush tips/single_line.0001.png
new file mode 100644
index 0000000..0f40e8c
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0001.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0002.png b/stuff/library/textures/brush tips/single_line.0002.png
new file mode 100644
index 0000000..609a575
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0002.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0003.png b/stuff/library/textures/brush tips/single_line.0003.png
new file mode 100644
index 0000000..6b57ff0
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0003.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0004.png b/stuff/library/textures/brush tips/single_line.0004.png
new file mode 100644
index 0000000..c2e4fb3
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0004.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0005.png b/stuff/library/textures/brush tips/single_line.0005.png
new file mode 100644
index 0000000..faaeb1e
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0005.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0006.png b/stuff/library/textures/brush tips/single_line.0006.png
new file mode 100644
index 0000000..921de42
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0006.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0007.png b/stuff/library/textures/brush tips/single_line.0007.png
new file mode 100644
index 0000000..9c3b110
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0007.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0008.png b/stuff/library/textures/brush tips/single_line.0008.png
new file mode 100644
index 0000000..9614aac
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0008.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0009.png b/stuff/library/textures/brush tips/single_line.0009.png
new file mode 100644
index 0000000..639df94
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0009.png differ
diff --git a/stuff/library/textures/brush tips/single_line.0010.png b/stuff/library/textures/brush tips/single_line.0010.png
new file mode 100644
index 0000000..e33798b
Binary files /dev/null and b/stuff/library/textures/brush tips/single_line.0010.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0001.png b/stuff/library/textures/brush tips/streaky.0001.png
new file mode 100644
index 0000000..5d60f30
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0001.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0002.png b/stuff/library/textures/brush tips/streaky.0002.png
new file mode 100644
index 0000000..714c178
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0002.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0003.png b/stuff/library/textures/brush tips/streaky.0003.png
new file mode 100644
index 0000000..de24bf7
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0003.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0004.png b/stuff/library/textures/brush tips/streaky.0004.png
new file mode 100644
index 0000000..84e92ae
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0004.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0005.png b/stuff/library/textures/brush tips/streaky.0005.png
new file mode 100644
index 0000000..a135361
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0005.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0006.png b/stuff/library/textures/brush tips/streaky.0006.png
new file mode 100644
index 0000000..db9dcb4
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0006.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0007.png b/stuff/library/textures/brush tips/streaky.0007.png
new file mode 100644
index 0000000..7696e86
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0007.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0008.png b/stuff/library/textures/brush tips/streaky.0008.png
new file mode 100644
index 0000000..f7a225e
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0008.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0009.png b/stuff/library/textures/brush tips/streaky.0009.png
new file mode 100644
index 0000000..fcace4d
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0009.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0010.png b/stuff/library/textures/brush tips/streaky.0010.png
new file mode 100644
index 0000000..ae27928
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0010.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0011.png b/stuff/library/textures/brush tips/streaky.0011.png
new file mode 100644
index 0000000..d70585c
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0011.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0012.png b/stuff/library/textures/brush tips/streaky.0012.png
new file mode 100644
index 0000000..bc8aee0
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0012.png differ
diff --git a/stuff/library/textures/brush tips/streaky.0013.png b/stuff/library/textures/brush tips/streaky.0013.png
new file mode 100644
index 0000000..16070ec
Binary files /dev/null and b/stuff/library/textures/brush tips/streaky.0013.png differ
diff --git a/stuff/library/textures/brush tips/thick.0001.png b/stuff/library/textures/brush tips/thick.0001.png
new file mode 100644
index 0000000..01779c0
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0001.png differ
diff --git a/stuff/library/textures/brush tips/thick.0002.png b/stuff/library/textures/brush tips/thick.0002.png
new file mode 100644
index 0000000..38d0cc3
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0002.png differ
diff --git a/stuff/library/textures/brush tips/thick.0003.png b/stuff/library/textures/brush tips/thick.0003.png
new file mode 100644
index 0000000..1a1b707
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0003.png differ
diff --git a/stuff/library/textures/brush tips/thick.0004.png b/stuff/library/textures/brush tips/thick.0004.png
new file mode 100644
index 0000000..c9efb79
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0004.png differ
diff --git a/stuff/library/textures/brush tips/thick.0005.png b/stuff/library/textures/brush tips/thick.0005.png
new file mode 100644
index 0000000..f30c417
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0005.png differ
diff --git a/stuff/library/textures/brush tips/thick.0006.png b/stuff/library/textures/brush tips/thick.0006.png
new file mode 100644
index 0000000..c304354
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0006.png differ
diff --git a/stuff/library/textures/brush tips/thick.0007.png b/stuff/library/textures/brush tips/thick.0007.png
new file mode 100644
index 0000000..817e0b6
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0007.png differ
diff --git a/stuff/library/textures/brush tips/thick.0008.png b/stuff/library/textures/brush tips/thick.0008.png
new file mode 100644
index 0000000..b331492
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0008.png differ
diff --git a/stuff/library/textures/brush tips/thick.0009.png b/stuff/library/textures/brush tips/thick.0009.png
new file mode 100644
index 0000000..85b9b43
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0009.png differ
diff --git a/stuff/library/textures/brush tips/thick.0010.png b/stuff/library/textures/brush tips/thick.0010.png
new file mode 100644
index 0000000..4539a03
Binary files /dev/null and b/stuff/library/textures/brush tips/thick.0010.png differ
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_FlowBlurFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_FlowBlurFx.xml
new file mode 100644
index 0000000..e2cb0bf
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_FlowBlurFx.xml
@@ -0,0 +1,12 @@
+
+
+ length
+ filterType
+
+ linear
+
+ gamma
+
+ referenceMode
+
+
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_FlowPaintBrushFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_FlowPaintBrushFx.xml
new file mode 100644
index 0000000..08c347a
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_FlowPaintBrushFx.xml
@@ -0,0 +1,32 @@
+
+
+
+ h_density
+ v_density
+ pos_randomness
+ pos_wobble
+
+ origin_pos
+ horizontal_pos
+ vertical_pos
+ curve_point
+ fill_gap_size
+
+ tip_width
+ width_randomness
+ sustain_width_to_skew
+ tip_length
+ length_randomness
+ angle_randomness
+ tip_alpha
+ tip_joints
+ bidirectional
+ anti_jaggy
+
+ reference_frame
+ reference_prevalence
+
+ random_seed
+ sort_by
+
+
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_MotionFlowFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_MotionFlowFx.xml
new file mode 100644
index 0000000..a41426c
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_MotionFlowFx.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ motionObjectType
+ motionObjectIndex
+
+ shutterLength
+ normalizeType
+
+ normalizeRange
+
+
+
+
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_TangentFlowFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_TangentFlowFx.xml
new file mode 100644
index 0000000..f9c0db2
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_TangentFlowFx.xml
@@ -0,0 +1,11 @@
+
+
+ iteration
+ kernelRadius
+ threshold
+ alignDirection
+
+ pivotAngle
+
+
+
diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst
index 0af39ce..bf424b0 100644
--- a/stuff/profiles/layouts/fxs/fxs.lst
+++ b/stuff/profiles/layouts/fxs/fxs.lst
@@ -23,6 +23,7 @@
STD_iwa_BokehFx
STD_iwa_BokehRefFx
STD_iwa_BokehAdvancedFx
+ STD_iwa_FlowBlurFx
STD_freeDistortFx
@@ -156,6 +157,7 @@
STD_iwa_TiledParticlesFx
STD_iwa_TimeCodeFx
STD_iwa_TextFx
+ STD_iwa_FlowPaintBrushFx
STD_colorEmbossFx
@@ -164,7 +166,9 @@
STD_mosaicFx
STD_inoMotionWindFx
STD_posterizeFx
- STD_solarizeFx
+ STD_solarizeFx
+ STD_iwa_TangentFlowFx
+ STD_iwa_MotionFlowFx
STD_artContourFx
diff --git a/toonz/sources/colorfx/CMakeLists.txt b/toonz/sources/colorfx/CMakeLists.txt
index d9c43c8..389201d 100644
--- a/toonz/sources/colorfx/CMakeLists.txt
+++ b/toonz/sources/colorfx/CMakeLists.txt
@@ -5,6 +5,7 @@ set(HEADERS
regionstyles.h
strokestyles.h
zigzagstyles.h
+ flowlinestrokestyle.h
)
set(SOURCES
@@ -14,6 +15,7 @@ set(SOURCES
regionstyles.cpp
strokestyles.cpp
zigzagstyles.cpp
+ flowlinestrokestyle.cpp
)
if(WITH_TRANSLATION)
@@ -40,4 +42,4 @@ _find_toonz_library(EXTRA_LIBS "tnzcore;tnzbase")
message("FIND_FILE:" ${EXTRA_LIBS})
-target_link_libraries(colorfx Qt5::Core ${GL_LIB} ${EXTRA_LIBS})
+target_link_libraries(colorfx Qt5::Core Qt5::Gui ${GL_LIB} ${EXTRA_LIBS})
diff --git a/toonz/sources/colorfx/colorfx.cpp b/toonz/sources/colorfx/colorfx.cpp
index 492b3b2..e644888 100644
--- a/toonz/sources/colorfx/colorfx.cpp
+++ b/toonz/sources/colorfx/colorfx.cpp
@@ -1,11 +1,12 @@
-
#include "strokestyles.h"
#include "regionstyles.h"
#include "rasterstyles.h"
#include "colorfx.h"
+#include "flowlinestrokestyle.h"
+
// static TPluginInfo info("ColorFxPlugin");
//-------------------------------------------------------------------
@@ -98,4 +99,6 @@ void initColorFx() {
add(new TAirbrushRasterStyle(TPixel32::Black, 10));
add(new TBlendRasterStyle(TPixel32::Black, 10));
add(new TNoColorRasterStyle());
+
+ add(new FlowLineStrokeStyle());
}
diff --git a/toonz/sources/colorfx/flowlinestrokestyle.cpp b/toonz/sources/colorfx/flowlinestrokestyle.cpp
new file mode 100644
index 0000000..0159c0e
--- /dev/null
+++ b/toonz/sources/colorfx/flowlinestrokestyle.cpp
@@ -0,0 +1,364 @@
+
+#include "flowlinestrokestyle.h"
+
+#include "tcolorfunctions.h"
+#include "tcurves.h"
+
+#define BUFFER_OFFSET(bytes) ((GLubyte *)NULL + (bytes))
+#define V_BUFFER_SIZE 1000
+
+namespace {
+double getMaxThickness(const TStroke *s) {
+ int count = s->getControlPointCount();
+ double maxThickness = -1;
+
+ for (int i = 0; i < s->getControlPointCount(); i++) {
+ double thick = s->getControlPoint(i).thick;
+ if (thick > maxThickness) maxThickness = thick;
+ }
+ return maxThickness;
+}
+
+struct float2 {
+ float x;
+ float y;
+};
+
+inline double dot(const TPointD &v1, const TPointD &v2) {
+ return v1.x * v2.x + v1.y * v2.y;
+}
+
+//-----------------------------------------------------------------------------
+
+inline bool isLinearPoint(const TPointD &p0, const TPointD &p1,
+ const TPointD &p2) {
+ return (tdistance(p0, p1) < 0.02) && (tdistance(p1, p2) < 0.02);
+}
+
+//-----------------------------------------------------------------------------
+
+//! Ritorna \b true se il punto \b p1 e' una cuspide.
+bool isCuspPoint(const TPointD &p0, const TPointD &p1, const TPointD &p2) {
+ TPointD p0_p1(p0 - p1), p2_p1(p2 - p1);
+ double n1 = norm(p0_p1), n2 = norm(p2_p1);
+
+ // Partial linear points are ALWAYS cusps (since directions from them are
+ // determined by neighbours, not by the points themselves)
+ if ((n1 < 0.02) || (n2 < 0.02)) return true;
+
+ p0_p1 = p0_p1 * (1.0 / n1);
+ p2_p1 = p2_p1 * (1.0 / n2);
+
+ return (p0_p1 * p2_p1 > 0) ||
+ (fabs(cross(p0_p1, p2_p1)) > 0.09); // more than 5�� is yes
+
+ // Distance-based check. Unscalable...
+ // return
+ // !areAlmostEqual(tdistance(p0,p2),tdistance(p0,p1)+tdistance(p1,p2),2);
+}
+
+//-----------------------------------------------------------------------------
+/*! Insert a point in the most long chunk between chunk \b indexA and chunk \b
+ * indexB. */
+void insertPoint(TStroke *stroke, int indexA, int indexB) {
+ assert(stroke);
+ int j = 0;
+ int chunkCount = indexB - indexA;
+ if (chunkCount % 2 == 0) return;
+ double length = 0;
+ double firstW, lastW;
+ for (j = indexA; j < indexB; j++) {
+ // cerco il chunk piu' lungo
+ double w0 = stroke->getW(stroke->getChunk(j)->getP0());
+ double w1;
+ if (j == stroke->getChunkCount() - 1)
+ w1 = 1;
+ else
+ w1 = stroke->getW(stroke->getChunk(j)->getP2());
+ double length0 = stroke->getLength(w0);
+ double length1 = stroke->getLength(w1);
+ if (length < length1 - length0) {
+ firstW = w0;
+ lastW = w1;
+ length = length1 - length0;
+ }
+ }
+ stroke->insertControlPoints((firstW + lastW) * 0.5);
+}
+}; // namespace
+//-----------------------------------------------------------------------------
+
+FlowLineStrokeStyle::FlowLineStrokeStyle()
+ : m_color(TPixel32(100, 200, 200, 255))
+ , m_density(0.25)
+ , m_extension(5.0)
+ , m_widthScale(5.0)
+ , m_straightenEnds(true) {}
+
+//-----------------------------------------------------------------------------
+
+TColorStyle *FlowLineStrokeStyle::clone() const {
+ return new FlowLineStrokeStyle(*this);
+}
+
+//-----------------------------------------------------------------------------
+
+int FlowLineStrokeStyle::getParamCount() const { return ParamCount; }
+
+//-----------------------------------------------------------------------------
+
+TColorStyle::ParamType FlowLineStrokeStyle::getParamType(int index) const {
+ assert(0 <= index && index < getParamCount());
+ switch (index) {
+ case Density:
+ case Extension:
+ case WidthScale:
+ return TColorStyle::DOUBLE;
+ case StraightenEnds:
+ return TColorStyle::BOOL;
+ }
+ return TColorStyle::DOUBLE;
+}
+
+//-----------------------------------------------------------------------------
+
+QString FlowLineStrokeStyle::getParamNames(int index) const {
+ assert(0 <= index && index < ParamCount);
+ switch (index) {
+ case Density:
+ return QCoreApplication::translate("FlowLineStrokeStyle", "Density");
+ case Extension:
+ return QCoreApplication::translate("FlowLineStrokeStyle", "Extension");
+ case WidthScale:
+ return QCoreApplication::translate("FlowLineStrokeStyle", "Width Scale");
+ case StraightenEnds:
+ return QCoreApplication::translate("FlowLineStrokeStyle",
+ "Straighten Ends");
+ }
+ return QString();
+}
+
+//-----------------------------------------------------------------------------
+
+void FlowLineStrokeStyle::getParamRange(int index, double &min,
+ double &max) const {
+ assert(0 <= index && index < ParamCount);
+ switch (index) {
+ case Density:
+ min = 0.2;
+ max = 5.0;
+ break;
+ case Extension:
+ min = 0.0;
+ max = 20.0;
+ break;
+ case WidthScale:
+ min = 1.0;
+ max = 50.0;
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+double FlowLineStrokeStyle::getParamValue(TColorStyle::double_tag,
+ int index) const {
+ assert(0 <= index && index < ParamCount);
+ switch (index) {
+ case Density:
+ return m_density;
+ case Extension:
+ return m_extension;
+ case WidthScale:
+ return m_widthScale;
+ }
+ return 0.0;
+}
+
+//-----------------------------------------------------------------------------
+
+void FlowLineStrokeStyle::setParamValue(int index, double value) {
+ assert(0 <= index && index < ParamCount);
+ switch (index) {
+ case Density:
+ m_density = value;
+ break;
+ case Extension:
+ m_extension = value;
+ break;
+ case WidthScale:
+ m_widthScale = value;
+ break;
+ }
+ updateVersionNumber();
+}
+
+//-----------------------------------------------------------------------------
+
+bool FlowLineStrokeStyle::getParamValue(TColorStyle::bool_tag,
+ int index) const {
+ assert(index == StraightenEnds);
+ return m_straightenEnds;
+}
+
+//-----------------------------------------------------------------------------
+
+void FlowLineStrokeStyle::setParamValue(int index, bool value) {
+ assert(index == StraightenEnds);
+ m_straightenEnds = value;
+}
+
+//-----------------------------------------------------------------------------
+
+void FlowLineStrokeStyle::drawStroke(const TColorFunction *cf,
+ const TStroke *stroke) const {
+ auto lerp = [](float val1, float val2, float ratio) {
+ return val1 * (1.0 - ratio) + val2 * ratio;
+ };
+ auto length2 = [](TPointD p) { return p.x * p.x + p.y * p.y; };
+
+ // Length and position of the stroke is in "actual size" (i.e. not in pixels).
+ // 1unit = 1/53.3333inches
+ // Stroke�̒�������W�͎����i�P�P�ʁ�1/53.3333�C���`�j
+ double length = stroke->getLength();
+ if (length <= 0) return;
+
+ double maxThickness = getMaxThickness(stroke) * m_widthScale;
+ if (maxThickness <= 0) return;
+
+ TStroke *tmpStroke = new TStroke(*stroke);
+
+ // straighten ends. replace the control point only if the new handle will
+ // become longer
+ if (m_straightenEnds && stroke->getControlPointCount() >= 5 &&
+ !stroke->isSelfLoop()) {
+ TPointD newPos = tmpStroke->getControlPoint(0) * 0.75 +
+ tmpStroke->getControlPoint(3) * 0.25;
+ TPointD oldVec =
+ tmpStroke->getControlPoint(1) - tmpStroke->getControlPoint(0);
+ TPointD newVec = newPos - tmpStroke->getControlPoint(0);
+ if (length2(oldVec) < length2(newVec))
+ tmpStroke->setControlPoint(1, newPos);
+
+ int lastId = stroke->getControlPointCount() - 1;
+ newPos = tmpStroke->getControlPoint(lastId) * 0.75 +
+ tmpStroke->getControlPoint(lastId - 3) * 0.25;
+ oldVec = tmpStroke->getControlPoint(lastId - 1) -
+ tmpStroke->getControlPoint(lastId);
+ newVec = newPos - tmpStroke->getControlPoint(lastId);
+ if (length2(oldVec) < length2(newVec))
+ tmpStroke->setControlPoint(lastId - 1, newPos);
+ }
+
+ // see ControlPointEditorStroke::adjustChunkParity() in
+ // controlpointselection.cpp
+ int firstChunk;
+ int secondChunk = tmpStroke->getChunkCount();
+ int i;
+ for (i = tmpStroke->getChunkCount() - 1; i > 0; i--) {
+ if (tdistance(tmpStroke->getChunk(i - 1)->getP0(),
+ tmpStroke->getChunk(i)->getP2()) < 0.5)
+ continue;
+ TPointD p0 = tmpStroke->getChunk(i - 1)->getP1();
+ TPointD p1 = tmpStroke->getChunk(i - 1)->getP2();
+ TPointD p2 = tmpStroke->getChunk(i)->getP1();
+ if (isCuspPoint(p0, p1, p2) || isLinearPoint(p0, p1, p2)) {
+ firstChunk = i;
+ insertPoint(tmpStroke, firstChunk, secondChunk);
+ secondChunk = firstChunk;
+ }
+ }
+ insertPoint(tmpStroke, 0, secondChunk);
+
+ // thin lines amount �א��̖{��
+ int count = 2 * (int)std::ceil(maxThickness * m_density) - 1;
+
+ TPixel32 color;
+ if (cf)
+ color = (*(cf))(m_color);
+ else
+ color = m_color;
+
+ float2 *vertBuf = new float2[V_BUFFER_SIZE];
+ int maxVertCount = 0;
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+
+ int centerId = (count - 1) / 2;
+
+ TThickPoint p0 = tmpStroke->getThickPoint(0);
+ TThickPoint p1 = tmpStroke->getThickPoint(1);
+ double d01 = tdistance(p0, p1);
+ if (d01 == 0) return;
+
+ int len = (int)(d01);
+ if (len == 0) return;
+
+ TPointD v0 = tmpStroke->getSpeed(0);
+ TPointD v1 = tmpStroke->getSpeed(1);
+
+ if (norm2(v0) == 0 || norm2(v1) == 0) return;
+
+ TPointD startVec = -normalize(v0);
+ TPointD endVec = normalize(v1);
+
+ for (int i = 0; i < count; i++) {
+ double widthRatio =
+ (count == 1) ? 0.0 : (double)(i - centerId) / (double)centerId;
+ double extensionLength =
+ (1.0 - std::abs(widthRatio)) * maxThickness * m_extension;
+
+ glColor4ub(color.r, color.g, color.b, color.m);
+
+ int divAmount = len * 5;
+ if (divAmount + 3 > V_BUFFER_SIZE) divAmount = V_BUFFER_SIZE - 3;
+
+ float2 *vp = vertBuf;
+
+ for (int j = 0; j <= divAmount; j++, vp++) {
+ // current position ratio ���̍��W�̊����ʒu
+ double t = double(j) / double(divAmount);
+
+ // position ratio on the stroke �X�g���[�N���ratio�ʒu
+ double w = t;
+ // unit vector perpendicular to the stroke ���������̒P�ʃx�N�g��
+ TPointD v = rotate90(normalize(tmpStroke->getSpeed(w)));
+ assert(0 <= w && w <= 1);
+ // position on the stroke �X�g���[�N��̈ʒu
+ TPointD p = tmpStroke->getPoint(w);
+
+ TPointD pos = p + v * maxThickness * widthRatio;
+
+ if (j == 0) {
+ TPointD sPos = pos + startVec * extensionLength;
+ *vp = {float(sPos.x), float(sPos.y)};
+ vp++;
+ }
+ *vp = {float(pos.x), float(pos.y)};
+ if (j == divAmount) {
+ vp++;
+ TPointD ePos = pos + endVec * extensionLength;
+ *vp = {float(ePos.x), float(ePos.y)};
+ }
+ }
+
+ glVertexPointer(2, GL_FLOAT, 0, vertBuf);
+ // draw
+ glDrawArrays(GL_LINE_STRIP, 0, divAmount + 3);
+ }
+ glColor4d(0, 0, 0, 1);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ delete[] vertBuf;
+ delete tmpStroke;
+}
+
+//-----------------------------------------------------------------------------
+
+TRectD FlowLineStrokeStyle::getStrokeBBox(const TStroke *stroke) const {
+ TRectD rect = TColorStyle::getStrokeBBox(stroke);
+ double margin =
+ getMaxThickness(stroke) * m_widthScale * std::max(1.0, m_extension);
+ return rect.enlarge(margin);
+}
+
+//-----------------------------------------------------------------------------
diff --git a/toonz/sources/colorfx/flowlinestrokestyle.h b/toonz/sources/colorfx/flowlinestrokestyle.h
new file mode 100644
index 0000000..25ebcd2
--- /dev/null
+++ b/toonz/sources/colorfx/flowlinestrokestyle.h
@@ -0,0 +1,98 @@
+#pragma once
+
+#ifndef FLOWLINESTROKESTYLE_H
+#define FLOWLINESTROKESTYLE_H
+
+// TnzCore includes
+#include "tsimplecolorstyles.h"
+#include "tvectorimage.h"
+#include "tstrokeprop.h"
+#include "tgl.h"
+
+#include "toonz/imagestyles.h"
+
+#include
+class TVectorRendeData;
+class TRandom;
+
+#undef DVAPI
+#undef DVVAR
+
+#ifdef COLORFX_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+class FlowLineStrokeStyle final : public TSimpleStrokeStyle {
+ TPixel32 m_color;
+
+ enum PARAMID {
+ Density = 0,
+ Extension,
+ WidthScale,
+ StraightenEnds,
+ ParamCount
+ };
+
+ // thin line's density �א��̖��x
+ double m_density;
+ // extend the edges �[���̐L����
+ double m_extension;
+ // extend the widths
+ // ���̊g��B���̐��̕��𑝂₷��region�̌v�Z�ɖc��Ȏ��Ԃ�������
+ double m_widthScale;
+
+ bool m_straightenEnds;
+
+public:
+ FlowLineStrokeStyle();
+
+ TColorStyle *clone() const override;
+
+ void invalidate() {}
+
+ QString getDescription() const override {
+ return QCoreApplication::translate("FlowLineStrokeStyle", "Flow Line");
+ }
+ std::string getBrushIdName() const override { return "FlowLineStrokeStyle"; }
+
+ bool hasMainColor() const override { return true; }
+ TPixel32 getMainColor() const override { return m_color; }
+ void setMainColor(const TPixel32 &color) override { m_color = color; }
+
+ int getParamCount() const override;
+ TColorStyle::ParamType getParamType(int index) const override;
+
+ QString getParamNames(int index) const override;
+
+ void getParamRange(int index, double &min, double &max) const override;
+ double getParamValue(TColorStyle::double_tag, int index) const override;
+ void setParamValue(int index, double value) override;
+
+ bool getParamValue(TColorStyle::bool_tag, int index) const override;
+ void setParamValue(int index, bool value) override;
+
+ void drawStroke(const TColorFunction *cf,
+ const TStroke *stroke) const override;
+
+ void loadData(TInputStreamInterface &is) override {
+ int straightenEnds;
+ is >> m_color >> m_density >> m_extension >> m_widthScale >> straightenEnds;
+ m_straightenEnds = (straightenEnds == 0) ? false : true;
+ }
+ void saveData(TOutputStreamInterface &os) const override {
+ int straightenEnds = m_straightenEnds ? 1 : 0;
+ os << m_color << m_density << m_extension << m_widthScale
+ << m_straightenEnds;
+ }
+ bool isSaveSupported() { return true; }
+
+ int getTagId() const override { return 201; }
+
+ TRectD getStrokeBBox(const TStroke *stroke) const override;
+};
+
+#endif // FLOWLINESTROKESTYLE_H
diff --git a/toonz/sources/common/tfx/trenderer.cpp b/toonz/sources/common/tfx/trenderer.cpp
index 93c4ca4..d4f50ae 100644
--- a/toonz/sources/common/tfx/trenderer.cpp
+++ b/toonz/sources/common/tfx/trenderer.cpp
@@ -27,8 +27,8 @@
#include
// Debug
-//#define DIAGNOSTICS
-//#include "diagnostics.h"
+// #define DIAGNOSTICS
+// #include "diagnostics.h"
#include
#include
@@ -1417,7 +1417,8 @@ void TRendererImp::startRendering(
// If the render contains offscreen render, then prepare the
// QOffscreenSurface
// in main (GUI) thread. For now it is used only in the plasticDeformerFx.
- if (alias.find("plasticDeformerFx") != std::string::npos &&
+ if ((alias.find("plasticDeformerFx") != std::string::npos ||
+ alias.find("iwa_FlowPaintBrushFx") != std::string::npos) &&
QThread::currentThread() == qGuiApp->thread()) {
rs.m_offScreenSurface.reset(new QOffscreenSurface());
rs.m_offScreenSurface->setFormat(QSurfaceFormat::defaultFormat());
diff --git a/toonz/sources/common/tvectorimage/tvectorimage.cpp b/toonz/sources/common/tvectorimage/tvectorimage.cpp
index cf8d938..8261828 100644
--- a/toonz/sources/common/tvectorimage/tvectorimage.cpp
+++ b/toonz/sources/common/tvectorimage/tvectorimage.cpp
@@ -1,16 +1,16 @@
#include "tcurves.h"
-//#include "tpalette.h"
+// #include "tpalette.h"
#include "tvectorimage.h"
#include "tvectorimageP.h"
#include "tstroke.h"
-//#include "tgl.h"
+// #include "tgl.h"
#include "tvectorrenderdata.h"
#include "tmathutil.h"
-//#include "tdebugmessage.h"
+// #include "tdebugmessage.h"
#include "tofflinegl.h"
-//#include "tcolorstyles.h"
+// #include "tcolorstyles.h"
#include "tpaletteutil.h"
#include "tthreadmessage.h"
#include "tsimplecolorstyles.h"
@@ -572,16 +572,14 @@ TRectD TVectorImage::getBBox() const {
TRectD bbox;
for (UINT i = 0; i < strokeCount; ++i) {
- TRectD r = m_imp->m_strokes[i]->m_s->getBBox();
+ TStroke *stroke = m_imp->m_strokes[i]->m_s;
TColorStyle *style = 0;
if (plt) style = plt->getStyle(m_imp->m_strokes[i]->m_s->getStyle());
- if (dynamic_cast(style) ||
- dynamic_cast(
- style)) // con i pattern style, il render a volte taglia sulla bbox
- // dello stroke....
- // aumento la bbox della meta' delle sue dimensioni:pezzaccia.
- r = r.enlarge(std::max(r.getLx() * 0.25, r.getLy() * 0.25));
- bbox = ((i == 0) ? r : bbox + r);
+ if (!style) continue;
+ // reimplemented in TRasterImagePatternStrokeStyle,
+ // TVectorImagePatternStrokeStyle and FlowLineStrokeStyle
+ TRectD r = style->getStrokeBBox(stroke);
+ bbox = ((i == 0) ? r : bbox + r);
}
return bbox;
@@ -624,7 +622,7 @@ void TVectorImage::render(const TVectorRenderData &rd, TRaster32P &ras) {
#endif
//-----------------------------------------------------------------------------
-//#include "timage_io.h"
+// #include "timage_io.h"
TRaster32P TVectorImage::render(bool onlyStrokes) {
TRect bBox = convert(getBBox());
@@ -826,8 +824,7 @@ bool TVectorImage::Imp::selectFill(const TRectD &selArea, TStroke *s,
for (UINT i = 0; i < m_regions.size(); i++) {
int index, j = 0;
- do
- index = m_regions[i]->getEdge(j++)->m_index;
+ do index = m_regions[i]->getEdge(j++)->m_index;
while (index < 0 && j < (int)m_regions[i]->getEdgeCount());
// if index<0, means that the region is purely of autoclose strokes!
if (m_insideGroup != TGroupId() && index >= 0 &&
@@ -1992,7 +1989,7 @@ static void computeEdgeList(TStroke *newS, const std::list &edgeList1,
//-----------------------------------------------------------------------------
#ifdef _DEBUG
-//#include "tpalette.h"
+// #include "tpalette.h"
#include "tcolorstyles.h"
void printEdges(std::ofstream &os, char *str, TPalette *plt,
diff --git a/toonz/sources/common/tvrender/tcolorstyles.cpp b/toonz/sources/common/tvrender/tcolorstyles.cpp
index a0c2588..daf1bc5 100644
--- a/toonz/sources/common/tvrender/tcolorstyles.cpp
+++ b/toonz/sources/common/tvrender/tcolorstyles.cpp
@@ -652,3 +652,7 @@ std::string TColorStyle::getBrushIdNameParam(std::string brushIdName) {
}
//-------------------------------------------------------------------
+
+TRectD TColorStyle::getStrokeBBox(const TStroke *stroke) const {
+ return stroke->getBBox();
+}
\ No newline at end of file
diff --git a/toonz/sources/common/tvrender/tsimplecolorstyles.cpp b/toonz/sources/common/tvrender/tsimplecolorstyles.cpp
index 2c68ad9..440ee0d 100644
--- a/toonz/sources/common/tvrender/tsimplecolorstyles.cpp
+++ b/toonz/sources/common/tvrender/tsimplecolorstyles.cpp
@@ -1385,6 +1385,14 @@ void TRasterImagePatternStrokeStyle::getObsoleteTagIds(
ids.push_back(100);
}
+//-----------------------------------------------------------------------------
+
+TRectD TRasterImagePatternStrokeStyle::getStrokeBBox(
+ const TStroke *stroke) const {
+ TRectD rect = TColorStyle::getStrokeBBox(stroke);
+ return rect.enlarge(std::max(rect.getLx() * 0.25, rect.getLy() * 0.25));
+}
+
//*************************************************************************************
// TVectorImagePatternStrokeStyle implementation
//*************************************************************************************
@@ -1817,6 +1825,14 @@ void TVectorImagePatternStrokeStyle::getObsoleteTagIds(
// ids.push_back(100);
}
+//-----------------------------------------------------------------------------
+
+TRectD TVectorImagePatternStrokeStyle::getStrokeBBox(
+ const TStroke *stroke) const {
+ TRectD rect = TColorStyle::getStrokeBBox(stroke);
+ return rect.enlarge(std::max(rect.getLx() * 0.25, rect.getLy() * 0.25));
+}
+
//*************************************************************************************
// Style declarations instances
//*************************************************************************************
diff --git a/toonz/sources/include/tcolorstyles.h b/toonz/sources/include/tcolorstyles.h
index f30f46b..594c69a 100644
--- a/toonz/sources/include/tcolorstyles.h
+++ b/toonz/sources/include/tcolorstyles.h
@@ -425,6 +425,8 @@ It is used when updates must be done after changes or creation of new styles.
return m_versionNumber;
} //!< Returns the version number of the style.
+ virtual TRectD getStrokeBBox(const TStroke *stroke) const;
+
protected:
virtual void makeIcon(const TDimension &d);
diff --git a/toonz/sources/include/tfxattributes.h b/toonz/sources/include/tfxattributes.h
index c8c361c..47d3195 100644
--- a/toonz/sources/include/tfxattributes.h
+++ b/toonz/sources/include/tfxattributes.h
@@ -33,6 +33,7 @@ class DVAPI TFxAttributes {
/*-- MotionBlurなどのFxのために、オブジェクトの軌跡のデータを取得する --*/
QList m_motionPoints;
+ TAffine m_motionAffine[2];
// to maintain backward compatibility in the fx
int m_fxVersion;
@@ -67,6 +68,16 @@ public:
m_motionPoints = motionPoints;
}
QList getMotionPoints() { return m_motionPoints; }
+
+ void setMotionAffines(TAffine aff_Before, TAffine aff_After) {
+ m_motionAffine[0] = aff_Before;
+ m_motionAffine[1] = aff_After;
+ }
+ void getMotionAffines(TAffine &aff_Before, TAffine &aff_After) {
+ aff_Before = m_motionAffine[0];
+ aff_After = m_motionAffine[1];
+ }
+
void setFxVersion(int version) { m_fxVersion = version; }
int getFxVersion() const { return m_fxVersion; };
diff --git a/toonz/sources/include/toonzqt/paramfield.h b/toonz/sources/include/toonzqt/paramfield.h
index 5c2a20a..ea0ee85 100644
--- a/toonz/sources/include/toonzqt/paramfield.h
+++ b/toonz/sources/include/toonzqt/paramfield.h
@@ -370,6 +370,7 @@ public:
void updateField(double value) override;
QSize getPreferredSize() override { return QSize(260, 26); }
+
void setPrecision(int precision) override;
protected slots:
diff --git a/toonz/sources/include/tparamuiconcept.h b/toonz/sources/include/tparamuiconcept.h
index 51595c8..8a92943 100644
--- a/toonz/sources/include/tparamuiconcept.h
+++ b/toonz/sources/include/tparamuiconcept.h
@@ -74,6 +74,8 @@ public:
VERTICAL_POS, // A horizontal line at given height
// { [TDoubleParamP] }
+ PARALLELOGRAM,
+
TYPESCOUNT
};
diff --git a/toonz/sources/include/tsimplecolorstyles.h b/toonz/sources/include/tsimplecolorstyles.h
index 4eb6145..d3b7f6d 100644
--- a/toonz/sources/include/tsimplecolorstyles.h
+++ b/toonz/sources/include/tsimplecolorstyles.h
@@ -301,6 +301,8 @@ public:
double getParamValue(TColorStyle::double_tag, int index) const override;
void setParamValue(int index, double value) override;
+ TRectD getStrokeBBox(const TStroke *stroke) const override;
+
protected:
void makeIcon(const TDimension &d) override;
@@ -379,6 +381,8 @@ public:
static void clearGlDisplayLists();
+ TRectD getStrokeBBox(const TStroke *stroke) const override;
+
protected:
void makeIcon(const TDimension &d) override;
diff --git a/toonz/sources/stdfx/CMakeLists.txt b/toonz/sources/stdfx/CMakeLists.txt
index ed196a7..4de06ec 100644
--- a/toonz/sources/stdfx/CMakeLists.txt
+++ b/toonz/sources/stdfx/CMakeLists.txt
@@ -87,6 +87,10 @@ set(HEADERS
iwa_bokeh_util.h
globalcontrollablefx.h
iwa_floorbumpfx.h
+ iwa_tangentflowfx.h
+ iwa_flowblurfx.h
+ iwa_flowpaintbrushfx.h
+ iwa_motionflowfx.h
)
if(OpenCV_FOUND)
@@ -282,6 +286,10 @@ set(SOURCES
iwa_bokeh_advancedfx.cpp
iwa_bokeh_util.cpp
iwa_floorbumpfx.cpp
+ iwa_tangentflowfx.cpp
+ iwa_flowblurfx.cpp
+ iwa_flowpaintbrushfx.cpp
+ iwa_motionflowfx.cpp
)
if(OpenCV_FOUND)
diff --git a/toonz/sources/stdfx/iwa_flowblurfx.cpp b/toonz/sources/stdfx/iwa_flowblurfx.cpp
new file mode 100644
index 0000000..1ab1d2c
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_flowblurfx.cpp
@@ -0,0 +1,505 @@
+//--------------------------------------------------------------
+// This Fx is based on & modified from the source code by Zhanping Liu, licensed
+// with the terms as follows:
+
+//----------------[Start of the License Notice]-----------------
+////////////////////////////////////////////////////////////////////////////
+/// Line Integral Convolution for Flow Visualization ///
+/// Initial Version ///
+/// May 15, 1999 ///
+/// by Zhanping Liu ///
+/// (zhanping@erc.msstate.edu) ///
+/// while with Graphics Laboratory, ///
+/// Peking University, P. R. China ///
+///----------------------------------------------------------------------///
+/// Later Condensed ///
+/// May 4, 2002 ///
+/// ///
+/// VAIL (Visualization Analysis & Imaging Laboratory) ///
+/// ERC (Engineering Research Center) ///
+/// Mississippi State University ///
+////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////
+/// This code was developed based on the original algorithm proposed by///
+/// Brian Cabral and Leith (Casey) Leedom in the paper "Imaging Vector///
+/// Fields Using Line Integral Convolution", published in Proceedings of///
+/// ACM SigGraph 93, Aug 2-6, Anaheim, California, pp. 263-270, 1993.///
+/// Permission to use, copy, modify, distribute and sell this code for any///
+/// purpose is hereby granted without fee, provided that the above notice///
+/// appears in all copies and that both that notice and this permission///
+/// appear in supporting documentation. The developer of this code makes///
+/// no representations about the suitability of this code for any///
+/// purpose. It is provided "as is" without express or implied warranty.///
+////////////////////////////////////////////////////////////////////////////
+//-----------------[End of the License Notice]------------------
+
+//--------------------------------------------------------------
+#include "iwa_flowblurfx.h"
+#include
+
+namespace {
+const double LINE_SQUARE_CLIP_MAX = 100000.0;
+const double VECTOR_COMPONENT_MIN = 0.05;
+
+inline double clamp01(double val) {
+ return (val > 1.0) ? 1.0 : (val < 0.0) ? 0.0 : val;
+}
+
+// convert sRGB color space to power space
+template
+inline T to_linear_color_space(T nonlinear_color, T exposure, T gamma) {
+ return std::pow(nonlinear_color, gamma) / exposure;
+}
+// convert power space to sRGB color space
+template
+inline T to_nonlinear_color_space(T linear_color, T exposure, T gamma) {
+ return std::pow(linear_color * exposure, T(1) / gamma);
+}
+
+} // namespace
+//------------------------------------------------------------
+template
+void Iwa_FlowBlurFx::setSourceTileToBuffer(const RASTER srcRas, double4 *buf,
+ bool isLinear, double gamma) {
+ double4 *buf_p = buf;
+ for (int j = 0; j < srcRas->getLy(); j++) {
+ PIXEL *pix = srcRas->pixels(j);
+ for (int i = 0; i < srcRas->getLx(); i++, pix++, buf_p++) {
+ (*buf_p).x = double(pix->r) / double(PIXEL::maxChannelValue);
+ (*buf_p).y = double(pix->g) / double(PIXEL::maxChannelValue);
+ (*buf_p).z = double(pix->b) / double(PIXEL::maxChannelValue);
+ (*buf_p).w = double(pix->m) / double(PIXEL::maxChannelValue);
+ if (isLinear && (*buf_p).w > 0.0) {
+ (*buf_p).x =
+ to_linear_color_space((*buf_p).x / (*buf_p).w, 1.0, gamma) *
+ (*buf_p).w;
+ (*buf_p).y =
+ to_linear_color_space((*buf_p).y / (*buf_p).w, 1.0, gamma) *
+ (*buf_p).w;
+ (*buf_p).z =
+ to_linear_color_space((*buf_p).z / (*buf_p).w, 1.0, gamma) *
+ (*buf_p).w;
+ }
+ }
+ }
+}
+//------------------------------------------------------------
+
+template
+void Iwa_FlowBlurFx::setFlowTileToBuffer(const RASTER flowRas, double2 *buf,
+ double *refBuf) {
+ double2 *buf_p = buf;
+ double *refBuf_p = refBuf;
+ for (int j = 0; j < flowRas->getLy(); j++) {
+ PIXEL *pix = flowRas->pixels(j);
+ for (int i = 0; i < flowRas->getLx(); i++, pix++, buf_p++) {
+ double val = double(pix->r) / double(PIXEL::maxChannelValue);
+ (*buf_p).x = val * 2.0 - 1.0;
+ val = double(pix->g) / double(PIXEL::maxChannelValue);
+ (*buf_p).y = val * 2.0 - 1.0;
+ if (refBuf != nullptr) {
+ *refBuf_p = double(pix->b) / double(PIXEL::maxChannelValue);
+ refBuf_p++;
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+template
+void Iwa_FlowBlurFx::setReferenceTileToBuffer(const RASTER refRas,
+ double *buf) {
+ double *buf_p = buf;
+ for (int j = 0; j < refRas->getLy(); j++) {
+ PIXEL *pix = refRas->pixels(j);
+ for (int i = 0; i < refRas->getLx(); i++, pix++, buf_p++) {
+ // Value = 0.3R 0.59G 0.11B
+ (*buf_p) = (double(pix->r) * 0.3 + double(pix->g) * 0.59 +
+ double(pix->b) * 0.11) /
+ double(PIXEL::maxChannelValue);
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+template
+void Iwa_FlowBlurFx::setOutputRaster(double4 *out_buf, const RASTER dstRas,
+ bool isLinear, double gamma) {
+ double4 *buf_p = out_buf;
+ for (int j = 0; j < dstRas->getLy(); j++) {
+ PIXEL *pix = dstRas->pixels(j);
+ for (int i = 0; i < dstRas->getLx(); i++, pix++, buf_p++) {
+ if (!isLinear || (*buf_p).w == 0.0) {
+ pix->r = (typename PIXEL::Channel)(clamp01((*buf_p).x) *
+ (double)PIXEL::maxChannelValue);
+ pix->g = (typename PIXEL::Channel)(clamp01((*buf_p).y) *
+ (double)PIXEL::maxChannelValue);
+ pix->b = (typename PIXEL::Channel)(clamp01((*buf_p).z) *
+ (double)PIXEL::maxChannelValue);
+ } else {
+ double val;
+ val = to_nonlinear_color_space((*buf_p).x / (*buf_p).w, 1.0, gamma) *
+ (*buf_p).w;
+ pix->r = (typename PIXEL::Channel)(clamp01(val) *
+ (double)PIXEL::maxChannelValue);
+ val = to_nonlinear_color_space((*buf_p).y / (*buf_p).w, 1.0, gamma) *
+ (*buf_p).w;
+ pix->g = (typename PIXEL::Channel)(clamp01(val) *
+ (double)PIXEL::maxChannelValue);
+ val = to_nonlinear_color_space((*buf_p).z / (*buf_p).w, 1.0, gamma) *
+ (*buf_p).w;
+ pix->b = (typename PIXEL::Channel)(clamp01(val) *
+ (double)PIXEL::maxChannelValue);
+ }
+
+ pix->m = (typename PIXEL::Channel)(clamp01((*buf_p).w) *
+ (double)PIXEL::maxChannelValue);
+ }
+ }
+}
+//------------------------------------------------------------
+
+void FlowBlurWorker::run() {
+ auto sourceAt = [&](int x, int y) {
+ if (x < 0 || x >= m_dim.lx || y < 0 || y >= m_dim.ly) return double4();
+ return m_source_buf[y * m_dim.lx + x];
+ };
+ auto flowAt = [&](int x, int y) {
+ if (x < 0 || x >= m_dim.lx || y < 0 || y >= m_dim.ly) return double2();
+ return m_flow_buf[y * m_dim.lx + x];
+ };
+
+ // ���W�X�e�B�b�N���z�̊m�����x�����i�[����(s = 1/3�A0-1�͈̔͂�100����)
+ double logDist[101];
+ if (m_filterType == Gaussian) {
+ double scale = 1.0 / 3.0;
+ for (int i = 0; i <= 101; i++) {
+ double x = (double)i / 100.0;
+ logDist[i] = std::tanh(x / (2.0 * scale));
+ }
+ }
+ // 0-1�ɐ��K�������ϐ��̗ݐϒl��Ԃ�
+ auto getCumulative = [&](double pos) {
+ if (pos > 1.0) return 1.0;
+ if (m_filterType == Linear)
+ return 2.0 * pos - pos * pos;
+ else if (m_filterType == Gaussian)
+ return logDist[(int)(std::ceil(pos * 100.0))];
+ else // Flat
+ return pos;
+ };
+
+ int max_ADVCTS = int(m_krnlen * 3); // MAXIMUM number of advection steps per
+ // direction to break dead loops
+
+ double4 *out_p = &m_out_buf[m_yFrom * m_dim.lx];
+
+ double *ref_p =
+ (m_reference_buf) ? &m_reference_buf[m_yFrom * m_dim.lx] : nullptr;
+
+ // for each pixel in the 2D output LIC image
+ for (int j = m_yFrom; j < m_yTo; j++) {
+ for (int i = 0; i < m_dim.lx; i++, out_p++) {
+ double4 t_acum[2]; // texture accumulators zero-initialized
+ double w_acum[2] = {0., 0.}; // weight accumulators zero-initialized
+
+ double blurLength = (ref_p) ? (*ref_p) * m_krnlen : m_krnlen;
+
+ if (blurLength == 0.0) {
+ (*out_p) = sourceAt(i, j);
+ if (ref_p) ref_p++;
+ continue;
+ }
+
+ // for either advection direction
+ for (int advDir = 0; advDir < 2; advDir++) {
+ // init the step counter, curve-length measurer, and streamline seed///
+ int advcts =
+ 0; // number of ADVeCTion stepS per direction (a step counter)
+ double curLen = 0.0; // CURrent LENgth of the streamline
+ double clp0_x =
+ (double)i + 0.5; // x-coordinate of CLiP point 0 (current)
+ double clp0_y =
+ (double)j + 0.5; // y-coordinate of CLiP point 0 (current)
+
+ // until the streamline is advected long enough or a tightly spiralling
+ // center / focus is encountered///
+
+ double2 pre_vctr = flowAt(int(clp0_x), int(clp0_y));
+ if (advDir == 1) {
+ pre_vctr.x = -pre_vctr.x;
+ pre_vctr.y = -pre_vctr.y;
+ }
+
+ while (curLen < blurLength && advcts < max_ADVCTS) {
+ // access the vector at the sample
+ double2 vctr = flowAt(int(clp0_x), int(clp0_y));
+
+ // in case of a critical point
+ if (vctr.x == 0.0 && vctr.y == 0.0) {
+ w_acum[advDir] = (advcts == 0) ? 1.0 : w_acum[advDir];
+ break;
+ }
+
+ // negate the vector for the backward-advection case
+ if (advDir == 1) {
+ vctr.x = -vctr.x;
+ vctr.y = -vctr.y;
+ }
+
+ // assuming that the flow field vector is bi-directional
+ // if the vector is at an acute angle to the previous vector, negate
+ // it.
+ if (vctr.x * pre_vctr.x + vctr.y * pre_vctr.y < 0) {
+ vctr.x = -vctr.x;
+ vctr.y = -vctr.y;
+ }
+
+ // clip the segment against the pixel boundaries --- find the shorter
+ // from the two clipped segments replace all if-statements whenever
+ // possible as they might affect the computational speed
+ double tmpLen;
+ double segLen = LINE_SQUARE_CLIP_MAX; // SEGment LENgth
+ segLen = (vctr.x < -VECTOR_COMPONENT_MIN)
+ ? (std::floor(clp0_x) - clp0_x) / vctr.x
+ : segLen;
+ segLen =
+ (vctr.x > VECTOR_COMPONENT_MIN)
+ ? (std::floor(std::floor(clp0_x) + 1.5) - clp0_x) / vctr.x
+ : segLen;
+ segLen = (vctr.y < -VECTOR_COMPONENT_MIN)
+ ? (((tmpLen = (std::floor(clp0_y) - clp0_y) / vctr.y) <
+ segLen)
+ ? tmpLen
+ : segLen)
+ : segLen;
+ segLen = (vctr.y > VECTOR_COMPONENT_MIN)
+ ? (((tmpLen = (std::floor(std::floor(clp0_y) + 1.5) -
+ clp0_y) /
+ vctr.y) < segLen)
+ ? tmpLen
+ : segLen)
+ : segLen;
+
+ // update the curve-length measurers
+ double prvLen = curLen;
+ curLen += segLen;
+ segLen += 0.0004;
+
+ // check if the filter has reached either end
+ segLen =
+ (curLen > m_krnlen) ? ((curLen = m_krnlen) - prvLen) : segLen;
+
+ // obtain the next clip point
+ double clp1_x = clp0_x + vctr.x * segLen;
+ double clp1_y = clp0_y + vctr.y * segLen;
+
+ // obtain the middle point of the segment as the texture-contributing
+ // sample
+ double2 samp;
+ samp.x = (clp0_x + clp1_x) * 0.5;
+ samp.y = (clp0_y + clp1_y) * 0.5;
+
+ // obtain the texture value of the sample
+ double4 texVal = sourceAt(int(samp.x), int(samp.y));
+
+ // update the accumulated weight and the accumulated composite texture
+ // (texture x weight)
+ double W_ACUM = getCumulative(curLen / blurLength);
+ // double W_ACUM = curLen;
+ // W_ACUM = wgtLUT[int(curLen * len2ID)];
+
+ double smpWgt = W_ACUM - w_acum[advDir];
+ w_acum[advDir] = W_ACUM;
+ t_acum[advDir].x += texVal.x * smpWgt;
+ t_acum[advDir].y += texVal.y * smpWgt;
+ t_acum[advDir].z += texVal.z * smpWgt;
+ t_acum[advDir].w += texVal.w * smpWgt;
+
+ // update the step counter and the "current" clip point
+ advcts++;
+ clp0_x = clp1_x;
+ clp0_y = clp1_y;
+
+ pre_vctr = vctr;
+ // check if the streamline has gone beyond the flow field///
+ if (clp0_x < 0.0 || clp0_x >= double(m_dim.lx) || clp0_y < 0.0 ||
+ clp0_y >= double(m_dim.ly))
+ break;
+ }
+ }
+
+ // normalize the accumulated composite texture
+ (*out_p).x = (t_acum[0].x + t_acum[1].x) / (w_acum[0] + w_acum[1]);
+ (*out_p).y = (t_acum[0].y + t_acum[1].y) / (w_acum[0] + w_acum[1]);
+ (*out_p).z = (t_acum[0].z + t_acum[1].z) / (w_acum[0] + w_acum[1]);
+ (*out_p).w = (t_acum[0].w + t_acum[1].w) / (w_acum[0] + w_acum[1]);
+
+ if (ref_p) ref_p++;
+ }
+ }
+}
+
+Iwa_FlowBlurFx::Iwa_FlowBlurFx()
+ : m_length(5.0)
+ , m_linear(false)
+ , m_gamma(2.2)
+ , m_filterType(new TIntEnumParam(Linear, "Linear"))
+ , m_referenceMode(new TIntEnumParam(REFERENCE, "Reference Image")) {
+ addInputPort("Source", m_source);
+ addInputPort("Flow", m_flow);
+ addInputPort("Reference", m_reference);
+
+ bindParam(this, "length", m_length);
+ bindParam(this, "linear", m_linear);
+ bindParam(this, "gamma", m_gamma);
+ bindParam(this, "filterType", m_filterType);
+ bindParam(this, "referenceMode", m_referenceMode);
+
+ m_length->setMeasureName("fxLength");
+ m_length->setValueRange(0., 100.0);
+ m_gamma->setValueRange(0.2, 5.0);
+
+ m_filterType->addItem(Gaussian, "Gaussian");
+ m_filterType->addItem(Flat, "Flat");
+
+ m_referenceMode->addItem(FLOW_BLUE_CHANNEL, "Blue Channel of Flow Image");
+}
+
+void Iwa_FlowBlurFx::doCompute(TTile &tile, double frame,
+ const TRenderSettings &settings) {
+ // do nothing if Source or Flow port is not connected
+ if (!m_source.isConnected() || !m_flow.isConnected()) {
+ tile.getRaster()->clear();
+ return;
+ }
+
+ // output size
+ TDimension dim = tile.getRaster()->getSize();
+
+ double fac = sqrt(fabs(settings.m_affine.det()));
+ double krnlen = fac * m_length->getValue(frame);
+ int max_ADVCTS = int(krnlen * 3); // MAXIMUM number of advection steps per
+ // direction to break dead loops
+
+ if (max_ADVCTS == 0) {
+ // No blur will be done. The underlying fx may pass by.
+ m_source->compute(tile, frame, settings);
+ return;
+ }
+
+ bool isLinear = m_linear->getValue();
+ double gamma = m_gamma->getValue(frame);
+
+ // obtain Source memory buffer (RGBA)
+ TTile sourceTile;
+ m_source->allocateAndCompute(sourceTile, tile.m_pos, dim, tile.getRaster(),
+ frame, settings);
+ // allocate buffer
+ double4 *source_buf;
+ TRasterGR8P source_buf_ras(dim.lx * dim.ly * sizeof(double4), 1);
+ source_buf_ras->lock();
+ source_buf = (double4 *)source_buf_ras->getRawData();
+ TRaster32P ras32 = tile.getRaster();
+ TRaster64P ras64 = tile.getRaster();
+ if (ras32)
+ setSourceTileToBuffer(sourceTile.getRaster(),
+ source_buf, isLinear, gamma);
+ else if (ras64)
+ setSourceTileToBuffer(sourceTile.getRaster(),
+ source_buf, isLinear, gamma);
+
+ // obtain Flow memory buffer (XY)
+ TTile flowTile;
+ m_flow->allocateAndCompute(flowTile, tile.m_pos, dim, tile.getRaster(), frame,
+ settings);
+ // allocate buffer
+ double2 *flow_buf;
+ TRasterGR8P flow_buf_ras(dim.lx * dim.ly * sizeof(double2), 1);
+ flow_buf_ras->lock();
+ flow_buf = (double2 *)flow_buf_ras->getRawData();
+
+ double *reference_buf = nullptr;
+ TRasterGR8P reference_buf_ras;
+ if (m_referenceMode->getValue() == FLOW_BLUE_CHANNEL ||
+ m_reference.isConnected()) {
+ reference_buf_ras = TRasterGR8P(dim.lx * dim.ly * sizeof(double), 1);
+ reference_buf_ras->lock();
+ reference_buf = (double *)reference_buf_ras->getRawData();
+ }
+
+ if (ras32)
+ setFlowTileToBuffer(flowTile.getRaster(), flow_buf,
+ reference_buf);
+ else if (ras64)
+ setFlowTileToBuffer(flowTile.getRaster(), flow_buf,
+ reference_buf);
+
+ if (m_referenceMode->getValue() == REFERENCE && m_reference.isConnected()) {
+ TTile referenceTile;
+ m_reference->allocateAndCompute(referenceTile, tile.m_pos, dim,
+ tile.getRaster(), frame, settings);
+ if (ras32)
+ setReferenceTileToBuffer(referenceTile.getRaster(),
+ reference_buf);
+ else if (ras64)
+ setReferenceTileToBuffer(referenceTile.getRaster(),
+ reference_buf);
+ }
+
+ // buffer for output raster
+ double4 *out_buf;
+ TRasterGR8P out_buf_ras(dim.lx * dim.ly * sizeof(double4), 1);
+ out_buf_ras->lock();
+ out_buf = (double4 *)out_buf_ras->getRawData();
+
+ int activeThreadCount = QThreadPool::globalInstance()->activeThreadCount();
+ // use half of the available threads
+ int threadAmount = std::max(1, activeThreadCount / 2);
+ FILTER_TYPE filterType = (FILTER_TYPE)m_filterType->getValue();
+ QList threadList;
+ int tmpStart = 0;
+ for (int t = 0; t < threadAmount; t++) {
+ int tmpEnd =
+ (int)std::round((float)(dim.ly * (t + 1)) / (float)threadAmount);
+
+ FlowBlurWorker *worker =
+ new FlowBlurWorker(source_buf, flow_buf, out_buf, reference_buf, dim,
+ krnlen, tmpStart, tmpEnd, filterType);
+ worker->start();
+ threadList.append(worker);
+ tmpStart = tmpEnd;
+ }
+
+ for (auto worker : threadList) {
+ worker->wait();
+ delete worker;
+ }
+
+ source_buf_ras->unlock();
+ flow_buf_ras->unlock();
+ if (reference_buf) reference_buf_ras->unlock();
+
+ // render vector field with red & green channels
+ if (ras32)
+ setOutputRaster(out_buf, ras32, isLinear, gamma);
+ else if (ras64)
+ setOutputRaster(out_buf, ras64, isLinear, gamma);
+
+ out_buf_ras->unlock();
+}
+
+bool Iwa_FlowBlurFx::doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &info) {
+ bBox = TConsts::infiniteRectD;
+ return true;
+}
+
+bool Iwa_FlowBlurFx::canHandle(const TRenderSettings &info, double frame) {
+ return true;
+}
+
+FX_PLUGIN_IDENTIFIER(Iwa_FlowBlurFx, "iwa_FlowBlurFx")
diff --git a/toonz/sources/stdfx/iwa_flowblurfx.h b/toonz/sources/stdfx/iwa_flowblurfx.h
new file mode 100644
index 0000000..80f7dde
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_flowblurfx.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#ifndef IWA_FLOWBLURFX
+#define IWA_FLOWBLURFX
+
+#include "stdfx.h"
+#include "tfxparam.h"
+
+#include
+
+struct double2 {
+ double x = 0., y = 0.;
+};
+
+struct double4 {
+ double x = 0., y = 0., z = 0, w = 0.;
+};
+
+enum FILTER_TYPE { Linear = 0, Gaussian, Flat };
+
+class FlowBlurWorker : public QThread {
+ double4 *m_source_buf;
+ double2 *m_flow_buf;
+ double4 *m_out_buf;
+ double *m_reference_buf;
+ TDimension m_dim;
+ double m_krnlen;
+ int m_yFrom, m_yTo;
+ FILTER_TYPE m_filterType;
+
+public:
+ FlowBlurWorker(double4 *source_buf, double2 *flow_buf, double4 *out_buf,
+ double *ref_buf, TDimension dim, double krnlen, int yFrom,
+ int yTo, FILTER_TYPE filterType)
+ : m_source_buf(source_buf)
+ , m_flow_buf(flow_buf)
+ , m_out_buf(out_buf)
+ , m_reference_buf(ref_buf)
+ , m_dim(dim)
+ , m_krnlen(krnlen)
+ , m_yFrom(yFrom)
+ , m_yTo(yTo)
+ , m_filterType(filterType) {}
+
+ void run();
+};
+
+class Iwa_FlowBlurFx final : public TStandardRasterFx {
+ FX_PLUGIN_DECLARATION(Iwa_FlowBlurFx)
+
+protected:
+ TRasterFxPort m_source;
+ TRasterFxPort m_flow;
+ TRasterFxPort m_reference;
+
+ TDoubleParamP m_length;
+
+ TBoolParamP m_linear;
+ TDoubleParamP m_gamma;
+
+ /*- リニア/ガウシアン/平均化 -*/
+ TIntEnumParamP m_filterType;
+
+ // Reference, Blue Channel of Flow Image
+ TIntEnumParamP m_referenceMode;
+
+ enum ReferenceMode { REFERENCE = 0, FLOW_BLUE_CHANNEL };
+
+ template
+ void setSourceTileToBuffer(const RASTER srcRas, double4 *buf, bool isLinear,
+ double gamma);
+ template
+ void setFlowTileToBuffer(const RASTER flowRas, double2 *buf, double *refBuf);
+ template
+ void setReferenceTileToBuffer(const RASTER srcRas, double *buf);
+
+ template
+ void setOutputRaster(double4 *out_buf, const RASTER dstRas, bool isLinear,
+ double gamma);
+
+public:
+ Iwa_FlowBlurFx();
+
+ void doCompute(TTile &tile, double frame,
+ const TRenderSettings &settings) override;
+
+ bool doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &info) override;
+
+ bool canHandle(const TRenderSettings &info, double frame) override;
+};
+#endif
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_flowpaintbrushfx.cpp b/toonz/sources/stdfx/iwa_flowpaintbrushfx.cpp
new file mode 100644
index 0000000..e17146c
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_flowpaintbrushfx.cpp
@@ -0,0 +1,1089 @@
+#include "iwa_flowpaintbrushfx.h"
+#include "tparamuiconcept.h"
+#include "tlevel.h"
+#include "trop.h"
+
+#include
+
+#include "tgl.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+
+inline double lerp(DoublePair range, double t) {
+ return range.first * (1.0 - t) + range.second * t;
+}
+
+bool strokeStackGraterThan(const BrushStroke &stroke1,
+ const BrushStroke &stroke2) {
+ return stroke1.stack > stroke2.stack;
+}
+
+} // namespace
+
+//------------------------------------------------------------
+// obtain raster data of brush tips �u���V�^�b�`�̃��X�^�[�f�[�^���擾
+void Iwa_FlowPaintBrushFx::getBrushRasters(std::vector &brushRasters,
+ TDimension &b_size, int &lastFrame,
+ TTile &tile,
+ const TRenderSettings &ri) {
+ // �u���V�e�N�X�`�����
+ TPointD b_offset;
+ const TFxTimeRegion &tr = m_brush->getTimeRegion();
+ lastFrame = tr.getLastFrame() + 1;
+ TLevelP partLevel = new TLevel();
+ partLevel->setName(m_brush->getAlias(0, ri));
+
+ // The particles offset must be calculated without considering the
+ // affine's translational component
+ TRenderSettings riZero(ri);
+ riZero.m_affine.a13 = riZero.m_affine.a23 = 0;
+
+ // Calculate the bboxes union
+ TRectD brushBox;
+ for (int t = 0; t < lastFrame; ++t) {
+ TRectD inputBox;
+ m_brush->getBBox(t, inputBox, riZero);
+ brushBox += inputBox;
+ }
+ if (brushBox.isEmpty()) {
+ lastFrame = 0;
+ return;
+ }
+ if (brushBox == TConsts::infiniteRectD) brushBox *= ri.m_cameraBox;
+
+ b_size.lx = (int)brushBox.getLx() + 1;
+ b_size.ly = (int)brushBox.getLy() + 1;
+ b_offset = TPointD(0.5 * (brushBox.x0 + brushBox.x1),
+ 0.5 * (brushBox.y0 + brushBox.y1));
+
+ for (int t = 0; t < lastFrame; ++t) {
+ TRasterP ras;
+ std::string alias;
+ TRasterImageP rimg;
+ rimg = partLevel->frame(t);
+ if (rimg) {
+ ras = rimg->getRaster();
+ } else {
+ alias = "BRUSH: " + m_brush->getAlias(t, ri);
+ rimg = TImageCache::instance()->get(alias, false);
+ if (rimg) {
+ ras = rimg->getRaster();
+
+ // Check that the raster resolution is sufficient for our purposes
+ if (ras->getLx() < b_size.lx || ras->getLy() < b_size.ly)
+ ras = 0;
+ else
+ b_size = TDimension(ras->getLx(), ras->getLy());
+ }
+ }
+ if (!ras) {
+ TTile auxTile;
+ TRenderSettings auxRi(ri);
+ auxRi.m_bpp = 32;
+ m_brush->allocateAndCompute(auxTile, brushBox.getP00(), b_size, 0, t,
+ auxRi);
+ ras = auxTile.getRaster();
+ addRenderCache(alias, TRasterImageP(ras));
+ }
+ if (ras) brushRasters.push_back(ras);
+ }
+}
+
+//------------------------------------------------------------
+
+template
+void Iwa_FlowPaintBrushFx::setFlowTileToBuffer(const RASTER flowRas,
+ double2 *buf) {
+ double2 *buf_p = buf;
+ for (int j = 0; j < flowRas->getLy(); j++) {
+ PIXEL *pix = flowRas->pixels(j);
+ for (int i = 0; i < flowRas->getLx(); i++, pix++, buf_p++) {
+ double val = double(pix->r) / double(PIXEL::maxChannelValue);
+ (*buf_p).x = val * 2.0 - 1.0;
+ val = double(pix->g) / double(PIXEL::maxChannelValue);
+ (*buf_p).y = val * 2.0 - 1.0;
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+template
+void Iwa_FlowPaintBrushFx::setAreaTileToBuffer(const RASTER areaRas,
+ double *buf) {
+ double *buf_p = buf;
+ for (int j = 0; j < areaRas->getLy(); j++) {
+ PIXEL *pix = areaRas->pixels(j);
+ for (int i = 0; i < areaRas->getLx(); i++, pix++, buf_p++) {
+ (*buf_p) = double(pix->m) / double(PIXEL::maxChannelValue);
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+template
+void Iwa_FlowPaintBrushFx::setColorTileToBuffer(const RASTER colorRas,
+ colorRGBA *buf) {
+ colorRGBA *buf_p = buf;
+ for (int j = 0; j < colorRas->getLy(); j++) {
+ PIXEL *pix = colorRas->pixels(j);
+ for (int i = 0; i < colorRas->getLx(); i++, pix++, buf_p++) {
+ (*buf_p).r = double(pix->r) / double(PIXEL::maxChannelValue);
+ (*buf_p).g = double(pix->g) / double(PIXEL::maxChannelValue);
+ (*buf_p).b = double(pix->b) / double(PIXEL::maxChannelValue);
+ (*buf_p).a = double(pix->m) / double(PIXEL::maxChannelValue);
+ }
+ }
+}
+
+//------------------------------------------------------------
+template
+void Iwa_FlowPaintBrushFx::setOutRaster(const RASTER outRas, double *buf) {
+ double *buf_p = buf;
+ for (int j = 0; j < outRas->getLy(); j++) {
+ PIXEL *pix = outRas->pixels(j);
+ for (int i = 0; i < outRas->getLx(); i++, pix++, buf_p++) {
+ typename PIXEL::Channel val =
+ (typename PIXEL::Channel)(*buf_p * double(PIXEL::maxChannelValue));
+ pix->r = val;
+ pix->g = val;
+ pix->b = val;
+ pix->m = PIXEL::maxChannelValue;
+ }
+ }
+}
+
+//------------------------------------------------------------
+Iwa_FlowPaintBrushFx::Iwa_FlowPaintBrushFx()
+ : m_h_density(10.0)
+ , m_v_density(10.0)
+ , m_pos_randomness(1.0)
+ , m_pos_wobble(0.0)
+ , m_tip_width(DoublePair(0.02, 0.05))
+ , m_tip_length(DoublePair(0.08, 0.2))
+ , m_tip_alpha(DoublePair(0.8, 1.0))
+ , m_tip_joints(3)
+ , m_bidirectional(true)
+ , m_width_randomness(0.0)
+ , m_length_randomness(0.0)
+ , m_angle_randomness(0.0)
+ , m_sustain_width_to_skew(0.0)
+ , m_anti_jaggy(false)
+ , m_origin_pos(TPointD(0.0, 0.0))
+ , m_horizontal_pos(TPointD(100.0, 0.0))
+ , m_vertical_pos(TPointD(0.0, 100.0))
+ , m_curve_point(TPointD(0.0, 0.0))
+ , m_fill_gap_size(0.0)
+ , m_reference_frame(0.0)
+ , m_reference_prevalence(0.0)
+ , m_random_seed(1)
+ , m_sortBy(new TIntEnumParam(NoSort, "None")) {
+ addInputPort("Brush", m_brush);
+ addInputPort("Flow", m_flow);
+ addInputPort("Area", m_area);
+ addInputPort("Color", m_color);
+
+ bindParam(this, "h_density", m_h_density);
+ bindParam(this, "v_density", m_v_density);
+ bindParam(this, "pos_randomness", m_pos_randomness);
+ bindParam(this, "pos_wobble", m_pos_wobble);
+ bindParam(this, "tip_width", m_tip_width);
+ bindParam(this, "tip_length", m_tip_length);
+ bindParam(this, "tip_alpha", m_tip_alpha);
+ bindParam(this, "tip_joints", m_tip_joints);
+ bindParam(this, "bidirectional", m_bidirectional);
+
+ bindParam(this, "width_randomness", m_width_randomness);
+ bindParam(this, "length_randomness", m_length_randomness);
+ bindParam(this, "angle_randomness", m_angle_randomness);
+ bindParam(this, "sustain_width_to_skew", m_sustain_width_to_skew);
+ bindParam(this, "anti_jaggy", m_anti_jaggy);
+
+ bindParam(this, "origin_pos", m_origin_pos);
+ bindParam(this, "horizontal_pos", m_horizontal_pos);
+ bindParam(this, "vertical_pos", m_vertical_pos);
+ bindParam(this, "curve_point", m_curve_point);
+ bindParam(this, "fill_gap_size", m_fill_gap_size);
+
+ bindParam(this, "reference_frame", m_reference_frame);
+ bindParam(this, "reference_prevalence", m_reference_prevalence);
+
+ bindParam(this, "random_seed", m_random_seed);
+ bindParam(this, "sort_by", m_sortBy);
+
+ m_h_density->setValueRange(1.0, 300.0);
+ m_v_density->setValueRange(1.0, 300.0);
+ m_pos_randomness->setValueRange(0.0, 2.0);
+ m_pos_wobble->setValueRange(0.0, 1.0);
+ m_tip_width->getMin()->setValueRange(0.0, 1.0);
+ m_tip_width->getMax()->setValueRange(0.0, 1.0);
+ m_tip_length->getMin()->setValueRange(0.0, 1.0);
+ m_tip_length->getMax()->setValueRange(0.0, 1.0);
+ m_tip_alpha->getMin()->setValueRange(0.0, 1.0);
+ m_tip_alpha->getMax()->setValueRange(0.0, 1.0);
+ m_tip_joints->setValueRange(0, 20);
+
+ m_width_randomness->setValueRange(0.0, 0.9);
+ m_length_randomness->setValueRange(0.0, 0.9);
+ m_angle_randomness->setValueRange(0.0, 180.0); // degree
+
+ m_sustain_width_to_skew->setValueRange(0.0, 1.0);
+
+ m_origin_pos->getX()->setMeasureName("fxLength");
+ m_origin_pos->getY()->setMeasureName("fxLength");
+ m_horizontal_pos->getX()->setMeasureName("fxLength");
+ m_horizontal_pos->getY()->setMeasureName("fxLength");
+ m_vertical_pos->getX()->setMeasureName("fxLength");
+ m_vertical_pos->getY()->setMeasureName("fxLength");
+ m_curve_point->getX()->setValueRange(-0.5, 0.5);
+ m_curve_point->getY()->setValueRange(-0.5, 0.5);
+
+ m_fill_gap_size->setMeasureName("fxLength");
+ m_fill_gap_size->setValueRange(0.0, 50.0);
+
+ m_reference_frame->setValueRange(0, (std::numeric_limits::max)());
+ m_reference_prevalence->setValueRange(0.0, 1.0);
+ m_random_seed->setValueRange(0, (std::numeric_limits::max)());
+ m_sortBy->addItem(Smaller, "Size - Smaller on Top");
+ m_sortBy->addItem(Larger, "Size - Larger on Top");
+ m_sortBy->addItem(Darker, "Brightness - Darker on Top");
+ m_sortBy->addItem(Brighter, "Brightness - Brighter on Top");
+ m_sortBy->addItem(Random, "Random");
+
+ // Version 1 (development version) : strokes are placed upward direction if
+ // there is no Flow input.
+ // Version 2: strokes are directed parallel to the
+ // vertical vector of the parallelogram if there is no Flow input.
+ setFxVersion(2);
+}
+
+//------------------------------------------------------------
+bool Iwa_FlowPaintBrushFx::doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &info) {
+ if (!m_brush.isConnected()) {
+ bBox = TRectD();
+ return false;
+ }
+ TPointD origin_pos = m_origin_pos->getValue(frame);
+ TPointD horiz_pos = m_horizontal_pos->getValue(frame);
+ TPointD vert_pos = m_vertical_pos->getValue(frame);
+ TPointD opposite_pos = horiz_pos + vert_pos - origin_pos;
+ bBox = boundingBox(origin_pos, horiz_pos, vert_pos, opposite_pos);
+ return true;
+}
+
+//--------------------------------------------------------------
+double Iwa_FlowPaintBrushFx::getSizePixelAmount(const double val,
+ const TAffine affine) {
+ /*--- Convert to vector --- */
+ TPointD vect;
+ vect.x = val;
+ vect.y = 0.0;
+ /*--- Apply geometrical transformation ---*/
+ // For the following lines I referred to lines 586-592 of
+ // sources/stdfx/motionblurfx.cpp
+ TAffine aff(affine);
+ aff.a13 = aff.a23 = 0; /* ignore translation */
+ vect = aff * vect;
+ /*--- return the length of the vector ---*/
+ return sqrt(vect.x * vect.x + vect.y * vect.y);
+}
+//------------------------------------------------------------
+
+FlowPaintBrushFxParam Iwa_FlowPaintBrushFx::getParam(
+ TTile &tile, double frame, const TRenderSettings &ri) {
+ FlowPaintBrushFxParam p;
+
+ TAffine aff = ri.m_affine;
+ p.origin_pos = aff * m_origin_pos->getValue(frame);
+ p.horiz_pos = aff * m_horizontal_pos->getValue(frame);
+ p.vert_pos = aff * m_vertical_pos->getValue(frame);
+ TPointD opposite_pos = p.horiz_pos + p.vert_pos - p.origin_pos;
+ p.bbox = boundingBox(p.origin_pos, p.horiz_pos, p.vert_pos, opposite_pos);
+ p.dim.lx = (int)p.bbox.getLx() + 1;
+ p.dim.ly = (int)p.bbox.getLy() + 1;
+ p.origin_pos -= p.bbox.getP00();
+ p.horiz_pos -= p.bbox.getP00();
+ p.vert_pos -= p.bbox.getP00();
+
+ p.fill_gap_size =
+ (int)getSizePixelAmount(m_fill_gap_size->getValue(frame), aff);
+
+ p.h_density = m_h_density->getValue(frame);
+ p.v_density = m_v_density->getValue(frame);
+ p.pos_randomness = m_pos_randomness->getValue(frame);
+ p.pos_wobble = m_pos_wobble->getValue(frame);
+
+ p.random_seed = m_random_seed->getValue();
+ p.tipLength = m_tip_length->getValue(frame);
+ p.tipWidth = m_tip_width->getValue(frame);
+ p.tipAlpha = m_tip_alpha->getValue(frame);
+ p.width_random = m_width_randomness->getValue(frame);
+ p.length_random = m_length_randomness->getValue(frame);
+ p.angle_random = m_angle_randomness->getValue(frame);
+
+ p.reso = m_tip_joints->getValue();
+ p.anti_jaggy = m_anti_jaggy->getValue();
+
+ p.hVec = p.horiz_pos - p.origin_pos;
+ p.vVec = p.vert_pos - p.origin_pos;
+ p.vVec_unit = double2{normalize(p.vVec).x, normalize(p.vVec).y};
+
+ p.brushAff = TAffine(p.hVec.x, p.vVec.x, p.origin_pos.x, p.hVec.y, p.vVec.y,
+ p.origin_pos.y);
+ return p;
+}
+
+//------------------------------------------------------------
+
+void Iwa_FlowPaintBrushFx::fillGapByDilateAndErode(double *buf,
+ const TDimension &dim,
+ const int fill_gap_size) {
+ TRasterGR8P tmp_buf_ras = TRasterGR8P(dim.lx * dim.ly * sizeof(double), 1);
+ tmp_buf_ras->lock();
+ double *tmp_buf = (double *)tmp_buf_ras->getRawData();
+
+ // dilate, then erode
+ for (int mode = 0; mode < 2; mode++) {
+ for (int i = 0; i < fill_gap_size; i++) {
+ double *fromBuf = (i % 2 == 0) ? buf : tmp_buf;
+ double *toBuf = (i % 2 == 0) ? tmp_buf : buf;
+
+ double *to_p = toBuf;
+ double *frm_p = fromBuf;
+ for (int y = 0; y < dim.ly; y++) {
+ double *n_up =
+ (y == 0) ? &fromBuf[y * dim.lx] : &fromBuf[(y - 1) * dim.lx];
+ double *n_dn = (y == dim.ly - 1) ? &fromBuf[y * dim.lx]
+ : &fromBuf[(y + 1) * dim.lx];
+ for (int x = 0; x < dim.lx; x++, n_up++, n_dn++, to_p++, frm_p++) {
+ if (mode == 0) {
+ *to_p = std::max(*frm_p, *n_up);
+ *to_p = std::max(*to_p, *n_dn);
+ if (x != 0) *to_p = std::max(*to_p, *(frm_p - 1));
+ if (x != dim.lx - 1) *to_p = std::max(*to_p, *(frm_p + 1));
+ } else {
+ *to_p = std::min(*frm_p, *n_up);
+ *to_p = std::min(*to_p, *n_dn);
+ if (x != 0) *to_p = std::min(*to_p, *(frm_p - 1));
+ if (x != dim.lx - 1) *to_p = std::min(*to_p, *(frm_p + 1));
+ }
+ }
+ }
+ }
+ }
+
+ // ���̃t�B���^�����O�̏ꍇ�A�v�Z���ʂ�tmp_buf�ɓ����Ă���̂�buf�ɃR�s�[����
+ if (fill_gap_size % 2 == 1) {
+ memcpy(buf, tmp_buf, dim.lx * dim.ly * sizeof(double));
+ }
+
+ tmp_buf_ras->unlock();
+}
+
+//------------------------------------------------------------
+void Iwa_FlowPaintBrushFx::computeBrushVertices(
+ QVector &brushVertices, QList &brushStrokes,
+ FlowPaintBrushFxParam &p, TTile &tile, double frame,
+ const TRenderSettings &ri) {
+ // �Œ肷��^�b�`�F��t���[����Area�AColor��p���Đ����A�J�����g�t���[����Flow��p���ė���
+ // �������^�b�`�FArea�AColor�AFlow���ׂăJ�����g�t���[����p����B
+ int referenceFrame = (int)std::round(m_reference_frame->getValue(frame)) - 1;
+ double reference_prevalence = m_reference_prevalence->getValue(frame);
+ bool bidirectional = m_bidirectional->getValue();
+
+ FlowPaintBrushFxParam pivP =
+ getParam(tile, referenceFrame, ri); // TODO: �����A��x��Ԗh����
+ pivP.lastFrame = p.lastFrame;
+ FlowPaintBrushFxParam *param[2] = {&p, &pivP};
+
+ double ref_frame[2] = {frame, (double)referenceFrame};
+ double2 *flow_buf[2] = {nullptr};
+ double *area_buf[2] = {nullptr};
+ colorRGBA *color_buf[2] = {nullptr};
+ TRasterGR8P flow_buf_ras[2], area_buf_ras[2], color_buf_ras[2];
+
+ TRaster32P ras32 = tile.getRaster();
+ TRaster64P ras64 = tile.getRaster();
+
+ // ���ꂼ��̎Q�Ɖ摜���擾
+ for (int f = 0; f < 2; f++) {
+ // Reference�t���[���̓t���[����-1����prevalence���O�̏ꍇ�v�Z���Ȃ�
+ int tmp_f = ref_frame[f];
+ if (f == 1 && (tmp_f < 0 || reference_prevalence == 0.0)) continue;
+
+ // �܂��AFlow���v�Z
+ if (m_flow.isConnected()) {
+ // obtain Flow memory buffer (XY)
+ TTile flowTile;
+ m_flow->allocateAndCompute(flowTile, param[f]->bbox.getP00(),
+ param[f]->dim, tile.getRaster(), tmp_f, ri);
+ // allocate buffer
+ flow_buf_ras[f] =
+ TRasterGR8P(param[f]->dim.lx * param[f]->dim.ly * sizeof(double2), 1);
+ flow_buf_ras[f]->lock();
+ flow_buf[f] = (double2 *)flow_buf_ras[f]->getRawData();
+ if (ras32)
+ setFlowTileToBuffer(flowTile.getRaster(),
+ flow_buf[f]);
+ else if (ras64)
+ setFlowTileToBuffer(flowTile.getRaster(),
+ flow_buf[f]);
+ }
+ // �J�����g�t���[����prevalence���P�̏ꍇFlow�̂v�Z
+ if (f == 0 && reference_prevalence == 1.0) continue;
+ // Area��Color���v�Z
+ if (m_area.isConnected()) {
+ TTile areaTile;
+ m_area->allocateAndCompute(areaTile, param[f]->bbox.getP00(),
+ param[f]->dim, tile.getRaster(), tmp_f, ri);
+ // allocate buffer
+ area_buf_ras[f] =
+ TRasterGR8P(param[f]->dim.lx * param[f]->dim.ly * sizeof(double), 1);
+ area_buf_ras[f]->lock();
+ area_buf[f] = (double *)area_buf_ras[f]->getRawData();
+ if (ras32)
+ setAreaTileToBuffer(areaTile.getRaster(),
+ area_buf[f]);
+ else if (ras64)
+ setAreaTileToBuffer(areaTile.getRaster(),
+ area_buf[f]);
+
+ // �Ԃ����߂�
+ if (param[f]->fill_gap_size > 0)
+ fillGapByDilateAndErode(area_buf[f], param[f]->dim,
+ param[f]->fill_gap_size);
+ }
+ if (m_color.isConnected()) {
+ TTile colorTile;
+ m_color->allocateAndCompute(colorTile, param[f]->bbox.getP00(),
+ param[f]->dim, tile.getRaster(), tmp_f, ri);
+ // allocate buffer
+ color_buf_ras[f] = TRasterGR8P(
+ param[f]->dim.lx * param[f]->dim.ly * sizeof(colorRGBA), 1);
+ color_buf_ras[f]->lock();
+ color_buf[f] = (colorRGBA *)color_buf_ras[f]->getRawData();
+ if (ras32)
+ setColorTileToBuffer(colorTile.getRaster(),
+ color_buf[f]);
+ else if (ras64)
+ setColorTileToBuffer(colorTile.getRaster(),
+ color_buf[f]);
+ }
+ }
+
+ enum FRAMETYPE { CURRENT = 0, REFERENCE };
+
+ auto getFlow = [&](TPointD pos, FRAMETYPE fType) {
+ if (!flow_buf[fType]) {
+ if (getFxVersion() == 1)
+ return double2{0, 1};
+ else
+ return param[CURRENT]->vVec_unit;
+ }
+ int u = (int)std::round(pos.x);
+ int v = (int)std::round(pos.y);
+ if (u < 0 || u >= param[fType]->dim.lx || v < 0 ||
+ v >= param[fType]->dim.ly) {
+ if (getFxVersion() == 1)
+ return double2{0, 1};
+ else
+ return param[CURRENT]->vVec_unit;
+ }
+ return flow_buf[fType][v * param[fType]->dim.lx + u];
+ };
+ auto getArea = [&](TPointD pos, FRAMETYPE fType) {
+ if (!area_buf[fType]) return 1.0;
+ int u = (int)std::round(pos.x);
+ int v = (int)std::round(pos.y);
+ if (u < 0 || u >= param[fType]->dim.lx || v < 0 ||
+ v >= param[fType]->dim.ly)
+ return 0.0;
+ return area_buf[fType][v * param[fType]->dim.lx + u];
+ };
+ auto getColor = [&](TPointD pos, FRAMETYPE fType) {
+ if (!color_buf[fType]) return colorRGBA{1.0, 1.0, 1.0, 1.0};
+ int u = (int)std::round(pos.x);
+ int v = (int)std::round(pos.y);
+ if (u < 0 || u >= param[fType]->dim.lx || v < 0 ||
+ v >= param[fType]->dim.ly)
+ return colorRGBA{1.0, 1.0, 1.0, 0.0};
+ return color_buf[fType][v * param[fType]->dim.lx + u];
+ };
+
+ // �����_���̐ݒ�
+ std::mt19937_64 mt, mt_cur; // , mt_ref;
+ mt.seed(p.random_seed);
+ mt_cur.seed(
+ (unsigned int)(p.random_seed +
+ (int)std::round(frame))); // �t���[�����Ƀo�������铮��
+ std::uniform_real_distribution<> random01(0.0, 1.0);
+ std::uniform_int_distribution<> random_textureId(0, p.lastFrame - 1);
+ std::uniform_real_distribution<> random_plusminus1(-1.0, 1.0);
+
+ // �^�b�`�̒��_���W��o�^���Ă���
+ double v_incr = 1.0 / p.v_density;
+ double h_incr = 1.0 / p.h_density;
+ double v_base_pos = v_incr * 0.5;
+ double segmentAmount = (double)p.reso * 2 - 1;
+
+ int id = 0; // Map�̃L�[�ɂȂ�ʂ��ԍ�
+ for (int v = 0; v < (int)p.v_density; v++, v_base_pos += v_incr) {
+ double h_base_pos = h_incr * 0.5;
+ for (int h = 0; h < (int)p.h_density; h++, h_base_pos += h_incr, id++) {
+ BrushStroke brushStroke;
+
+ // reference���J�����g��
+ FRAMETYPE frameType =
+ (referenceFrame >= 0 && random01(mt) <= reference_prevalence)
+ ? REFERENCE
+ : CURRENT;
+
+ double pos_x = h_base_pos;
+ double pos_y = v_base_pos;
+ if (frameType == CURRENT) {
+ pos_x += (random01(mt) - 0.5) * p.pos_randomness * h_incr;
+ pos_y += (random01(mt) - 0.5) * p.pos_randomness * v_incr;
+ } else { // REFERENCE case
+ pos_x += (random01(mt) - 0.5) * pivP.pos_randomness * h_incr;
+ pos_y += (random01(mt) - 0.5) * pivP.pos_randomness * v_incr;
+ }
+ if (pos_x < 0.0 || pos_x > 1.0 || pos_y < 0.0 || pos_y > 1.0) {
+ mt.discard(7); // �����A���̌�̃����_��������������
+ mt_cur.discard(2);
+ continue;
+ }
+
+ // Area�l���E�����W
+ TPointD areaSamplePos =
+ param[frameType]->brushAff * TPointD(pos_x, pos_y);
+ // �����͈͂̎Q�Ɖ摜�̋P�x���擾
+ double areaVal = getArea(areaSamplePos, frameType);
+ // �������邩���Ȃ���
+ if (random01(mt) > areaVal) {
+ mt.discard(6); // �����ɂ��A���̌�̃����_��������������
+ mt_cur.discard(2);
+ continue;
+ }
+
+ double2 wobble;
+ wobble.x = (random01(mt_cur) - 0.5) * p.pos_wobble * h_incr;
+ wobble.y = (random01(mt_cur) - 0.5) * p.pos_wobble * v_incr;
+ // ���݂̃Z�O�����g�̃����_�����O���W
+ brushStroke.originPos = TPointD(pos_x + wobble.x, pos_y + wobble.y);
+
+ brushStroke.color = getColor(areaSamplePos, frameType);
+ double alpha = lerp(param[frameType]->tipAlpha, areaVal);
+ // premultiply �Ȃ̂�RGB�l�ɂ�������
+ brushStroke.color.r *= alpha;
+ brushStroke.color.g *= alpha;
+ brushStroke.color.b *= alpha;
+ brushStroke.color.a *= alpha;
+
+ brushStroke.length =
+ lerp(param[frameType]->tipLength, areaVal) *
+ (1.0 + random_plusminus1(mt) * param[frameType]->length_random);
+ brushStroke.widthHalf =
+ lerp(param[frameType]->tipWidth, areaVal) *
+ (1.0 + random_plusminus1(mt) * param[frameType]->width_random) * 0.5;
+
+ if (p.anti_jaggy) brushStroke.widthHalf *= 3.0;
+
+ brushStroke.angle =
+ random_plusminus1(mt) * param[frameType]->angle_random;
+ TAffine flow_rot = TRotation(brushStroke.angle);
+
+ brushStroke.textureId = random_textureId(mt);
+ brushStroke.randomVal = random01(mt);
+
+ bool inv = random01(mt) < 0.5;
+ brushStroke.invert = (bidirectional) ? inv : false;
+
+ // �܂��A�c�ɂȂ镔���̍��W���v�Z����
+ QVector centerPosHalf[2];
+
+ double segLen =
+ brushStroke.length /
+ segmentAmount; // �Z�O�����g�ЂƂ��̒����B�i�u���V���W�n�j
+
+ bool tooCurve[2] = {false, false};
+ TPointD originFlow;
+ // �O�����
+ for (int dir = 0; dir < 2; dir++) {
+ // curPos�������ʒu��
+ TPointD curPos(brushStroke.originPos);
+ TPointD preFlowUV;
+ TPointD startFlow;
+ for (int s = 0; s < param[frameType]->reso; s++) {
+ // �@���݂̃Z�O�����g�̃����_�����O���W
+ TPointD samplePos = param[CURRENT]->brushAff * curPos;
+
+ // �����_�����O���W���痬��x�N�g������擾
+ double2 tmp_flow = getFlow(samplePos, CURRENT);
+
+ // �@����x�N�g������u���V���W�n�ɕϊ�
+ TPointD flow_uv =
+ param[CURRENT]->brushAff.inv() * TPointD(tmp_flow.x, tmp_flow.y) -
+ param[CURRENT]->brushAff.inv() * TPointD(0., 0.);
+ if (s == 0 && dir == 0) originFlow = flow_uv;
+
+ // �x�N�g���𐳋K��
+ flow_uv = normalize(flow_uv);
+
+ // �߂�����̂Ƃ��̓x�N�g���]
+ if (dir == 1) flow_uv = -flow_uv;
+
+ // ����̕�������]
+ flow_uv = flow_rot * flow_uv;
+
+ if (s != 0) {
+ double dot = flow_uv.x * preFlowUV.x + flow_uv.y * preFlowUV.y;
+ if (dot < 0.0) flow_uv = -flow_uv;
+
+ double dotVsStart =
+ startFlow.x * flow_uv.x + startFlow.y * flow_uv.y;
+ if (dotVsStart < 0.3) {
+ tooCurve[dir] = true;
+ break;
+ }
+ }
+ preFlowUV = flow_uv;
+
+ if (s == 0) startFlow = flow_uv;
+
+ // �x�N�g�����Z�O�����g�����̂��A���̓_�Ƃ���B�ŏ��̃Z�O�����g�͔����̒����B
+ TPointD nextPos = curPos + flow_uv * segLen * ((s == 0) ? 0.5 : 1.0);
+
+ // ���_���i�[
+ centerPosHalf[dir].push_back(nextPos);
+
+ // curPos��i�߂�
+ curPos = nextPos;
+ }
+ }
+
+ if (tooCurve[0] && tooCurve[1]) continue;
+ // �������炩�ȏꍇ
+ if (!tooCurve[0] && !tooCurve[1]) {
+ brushStroke.centerPos = centerPosHalf[1];
+ for (auto pos : centerPosHalf[0]) brushStroke.centerPos.push_front(pos);
+ }
+ // �Е������}�J�[�u�̏ꍇ�A���炩�ȕ��̃J�[�u�Α��ɔ��]���ĉ�������
+ else {
+ int OkId = (tooCurve[0]) ? 1 : 0;
+ TPointD origin(pos_x, pos_y);
+ TPointD okInitialVec = centerPosHalf[OkId].at(0) - origin;
+ double thetaDeg = std::atan2(okInitialVec.y, okInitialVec.x) / M_PI_180;
+ TAffine reflectAff = TTranslation(origin) * TRotation(thetaDeg) *
+ TScale(-1.0, 1.0) * TRotation(-thetaDeg) *
+ TTranslation(-origin);
+ brushStroke.centerPos = centerPosHalf[OkId];
+ for (auto pos : centerPosHalf[OkId]) {
+ TPointD refPos = reflectAff * pos;
+ brushStroke.centerPos.push_front(reflectAff * pos);
+ }
+ }
+
+ // �����ŁA��t���[���̗���̌����ɏ]���ăe�N�X�`���̌��������낦�Ă݂�
+ if (referenceFrame >= 0) {
+ TPointD referenceSamplePos =
+ param[REFERENCE]->brushAff * TPointD(pos_x, pos_y);
+ double2 reference_flow = getFlow(referenceSamplePos, REFERENCE);
+ // �@����x�N�g������u���V���W�n�ɕϊ�
+ TPointD reference_flow_uv =
+ param[REFERENCE]->brushAff.inv() *
+ TPointD(reference_flow.x, reference_flow.y) -
+ param[REFERENCE]->brushAff.inv() * TPointD(0., 0.);
+ // ���ς����A���]���邩���f����
+ double dot = originFlow.x * reference_flow_uv.x +
+ originFlow.y * reference_flow_uv.y;
+ if (dot < 0.0) {
+ brushStroke.invert = !brushStroke.invert;
+ }
+ }
+ // �o�^
+ brushStrokes.append(brushStroke);
+ }
+ }
+
+ // ���X�^�[���������
+ for (int f = 0; f < 2; f++) {
+ if (flow_buf[f]) flow_buf_ras[f]->unlock();
+ if (area_buf[f]) area_buf_ras[f]->unlock();
+ if (color_buf[f]) color_buf_ras[f]->unlock();
+ }
+
+ // �\�[�g
+ StackMode stackMode = (StackMode)m_sortBy->getValue();
+ if (stackMode != NoSort) {
+ // �傫�������z��őO�A���Ȃ킿��i���j�ɕ`�����悤�ɂ���
+ for (auto &stroke : brushStrokes) {
+ switch (stackMode) {
+ case Smaller:
+ stroke.stack = stroke.length * stroke.widthHalf;
+ break;
+ case Larger:
+ stroke.stack = -stroke.length * stroke.widthHalf;
+ break;
+ // Value = 0.3R 0.59G 0.11B
+ case Darker:
+ stroke.stack = 0.3 * stroke.color.r + 0.59 * stroke.color.g +
+ 0.11 * stroke.color.b;
+ break;
+ case Brighter:
+ stroke.stack = -(0.3 * stroke.color.r + 0.59 * stroke.color.g +
+ 0.11 * stroke.color.b);
+ break;
+ case Random:
+ stroke.stack = stroke.randomVal;
+ break;
+ }
+ }
+ qSort(brushStrokes.begin(), brushStrokes.end(), strokeStackGraterThan);
+ }
+
+ // �^�b�`������f�ɑ��Ă����������đ������銄��
+ double sustain_width_to_skew = m_sustain_width_to_skew->getValue(frame);
+ TPointD curve_point = m_curve_point->getValue(frame);
+ // ���_���W��o�^������
+ for (auto stroke : brushStrokes) {
+ int jointCount = stroke.centerPos.size();
+
+ // ���_�ʒu���J�[�u�����Ă䂪�߂�e�X�g
+ if (curve_point != TPointD()) {
+ for (int s = 0; s < stroke.centerPos.size(); s++) {
+ TPointD p = stroke.centerPos[s];
+ TPointD A = (1.0 - p.x) * (1.0 - p.x) * TPointD(0.0, 0.5) +
+ 2.0 * (1.0 - p.x) * p.x *
+ TPointD(0.5 + curve_point.x, 0.5 + curve_point.y) +
+ p.x * p.x * TPointD(1.0, 0.5);
+ stroke.centerPos[s] = (1.0 - p.y) * (1.0 - p.y) * TPointD(p.x, 0.0) +
+ 2.0 * (1.0 - p.y) * p.y * TPointD(A.x, A.y) +
+ p.y * p.y * TPointD(p.x, 1.0);
+ }
+ }
+
+ for (int s = 0; s < jointCount; s++) {
+ // �O��̓_
+ TPointD back = (s == 0) ? stroke.centerPos[0] : stroke.centerPos[s - 1];
+ TPointD fore = (s == jointCount - 1) ? stroke.centerPos[jointCount - 1]
+ : stroke.centerPos[s + 1];
+ TPointD vec = normalize(fore - back);
+ TPointD n(-vec.y * stroke.widthHalf, vec.x * stroke.widthHalf);
+
+ // �����ŁA�e�N�X�`�����������Ɍ����鏈���������
+ if (sustain_width_to_skew > 0.0) {
+ // ��ʍ��W�ɕϊ�
+ TPointD nr = param[CURRENT]->brushAff * n -
+ param[CURRENT]->brushAff * TPointD(0, 0);
+ TPointD vr = param[CURRENT]->brushAff * vec -
+ param[CURRENT]->brushAff * TPointD(0, 0);
+ // nr �� vr�������ɂ����Â��悤�ɁAnr����]������
+ double nr_len = norm(nr);
+
+ nr = (1.0 / nr_len) * nr;
+ vr = normalize(vr);
+
+ // ����
+ double theta = std::acos(nr * vr);
+ double new_theta = sustain_width_to_skew * M_PI * 0.5 +
+ (1.0 - sustain_width_to_skew) * theta;
+ // �O�ς̐�������Anr��vr�̂ǂ������ɂ��邩���f
+ if (cross(vr, nr) < 0) new_theta = -new_theta;
+ TPointD new_nr(vr.x * cos(new_theta) - vr.y * sin(new_theta),
+ vr.x * sin(new_theta) + vr.y * cos(new_theta));
+ new_nr = nr_len * new_nr;
+
+ // �u���V���W�ɂ��ǂ�
+ n = param[CURRENT]->brushAff.inv() *
+ (new_nr + param[CURRENT]->brushAff * TPointD(0, 0));
+ }
+
+ // �͂�����
+ if (p.anti_jaggy && s == 0) {
+ TPointD edgePos = stroke.centerPos[0] * 2.0 - stroke.originPos;
+ brushVertices.append(
+ BrushVertex(edgePos + n, 0.0, (stroke.invert) ? 1.0 : 0.0));
+ brushVertices.append(
+ BrushVertex(edgePos - n, 1.0, (stroke.invert) ? 1.0 : 0.0));
+ }
+
+ double texCoord_v = (stroke.invert) ? 1.0 - (double(s) / segmentAmount)
+ : double(s) / segmentAmount;
+
+ if (p.anti_jaggy) texCoord_v = 0.25 + 0.5 * texCoord_v;
+
+ brushVertices.append(
+ BrushVertex(stroke.centerPos[s] + n, 0.0, texCoord_v));
+ brushVertices.append(
+ BrushVertex(stroke.centerPos[s] - n, 1.0, texCoord_v));
+
+ // ���̂͂�����
+ if (p.anti_jaggy && s == jointCount - 1) {
+ TPointD edgePos =
+ stroke.centerPos[jointCount - 1] * 2.0 - stroke.originPos;
+ brushVertices.append(
+ BrushVertex(edgePos + n, 0.0, (stroke.invert) ? 0.0 : 1.0));
+ brushVertices.append(
+ BrushVertex(edgePos - n, 1.0, (stroke.invert) ? 0.0 : 1.0));
+ }
+ }
+ }
+}
+//------------------------------------------------------------
+void Iwa_FlowPaintBrushFx::doCompute(TTile &tile, double frame,
+ const TRenderSettings &ri) {
+ if (!m_brush.isConnected()) {
+ tile.getRaster()->clear();
+ return;
+ }
+
+ TDimension b_size(0, 0);
+ int lastFrame = 0;
+ std::vector brushRasters;
+ // �u���V�^�b�`�̃��X�^�[�f�[�^���擾
+ getBrushRasters(brushRasters, b_size, lastFrame, tile, ri);
+
+ if (lastFrame == 0) {
+ tile.getRaster()->clear();
+ return;
+ }
+
+ // �p�����[�^�擾
+ FlowPaintBrushFxParam p = getParam(tile, frame, ri);
+ p.lastFrame = lastFrame;
+
+ int vCount = p.reso * 4;
+ // �㉺�Ƀ}�[�W���p�̒��_��lj�
+ if (p.anti_jaggy) vCount += 4;
+
+ QVector brushVertices;
+ QList brushStrokes;
+ computeBrushVertices(brushVertices, brushStrokes, p, tile, frame, ri);
+
+ GLfloat m[16] = {(float)p.hVec.x,
+ (float)p.hVec.y,
+ 0.f,
+ 0.f,
+ (float)p.vVec.x,
+ (float)p.vVec.y,
+ 0.f,
+ 0.f,
+ 0.f,
+ 0.f,
+ 1.f,
+ 0.f,
+ (float)p.origin_pos.x,
+ (float)p.origin_pos.y,
+ 0.f,
+ 1.f};
+
+ QOpenGLContext *context = new QOpenGLContext();
+ if (QOpenGLContext::currentContext())
+ context->setShareContext(QOpenGLContext::currentContext());
+ context->setFormat(QSurfaceFormat::defaultFormat());
+ context->create();
+ context->makeCurrent(ri.m_offScreenSurface.get());
+
+ // �e�N�X�`���̊m��
+ std::vector brushTextures;
+ for (auto texRas : brushRasters) {
+ QImage texImg(texRas->getRawData(), b_size.lx, b_size.ly,
+ QImage::Format_RGBA8888);
+ // ������3�{�ɂ���i���E��x1�̃}�[�W���j
+ if (p.anti_jaggy) {
+ QImage resizedImage(texImg.width() * 3, texImg.height() * 2,
+ QImage::Format_RGBA8888);
+ QPainter painter(&resizedImage);
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.fillRect(resizedImage.rect(), Qt::transparent);
+ painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+ painter.drawImage(QPoint(texImg.width(), texImg.height() / 2), texImg);
+ painter.end();
+ texImg = resizedImage;
+ }
+
+ QOpenGLTexture *texture = new QOpenGLTexture(texImg.rgbSwapped());
+ texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
+ texture->setMagnificationFilter(QOpenGLTexture::Linear);
+ brushTextures.push_back(texture);
+ }
+
+ TDimensionI outDim = tile.getRaster()->getSize();
+
+ // �`��
+ {
+ std::unique_ptr fb(
+ new QOpenGLFramebufferObject(p.dim.lx, p.dim.ly));
+
+ fb->bind();
+
+ glViewport(0, 0, p.dim.lx, p.dim.ly);
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluOrtho2D(0, p.dim.lx, 0, p.dim.ly);
+
+ glMatrixMode(GL_MODELVIEW);
+
+ glLoadIdentity();
+ glLoadMatrixf(m);
+
+ glDisable(GL_POLYGON_SMOOTH);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_TEXTURE_2D);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ {
+ glVertexPointer(2, GL_DOUBLE, sizeof(BrushVertex), brushVertices.data());
+ glTexCoordPointer(2, GL_DOUBLE, sizeof(BrushVertex),
+ (double *)brushVertices.data() + 2);
+ }
+
+ int first = 0;
+
+ for (auto stroke : brushStrokes) {
+ brushTextures[stroke.textureId]->bind();
+
+ glColor4d(stroke.color.r, stroke.color.g, stroke.color.b, stroke.color.a);
+ glDrawArrays(GL_TRIANGLE_STRIP, first, vCount);
+ first += vCount;
+ }
+
+ glFlush();
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ QImage img =
+ fb->toImage().scaled(QSize(p.dim.lx, p.dim.ly), Qt::IgnoreAspectRatio,
+ Qt::SmoothTransformation);
+ // y���W�͏㉺���]���Ă��邱�Ƃɒ��ӁI�I
+ QRect subRect(tile.m_pos.x - (int)std::round(p.bbox.getP00().x),
+ (int)std::round(p.bbox.getP01().y) - tile.m_pos.y - outDim.ly,
+ outDim.lx, outDim.ly);
+ QImage subImg = img.copy(subRect);
+
+ TRaster32P ras32 = tile.getRaster();
+ TRaster64P ras64 = tile.getRaster();
+ TRaster32P resultRas;
+ if (ras32)
+ resultRas = ras32;
+ else if (ras64) {
+ resultRas = TRaster32P(outDim.lx, outDim.ly);
+ resultRas->lock();
+ }
+
+ for (int y = 0; y < outDim.ly; y++) {
+ QRgb *rgb_p =
+ reinterpret_cast(subImg.scanLine(outDim.ly - 1 - y));
+ TPixel32 *dst_p = resultRas->pixels(y);
+ for (int x = 0; x < outDim.lx; x++, rgb_p++, dst_p++) {
+ (*dst_p).r = (unsigned char)qRed(*rgb_p);
+ (*dst_p).g = (unsigned char)qGreen(*rgb_p);
+ (*dst_p).b = (unsigned char)qBlue(*rgb_p);
+ (*dst_p).m = (unsigned char)qAlpha(*rgb_p);
+ }
+ }
+
+ if (ras64) {
+ TRop::convert(ras64, resultRas);
+ resultRas->unlock();
+ }
+
+ fb->release();
+ }
+ context->deleteLater();
+
+ for (auto tex : brushTextures) {
+ delete tex;
+ }
+}
+
+//------------------------------------------------------------
+
+void Iwa_FlowPaintBrushFx::getParamUIs(TParamUIConcept *&concepts,
+ int &length) {
+ concepts = new TParamUIConcept[length = 4];
+
+ concepts[0].m_type = TParamUIConcept::POINT;
+ concepts[0].m_label = "Origin";
+ concepts[0].m_params.push_back(m_origin_pos);
+
+ concepts[1].m_type = TParamUIConcept::POINT;
+ concepts[1].m_label = "Horizontal Range";
+ concepts[1].m_params.push_back(m_horizontal_pos);
+
+ concepts[2].m_type = TParamUIConcept::POINT;
+ concepts[2].m_label = "Vertical Range";
+ concepts[2].m_params.push_back(m_vertical_pos);
+
+ concepts[3].m_type = TParamUIConcept::PARALLELOGRAM;
+ concepts[3].m_params.push_back(m_origin_pos);
+ concepts[3].m_params.push_back(m_horizontal_pos);
+ concepts[3].m_params.push_back(m_vertical_pos);
+ concepts[3].m_params.push_back(m_curve_point);
+}
+
+//------------------------------------------------------------
+
+std::string Iwa_FlowPaintBrushFx::getAlias(double frame,
+ const TRenderSettings &info) const {
+ double refFrame = m_reference_frame->getValue(frame);
+ double prevalence = m_reference_prevalence->getValue(frame);
+ if (refFrame < 0 || prevalence == 0.0) {
+ // check if the brush is wobbled
+ double wobble = m_pos_wobble->getValue(frame);
+ std::string alias = TStandardRasterFx::getAlias(frame, info);
+ if (areAlmostEqual(wobble, 0.0))
+ return alias;
+ else {
+ alias.insert(getFxType().length() + 1, std::to_string(frame) + ",");
+ return alias;
+ }
+ }
+
+ std::string alias = getFxType();
+ alias += "[";
+
+ // alias degli effetti connessi alle porte di input separati da virgole
+ // una porta non connessa da luogo a un alias vuoto (stringa vuota)
+ for (int i = 0; i < getInputPortCount(); ++i) {
+ TFxPort *port = getInputPort(i);
+ if (port->isConnected()) {
+ TRasterFxP ifx = port->getFx();
+ assert(ifx);
+ alias += ifx->getAlias(frame, info);
+ // add alias of flow, area and color map in the reference frame
+ if (getInputPortName(i) != "Brush") {
+ alias += ",";
+ alias += ifx->getAlias(refFrame, info);
+ }
+ }
+ alias += ",";
+ }
+
+ std::string paramalias("");
+ for (int i = 0; i < getParams()->getParamCount(); ++i) {
+ TParam *param = getParams()->getParam(i);
+ paramalias += param->getName() + "=" + param->getValueAlias(frame, 3);
+ }
+
+ return alias + std::to_string(frame) + "," + std::to_string(getIdentifier()) +
+ paramalias + "]";
+}
+
+FX_PLUGIN_IDENTIFIER(Iwa_FlowPaintBrushFx, "iwa_FlowPaintBrushFx")
diff --git a/toonz/sources/stdfx/iwa_flowpaintbrushfx.h b/toonz/sources/stdfx/iwa_flowpaintbrushfx.h
new file mode 100644
index 0000000..57e1b84
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_flowpaintbrushfx.h
@@ -0,0 +1,179 @@
+#pragma once
+
+#ifndef IWA_PAINTBRUSHFX_H
+#define IWA_PAINTBRUSHFX_H
+
+#include "stdfx.h"
+#include "tfxparam.h"
+#include "tparamset.h"
+
+#include
+#include
+
+struct double2 {
+ double x, y;
+};
+
+struct colorRGBA {
+ double r, g, b, a;
+};
+
+struct BrushStroke {
+ QVector centerPos;
+ TPointD originPos;
+ colorRGBA color;
+ double length;
+ double widthHalf;
+ double angle;
+ int textureId;
+ bool invert;
+ double randomVal;
+ double stack; // ����p�̒l
+};
+
+struct BrushVertex {
+ double pos[2];
+ double texCoord[2];
+ BrushVertex(const TPointD _pos, double u, double v) {
+ pos[0] = _pos.x;
+ pos[1] = _pos.y;
+ texCoord[0] = u;
+ texCoord[1] = v;
+ }
+ BrushVertex() : BrushVertex(TPointD(), 0, 0) {}
+};
+
+struct FlowPaintBrushFxParam {
+ TDimensionI dim;
+ TPointD origin_pos;
+ TPointD horiz_pos;
+ TPointD vert_pos;
+ TRectD bbox;
+ int fill_gap_size;
+
+ double h_density;
+ double v_density;
+ double pos_randomness;
+ double pos_wobble;
+
+ int random_seed;
+ DoublePair tipLength;
+ DoublePair tipWidth;
+ DoublePair tipAlpha;
+ double width_random;
+ double length_random;
+ double angle_random;
+
+ int reso;
+ bool anti_jaggy;
+
+ TPointD hVec;
+ TPointD vVec;
+ double2 vVec_unit;
+
+ TAffine brushAff;
+
+ int lastFrame;
+};
+
+class Iwa_FlowPaintBrushFx final : public TStandardRasterFx {
+ FX_PLUGIN_DECLARATION(Iwa_FlowPaintBrushFx)
+public:
+ enum StackMode {
+ NoSort = 0,
+ Smaller,
+ Larger,
+ Darker,
+ Brighter,
+ Random
+ //,TestModeArea
+ };
+
+private:
+ TRasterFxPort m_brush;
+ TRasterFxPort m_flow;
+ TRasterFxPort m_area;
+ TRasterFxPort m_color;
+
+ // ���x
+ TDoubleParamP m_h_density;
+ TDoubleParamP m_v_density;
+ // �ʒu�̃����_�����i0�F�i�q�� 1�F��l�Ƀ����_�� <1: ��������j
+ TDoubleParamP m_pos_randomness;
+ TDoubleParamP m_pos_wobble;
+ // �^�b�`�̃T�C�Y�iArea�̒l�ɂ���ĕω��j
+ TRangeParamP m_tip_width;
+ TRangeParamP m_tip_length;
+ // �^�b�`�̕s�����x
+ TRangeParamP m_tip_alpha;
+ TIntParamP m_tip_joints;
+ TBoolParamP m_bidirectional;
+
+ // ���
+ TDoubleParamP m_width_randomness;
+ TDoubleParamP m_length_randomness;
+ TDoubleParamP m_angle_randomness; // degree��
+
+ TDoubleParamP m_sustain_width_to_skew;
+ TBoolParamP m_anti_jaggy;
+
+ // �����͈�
+ TPointParamP m_origin_pos;
+ TPointParamP m_horizontal_pos;
+ TPointParamP m_vertical_pos;
+ TPointParamP m_curve_point;
+ TDoubleParamP m_fill_gap_size;
+
+ // ��t���[��
+ TDoubleParamP m_reference_frame;
+ // ��t���[�����g���Đ�������^�b�`�̊���
+ TDoubleParamP m_reference_prevalence;
+
+ // �����_���V�[�h
+ TIntParamP m_random_seed;
+ // ���בւ�
+ TIntEnumParamP m_sortBy;
+
+ // �u���V�^�b�`�̃��X�^�[�f�[�^���擾
+ void getBrushRasters(std::vector &brushRasters, TDimension &b_size,
+ int &lastFrame, TTile &tile, const TRenderSettings &ri);
+
+ template
+ void setFlowTileToBuffer(const RASTER flowRas, double2 *buf);
+ template
+ void setAreaTileToBuffer(const RASTER areaRas, double *buf);
+ template
+ void setColorTileToBuffer(const RASTER colorRas, colorRGBA *buf);
+
+ // ���߂���
+ template
+ void setOutRaster(const RASTER outRas, double *buf);
+
+ void fillGapByDilateAndErode(double *buf, const TDimension &dim,
+ const int fill_gap_size);
+
+ void computeBrushVertices(QVector &brushVertices,
+ QList &brushStrokes,
+ FlowPaintBrushFxParam &p, TTile &tile, double frame,
+ const TRenderSettings &ri);
+
+ double getSizePixelAmount(const double val, const TAffine affine);
+ FlowPaintBrushFxParam getParam(TTile &tile, double frame,
+ const TRenderSettings &ri);
+
+public:
+ Iwa_FlowPaintBrushFx();
+
+ bool canHandle(const TRenderSettings &info, double frame) override {
+ return true;
+ }
+ bool doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &info) override;
+ void doCompute(TTile &tile, double frame, const TRenderSettings &ri) override;
+ void getParamUIs(TParamUIConcept *&concepts, int &length) override;
+
+ std::string getAlias(double frame,
+ const TRenderSettings &info) const override;
+};
+
+#endif
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_motionflowfx.cpp b/toonz/sources/stdfx/iwa_motionflowfx.cpp
new file mode 100644
index 0000000..b08e4ca
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_motionflowfx.cpp
@@ -0,0 +1,150 @@
+#include "iwa_motionflowfx.h"
+
+#include "tfxattributes.h"
+
+#include "toonz/tstageobject.h"
+
+#include "trop.h"
+
+namespace {
+double getSizePixelAmount(const double val, const TAffine affine) {
+ /*--- Convert to vector --- */
+ TPointD vect;
+ vect.x = val;
+ vect.y = 0.0;
+ /*--- Apply geometrical transformation ---*/
+ // For the following lines I referred to lines 586-592 of
+ // sources/stdfx/motionblurfx.cpp
+ TAffine aff(affine);
+ aff.a13 = aff.a23 = 0; /* ignore translation */
+ vect = aff * vect;
+ /*--- return the length of the vector ---*/
+ return std::sqrt(vect.x * vect.x + vect.y * vect.y);
+}
+
+} // namespace
+
+//------------------------------------------------------------
+
+Iwa_MotionFlowFx::Iwa_MotionFlowFx()
+ : m_normalizeType(new TIntEnumParam(NORMALIZE_AUTO, "Auto"))
+ , m_normalizeRange(1.0) {
+ bindParam(this, "shutterLength", m_shutterLength);
+ bindParam(this, "motionObjectType", m_motionObjectType);
+ bindParam(this, "motionObjectIndex", m_motionObjectIndex);
+ bindParam(this, "normalizeType", m_normalizeType);
+ bindParam(this, "normalizeRange", m_normalizeRange);
+
+ m_normalizeType->addItem(NORMALIZE_MANUAL, "Manual");
+ m_normalizeRange->setMeasureName("fxLength");
+ m_normalizeRange->setValueRange(0.01, 1000.0);
+
+ getAttributes()->setIsSpeedAware(true);
+}
+
+//------------------------------------------------------------
+
+void Iwa_MotionFlowFx::doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) {
+ /* Get parameters */
+ TAffine aff_Before, aff_After;
+ getAttributes()->getMotionAffines(aff_Before, aff_After);
+
+ TDimensionI dimOut(tile.getRaster()->getLx(), tile.getRaster()->getLy());
+ /* output */
+ TRasterGR8P out_ras(sizeof(double3) * dimOut.lx * dimOut.ly, 1);
+ out_ras->lock();
+ double3* out_ras_p = (double3*)out_ras->getRawData();
+
+ double3* out_p = out_ras_p;
+
+ double norm_range = 0.0;
+
+ for (int y = 0; y < dimOut.ly; y++) {
+ for (int x = 0; x < dimOut.lx; x++, out_p++) {
+ TPointD pos = TPointD((double)x, (double)y) + tile.m_pos;
+ TPointD vec = pos - (aff_Before * aff_After.inv() * pos);
+ out_p->z = std::sqrt(vec.x * vec.x + vec.y * vec.y);
+ if (out_p->z > 0.0) {
+ out_p->x = vec.x / out_p->z;
+ out_p->y = vec.y / out_p->z;
+ norm_range = std::max(norm_range, out_p->z);
+ } else {
+ out_p->x = 0.0;
+ out_p->y = 0.0;
+ }
+ }
+ }
+
+ if (m_normalizeType->getValue() == NORMALIZE_MANUAL)
+ norm_range = getSizePixelAmount(m_normalizeRange->getValue(frame),
+ settings.m_affine);
+
+ tile.getRaster()->clear();
+ TRaster32P outRas32 = (TRaster32P)tile.getRaster();
+ TRaster64P outRas64 = (TRaster64P)tile.getRaster();
+ if (outRas32)
+ setOutRas(outRas32, out_ras_p, norm_range);
+ else if (outRas64)
+ setOutRas(outRas64, out_ras_p, norm_range);
+
+ out_ras->unlock();
+}
+
+//------------------------------------------------------------
+
+template
+void Iwa_MotionFlowFx::setOutRas(RASTER ras, double3* outBuf,
+ double norm_range) {
+ double3* buf_p = outBuf;
+ for (int j = 0; j < ras->getLy(); j++) {
+ PIXEL* pix = ras->pixels(j);
+ for (int i = 0; i < ras->getLx(); i++, pix++, buf_p++) {
+ double val = 0.5 + buf_p->x * 0.5;
+ pix->r = (typename PIXEL::Channel)(val * double(PIXEL::maxChannelValue));
+ val = 0.5 + buf_p->y * 0.5;
+ pix->g = (typename PIXEL::Channel)(val * double(PIXEL::maxChannelValue));
+ val = std::min(1.0, buf_p->z / norm_range);
+ pix->b = (typename PIXEL::Channel)(val * double(PIXEL::maxChannelValue));
+ pix->m = PIXEL::maxChannelValue;
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+bool Iwa_MotionFlowFx::doGetBBox(double frame, TRectD& bBox,
+ const TRenderSettings& info) {
+ bBox = TConsts::infiniteRectD;
+ return true;
+}
+
+//------------------------------------------------------------
+
+bool Iwa_MotionFlowFx::canHandle(const TRenderSettings& info, double frame) {
+ return true;
+}
+
+/*------------------------------------------------------------
+ Since there is a possibility that the reference object is moving,
+ Change the alias every frame
+------------------------------------------------------------*/
+
+std::string Iwa_MotionFlowFx::getAlias(double frame,
+ const TRenderSettings& info) const {
+ std::string alias = getFxType();
+ alias += "[";
+
+ // alias of the effects related to the input ports separated by commas
+ // a port that is not connected to an alias blank (empty string)
+
+ std::string paramalias("");
+ for (int i = 0; i < getParams()->getParamCount(); i++) {
+ TParam* param = getParams()->getParam(i);
+ paramalias += param->getName() + "=" + param->getValueAlias(frame, 3);
+ }
+ unsigned long id = getIdentifier();
+ return alias + std::to_string(frame) + "," + std::to_string(id) + paramalias +
+ "]";
+}
+FX_PLUGIN_IDENTIFIER(Iwa_MotionFlowFx, "iwa_MotionFlowFx")
diff --git a/toonz/sources/stdfx/iwa_motionflowfx.h b/toonz/sources/stdfx/iwa_motionflowfx.h
new file mode 100644
index 0000000..651d44e
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_motionflowfx.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#ifndef IWA_MOTION_FLOW_FX_H
+#define IWA_MOTION_FLOW_FX_H
+
+#include "tfxparam.h"
+#include "stdfx.h"
+
+#include "motionawarebasefx.h"
+
+struct double3 {
+ double x, y, z;
+};
+
+class Iwa_MotionFlowFx final : public MotionAwareAffineFx {
+ FX_PLUGIN_DECLARATION(Iwa_MotionFlowFx)
+
+ TIntEnumParamP m_normalizeType;
+ TDoubleParamP m_normalizeRange;
+
+ enum NormalizeType { NORMALIZE_AUTO = 0, NORMALIZE_MANUAL };
+
+public:
+ Iwa_MotionFlowFx();
+
+ void doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) override;
+
+ template
+ void setOutRas(RASTER ras, double3* outBuf, double norm_range);
+
+ bool doGetBBox(double frame, TRectD& bBox,
+ const TRenderSettings& info) override;
+ bool canHandle(const TRenderSettings& info, double frame) override;
+ // Since there is a possibility that the reference object is moving,
+ // Change the alias every frame
+ std::string getAlias(double frame,
+ const TRenderSettings& info) const override;
+};
+#endif
diff --git a/toonz/sources/stdfx/iwa_tangentflowfx.cpp b/toonz/sources/stdfx/iwa_tangentflowfx.cpp
new file mode 100644
index 0000000..ceb4fef
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_tangentflowfx.cpp
@@ -0,0 +1,485 @@
+#include "iwa_tangentflowfx.h"
+
+#include
+
+namespace {
+inline double dotProduct(const double2 v1, const double2 v2) {
+ return v1.x * v2.x + v1.y * v2.y;
+}
+inline double clamp01(double val) {
+ return (val > 1.0) ? 1.0 : (val < 0.0) ? 0.0 : val;
+}
+
+const int MAX_OFFSET = 10000;
+}; // namespace
+//------------------------------------------------------------
+// obtain source tile brightness, normalizing 0 to 1
+template
+void Iwa_TangentFlowFx::setSourceTileToBuffer(const RASTER srcRas,
+ double* buf) {
+ double* buf_p = buf;
+ for (int j = 0; j < srcRas->getLy(); j++) {
+ PIXEL* pix = srcRas->pixels(j);
+ for (int i = 0; i < srcRas->getLx(); i++, pix++, buf_p++) {
+ // Value = 0.3R 0.59G 0.11B
+ (*buf_p) = (double(pix->r) * 0.3 + double(pix->g) * 0.59 +
+ double(pix->b) * 0.11) /
+ double(PIXEL::maxChannelValue);
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+void Iwa_TangentFlowFx::alignFlowDirection(double2* flow_buf,
+ const TDimension dim,
+ const double2& pivotVec) {
+ double2* fbuf_p = flow_buf;
+ for (int i = 0; i < dim.lx * dim.ly; i++, fbuf_p++) {
+ // 基準ベクトルに揃える
+ if ((*fbuf_p).x * pivotVec.x + (*fbuf_p).y * pivotVec.y < 0.0) {
+ (*fbuf_p).x = -(*fbuf_p).x;
+ (*fbuf_p).y = -(*fbuf_p).y;
+ }
+ }
+}
+
+//------------------------------------------------------------
+// render vector field in red & green channels
+template
+void Iwa_TangentFlowFx::setOutputRaster(double2* flow_buf, double* grad_buf,
+ const RASTER dstRas) {
+ double2* fbuf_p = flow_buf;
+ double* gbuf_p = grad_buf;
+ for (int j = 0; j < dstRas->getLy(); j++) {
+ PIXEL* pix = dstRas->pixels(j);
+ for (int i = 0; i < dstRas->getLx(); i++, pix++, fbuf_p++, gbuf_p++) {
+ double val;
+ val = clamp01((*fbuf_p).x * 0.5 + 0.5) * (double)PIXEL::maxChannelValue;
+ pix->r = (typename PIXEL::Channel)(val);
+ val = clamp01((*fbuf_p).y * 0.5 + 0.5) * (double)PIXEL::maxChannelValue;
+ pix->g = (typename PIXEL::Channel)(val);
+ val = clamp01(*gbuf_p) * (double)PIXEL::maxChannelValue;
+ pix->b = (typename PIXEL::Channel)(val);
+ pix->m = (typename PIXEL::Channel)PIXEL::maxChannelValue;
+ }
+ }
+}
+
+//------------------------------------------------------------
+// compute the initial vector field.
+// apply sobel filter, rotate by 90 degrees and normalize.
+// also obtaining gradient magnitude (length of the vector length)
+// empty regions will be filled by using Fast Sweeping Method
+
+void SobelFilterWorker::run() {
+ // the 5x5 Sobel filter is already rotated by 90 degrees.
+ // ( i.e. converted as X = -y, Y = x )
+ const double kernel_x[5][5] = {{0.25, 0.4, 0.5, 0.4, 0.25},
+ {0.2, 0.5, 1., 0.5, 0.2},
+ {0., 0., 0., 0., 0.},
+ {-0.2, -0.5, -1., -0.5, -0.2},
+ {-0.25, -0.4, -0.5, -0.4, -0.25}};
+ const double kernel_y[5][5] = {{-0.25, -0.2, 0., 0.2, 0.25},
+ {-0.4, -0.5, 0., 0.5, 0.4},
+ {-0.5, -1., 0., 1., 0.5},
+ {-0.4, -0.5, 0., 0.5, 0.4},
+ {-0.25, -0.2, 0., 0.2, 0.25}};
+ /*
+ const double kernel_x[5][5] = {
+ { 0.0297619, 0.047619, 0.0595238, 0.047619, 0.0297619 },
+ { 0.0238, 0.0595238, 0.119, 0.0595238, 0.0238 },
+ { 0., 0., 0., 0., 0. },
+ { -0.0238, -0.0595238, -0.119, -0.0595238, -0.0238 },
+ { -0.0297619, -0.047619, -0.0595238, -0.047619, -0.0297619 }
+ };
+ const double kernel_y[5][5] = {
+ { -0.0297619, -0.0238, 0., 0.0238, 0.0297619 },
+ { -0.047619, -0.0595238, 0., 0.0595238, 0.047619 },
+ { -0.0595238, -0.119, 0., 0.119, 0.0595238 },
+ { -0.047619, -0.0595238, 0., 0.0595238, 0.047619 },
+ { -0.0297619, -0.0238, 0., 0.0238, 0.0297619 }
+ };
+ */
+
+ auto source = [&](int xPos, int yPos) {
+ return m_source_buf[yPos * m_dim.lx + xPos];
+ };
+
+ double2* flow_p = &m_flow_buf[m_yFrom * m_dim.lx];
+ double* grad_mag_p = &m_grad_mag_buf[m_yFrom * m_dim.lx];
+ int2* offset_p = &m_offset_buf[m_yFrom * m_dim.lx];
+
+ for (int y = m_yFrom; y < m_yTo; y++) {
+ for (int x = 0; x < m_dim.lx; x++, flow_p++, grad_mag_p++, offset_p++) {
+ double x_accum = 0.;
+ double y_accum = 0.;
+ for (int ky = 0; ky < 5; ky++) {
+ int yPos = y + ky - 2; // y coordinate of sample pos
+ if (yPos < 0) continue;
+ if (yPos >= m_dim.ly) break;
+ for (int kx = 0; kx < 5; kx++) {
+ int xPos = x + kx - 2; // x coordinate of sample pos
+ if (xPos < 0) continue;
+ if (xPos >= m_dim.lx) break;
+ if (kx == 2 && ky == 2) continue;
+
+ double sourceVal = source(xPos, yPos);
+ x_accum += kernel_x[ky][kx] * sourceVal;
+ y_accum += kernel_y[ky][kx] * sourceVal;
+ }
+ }
+ // storing Gradient Magnitude
+ *grad_mag_p = std::sqrt(x_accum * x_accum + y_accum * y_accum);
+ (*flow_p).x = (*grad_mag_p == 0.) ? 0.0 : (x_accum / (*grad_mag_p));
+ (*flow_p).y = (*grad_mag_p == 0.) ? 0.0 : (y_accum / (*grad_mag_p));
+
+ // offset will be used later in Fast Sweeping Method
+ if (*grad_mag_p < m_mag_threshold) {
+ (*offset_p).x = MAX_OFFSET;
+ (*offset_p).y = MAX_OFFSET;
+ m_hasEmptyVector = true;
+ } else {
+ (*offset_p).x = 0;
+ (*offset_p).y = 0;
+ }
+ }
+ }
+}
+
+void Iwa_TangentFlowFx::computeInitialFlow(double* source_buf,
+ double2* flow_buf,
+ double* grad_mag_buf,
+ const TDimension dim,
+ double mag_threshold) {
+ // empty regions will be filled by using Fast Sweeping Method
+ // allocate offset buffer
+ int2* offset_buf;
+ TRasterGR8P offset_buf_ras(dim.lx * dim.ly * sizeof(int2), 1);
+ offset_buf_ras->lock();
+ offset_buf = (int2*)offset_buf_ras->getRawData();
+
+ int activeThreadCount = QThreadPool::globalInstance()->activeThreadCount();
+ // use half of the available threads
+ int threadAmount = std::max(1, activeThreadCount / 2);
+ QList threadList;
+
+ int tmpStart = 0;
+ for (int t = 0; t < threadAmount; t++) {
+ int tmpEnd =
+ (int)std::round((float)(dim.ly * (t + 1)) / (float)threadAmount);
+
+ SobelFilterWorker* worker =
+ new SobelFilterWorker(source_buf, flow_buf, grad_mag_buf, offset_buf,
+ mag_threshold, dim, tmpStart, tmpEnd);
+ worker->start();
+ threadList.append(worker);
+ tmpStart = tmpEnd;
+ }
+
+ bool hasEmptyVector = false;
+ for (auto worker : threadList) {
+ worker->wait();
+ hasEmptyVector = hasEmptyVector | worker->hasEmptyVector();
+ delete worker;
+ }
+
+ // return if there is no empty region
+ if (!hasEmptyVector) {
+ offset_buf_ras->unlock();
+ return;
+ }
+
+ auto getFlow = [&](int2 xy) { return &flow_buf[xy.y * dim.lx + xy.x]; };
+ auto getOffset = [&](int2 xy) { return &offset_buf[xy.y * dim.lx + xy.x]; };
+ auto getGradMag = [&](int2 xy) {
+ return &grad_mag_buf[xy.y * dim.lx + xy.x];
+ };
+
+ // update vector field by using Fast Sweeping Method
+ double2* neighbor_flow[2];
+ int2* neighbor_offset[2];
+ double* neighbor_gradMag[2];
+ double2* flow_p;
+ int2* offset_p;
+ double* gradMag_p;
+ int2 offset_incr[2];
+ // positive/negative in y axis
+ for (int ny = -1; ny <= 1; ny += 2) {
+ offset_incr[0] = {0, ny};
+ int y_start = (ny == 1) ? 1 : dim.ly - 2;
+ int y_end = (ny == 1) ? dim.ly : 0;
+
+ // positive/negative in x axis
+ for (int nx = -1; nx <= 1; nx += 2) {
+ offset_incr[1] = {nx, 0};
+ int x_start = (nx == 1) ? 1 : dim.lx - 2;
+ int x_end = (nx == 1) ? dim.lx : 0;
+
+ for (int y = y_start; y != y_end; y += ny) {
+ int2 startPos(x_start, y);
+ for (int n = 0; n < 2; n++) {
+ neighbor_flow[n] = getFlow(startPos - offset_incr[n]);
+ neighbor_offset[n] = getOffset(startPos - offset_incr[n]);
+ neighbor_gradMag[n] = getGradMag(startPos - offset_incr[n]);
+ }
+ flow_p = getFlow(startPos);
+ offset_p = getOffset(startPos);
+ gradMag_p = getGradMag(startPos);
+
+ for (int x = x_start; x != x_end; x += nx, neighbor_flow[0] += nx,
+ neighbor_flow[1] += nx, flow_p += nx, neighbor_offset[0] += nx,
+ neighbor_offset[1] += nx, offset_p += nx,
+ neighbor_gradMag[0] += nx, neighbor_gradMag[1] += nx,
+ gradMag_p += nx) {
+ // continue if this pixel is already filled
+ if ((*offset_p).x == 0 && (*offset_p).y == 0) continue;
+ // for each neighbor pixel
+ for (int n = 0; n < 2; n++) {
+ // continue if the neighbor is still empty
+ if ((*neighbor_offset[n]).x == MAX_OFFSET ||
+ (*neighbor_offset[n]).y == MAX_OFFSET)
+ continue;
+ int2 tmpOffset = (*neighbor_offset[n]) + offset_incr[n];
+ if (tmpOffset.len2() < (*offset_p).len2()) {
+ (*offset_p) = tmpOffset;
+ (*flow_p) = (*neighbor_flow[n]);
+ (*gradMag_p) = (*neighbor_gradMag[n]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ offset_buf_ras->unlock();
+}
+
+//------------------------------------------------------------
+
+Iwa_TangentFlowFx::Iwa_TangentFlowFx()
+ : m_iteration(4)
+ , m_kernelRadius(2.5)
+ , m_threshold(0.15)
+ , m_alignDirection(false)
+ , m_pivotAngle(45.0) {
+ addInputPort("Source", m_source);
+
+ bindParam(this, "iteration", m_iteration);
+ bindParam(this, "kernelRadius", m_kernelRadius);
+ bindParam(this, "threshold", m_threshold);
+ bindParam(this, "alignDirection", m_alignDirection);
+ bindParam(this, "pivotAngle", m_pivotAngle);
+
+ m_iteration->setValueRange(0, 10);
+ m_kernelRadius->setMeasureName("fxLength");
+ m_kernelRadius->setValueRange(0.5, 10);
+ m_threshold->setValueRange(0.0, 1.0);
+
+ m_pivotAngle->setValueRange(-180.0, 180.0);
+}
+
+//------------------------------------------------------------
+
+void TangentFlowWorker::run() {
+ // flow to be computed
+ double2* flow_new_p = &m_flow_new_buf[m_yFrom * m_dim.lx];
+ // current flow
+ double2* flow_cur_p = &m_flow_cur_buf[m_yFrom * m_dim.lx];
+ // Gradient Magnitude of the current pos
+ double* grad_mag_p = &m_grad_mag_buf[m_yFrom * m_dim.lx];
+ int kr2 = m_kernelRadius * m_kernelRadius;
+
+ // loop for Y
+ for (int y = m_yFrom; y < m_yTo; y++) {
+ // loop for X
+ for (int x = 0; x < m_dim.lx;
+ x++, flow_new_p++, grad_mag_p++, flow_cur_p++) {
+ // accumulation vector
+ double2 flow_new_accum;
+
+ // loop for kernel y
+ for (int ky = -m_kernelRadius; ky <= m_kernelRadius; ky++) {
+ int yPos = y + ky;
+ // boundary condition
+ if (yPos < 0) continue;
+ if (yPos >= m_dim.ly) break;
+
+ // loop for kernel x
+ for (int kx = -m_kernelRadius; kx <= m_kernelRadius; kx++) {
+ int xPos = x + kx;
+ // boundary condition
+ if (xPos < 0) continue;
+ if (xPos >= m_dim.lx) break;
+
+ // Eq.(2) continue if the sample pos is outside of the kernel
+ if (kx * kx + ky * ky > kr2) continue;
+ double2 flow_k = m_flow_cur_buf[yPos * m_dim.lx + xPos];
+ if (flow_k.x == 0.0 && flow_k.y == 0.0) continue;
+
+ // Eq.(3) calculate the magnitude weight function (wm)
+ double grad_mag_k = m_grad_mag_buf[yPos * m_dim.lx + xPos];
+ double w_m = (1.0 + std::tanh(grad_mag_k - (*grad_mag_p))) / 2.0;
+ if (w_m == 0.0) continue;
+
+ // Eq.(4) calculate the direction weight function (wd)
+ double flowDot = dotProduct((*flow_cur_p), flow_k);
+ double w_d = std::abs(flowDot);
+
+ // Eq.(5) calculate phi
+ double phi = (flowDot > 0.) ? 1.0 : -1.0;
+
+ // accumulate vector
+ flow_new_accum.x += phi * w_m * w_d * flow_k.x;
+ flow_new_accum.y += phi * w_m * w_d * flow_k.y;
+ }
+ }
+
+ // normalize vector
+ double len = std::sqrt(flow_new_accum.x * flow_new_accum.x +
+ flow_new_accum.y * flow_new_accum.y);
+ if (len != 0.0) {
+ flow_new_accum.x /= len;
+ flow_new_accum.y /= len;
+ }
+
+ // update vector
+ (*flow_new_p).x = flow_new_accum.x;
+ (*flow_new_p).y = flow_new_accum.y;
+ }
+ }
+}
+
+void Iwa_TangentFlowFx::doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) {
+ if (!m_source.isConnected()) {
+ tile.getRaster()->clear();
+ return;
+ }
+
+ TDimension dim = tile.getRaster()->getSize();
+
+ // std::cout << "Iwa_TangentFlowFx dim = (" << dim.lx << ", " << dim.ly <<
+ // ")";
+
+ double fac = sqrt(fabs(settings.m_affine.det()));
+ int kernelRadius = std::round(fac * m_kernelRadius->getValue(frame));
+ if (kernelRadius == 0) kernelRadius = 1;
+ int iterationCount = m_iteration->getValue();
+
+ double mag_threshold = m_threshold->getValue(frame) / fac;
+
+ TTile sourceTile;
+ m_source->allocateAndCompute(sourceTile, tile.m_pos, dim, tile.getRaster(),
+ frame, settings);
+
+ // allocate source image buffer
+ double* source_buf;
+ TRasterGR8P source_buf_ras(dim.lx * dim.ly * sizeof(double), 1);
+ source_buf_ras->lock();
+ source_buf = (double*)source_buf_ras->getRawData();
+
+ // obtain source tile brightness, normalizing 0 to 1
+ TRaster32P ras32 = tile.getRaster();
+ TRaster64P ras64 = tile.getRaster();
+ if (ras32)
+ setSourceTileToBuffer(sourceTile.getRaster(),
+ source_buf);
+ else if (ras64)
+ setSourceTileToBuffer(sourceTile.getRaster(),
+ source_buf);
+
+ // allocate flow buffers (for both current and new)
+ double2 *flow_cur_buf, *flow_new_buf;
+ TRasterGR8P flow_cur_buf_ras(dim.lx * dim.ly * sizeof(double2), 1);
+ TRasterGR8P flow_new_buf_ras(dim.lx * dim.ly * sizeof(double2), 1);
+ flow_cur_buf_ras->lock();
+ flow_new_buf_ras->lock();
+ flow_cur_buf = (double2*)flow_cur_buf_ras->getRawData();
+ flow_new_buf = (double2*)flow_new_buf_ras->getRawData();
+
+ // allocate Gradient Magnitude image buffer
+ double* grad_mag_buf;
+ TRasterGR8P grad_mag_buf_ras(dim.lx * dim.ly * sizeof(double), 1);
+ grad_mag_buf_ras->lock();
+ grad_mag_buf = (double*)grad_mag_buf_ras->getRawData();
+
+ // compute the initial vector field.
+ // apply sobel filter, rotate by 90 degrees and normalize.
+ // also obtaining gradient magnitude (length of the vector length)
+ // empty regions will be filled by using Fast Sweeping Method
+ computeInitialFlow(source_buf, flow_cur_buf, grad_mag_buf, dim,
+ mag_threshold);
+
+ source_buf_ras->unlock();
+
+ int activeThreadCount = QThreadPool::globalInstance()->activeThreadCount();
+ // use half of the available threads
+ int threadAmount = std::max(1, activeThreadCount / 2);
+
+ // start iteration
+ for (int i = 0; i < iterationCount; i++) {
+ QList threadList;
+ int tmpStart = 0;
+ for (int t = 0; t < threadAmount; t++) {
+ int tmpEnd =
+ (int)std::round((float)(dim.ly * (t + 1)) / (float)threadAmount);
+
+ TangentFlowWorker* worker =
+ new TangentFlowWorker(flow_cur_buf, flow_new_buf, grad_mag_buf, dim,
+ kernelRadius, tmpStart, tmpEnd);
+ worker->start();
+ threadList.append(worker);
+ tmpStart = tmpEnd;
+ }
+
+ for (auto worker : threadList) {
+ worker->wait();
+ delete worker;
+ }
+
+ // swap buffer pointers
+ double2* tmp = flow_cur_buf;
+ flow_cur_buf = flow_new_buf;
+ flow_new_buf = tmp;
+ }
+
+ flow_new_buf_ras->unlock();
+
+ // 基準の角度に向きを合わせる
+ if (m_alignDirection->getValue()) {
+ double pivotAngle =
+ m_pivotAngle->getValue(frame) * M_PI_180; // convert to radian
+ double2 pivotVec = {std::cos(pivotAngle), std::sin(pivotAngle)};
+ alignFlowDirection(flow_cur_buf, dim, pivotVec);
+ }
+
+ // render vector field in red & green channels
+ if (ras32)
+ setOutputRaster(flow_cur_buf, grad_mag_buf, ras32);
+ else if (ras64)
+ setOutputRaster(flow_cur_buf, grad_mag_buf, ras64);
+
+ flow_cur_buf_ras->unlock();
+ grad_mag_buf_ras->unlock();
+}
+
+//------------------------------------------------------------
+
+bool Iwa_TangentFlowFx::doGetBBox(double frame, TRectD& bBox,
+ const TRenderSettings& info) {
+ if (m_source.isConnected()) {
+ bBox = TConsts::infiniteRectD;
+ return true;
+ // return m_source->doGetBBox(frame, bBox, info);
+ }
+ return false;
+}
+
+//------------------------------------------------------------
+
+bool Iwa_TangentFlowFx::canHandle(const TRenderSettings& info, double frame) {
+ return false;
+}
+
+FX_PLUGIN_IDENTIFIER(Iwa_TangentFlowFx, "iwa_TangentFlowFx")
diff --git a/toonz/sources/stdfx/iwa_tangentflowfx.h b/toonz/sources/stdfx/iwa_tangentflowfx.h
new file mode 100644
index 0000000..e191f4d
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_tangentflowfx.h
@@ -0,0 +1,137 @@
+#pragma once
+
+/*--------------------------------
+Iwa_TangentFlowFx
+Computing a smooth, feature-preserving local edge flow (edge tangent flow, ETF)
+Implementation of "Coherent Line Drawing" by H.Kang et al, Proc. NPAR 2007.
+----------------------------------*/
+
+#ifndef IWA_TANGENT_FLOW_FX_H
+#define IWA_TANGENT_FLOW_FX_H
+
+#include "stdfx.h"
+#include "tfxparam.h"
+
+#include
+
+struct double2 {
+ double x, y;
+ double2(double _x = 0., double _y = 0.) {
+ x = _x;
+ y = _y;
+ }
+};
+struct int2 {
+ int x, y;
+
+ int2 operator+(const int2& other) const {
+ return int2{x + other.x, y + other.y};
+ }
+ int2 operator-(const int2& other) const {
+ return int2{x - other.x, y - other.y};
+ }
+ int len2() const { return x * x + y * y; }
+ int2(int _x = 0, int _y = 0) {
+ x = _x;
+ y = _y;
+ }
+};
+
+class SobelFilterWorker : public QThread {
+ double* m_source_buf;
+ double2* m_flow_buf;
+ double* m_grad_mag_buf;
+ int2* m_offset_buf;
+ double m_mag_threshold;
+ TDimension m_dim;
+ int m_yFrom, m_yTo;
+
+ bool m_hasEmptyVector = false;
+
+public:
+ SobelFilterWorker(double* source_buf, double2* flow_buf, double* grad_mag_buf,
+ int2* offset_buf, double mag_threshold, TDimension dim,
+ int yFrom, int yTo)
+ : m_source_buf(source_buf)
+ , m_flow_buf(flow_buf)
+ , m_grad_mag_buf(grad_mag_buf)
+ , m_offset_buf(offset_buf)
+ , m_mag_threshold(mag_threshold)
+ , m_dim(dim)
+ , m_yFrom(yFrom)
+ , m_yTo(yTo) {}
+
+ void run();
+
+ bool hasEmptyVector() { return m_hasEmptyVector; }
+};
+
+class TangentFlowWorker : public QThread {
+ double2* m_flow_cur_buf;
+ double2* m_flow_new_buf;
+ double* m_grad_mag_buf;
+ TDimension m_dim;
+ int m_kernelRadius;
+ int m_yFrom, m_yTo;
+
+public:
+ TangentFlowWorker(double2* flow_cur_buf, double2* flow_new_buf,
+ double* grad_mag_buf, TDimension dim, int kernelRadius,
+ int yFrom, int yTo)
+ : m_flow_cur_buf(flow_cur_buf)
+ , m_flow_new_buf(flow_new_buf)
+ , m_grad_mag_buf(grad_mag_buf)
+ , m_dim(dim)
+ , m_kernelRadius(kernelRadius)
+ , m_yFrom(yFrom)
+ , m_yTo(yTo) {}
+
+ void run();
+};
+
+class Iwa_TangentFlowFx final : public TStandardRasterFx {
+ FX_PLUGIN_DECLARATION(Iwa_TangentFlowFx)
+
+protected:
+ TRasterFxPort m_source;
+
+ TIntParamP m_iteration;
+ TDoubleParamP m_kernelRadius;
+
+ TDoubleParamP m_threshold;
+
+ TBoolParamP m_alignDirection;
+ TDoubleParamP m_pivotAngle;
+
+ // obtain source tile brightness, normalizing 0 to 1
+ template
+ void setSourceTileToBuffer(const RASTER srcRas, double* buf);
+
+ // render vector field in red & green channels
+ template
+ void setOutputRaster(double2* buf, double* grad_buf, const RASTER dstRas);
+
+ // compute the initial vector field.
+ // apply sobel filter, rotate by 90 degrees and normalize.
+ // also obtaining gradient magnitude (length of the vector length)
+ // empty regions will be filled by using Fast Sweeping Method
+ void computeInitialFlow(double* source_buf, double2* flow_buf,
+ double* grad_mag_buf, const TDimension dim,
+ double mag_threshold);
+ // 基準の角度に向きを合わせる
+ void alignFlowDirection(double2* flow_buf, const TDimension dim,
+ const double2& pivotVec);
+
+public:
+ Iwa_TangentFlowFx();
+
+ void doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) override;
+
+ bool doGetBBox(double frame, TRectD& bBox,
+ const TRenderSettings& info) override;
+
+ bool canHandle(const TRenderSettings& info, double frame) override;
+};
+
+#endif
diff --git a/toonz/sources/stdfx/motionawarebasefx.h b/toonz/sources/stdfx/motionawarebasefx.h
index 9c2d994..ed24b16 100644
--- a/toonz/sources/stdfx/motionawarebasefx.h
+++ b/toonz/sources/stdfx/motionawarebasefx.h
@@ -54,4 +54,35 @@ public:
TIntParamP getMotionObjectIndex() { return m_motionObjectIndex; }
};
+// flow motion blurで使う
+
+class MotionAwareAffineFx : public TStandardZeraryFx {
+protected:
+ TDoubleParamP m_shutterLength; // 前後のシャッター解放時間
+
+ TIntEnumParamP m_motionObjectType;
+ TIntParamP m_motionObjectIndex;
+
+public:
+ MotionAwareAffineFx()
+ : m_shutterLength(0.1)
+ , m_motionObjectType(new TIntEnumParam(OBJTYPE_OWN, "Own Motion"))
+ , m_motionObjectIndex(1) {
+ m_shutterLength->setValueRange(0.01, 1.0);
+ m_motionObjectType->addItem(OBJTYPE_COLUMN, "Column");
+ m_motionObjectType->addItem(OBJTYPE_PEGBAR, "Pegbar");
+ m_motionObjectType->addItem(OBJTYPE_TABLE, "Table");
+ m_motionObjectType->addItem(OBJTYPE_CAMERA, "Camera");
+
+ getAttributes()->setIsSpeedAware(true);
+ }
+
+ /*-- 軌跡情報を得るのに必要なパラメータを取得させる --*/
+ TDoubleParamP getShutterLength() { return m_shutterLength; }
+ MotionObjectType getMotionObjectType() {
+ return (MotionObjectType)m_motionObjectType->getValue();
+ }
+ TIntParamP getMotionObjectIndex() { return m_motionObjectIndex; }
+};
+
#endif
diff --git a/toonz/sources/tnztools/edittoolgadgets.cpp b/toonz/sources/tnztools/edittoolgadgets.cpp
index 5f0252d..867a1ad 100644
--- a/toonz/sources/tnztools/edittoolgadgets.cpp
+++ b/toonz/sources/tnztools/edittoolgadgets.cpp
@@ -2438,6 +2438,185 @@ void VerticalPosFxGadget::leftButtonDrag(const TPointD &pos,
if (m_yParam) setValue(m_yParam, pos.y);
}
+//=============================================================================
+
+class ParallelogramFxGadget final : public FxGadget {
+ TPointParamP m_pcenter, m_phoriz, m_pvert;
+ VectorFxGadget *m_hVecGadget, *m_vVecGadget;
+ TPointD m_clickedPos;
+ TPointParamP m_pcurve;
+
+ enum HANDLE { Body = 0, CurveAnchor, Rotation, None } m_handle = None;
+
+public:
+ ParallelogramFxGadget(FxGadgetController *controller, const TPointParamP &pc,
+ const TPointParamP &ph, const TPointParamP &pv)
+ : FxGadget(controller)
+ , m_pcenter(pc)
+ , m_phoriz(ph)
+ , m_pvert(pv)
+ , m_hVecGadget(new VectorFxGadget(controller, pc, ph))
+ , m_vVecGadget(new VectorFxGadget(controller, pc, pv)) {
+ addParam(pc->getX());
+ addParam(pc->getY());
+ addParam(ph->getX());
+ addParam(ph->getY());
+ addParam(pv->getX());
+ addParam(pv->getY());
+ }
+
+ ParallelogramFxGadget(FxGadgetController *controller, const TPointParamP &pc,
+ const TPointParamP &ph, const TPointParamP &pv,
+ const TPointParamP &pcurve)
+ : FxGadget(controller, 3)
+ , m_pcenter(pc)
+ , m_phoriz(ph)
+ , m_pvert(pv)
+ , m_pcurve(pcurve)
+ , m_hVecGadget(new VectorFxGadget(controller, pc, ph))
+ , m_vVecGadget(new VectorFxGadget(controller, pc, pv)) {
+ addParam(pc->getX());
+ addParam(pc->getY());
+ addParam(ph->getX());
+ addParam(ph->getY());
+ addParam(pv->getX());
+ addParam(pv->getY());
+ }
+
+ ~ParallelogramFxGadget() {
+ delete m_hVecGadget;
+ delete m_vVecGadget;
+ }
+
+ void draw(bool picking) override {
+ auto setColorById = [&](int id) {
+ if (isSelected(id))
+ glColor3dv(m_selectedColor);
+ else
+ glColor3d(0, 0, 1);
+ };
+
+ setPixelSize();
+ setColorById(Body);
+ glPushName(getId() + Body);
+
+ double pixelSize = getPixelSize();
+ double c = pixelSize * 4;
+ TPointD pc = getValue(m_pcenter);
+ TPointD ph = getValue(m_phoriz);
+ TPointD pv = getValue(m_pvert);
+
+ TPointD vec_h = ph - pc;
+ TPointD vec_v = pv - pc;
+ TPointD po = ph + vec_v;
+ TPointD unit_h = vec_h * (1.0 / sqrt(norm2(vec_h)));
+ TPointD unit_v = vec_v * (1.0 / sqrt(norm2(vec_v)));
+
+ glLineStipple(1, 0xAAAA);
+ glEnable(GL_LINE_STIPPLE);
+ tglDrawSegment(ph + unit_v * c, po);
+ tglDrawSegment(pv + unit_h * c, po);
+ glDisable(GL_LINE_STIPPLE);
+ glPopName();
+
+ if (m_pcurve.getPointer()) {
+ TPointD pcurve = getValue(m_pcurve);
+ TPointD ppivot = pc + (pcurve.x + 0.5) * vec_h + (pcurve.y + 0.5) * vec_v;
+
+ setColorById(Body);
+ glPushName(getId() + Body);
+ glEnable(GL_LINE_STIPPLE);
+ if (pcurve == TPointD()) {
+ tglDrawSegment((pc + ph) * 0.5, vec_v + (pc + ph) * 0.5);
+ tglDrawSegment((pc + pv) * 0.5, vec_h + (pc + pv) * 0.5);
+ } else {
+ TPointD p[2][2] = {{pc + vec_h * 0.5, pc + vec_h * 0.5 + vec_v},
+ {pc + vec_v * 0.5, pc + vec_v * 0.5 + vec_h}};
+ for (int k = 0; k < 2; k++) { // ���Ă悱
+ glBegin(GL_LINE_STRIP);
+ for (int i = 0; i <= 10; i++) { // ����
+ double t = (double)i * 0.1;
+ tglVertex((1.0 - t) * (1.0 - t) * p[k][0] +
+ 2.0 * (1.0 - t) * t * ppivot + t * t * p[k][1]);
+ }
+ glEnd();
+ }
+ }
+ glDisable(GL_LINE_STIPPLE);
+ glPopName();
+
+ setColorById(CurveAnchor);
+ glPushName(getId() + CurveAnchor);
+ glPushMatrix();
+ glTranslated(ppivot.x, ppivot.y, 0);
+ double r = pixelSize * 3;
+ tglDrawRect(-r, -r, r, r);
+ glPopMatrix();
+ glPopName();
+ }
+
+ setColorById(Rotation);
+ glPushName(getId() + Rotation);
+ double a = pixelSize * 10, b = pixelSize * 3;
+ TPointD diagonal = normalize(po - pc);
+ TPointD v = rotate90(diagonal);
+ tglDrawSegment(po + v * a, po - v * a);
+ tglDrawSegment(po + diagonal * b + v * a, po + diagonal * b - v * a);
+ glPopName();
+
+ m_hVecGadget->draw(picking);
+ m_vVecGadget->draw(picking);
+ }
+
+ void leftButtonDown(const TPointD &pos, const TMouseEvent &) override {
+ m_handle = (HANDLE)m_selected;
+ if (m_handle == None) return;
+ m_clickedPos = pos;
+ }
+ void leftButtonDrag(const TPointD &pos, const TMouseEvent &) override {
+ if (m_handle == None) return;
+ if (m_handle == Body) {
+ TPointD d = pos - m_clickedPos;
+ setValue(m_pcenter, getValue(m_pcenter) + d);
+ setValue(m_phoriz, getValue(m_phoriz) + d);
+ setValue(m_pvert, getValue(m_pvert) + d);
+ } else if (m_handle == CurveAnchor && m_pcurve.getPointer()) {
+ TPointD pc = getValue(m_pcenter);
+ TPointD ph = getValue(m_phoriz);
+ TPointD pv = getValue(m_pvert);
+ TPointD vec_h = ph - pc;
+ TPointD vec_v = pv - pc;
+ TAffine aff(vec_h.x, vec_v.x, pc.x, vec_h.y, vec_v.y, pc.y);
+ TPointD p_conv = aff.inv() * pos;
+ if (p_conv.x < 0.0)
+ p_conv.x = 0.0;
+ else if (p_conv.x > 1.0)
+ p_conv.x = 1.0;
+ if (p_conv.y < 0.0)
+ p_conv.y = 0.0;
+ else if (p_conv.y > 1.0)
+ p_conv.y = 1.0;
+ setValue(m_pcurve, p_conv - TPointD(0.5, 0.5));
+ } else if (m_handle == Rotation) {
+ TPointD ph = getValue(m_phoriz);
+ TPointD pv = getValue(m_pvert);
+ TPointD pivot = (ph + pv) * 0.5;
+ TPointD before = m_clickedPos - pivot;
+ TPointD after = pos - pivot;
+ double angle =
+ std::atan2(after.y, after.x) - std::atan2(before.y, before.x);
+ TAffine aff = TTranslation(pivot) * TRotation(angle * M_180_PI) *
+ TTranslation(-pivot);
+
+ setValue(m_pcenter, aff * getValue(m_pcenter));
+ setValue(m_phoriz, aff * getValue(m_phoriz));
+ setValue(m_pvert, aff * getValue(m_pvert));
+ }
+ m_clickedPos = pos;
+ }
+ void leftButtonUp() override { m_handle = None; }
+};
+
//*************************************************************************************
// FxGadgetController implementation
//*************************************************************************************
@@ -2680,6 +2859,19 @@ FxGadget *FxGadgetController::allocateGadget(const TParamUIConcept &uiConcept) {
break;
}
+ case TParamUIConcept::PARALLELOGRAM: {
+ assert(uiConcept.m_params.size() == 3 || uiConcept.m_params.size() == 4);
+ if (uiConcept.m_params.size() == 3) {
+ gadget = new ParallelogramFxGadget(this, uiConcept.m_params[0],
+ uiConcept.m_params[1],
+ uiConcept.m_params[2]);
+ } else
+ gadget = new ParallelogramFxGadget(
+ this, uiConcept.m_params[0], uiConcept.m_params[1],
+ uiConcept.m_params[2], uiConcept.m_params[3]);
+ break;
+ }
+
default:
break;
}
diff --git a/toonz/sources/toonzlib/scenefx.cpp b/toonz/sources/toonzlib/scenefx.cpp
index 1f8d69e..0eb7362 100644
--- a/toonz/sources/toonzlib/scenefx.cpp
+++ b/toonz/sources/toonzlib/scenefx.cpp
@@ -267,21 +267,17 @@ public:
, m_isPostXsheetNode(false) {}
bool operator<(const PlacedFx &pf) const {
- return (m_z < pf.m_z)
- ? true
- : (m_z > pf.m_z)
- ? false
- : (m_so < pf.m_so)
- ? true
- : (m_so > pf.m_so)
- ? false
- : (m_columnIndex < pf.m_columnIndex);
+ return (m_z < pf.m_z) ? true
+ : (m_z > pf.m_z) ? false
+ : (m_so < pf.m_so) ? true
+ : (m_so > pf.m_so) ? false
+ : (m_columnIndex < pf.m_columnIndex);
}
TFxP makeFx() {
- return (!m_fx)
- ? TFxP()
- : (m_aff == TAffine()) ? m_fx : TFxUtil::makeAffine(m_fx, m_aff);
+ return (!m_fx) ? TFxP()
+ : (m_aff == TAffine()) ? m_fx
+ : TFxUtil::makeAffine(m_fx, m_aff);
}
};
@@ -514,6 +510,57 @@ static QList getColumnMotionPoints(TXsheet *xsh, double row, int col,
return points;
}
+//-------------------------------------------------------------------
+/*--
+ フレーム前後のアフィン変換を得る
+ objectId: 移動の参考にするオブジェクト。自分自身の場合はNoneId
+--*/
+static void getColumnMotionAffines(TAffine &aff_Before, TAffine &aff_After,
+ TXsheet *xsh, double row, int col,
+ TStageObjectId &objectId, bool isPreview,
+ double shutterLength) {
+ /*-- 現在のカメラを得る --*/
+ TStageObjectId cameraId;
+ if (isPreview)
+ cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
+ else
+ cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
+ TStageObject *camera = xsh->getStageObject(cameraId);
+ TAffine dpiAff = getDpiAffine(camera->getCamera());
+
+ /*-- objectIdが有効なものかどうかチェック --*/
+ bool useOwnMotion = false;
+ if (objectId == TStageObjectId::NoneId ||
+ !xsh->getStageObjectTree()->getStageObject(objectId, false)) {
+ useOwnMotion = true;
+ }
+
+ /*-- 結果を収めるリスト --*/
+ TAffine retAff[2];
+ TAffine aff;
+
+ /*-- 各点の位置を、基準点との差分で格納していく --*/
+ for (int i = 0; i < 2; i++) {
+ /*-- 基準位置とのフレーム差 --*/
+ double frameOffset = (i == 0) ? -shutterLength : shutterLength;
+ double targetFrame = row + frameOffset;
+ // Proper position cannot be obtained for frame = -1.0
+ if (targetFrame == -1.0) targetFrame = -0.9999;
+
+ /*-- 自分自身の動きを使うか、別オブジェクトの動きを使うか --*/
+ if (useOwnMotion)
+ getColumnPlacement(aff, xsh, targetFrame, col, isPreview);
+ else
+ getStageObjectPlacement(aff, xsh, targetFrame, objectId, isPreview);
+
+ TAffine cameraAff = camera->getPlacement(targetFrame);
+ retAff[i] = dpiAff.inv() * aff * cameraAff.inv();
+ }
+
+ aff_Before = retAff[0];
+ aff_After = retAff[1];
+}
+
namespace {
QString getNoteText(TXsheet *xsh, double row, int col, int noteColumnIndex,
@@ -1023,6 +1070,21 @@ PlacedFx FxBuilder::makePF(TZeraryColumnFx *zcfx) {
noteColumnIndex, getNeighbor));
}
}
+ if (pf.m_fx->getAttributes()->isSpeedAware()) {
+ MotionAwareAffineFx *maafx =
+ dynamic_cast(pf.m_fx.getPointer());
+ if (maafx) {
+ double shutterLength = maafx->getShutterLength()->getValue(m_frame);
+ MotionObjectType type = maafx->getMotionObjectType();
+ int index = maafx->getMotionObjectIndex()->getValue();
+ TStageObjectId objectId = getMotionObjectId(type, index);
+ TAffine aff_Before, aff_After;
+ getColumnMotionAffines(aff_Before, aff_After, m_xsh, m_frame,
+ pf.m_columnIndex, objectId, m_isPreview,
+ shutterLength);
+ pf.m_fx->getAttributes()->setMotionAffines(aff_Before, aff_After);
+ }
+ }
return pf;
}
@@ -1167,8 +1229,14 @@ PlacedFx FxBuilder::makePFfromGenericFx(TFx *fx) {
int m = fx->getInputPortCount();
for (int i = 0; i < m; ++i) {
if (TFxP inputFx = fx->getInputPort(i)->getFx()) {
- PlacedFx inputPF = makePF(inputFx.getPointer());
- inputFx = inputPF.m_fx;
+ PlacedFx inputPF;
+ if (fx->getFxType() == "STD_iwa_FlowPaintBrushFx") {
+ m_particleDescendentCount++;
+ inputPF = makePF(inputFx.getPointer());
+ m_particleDescendentCount--;
+ } else
+ inputPF = makePF(inputFx.getPointer());
+ inputFx = inputPF.m_fx;
// check the column index instead of inputFx
// so that the firstly-found input column always inherits
// its placement even if the current cell is empty.