From 8af50f802598dfb7f4f62eb537beb37d3f9bc132 Mon Sep 17 00:00:00 2001 From: Rodney Date: Dec 08 2022 12:09:14 +0000 Subject: Merge pull request #4633 from shun-iwasawa/g/flow New Fxs: Flow Fx series --- 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.