From fc28b39969bc9a1f5cab93310a814f8d3afe5af1 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Fri, 10 May 2024 12:33:38 +0100 Subject: [PATCH 01/38] initial commit - vue2 & vite --- .editorconfig | 18 +- .eslintignore | 4 + .eslintrc.cjs | 15 + .prettierignore | 6 + .vscode/extensions.json | 8 +- .vscode/launch.json | 39 + .vscode/settings.json | 11 + README.md | 128 +- build/entitlements.mac.plist | 8 +- build/icon.icns | Bin 0 -> 85649 bytes build/icon.ico | Bin 59276 -> 123545 bytes build/icon.png | Bin 36777 -> 35949 bytes dev-app-update.yml | 3 + electron-builder.yml | 42 + electron.vite.config.mjs | 35 + out/main/index.js | 89 + out/preload/index.js | 38 + package.json | 124 +- resources/icon.png | Bin 0 -> 35949 bytes src/main/index.js | 107 + src/main/media-manager/add-read-handler.js | 43 + src/main/media-manager/add-save-handler.js | 7 + src/main/media-manager/create-watcher.js | 58 + src/main/media-manager/fs-create-profile.js | 80 + src/main/media-manager/index.js | 153 + src/main/media-manager/log.js | 27 + .../media-manager-utils/stream-to-string.js | 9 + src/main/media-manager/parse-message.js | 68 + src/main/media-manager/read-file.js | 64 + src/main/media-manager/read-handlers/image.js | 156 + src/main/media-manager/read-handlers/isf.js | 170 + .../media-manager/read-handlers/module.js | 168 + .../media-manager/read-handlers/palette.js | 12 + .../media-manager/read-handlers/preset.js | 12 + src/main/media-manager/read-handlers/video.js | 50 + .../media-manager/save-handlers/preset.js | 12 + src/main/media-manager/store/index.js | 34 + src/main/media-manager/store/modules/media.js | 100 + .../media-manager/store/modules/plugins.js | 56 + .../store/modules/read-handlers.js | 64 + .../store/modules/save-handlers.js | 60 + src/preload/index.js | 27 + src/renderer/index.html | 18 + src/renderer/output-window.html | 26 + src/renderer/src/App.vue | 504 + src/renderer/src/application/constants.js | 21 + .../src/application/createWebcodecVideo.js | 42 + src/renderer/src/application/index.js | 336 + .../src/application/install-plugin.js | 45 + .../plugins/feature-assignment/index.js | 38 + .../plugins/feature-assignment/store.js | 36 + .../src/application/plugins/grab-canvas.js | 186 + src/renderer/src/application/renderers/2d.js | 99 + src/renderer/src/application/renderers/isf.js | 225 + .../src/application/renderers/shader.js | 2 + .../renderers/shader/default-shader.js | 83 + .../src/application/renderers/shader/index.js | 231 + .../src/application/renderers/three.js | 198 + .../src/application/sample-modules/Ball.js | 230 + .../src/application/sample-modules/Bar.js | 71 + .../sample-modules/ChromaticAbberation.js | 40 + .../chromaticAbberation.frag | 28 + .../application/sample-modules/Concentrics.js | 158 + .../src/application/sample-modules/Counter.js | 35 + .../src/application/sample-modules/Cube.js | 119 + .../src/application/sample-modules/Fisheye.js | 24 + .../sample-modules/Fisheye/fisheye.frag | 47 + .../sample-modules/GreatBallOfFire.js | 249 + .../application/sample-modules/GridStretch.js | 99 + .../src/application/sample-modules/Line.js | 137 + .../sample-modules/MattiasCRT-2.0.js | 12 + .../sample-modules/MattiasCRT/mattiasCrt.frag | 63 + .../application/sample-modules/Pixelate.js | 107 + .../src/application/sample-modules/Plasma.js | 40 + .../sample-modules/Plasma/plasma.frag | 26 + .../src/application/sample-modules/Polygon.js | 173 + .../src/application/sample-modules/Smear.js | 41 + .../src/application/sample-modules/Text.js | 185 + .../application/sample-modules/Texture2d.js | 107 + .../application/sample-modules/Waveform.js | 102 + .../src/application/sample-modules/Webcam.js | 45 + .../src/application/sample-modules/Wobble.js | 33 + .../sample-modules/Wobble/wobble.frag | 22 + .../src/application/sample-modules/X-Drips.js | 156 + .../sample-modules/isf/ASCII Art.fs | 54 + .../application/sample-modules/isf/Angular.fs | 71 + .../sample-modules/isf/Auto Color Tone.fs | 347 + .../sample-modules/isf/Auto Levels.fs | 322 + .../sample-modules/isf/Auto Levels.vs | 39 + .../application/sample-modules/isf/Bloom.fs | 134 + .../application/sample-modules/isf/Bloom.vs | 70 + .../application/sample-modules/isf/Bounce.fs | 95 + .../sample-modules/isf/Bow Tie Horizontal.fs | 165 + .../sample-modules/isf/Bow Tie Vertical.fs | 158 + .../sample-modules/isf/Boxinator.fs | 146 + .../sample-modules/isf/Brick Pattern.fs | 102 + .../application/sample-modules/isf/Bright.fs | 24 + .../sample-modules/isf/BrightnessContrast.fs | 34 + .../sample-modules/isf/Broken LCD.fs | 660 + .../sample-modules/isf/Bump Distortion.fs | 90 + .../application/sample-modules/isf/Burn.fs | 63 + .../isf/Butterfly Wave Scrawler.fs | 90 + .../isf/CMYK Halftone-Lookaround.fs | 137 + .../sample-modules/isf/CMYK Halftone.fs | 87 + .../sample-modules/isf/Channel Slide.fs | 50 + .../sample-modules/isf/Checkerboard.fs | 92 + .../sample-modules/isf/Chroma Zoom.fs | 83 + .../sample-modules/isf/Circle Crop.fs | 70 + .../sample-modules/isf/Circle Open.fs | 67 + .../isf/Circle Splash Distortion.fs | 102 + .../sample-modules/isf/Circle Trails.fs | 160 + .../sample-modules/isf/Circle Warp.fs | 78 + .../isf/Circle Wrap Distortion.fs | 97 + .../application/sample-modules/isf/Circle.fs | 87 + .../sample-modules/isf/Circuits.fs | 189 + .../isf/Circular Feedback Mask.fs | 158 + .../sample-modules/isf/Circular Screen.fs | 111 + .../sample-modules/isf/Circular Screen.vs | 39 + .../sample-modules/isf/City Lights.fs | 289 + .../sample-modules/isf/City Lights.vs | 105 + .../application/sample-modules/isf/Collage.fs | 113 + .../isf/CollapsingArchitecture.fs | 239 + .../sample-modules/isf/Color Blowout.fs | 114 + .../sample-modules/isf/Color Blowout.vs | 39 + .../sample-modules/isf/Color Controls.fs | 92 + .../sample-modules/isf/Color History.fs | 88 + .../sample-modules/isf/Color Invert.fs | 19 + .../sample-modules/isf/Color Levels.fs | 156 + .../sample-modules/isf/Color Monochrome.fs | 46 + .../sample-modules/isf/Color Phase.fs | 76 + .../sample-modules/isf/Color Posterize.fs | 32 + .../sample-modules/isf/Color Relookup.fs | 38 + .../sample-modules/isf/Color Scales.fs | 505 + .../sample-modules/isf/Color Schemes.fs | 267 + .../sample-modules/isf/Color Test Grid.fs | 88 + .../sample-modules/isf/Colour Distance.fs | 67 + .../sample-modules/isf/Comet Tails.fs | 99 + .../sample-modules/isf/Convergence.fs | 108 + .../sample-modules/isf/Corner Color Tint.fs | 98 + .../sample-modules/isf/Corner Colors.fs | 89 + .../isf/Crazy Parametric Fun.fs | 82 + .../sample-modules/isf/CrossZoom.fs | 111 + .../sample-modules/isf/Crosshatch.fs | 85 + .../sample-modules/isf/Crosswarp.fs | 51 + .../sample-modules/isf/Cubic Warp.fs | 82 + .../sample-modules/isf/Deinterlace.fs | 32 + .../sample-modules/isf/Diagonal Blur.fs | 82 + .../sample-modules/isf/Diagonalize.fs | 108 + .../sample-modules/isf/Digital Clock.fs | 184 + .../sample-modules/isf/Directional Warp.fs | 74 + .../sample-modules/isf/Directional Wipe.fs | 82 + .../sample-modules/isf/Directional.fs | 73 + .../sample-modules/isf/Dirty Lens.fs | 166 + .../sample-modules/isf/Displace.fs | 108 + .../sample-modules/isf/Displacement.fs | 75 + .../sample-modules/isf/Dither-Bayer.fs | 216 + .../isf/Doom Screen Transition.fs | 130 + .../application/sample-modules/isf/Doorway.fs | 112 + .../sample-modules/isf/Dot Screen.fs | 110 + .../sample-modules/isf/Dot Screen.vs | 39 + .../sample-modules/isf/Double Vision.fs | 72 + .../sample-modules/isf/Dreamy Zoom.fs | 98 + .../application/sample-modules/isf/Dreamy.fs | 56 + .../isf/Dual Side Scroller And Flip.fs | 91 + .../application/sample-modules/isf/Duotone.fs | 85 + .../sample-modules/isf/Echo Trace.fs | 65 + .../sample-modules/isf/Edge Blowout.fs | 230 + .../sample-modules/isf/Edge Blur.fs | 184 + .../sample-modules/isf/Edge Blur.vs | 103 + .../sample-modules/isf/Edge Distort.fs | 85 + .../sample-modules/isf/Edge Distort.vs | 27 + .../sample-modules/isf/Edge Trace.fs | 86 + .../sample-modules/isf/Edge Trace.vs | 39 + .../application/sample-modules/isf/Edges.fs | 110 + .../application/sample-modules/isf/Edges.vs | 39 + .../application/sample-modules/isf/Emboss.fs | 64 + .../application/sample-modules/isf/Emboss.vs | 23 + .../sample-modules/isf/Etch-a-Sketch.fs | 138 + .../sample-modules/isf/Exposure Adjust.fs | 30 + .../sample-modules/isf/Fade Color.fs | 69 + .../sample-modules/isf/Fade Gray Scale.fs | 67 + .../application/sample-modules/isf/Fade.fs | 55 + .../sample-modules/isf/False Color.fs | 42 + .../sample-modules/isf/Fast Blur.fs | 122 + .../sample-modules/isf/Fast Blur.vs | 70 + .../sample-modules/isf/FastMosh.fs | 160 + .../sample-modules/isf/FastMosh.vs | 39 + .../sample-modules/isf/Film Burn.fs | 109 + .../application/sample-modules/isf/Flip H.fs | 24 + .../application/sample-modules/isf/Flip V.fs | 24 + .../sample-modules/isf/Flipbook.fs | 142 + .../application/sample-modules/isf/Fly Eye.fs | 79 + .../isf/FractilianParabolicCircleInversion.fs | 176 + .../sample-modules/isf/Freeze Frame.fs | 38 + .../sample-modules/isf/Frosted Glass.fs | 131 + .../sample-modules/isf/Gamma Correction.fs | 33 + .../sample-modules/isf/Ghosting.fs | 130 + .../sample-modules/isf/Glitch Displace.fs | 120 + .../sample-modules/isf/Glitch Memories.fs | 60 + .../sample-modules/isf/Glitch Shifter.fs | 155 + .../application/sample-modules/isf/Gloom.fs | 131 + .../application/sample-modules/isf/Gloom.vs | 70 + .../sample-modules/isf/Glow-Fast.fs | 154 + .../sample-modules/isf/Glow-Fast.vs | 70 + .../application/sample-modules/isf/Glow.fs | 158 + .../application/sample-modules/isf/Glow.vs | 33 + .../sample-modules/isf/Graph Paper.fs | 172 + .../sample-modules/isf/GreatBallOfFire.fs | 240 + .../sample-modules/isf/Grid Flip.fs | 158 + .../sample-modules/isf/Grid Warp.fs | 232 + .../sample-modules/isf/HSVtoRGB.fs | 46 + .../sample-modules/isf/Hatch Blur.fs | 95 + .../sample-modules/isf/Heart Transition.fs | 61 + .../application/sample-modules/isf/Heart.fs | 44 + .../sample-modules/isf/HexVortex.fs | 148 + .../sample-modules/isf/Hexagonalize.fs | 131 + .../sample-modules/isf/Histogram Viewer.fs | 35 + .../sample-modules/isf/HorizVertHold.fs | 65 + .../sample-modules/isf/Hue-Saturation.fs | 54 + .../sample-modules/isf/Hyperspace.fs | 85 + .../sample-modules/isf/Interlace Mirror.fs | 45 + .../sample-modules/isf/Interlace.fs | 57 + .../sample-modules/isf/Inverted Page Curl.fs | 259 + .../sample-modules/isf/Kaleidoscope Tile.fs | 122 + .../isf/Kaleidoscope Transition.fs | 84 + .../sample-modules/isf/Kaleidoscope.fs | 50 + .../sample-modules/isf/Key Frame Artifacts.fs | 126 + .../sample-modules/isf/Layer Mask.fs | 182 + .../sample-modules/isf/Layer Position.fs | 57 + .../sample-modules/isf/Lens Flare.fs | 248 + .../sample-modules/isf/Lens Flare.vs | 39 + .../application/sample-modules/isf/Life.fs | 154 + .../application/sample-modules/isf/Life.vs | 38 + .../sample-modules/isf/Line Screen.fs | 109 + .../sample-modules/isf/Line Screen.vs | 39 + .../sample-modules/isf/Linear Blur.fs | 76 + .../sample-modules/isf/Linear Gradient.fs | 95 + .../application/sample-modules/isf/Lines.fs | 98 + .../sample-modules/isf/LogTransWarpSpiral.fs | 55 + .../sample-modules/isf/Long Exposure.fs | 66 + .../sample-modules/isf/Luma Transition.fs | 60 + .../sample-modules/isf/Luminance Melt.fs | 183 + .../sample-modules/isf/Luminance Posterize.fs | 58 + .../application/sample-modules/isf/MBOX3.fs | 286 + .../sample-modules/isf/Maximum Component.fs | 19 + .../application/sample-modules/isf/Median.fs | 70 + .../sample-modules/isf/Meta Image.fs | 97 + .../sample-modules/isf/Micro Buffer RGB.fs | 316 + .../sample-modules/isf/Micro Buffer.fs | 312 + .../sample-modules/isf/Minimum Component.fs | 21 + .../sample-modules/isf/Mirror Edge.fs | 46 + .../sample-modules/isf/Mirror Edge.vs | 18 + .../application/sample-modules/isf/Mirror.fs | 41 + .../application/sample-modules/isf/Morph.fs | 66 + .../application/sample-modules/isf/Mosaic.fs | 98 + .../sample-modules/isf/Multi Gradient.fs | 339 + .../sample-modules/isf/Multi Hue Shift.fs | 89 + .../isf/Multi Pass Gaussian Blur.fs | 183 + .../isf/Multi Pass Gaussian Blur.vs | 98 + .../sample-modules/isf/Multi-Pixellate.fs | 139 + .../sample-modules/isf/MultiFrame 2x2.fs | 145 + .../sample-modules/isf/MultiFrame 3x3.fs | 268 + .../sample-modules/isf/Multiply Blend.fs | 62 + .../application/sample-modules/isf/Neon.fs | 125 + .../application/sample-modules/isf/Neon.vs | 28 + .../sample-modules/isf/Night Vision.fs | 99 + .../sample-modules/isf/Noise Adapt.fs | 98 + .../sample-modules/isf/Noise Displace.fs | 77 + .../sample-modules/isf/Noise Pixellate.fs | 120 + .../application/sample-modules/isf/Noise.fs | 172 + .../isf/Optical Flow Distort.fs | 182 + .../isf/Optical Flow Distort.vs | 27 + .../isf/Optical Flow Generator.fs | 125 + .../sample-modules/isf/Perlin Transition.fs | 126 + .../application/sample-modules/isf/Pinch.fs | 55 + .../sample-modules/isf/Pinwheel.fs | 66 + .../sample-modules/isf/Pixel Shifter.fs | 114 + .../sample-modules/isf/Pixelize.fs | 80 + .../sample-modules/isf/Pixellate.fs | 82 + .../sample-modules/isf/Polar Function.fs | 70 + .../sample-modules/isf/Polka Dots Curtain.fs | 75 + .../sample-modules/isf/Poly Star.fs | 95 + .../sample-modules/isf/Posterize.fs | 46 + .../sample-modules/isf/Power Warp.fs | 109 + .../sample-modules/isf/Quad Mask.fs | 150 + .../sample-modules/isf/Quad Tile.fs | 136 + .../isf/RE RGB Gradient Generator.fs | 344 + .../application/sample-modules/isf/RGB EQ.fs | 61 + .../isf/RGB Halftone-lookaround.fs | 106 + .../sample-modules/isf/RGB Halftone.fs | 67 + .../sample-modules/isf/RGB Invert.fs | 48 + .../sample-modules/isf/RGB Strobe.fs | 108 + .../sample-modules/isf/RGB Trails 3.0.fs | 53 + .../sample-modules/isf/RGBA Swap.fs | 166 + .../sample-modules/isf/RGBtoHSV.fs | 46 + .../sample-modules/isf/Radial Gradient.fs | 76 + .../sample-modules/isf/Radial Replicate.fs | 104 + .../application/sample-modules/isf/Radial.fs | 66 + .../sample-modules/isf/Random Checkerboard.fs | 121 + .../sample-modules/isf/Random Freeze.fs | 97 + .../sample-modules/isf/Random Lines.fs | 300 + .../sample-modules/isf/Random Shape Blast.fs | 224 + .../sample-modules/isf/Random Shape.fs | 280 + .../sample-modules/isf/Random Squares Mask.fs | 100 + .../sample-modules/isf/Random Squares.fs | 80 + .../sample-modules/isf/Random Stripes.fs | 116 + .../sample-modules/isf/Replicate Random.fs | 97 + .../sample-modules/isf/Replicate.fs | 127 + .../sample-modules/isf/Resize Glitch.fs | 115 + .../sample-modules/isf/Ripple Transition.fs | 72 + .../application/sample-modules/isf/Ripples.fs | 118 + .../sample-modules/isf/Rotate Scale Fade.fs | 112 + .../application/sample-modules/isf/Rotate.fs | 39 + .../application/sample-modules/isf/Rotate.vs | 21 + .../sample-modules/isf/Saturation Bleed.fs | 143 + .../sample-modules/isf/Saturation Bleed.vs | 39 + .../sample-modules/isf/Sepia Tone.fs | 44 + .../sample-modules/isf/Set Alpha.fs | 32 + .../application/sample-modules/isf/Shake.fs | 54 + .../sample-modules/isf/Shape Mask.fs | 304 + .../isf/Shape Morph Feedback Mask.fs | 321 + .../sample-modules/isf/Shape Morph Wrap.fs | 358 + .../sample-modules/isf/Sharpen Luminance.fs | 66 + .../sample-modules/isf/Sharpen Luminance.vs | 39 + .../sample-modules/isf/Sharpen RGB.fs | 83 + .../sample-modules/isf/Sharpen RGB.vs | 39 + .../sample-modules/isf/Shockwave Pulse.fs | 105 + .../sample-modules/isf/Shockwave.fs | 82 + .../sample-modules/isf/Show Alpha.fs | 22 + .../isf/Side Scroller And Flip.fs | 58 + .../isf/Simple Zoom Transition.fs | 68 + .../sample-modules/isf/Sine Warp Gradient.fs | 119 + .../sample-modules/isf/Sine Warp Tile.fs | 80 + .../application/sample-modules/isf/Sketch.fs | 72 + .../application/sample-modules/isf/Sketch.vs | 39 + .../application/sample-modules/isf/Slice.fs | 54 + .../sample-modules/isf/Sliding Strips.fs | 76 + .../sample-modules/isf/Slit Scan Mask.fs | 91 + .../sample-modules/isf/Slit Scan.fs | 87 + .../sample-modules/isf/Smoke Screen.fs | 105 + .../sample-modules/isf/Smudged Lens.fs | 217 + .../sample-modules/isf/Smudged Lens.vs | 39 + .../sample-modules/isf/Soft Blur.fs | 96 + .../sample-modules/isf/Soft Blur.vs | 39 + .../sample-modules/isf/Soft Flip.fs | 109 + .../sample-modules/isf/Solarize.fs | 91 + .../sample-modules/isf/Solid Color.fs | 27 + .../sample-modules/isf/Sorting Smear.fs | 119 + .../sample-modules/isf/Sorting Smear.vs | 24 + .../sample-modules/isf/Sphere Map.fs | 76 + .../application/sample-modules/isf/Spiral.fs | 107 + .../sample-modules/isf/Squares Wire.fs | 100 + .../application/sample-modules/isf/Squeeze.fs | 69 + .../application/sample-modules/isf/Star.fs | 109 + .../sample-modules/isf/Stereo Viewer.fs | 269 + .../application/sample-modules/isf/Stripes.fs | 76 + .../application/sample-modules/isf/Strobe.fs | 91 + .../sample-modules/isf/Swap Transition.fs | 122 + .../application/sample-modules/isf/Swirl.fs | 75 + .../sample-modules/isf/TV Static.fs | 75 + .../sample-modules/isf/Time Glitch RGB.fs | 461 + .../application/sample-modules/isf/Toon.fs | 77 + .../sample-modules/isf/Trail Mask.fs | 173 + .../isf/Trapezoid Distortion.fs | 58 + .../sample-modules/isf/Triangle Warp.fs | 60 + .../sample-modules/isf/Triangle.fs | 110 + .../sample-modules/isf/Triangles.fs | 123 + .../sample-modules/isf/Trio Tone.fs | 95 + .../sample-modules/isf/Triple Rotate.fs | 96 + .../sample-modules/isf/Truchet Tile.fs | 110 + .../application/sample-modules/isf/Twirl.fs | 75 + .../sample-modules/isf/UltimateFlame.fs | 303 + .../sample-modules/isf/UltimateSpiral.fs | 113 + .../sample-modules/isf/Undulating Burn Out.fs | 121 + .../sample-modules/isf/Unsharp Mask.fs | 66 + .../sample-modules/isf/Unsharp Mask.vs | 39 + .../sample-modules/isf/VHS Glitch.fs | 214 + .../sample-modules/isf/VHS Glitch.fs.fs | 211 + .../sample-modules/isf/VU Meter.fs | 78 + .../sample-modules/isf/VVMotionBlur 3.0.fs | 37 + .../sample-modules/isf/Vertex Manipulator.fs | 58 + .../sample-modules/isf/Vertex Manipulator.vs | 31 + .../sample-modules/isf/Vertical Tearing.fs | 51 + .../sample-modules/isf/Vibrance.fs | 54 + .../sample-modules/isf/Vignette.fs | 41 + .../sample-modules/isf/Water Drop.fs | 72 + .../sample-modules/isf/WaveLines.fs | 115 + .../sample-modules/isf/White Point Adjust.fs | 31 + .../application/sample-modules/isf/Wind.fs | 69 + .../sample-modules/isf/Window Blinds.fs | 60 + .../sample-modules/isf/Window Slice.fs | 67 + .../sample-modules/isf/Wipe Down.fs | 54 + .../sample-modules/isf/Wipe Left.fs | 54 + .../sample-modules/isf/Wipe Right.fs | 54 + .../application/sample-modules/isf/Wipe Up.fs | 54 + .../application/sample-modules/isf/XYZoom.fs | 60 + .../sample-modules/isf/Y-C Time Blur.fs | 84 + .../application/sample-modules/isf/Zebre.fs | 192 + .../sample-modules/isf/Zoom In Circles.fs | 83 + .../application/sample-modules/isf/Zoom.fs | 53 + .../sample-modules/isf/Zooming Feedback.fs | 230 + .../application/sample-modules/isf/badtv.fs | 184 + .../sample-modules/isf/block-color.fs | 49 + .../application/sample-modules/isf/cube.fs | 135 + .../isf/digital-crystal-tunnel.fs | 203 + .../sample-modules/isf/feedback.fs | 56 + .../sample-modules/isf/film-grain.fs | 54 + .../sample-modules/isf/hexagons.fs | 266 + .../application/sample-modules/isf/plasma.fs | 47 + .../sample-modules/isf/rgbglitchmod.fs | 42 + .../sample-modules/isf/rgbtimeglitch.fs | 462 + .../sample-modules/isf/rotozoomer.fs | 77 + .../application/sample-modules/isf/scale.fs | 36 + .../isf/spherical-shader-tut.fs | 324 + .../sample-modules/isf/st_Ms2SD1.fs.fs | 252 + .../sample-modules/isf/st_lsfGDH.fs | 48 + .../sample-modules/isf/tapestryfract.fs | 58 + .../sample-modules/isf/v002 Bleach Bypass.fs | 55 + .../sample-modules/isf/v002 Crosshatch.fs | 114 + .../sample-modules/isf/v002 Dilate.fs | 58 + .../sample-modules/isf/v002 Dilate.vs | 40 + .../sample-modules/isf/v002 Erode.fs | 57 + .../sample-modules/isf/v002 Erode.vs | 40 + .../sample-modules/isf/v002 Light Leak.fs | 83 + .../sample-modules/isf/v002 Light Leak.vs | 21 + .../sample-modules/isf/v002 Technicolor.fs | 109 + .../sample-modules/isf/v002 Vignette.fs | 66 + .../isf/v002-CRT-Displacement.fs | 55 + .../isf/v002-CRT-Mask-RGB-Shadow.png | Bin 0 -> 6658 bytes .../isf/v002-CRT-Mask-RGB-Staggered.png | Bin 0 -> 4515 bytes .../isf/v002-CRT-Mask-RGB-Straight.png | Bin 0 -> 6574 bytes .../isf/v002-CRT-Mask-Scanline-Staggered.png | Bin 0 -> 6577 bytes .../sample-modules/isf/v002-CRT-Mask.fs | 77 + .../src/application/setup-beat-detektor.js | 22 + .../src/application/setup-grandiose.js | 38 + src/renderer/src/application/setup-media.js | 224 + src/renderer/src/application/setup-midi.js | 165 + src/renderer/src/application/setup-tweens.js | 0 src/renderer/src/application/use.js | 25 + .../src/application/utils/apply-expression.js | 24 + .../application/utils/conform-file-path.js | 6 + .../src/application/utils/get-next-name.js | 29 + .../src/application/utils/get-prop-default.js | 55 + src/renderer/src/application/utils/lerp.js | 3 + src/renderer/src/application/utils/map.js | 4 + .../src/application/window-handler.js | 140 + .../src/application/worker/audio-features.js | 48 + .../src/application/worker/frame-counter.js | 38 + .../src/application/worker/index.worker.js | 406 + src/renderer/src/application/worker/loop.js | 381 + .../src/application/worker/store/index.js | 75 + .../application/worker/store/modules/beats.js | 45 + .../worker/store/modules/common/swap.js | 89 + .../worker/store/modules/dataTypes.js | 146 + .../worker/store/modules/errors.js | 37 + .../worker/store/modules/expressions.js | 153 + .../application/worker/store/modules/fonts.js | 30 + .../application/worker/store/modules/fps.js | 31 + .../worker/store/modules/groups.js | 430 + .../worker/store/modules/images.js | 62 + .../worker/store/modules/inputs.js | 269 + .../application/worker/store/modules/media.js | 65 + .../worker/store/modules/mediaStream.js | 39 + .../worker/store/modules/metrics.js | 15 + .../application/worker/store/modules/meyda.js | 96 + .../application/worker/store/modules/midi.js | 78 + .../worker/store/modules/modules.js | 823 + .../application/worker/store/modules/ndi.js | 207 + .../worker/store/modules/outputs.js | 196 + .../worker/store/modules/plugins.js | 154 + .../worker/store/modules/projects.js | 22 + .../worker/store/modules/renderers.js | 26 + .../application/worker/store/modules/size.js | 51 + .../worker/store/modules/tweens.js | 259 + .../worker/store/modules/videos.js | 126 + .../worker/store/modules/windows.js | 46 + .../src/assets/fonts/Inter-italic.var.woff2 | Bin 0 -> 245036 bytes .../src/assets/fonts/Inter-roman.var.woff2 | Bin 0 -> 227180 bytes .../src/assets/fonts/iaw-mono-Bold.woff | Bin 0 -> 47360 bytes .../src/assets/fonts/iaw-mono-Bold.woff2 | Bin 0 -> 37540 bytes .../src/assets/fonts/iaw-mono-BoldItalic.woff | Bin 0 -> 51980 bytes .../assets/fonts/iaw-mono-BoldItalic.woff2 | Bin 0 -> 41392 bytes .../src/assets/fonts/iaw-mono-Italic.woff | Bin 0 -> 51240 bytes .../src/assets/fonts/iaw-mono-Italic.woff2 | Bin 0 -> 40708 bytes .../src/assets/fonts/iaw-mono-Regular.woff | Bin 0 -> 46964 bytes .../src/assets/fonts/iaw-mono-Regular.woff2 | Bin 0 -> 36952 bytes .../src/assets/graphics/Arrow-vertical.svg | 3 + src/renderer/src/assets/graphics/Arrow.svg | 3 + src/renderer/src/components/ABSwap.vue | 16 + src/renderer/src/components/ActiveModule.vue | 322 + .../src/components/CollapsibleRow.vue | 59 + src/renderer/src/components/Control.vue | 275 + .../Controls/CollapsibleControl.vue | 51 + .../src/components/Controls/ColorControl.vue | 301 + .../src/components/Controls/FontControl.vue | 207 + .../components/Controls/PaletteControl.vue | 267 + .../src/components/Controls/RangeControl.vue | 396 + .../components/Controls/TextureControl.vue | 251 + .../src/components/Controls/TweenControl.vue | 181 + .../src/components/Controls/Vec2DControl.vue | 166 + .../src/components/Controls/Vec2DXY.vue | 259 + .../src/components/Controls/Vec3Control.vue | 201 + .../src/components/Controls/Vec4Control.vue | 240 + .../src/components/Controls/VideoControl.vue | 104 + src/renderer/src/components/Dialog.vue | 76 + src/renderer/src/components/ElectronLink.vue | 29 + src/renderer/src/components/ErrorWatcher.vue | 42 + src/renderer/src/components/Gallery.vue | 283 + src/renderer/src/components/GalleryItem.vue | 215 + src/renderer/src/components/Group.vue | 771 + src/renderer/src/components/Groups.vue | 115 + src/renderer/src/components/InfoView.vue | 42 + src/renderer/src/components/InputConfig.vue | 213 + .../InputDeviceConfig/AudioVideo.vue | 176 + .../src/components/InputDeviceConfig/BPM.vue | 103 + .../src/components/InputDeviceConfig/MIDI.vue | 112 + .../src/components/InputDeviceConfig/NDI.vue | 146 + .../InputLinkComponents/AudioFeatures.vue | 215 + .../InputLinkComponents/Expression.vue | 84 + .../components/InputLinkComponents/MIDI.vue | 76 + .../components/InputLinkComponents/Tween.vue | 77 + src/renderer/src/components/ModuleControl.vue | 92 + .../src/components/ModuleInspector.vue | 101 + src/renderer/src/components/PluginControl.vue | 85 + src/renderer/src/components/Plugins.vue | 91 + src/renderer/src/components/Preview.vue | 137 + src/renderer/src/components/Search.vue | 298 + .../src/components/StatusBar/BPMDisplay.vue | 28 + .../src/components/StatusBar/FPSDisplay.vue | 31 + .../src/components/StatusBar/SizeDisplay.vue | 5 + .../components/StatusBar/StatusBarItem.vue | 33 + .../src/components/StatusBar/index.vue | 81 + .../src/components/TooltipDisplay.vue | 16 + .../components/dialogs/FrameRateDialog.vue | 46 + .../src/components/directives/ContextMenu.js | 25 + .../src/components/directives/InfoView.js | 41 + .../src/components/directives/Search.js | 30 + .../src/components/directives/ValueTooltip.js | 176 + src/renderer/src/components/inputs/Button.vue | 38 + .../src/components/inputs/Checkbox.vue | 106 + src/renderer/src/components/inputs/Number.vue | 44 + src/renderer/src/components/inputs/Range.vue | 75 + .../inputs/RightClickNumberInput.vue | 77 + src/renderer/src/components/inputs/Select.vue | 64 + .../src/components/inputs/TextInput.vue | 43 + .../src/components/inputs/Textarea.vue | 35 + src/renderer/src/components/inputs/index.js | 16 + .../src/components/mixins/has-input-link.js | 7 + .../src/components/mixins/input-is-focused.js | 7 + src/renderer/src/css/golden-layout_theme.css | 156 + src/renderer/src/css/iaw.css | 29 + src/renderer/src/css/inter.css | 35 + src/renderer/src/css/raster.css | 2 + src/renderer/src/main.js | 80 + .../menus/context/activeModuleContextMenu.js | 59 + .../context/activeModuleControlContextMenu.js | 36 + .../src/menus/context/bpmContextMenu.js | 14 + .../menus/context/galleryItemContextMenu.js | 36 + .../src/menus/context/groupContextMenu.js | 17 + src/renderer/src/ui-store/index.js | 23 + src/renderer/src/ui-store/modules/dialogs.js | 27 + src/renderer/src/ui-store/modules/focus.js | 32 + src/renderer/src/ui-store/modules/infoView.js | 68 + src/renderer/src/ui-store/modules/search.js | 142 + .../src/ui-store/modules/ui-groups.js | 36 + .../src/ui-store/modules/ui-modules.js | 65 + src/renderer/src/util/composite-operations.js | 124 + src/renderer/src/util/font-family.js | 1 + yarn.lock | 13223 +++------------- 569 files changed, 59661 insertions(+), 11162 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.cjs create mode 100644 .prettierignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 build/icon.icns create mode 100644 dev-app-update.yml create mode 100644 electron-builder.yml create mode 100644 electron.vite.config.mjs create mode 100644 out/main/index.js create mode 100644 out/preload/index.js create mode 100644 resources/icon.png create mode 100644 src/main/index.js create mode 100644 src/main/media-manager/add-read-handler.js create mode 100644 src/main/media-manager/add-save-handler.js create mode 100644 src/main/media-manager/create-watcher.js create mode 100644 src/main/media-manager/fs-create-profile.js create mode 100644 src/main/media-manager/index.js create mode 100644 src/main/media-manager/log.js create mode 100644 src/main/media-manager/media-manager-utils/stream-to-string.js create mode 100644 src/main/media-manager/parse-message.js create mode 100644 src/main/media-manager/read-file.js create mode 100644 src/main/media-manager/read-handlers/image.js create mode 100644 src/main/media-manager/read-handlers/isf.js create mode 100644 src/main/media-manager/read-handlers/module.js create mode 100644 src/main/media-manager/read-handlers/palette.js create mode 100644 src/main/media-manager/read-handlers/preset.js create mode 100644 src/main/media-manager/read-handlers/video.js create mode 100644 src/main/media-manager/save-handlers/preset.js create mode 100644 src/main/media-manager/store/index.js create mode 100644 src/main/media-manager/store/modules/media.js create mode 100644 src/main/media-manager/store/modules/plugins.js create mode 100644 src/main/media-manager/store/modules/read-handlers.js create mode 100644 src/main/media-manager/store/modules/save-handlers.js create mode 100644 src/preload/index.js create mode 100644 src/renderer/index.html create mode 100644 src/renderer/output-window.html create mode 100644 src/renderer/src/App.vue create mode 100644 src/renderer/src/application/constants.js create mode 100644 src/renderer/src/application/createWebcodecVideo.js create mode 100644 src/renderer/src/application/index.js create mode 100644 src/renderer/src/application/install-plugin.js create mode 100644 src/renderer/src/application/plugins/feature-assignment/index.js create mode 100644 src/renderer/src/application/plugins/feature-assignment/store.js create mode 100644 src/renderer/src/application/plugins/grab-canvas.js create mode 100644 src/renderer/src/application/renderers/2d.js create mode 100644 src/renderer/src/application/renderers/isf.js create mode 100644 src/renderer/src/application/renderers/shader.js create mode 100644 src/renderer/src/application/renderers/shader/default-shader.js create mode 100644 src/renderer/src/application/renderers/shader/index.js create mode 100644 src/renderer/src/application/renderers/three.js create mode 100644 src/renderer/src/application/sample-modules/Ball.js create mode 100644 src/renderer/src/application/sample-modules/Bar.js create mode 100644 src/renderer/src/application/sample-modules/ChromaticAbberation.js create mode 100644 src/renderer/src/application/sample-modules/ChromaticAbberation/chromaticAbberation.frag create mode 100644 src/renderer/src/application/sample-modules/Concentrics.js create mode 100644 src/renderer/src/application/sample-modules/Counter.js create mode 100644 src/renderer/src/application/sample-modules/Cube.js create mode 100644 src/renderer/src/application/sample-modules/Fisheye.js create mode 100644 src/renderer/src/application/sample-modules/Fisheye/fisheye.frag create mode 100644 src/renderer/src/application/sample-modules/GreatBallOfFire.js create mode 100644 src/renderer/src/application/sample-modules/GridStretch.js create mode 100644 src/renderer/src/application/sample-modules/Line.js create mode 100644 src/renderer/src/application/sample-modules/MattiasCRT-2.0.js create mode 100644 src/renderer/src/application/sample-modules/MattiasCRT/mattiasCrt.frag create mode 100644 src/renderer/src/application/sample-modules/Pixelate.js create mode 100644 src/renderer/src/application/sample-modules/Plasma.js create mode 100644 src/renderer/src/application/sample-modules/Plasma/plasma.frag create mode 100644 src/renderer/src/application/sample-modules/Polygon.js create mode 100644 src/renderer/src/application/sample-modules/Smear.js create mode 100644 src/renderer/src/application/sample-modules/Text.js create mode 100644 src/renderer/src/application/sample-modules/Texture2d.js create mode 100644 src/renderer/src/application/sample-modules/Waveform.js create mode 100644 src/renderer/src/application/sample-modules/Webcam.js create mode 100644 src/renderer/src/application/sample-modules/Wobble.js create mode 100644 src/renderer/src/application/sample-modules/Wobble/wobble.frag create mode 100644 src/renderer/src/application/sample-modules/X-Drips.js create mode 100644 src/renderer/src/application/sample-modules/isf/ASCII Art.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Angular.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Auto Color Tone.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Auto Levels.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Auto Levels.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Bloom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Bloom.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Bounce.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Bow Tie Horizontal.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Bow Tie Vertical.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Boxinator.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Brick Pattern.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Bright.fs create mode 100644 src/renderer/src/application/sample-modules/isf/BrightnessContrast.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Broken LCD.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Bump Distortion.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Burn.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Butterfly Wave Scrawler.fs create mode 100644 src/renderer/src/application/sample-modules/isf/CMYK Halftone-Lookaround.fs create mode 100644 src/renderer/src/application/sample-modules/isf/CMYK Halftone.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Channel Slide.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Checkerboard.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Chroma Zoom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle Crop.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle Open.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle Splash Distortion.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle Trails.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle Warp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle Wrap Distortion.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circle.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circuits.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circular Feedback Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circular Screen.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Circular Screen.vs create mode 100644 src/renderer/src/application/sample-modules/isf/City Lights.fs create mode 100644 src/renderer/src/application/sample-modules/isf/City Lights.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Collage.fs create mode 100644 src/renderer/src/application/sample-modules/isf/CollapsingArchitecture.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Blowout.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Blowout.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Controls.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color History.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Invert.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Levels.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Monochrome.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Phase.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Posterize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Relookup.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Scales.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Schemes.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Color Test Grid.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Colour Distance.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Comet Tails.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Convergence.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Corner Color Tint.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Corner Colors.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Crazy Parametric Fun.fs create mode 100644 src/renderer/src/application/sample-modules/isf/CrossZoom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Crosshatch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Crosswarp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Cubic Warp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Deinterlace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Diagonal Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Diagonalize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Digital Clock.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Directional Warp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Directional Wipe.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Directional.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dirty Lens.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Displace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Displacement.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dither-Bayer.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Doom Screen Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Doorway.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dot Screen.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dot Screen.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Double Vision.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dreamy Zoom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dreamy.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Dual Side Scroller And Flip.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Duotone.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Echo Trace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Blowout.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Blur.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Distort.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Distort.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Trace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Edge Trace.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Edges.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Edges.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Emboss.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Emboss.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Etch-a-Sketch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Exposure Adjust.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Fade Color.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Fade Gray Scale.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Fade.fs create mode 100644 src/renderer/src/application/sample-modules/isf/False Color.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Fast Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Fast Blur.vs create mode 100644 src/renderer/src/application/sample-modules/isf/FastMosh.fs create mode 100644 src/renderer/src/application/sample-modules/isf/FastMosh.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Film Burn.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Flip H.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Flip V.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Flipbook.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Fly Eye.fs create mode 100644 src/renderer/src/application/sample-modules/isf/FractilianParabolicCircleInversion.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Freeze Frame.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Frosted Glass.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Gamma Correction.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Ghosting.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Glitch Displace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Glitch Memories.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Glitch Shifter.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Gloom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Gloom.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Glow-Fast.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Glow-Fast.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Glow.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Glow.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Graph Paper.fs create mode 100644 src/renderer/src/application/sample-modules/isf/GreatBallOfFire.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Grid Flip.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Grid Warp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/HSVtoRGB.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Hatch Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Heart Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Heart.fs create mode 100644 src/renderer/src/application/sample-modules/isf/HexVortex.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Hexagonalize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Histogram Viewer.fs create mode 100644 src/renderer/src/application/sample-modules/isf/HorizVertHold.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Hue-Saturation.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Hyperspace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Interlace Mirror.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Interlace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Inverted Page Curl.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Kaleidoscope Tile.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Kaleidoscope Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Kaleidoscope.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Key Frame Artifacts.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Layer Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Layer Position.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Lens Flare.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Lens Flare.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Life.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Life.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Line Screen.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Line Screen.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Linear Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Linear Gradient.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Lines.fs create mode 100644 src/renderer/src/application/sample-modules/isf/LogTransWarpSpiral.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Long Exposure.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Luma Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Luminance Melt.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Luminance Posterize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/MBOX3.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Maximum Component.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Median.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Meta Image.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Micro Buffer RGB.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Micro Buffer.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Minimum Component.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Mirror Edge.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Mirror Edge.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Mirror.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Morph.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Mosaic.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Multi Gradient.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Multi Hue Shift.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Multi-Pixellate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/MultiFrame 2x2.fs create mode 100644 src/renderer/src/application/sample-modules/isf/MultiFrame 3x3.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Multiply Blend.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Neon.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Neon.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Night Vision.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Noise Adapt.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Noise Displace.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Noise Pixellate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Noise.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Optical Flow Distort.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Optical Flow Distort.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Optical Flow Generator.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Perlin Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Pinch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Pinwheel.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Pixel Shifter.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Pixelize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Pixellate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Polar Function.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Polka Dots Curtain.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Poly Star.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Posterize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Power Warp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Quad Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Quad Tile.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RE RGB Gradient Generator.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGB EQ.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGB Halftone-lookaround.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGB Halftone.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGB Invert.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGB Strobe.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGB Trails 3.0.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGBA Swap.fs create mode 100644 src/renderer/src/application/sample-modules/isf/RGBtoHSV.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Radial Gradient.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Radial Replicate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Radial.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Checkerboard.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Freeze.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Lines.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Shape Blast.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Shape.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Squares Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Squares.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Random Stripes.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Replicate Random.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Replicate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Resize Glitch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Ripple Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Ripples.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Rotate Scale Fade.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Rotate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Rotate.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Saturation Bleed.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Saturation Bleed.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Sepia Tone.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Set Alpha.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Shake.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Shape Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Shape Morph Feedback Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Shape Morph Wrap.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sharpen Luminance.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sharpen Luminance.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Sharpen RGB.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sharpen RGB.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Shockwave Pulse.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Shockwave.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Show Alpha.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Side Scroller And Flip.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Simple Zoom Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sine Warp Gradient.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sine Warp Tile.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sketch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sketch.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Slice.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sliding Strips.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Slit Scan Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Slit Scan.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Smoke Screen.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Smudged Lens.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Smudged Lens.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Soft Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Soft Blur.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Soft Flip.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Solarize.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Solid Color.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sorting Smear.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Sorting Smear.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Sphere Map.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Spiral.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Squares Wire.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Squeeze.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Star.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Stereo Viewer.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Stripes.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Strobe.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Swap Transition.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Swirl.fs create mode 100644 src/renderer/src/application/sample-modules/isf/TV Static.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Time Glitch RGB.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Toon.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Trail Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Trapezoid Distortion.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Triangle Warp.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Triangle.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Triangles.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Trio Tone.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Triple Rotate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Truchet Tile.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Twirl.fs create mode 100644 src/renderer/src/application/sample-modules/isf/UltimateFlame.fs create mode 100644 src/renderer/src/application/sample-modules/isf/UltimateSpiral.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Undulating Burn Out.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Unsharp Mask.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Unsharp Mask.vs create mode 100644 src/renderer/src/application/sample-modules/isf/VHS Glitch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/VHS Glitch.fs.fs create mode 100644 src/renderer/src/application/sample-modules/isf/VU Meter.fs create mode 100644 src/renderer/src/application/sample-modules/isf/VVMotionBlur 3.0.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Vertex Manipulator.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Vertex Manipulator.vs create mode 100644 src/renderer/src/application/sample-modules/isf/Vertical Tearing.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Vibrance.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Vignette.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Water Drop.fs create mode 100644 src/renderer/src/application/sample-modules/isf/WaveLines.fs create mode 100644 src/renderer/src/application/sample-modules/isf/White Point Adjust.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Wind.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Window Blinds.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Window Slice.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Wipe Down.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Wipe Left.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Wipe Right.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Wipe Up.fs create mode 100644 src/renderer/src/application/sample-modules/isf/XYZoom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Y-C Time Blur.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Zebre.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Zoom In Circles.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Zoom.fs create mode 100644 src/renderer/src/application/sample-modules/isf/Zooming Feedback.fs create mode 100644 src/renderer/src/application/sample-modules/isf/badtv.fs create mode 100644 src/renderer/src/application/sample-modules/isf/block-color.fs create mode 100644 src/renderer/src/application/sample-modules/isf/cube.fs create mode 100644 src/renderer/src/application/sample-modules/isf/digital-crystal-tunnel.fs create mode 100644 src/renderer/src/application/sample-modules/isf/feedback.fs create mode 100644 src/renderer/src/application/sample-modules/isf/film-grain.fs create mode 100644 src/renderer/src/application/sample-modules/isf/hexagons.fs create mode 100644 src/renderer/src/application/sample-modules/isf/plasma.fs create mode 100644 src/renderer/src/application/sample-modules/isf/rgbglitchmod.fs create mode 100644 src/renderer/src/application/sample-modules/isf/rgbtimeglitch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/rotozoomer.fs create mode 100644 src/renderer/src/application/sample-modules/isf/scale.fs create mode 100644 src/renderer/src/application/sample-modules/isf/spherical-shader-tut.fs create mode 100644 src/renderer/src/application/sample-modules/isf/st_Ms2SD1.fs.fs create mode 100644 src/renderer/src/application/sample-modules/isf/st_lsfGDH.fs create mode 100644 src/renderer/src/application/sample-modules/isf/tapestryfract.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Bleach Bypass.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Crosshatch.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Dilate.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Dilate.vs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Erode.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Erode.vs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Light Leak.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Light Leak.vs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Technicolor.fs create mode 100644 src/renderer/src/application/sample-modules/isf/v002 Vignette.fs create mode 100755 src/renderer/src/application/sample-modules/isf/v002-CRT-Displacement.fs create mode 100755 src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Shadow.png create mode 100755 src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Staggered.png create mode 100755 src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Straight.png create mode 100755 src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-Scanline-Staggered.png create mode 100755 src/renderer/src/application/sample-modules/isf/v002-CRT-Mask.fs create mode 100644 src/renderer/src/application/setup-beat-detektor.js create mode 100644 src/renderer/src/application/setup-grandiose.js create mode 100644 src/renderer/src/application/setup-media.js create mode 100644 src/renderer/src/application/setup-midi.js create mode 100644 src/renderer/src/application/setup-tweens.js create mode 100644 src/renderer/src/application/use.js create mode 100644 src/renderer/src/application/utils/apply-expression.js create mode 100644 src/renderer/src/application/utils/conform-file-path.js create mode 100644 src/renderer/src/application/utils/get-next-name.js create mode 100644 src/renderer/src/application/utils/get-prop-default.js create mode 100644 src/renderer/src/application/utils/lerp.js create mode 100644 src/renderer/src/application/utils/map.js create mode 100644 src/renderer/src/application/window-handler.js create mode 100644 src/renderer/src/application/worker/audio-features.js create mode 100644 src/renderer/src/application/worker/frame-counter.js create mode 100644 src/renderer/src/application/worker/index.worker.js create mode 100644 src/renderer/src/application/worker/loop.js create mode 100644 src/renderer/src/application/worker/store/index.js create mode 100644 src/renderer/src/application/worker/store/modules/beats.js create mode 100644 src/renderer/src/application/worker/store/modules/common/swap.js create mode 100644 src/renderer/src/application/worker/store/modules/dataTypes.js create mode 100644 src/renderer/src/application/worker/store/modules/errors.js create mode 100644 src/renderer/src/application/worker/store/modules/expressions.js create mode 100644 src/renderer/src/application/worker/store/modules/fonts.js create mode 100644 src/renderer/src/application/worker/store/modules/fps.js create mode 100644 src/renderer/src/application/worker/store/modules/groups.js create mode 100644 src/renderer/src/application/worker/store/modules/images.js create mode 100644 src/renderer/src/application/worker/store/modules/inputs.js create mode 100644 src/renderer/src/application/worker/store/modules/media.js create mode 100644 src/renderer/src/application/worker/store/modules/mediaStream.js create mode 100644 src/renderer/src/application/worker/store/modules/metrics.js create mode 100644 src/renderer/src/application/worker/store/modules/meyda.js create mode 100644 src/renderer/src/application/worker/store/modules/midi.js create mode 100644 src/renderer/src/application/worker/store/modules/modules.js create mode 100644 src/renderer/src/application/worker/store/modules/ndi.js create mode 100644 src/renderer/src/application/worker/store/modules/outputs.js create mode 100644 src/renderer/src/application/worker/store/modules/plugins.js create mode 100644 src/renderer/src/application/worker/store/modules/projects.js create mode 100644 src/renderer/src/application/worker/store/modules/renderers.js create mode 100644 src/renderer/src/application/worker/store/modules/size.js create mode 100644 src/renderer/src/application/worker/store/modules/tweens.js create mode 100644 src/renderer/src/application/worker/store/modules/videos.js create mode 100644 src/renderer/src/application/worker/store/modules/windows.js create mode 100644 src/renderer/src/assets/fonts/Inter-italic.var.woff2 create mode 100644 src/renderer/src/assets/fonts/Inter-roman.var.woff2 create mode 100644 src/renderer/src/assets/fonts/iaw-mono-Bold.woff create mode 100644 src/renderer/src/assets/fonts/iaw-mono-Bold.woff2 create mode 100644 src/renderer/src/assets/fonts/iaw-mono-BoldItalic.woff create mode 100644 src/renderer/src/assets/fonts/iaw-mono-BoldItalic.woff2 create mode 100644 src/renderer/src/assets/fonts/iaw-mono-Italic.woff create mode 100644 src/renderer/src/assets/fonts/iaw-mono-Italic.woff2 create mode 100644 src/renderer/src/assets/fonts/iaw-mono-Regular.woff create mode 100644 src/renderer/src/assets/fonts/iaw-mono-Regular.woff2 create mode 100644 src/renderer/src/assets/graphics/Arrow-vertical.svg create mode 100644 src/renderer/src/assets/graphics/Arrow.svg create mode 100644 src/renderer/src/components/ABSwap.vue create mode 100644 src/renderer/src/components/ActiveModule.vue create mode 100644 src/renderer/src/components/CollapsibleRow.vue create mode 100644 src/renderer/src/components/Control.vue create mode 100644 src/renderer/src/components/Controls/CollapsibleControl.vue create mode 100644 src/renderer/src/components/Controls/ColorControl.vue create mode 100644 src/renderer/src/components/Controls/FontControl.vue create mode 100644 src/renderer/src/components/Controls/PaletteControl.vue create mode 100644 src/renderer/src/components/Controls/RangeControl.vue create mode 100644 src/renderer/src/components/Controls/TextureControl.vue create mode 100644 src/renderer/src/components/Controls/TweenControl.vue create mode 100644 src/renderer/src/components/Controls/Vec2DControl.vue create mode 100644 src/renderer/src/components/Controls/Vec2DXY.vue create mode 100644 src/renderer/src/components/Controls/Vec3Control.vue create mode 100644 src/renderer/src/components/Controls/Vec4Control.vue create mode 100644 src/renderer/src/components/Controls/VideoControl.vue create mode 100644 src/renderer/src/components/Dialog.vue create mode 100644 src/renderer/src/components/ElectronLink.vue create mode 100644 src/renderer/src/components/ErrorWatcher.vue create mode 100644 src/renderer/src/components/Gallery.vue create mode 100644 src/renderer/src/components/GalleryItem.vue create mode 100644 src/renderer/src/components/Group.vue create mode 100644 src/renderer/src/components/Groups.vue create mode 100644 src/renderer/src/components/InfoView.vue create mode 100644 src/renderer/src/components/InputConfig.vue create mode 100644 src/renderer/src/components/InputDeviceConfig/AudioVideo.vue create mode 100644 src/renderer/src/components/InputDeviceConfig/BPM.vue create mode 100644 src/renderer/src/components/InputDeviceConfig/MIDI.vue create mode 100644 src/renderer/src/components/InputDeviceConfig/NDI.vue create mode 100644 src/renderer/src/components/InputLinkComponents/AudioFeatures.vue create mode 100644 src/renderer/src/components/InputLinkComponents/Expression.vue create mode 100644 src/renderer/src/components/InputLinkComponents/MIDI.vue create mode 100644 src/renderer/src/components/InputLinkComponents/Tween.vue create mode 100644 src/renderer/src/components/ModuleControl.vue create mode 100644 src/renderer/src/components/ModuleInspector.vue create mode 100644 src/renderer/src/components/PluginControl.vue create mode 100644 src/renderer/src/components/Plugins.vue create mode 100644 src/renderer/src/components/Preview.vue create mode 100644 src/renderer/src/components/Search.vue create mode 100644 src/renderer/src/components/StatusBar/BPMDisplay.vue create mode 100644 src/renderer/src/components/StatusBar/FPSDisplay.vue create mode 100644 src/renderer/src/components/StatusBar/SizeDisplay.vue create mode 100644 src/renderer/src/components/StatusBar/StatusBarItem.vue create mode 100644 src/renderer/src/components/StatusBar/index.vue create mode 100644 src/renderer/src/components/TooltipDisplay.vue create mode 100644 src/renderer/src/components/dialogs/FrameRateDialog.vue create mode 100644 src/renderer/src/components/directives/ContextMenu.js create mode 100644 src/renderer/src/components/directives/InfoView.js create mode 100644 src/renderer/src/components/directives/Search.js create mode 100644 src/renderer/src/components/directives/ValueTooltip.js create mode 100644 src/renderer/src/components/inputs/Button.vue create mode 100644 src/renderer/src/components/inputs/Checkbox.vue create mode 100644 src/renderer/src/components/inputs/Number.vue create mode 100644 src/renderer/src/components/inputs/Range.vue create mode 100644 src/renderer/src/components/inputs/RightClickNumberInput.vue create mode 100644 src/renderer/src/components/inputs/Select.vue create mode 100644 src/renderer/src/components/inputs/TextInput.vue create mode 100644 src/renderer/src/components/inputs/Textarea.vue create mode 100644 src/renderer/src/components/inputs/index.js create mode 100644 src/renderer/src/components/mixins/has-input-link.js create mode 100644 src/renderer/src/components/mixins/input-is-focused.js create mode 100644 src/renderer/src/css/golden-layout_theme.css create mode 100644 src/renderer/src/css/iaw.css create mode 100644 src/renderer/src/css/inter.css create mode 100644 src/renderer/src/css/raster.css create mode 100644 src/renderer/src/main.js create mode 100644 src/renderer/src/menus/context/activeModuleContextMenu.js create mode 100644 src/renderer/src/menus/context/activeModuleControlContextMenu.js create mode 100644 src/renderer/src/menus/context/bpmContextMenu.js create mode 100644 src/renderer/src/menus/context/galleryItemContextMenu.js create mode 100644 src/renderer/src/menus/context/groupContextMenu.js create mode 100644 src/renderer/src/ui-store/index.js create mode 100644 src/renderer/src/ui-store/modules/dialogs.js create mode 100644 src/renderer/src/ui-store/modules/focus.js create mode 100644 src/renderer/src/ui-store/modules/infoView.js create mode 100644 src/renderer/src/ui-store/modules/search.js create mode 100644 src/renderer/src/ui-store/modules/ui-groups.js create mode 100644 src/renderer/src/ui-store/modules/ui-modules.js create mode 100644 src/renderer/src/util/composite-operations.js create mode 100644 src/renderer/src/util/font-family.js diff --git a/.editorconfig b/.editorconfig index 9d08a1a82..cf640d53f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,9 @@ -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..a6f34fea7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +out +.gitignore diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..55db58d0e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,15 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-recommended', + '@electron-toolkit', + '@vue/eslint-config-prettier' + ], + rules: { + 'vue/require-default-prop': 'off', + 'vue/multi-word-component-names': 'off' + } +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..9c6b791d5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +out +dist +pnpm-lock.yaml +LICENSE.md +tsconfig.json +tsconfig.*.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 86d085f84..940260d85 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,3 @@ { - "recommendations": [ - "vue.volar", - "esbenp.prettier-vscode", - "dbaeumer.vscode-eslint" - ] -} \ No newline at end of file + "recommendations": ["dbaeumer.vscode-eslint"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..0b6b9a649 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" + }, + "runtimeArgs": ["--sourcemap"], + "env": { + "REMOTE_DEBUGGING_PORT": "9222" + } + }, + { + "name": "Debug Renderer Process", + "port": 9222, + "request": "attach", + "type": "chrome", + "webRoot": "${workspaceFolder}/src/renderer", + "timeout": 60000, + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "Debug All", + "configurations": ["Debug Main Process", "Debug Renderer Process"], + "presentation": { + "order": 1 + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..4c05394ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/README.md b/README.md index 995b9a3dc..9da34f167 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,34 @@ -

modV

-

modV logo

+# modv-vite -

- - GitHub release (latest by date including pre-releases) - - - Documentation - - - Maintenance - - - License: MIT - - - Twitter: @_modV_ - -

-

-modV is a modular audio visualisation environment built upon web technologies. -

+An Electron application with Vue +## Recommended IDE Setup -## Download +- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) -Find the latest modV binaries available for download in the releases section. +## Project Setup -https://github.com/vcync/modV/releases/latest - - - -## Project development setup +### Install -``` -yarn +```bash +$ yarn ``` -### Compiles and hot-reloads for development +### Development +```bash +$ yarn dev ``` -yarn electron:serve -``` - -### Builds for release - -``` -yarn electron:build -``` - -### Caveats - -#### Removing a package -If you want to remove a package with `yarn remove`, make sure to run `yarn` afterwards as we are using `patch-package` without `postinstall-postinstall` because of https://github.com/vcync/modV/issues/554. +### Build +```bash +# For windows +$ yarn build:win +# For macOS +$ yarn build:mac -## Platform specifics for building and development - -### Windows - -#### Shell - -CMD or PowerShell is required as native binaries need to be compiled or fetched for Windows. WSL (Windows Subsystem for Linux) or WSL2 are not supported as they will try to compile those binaries for Ubuntu. - -#### Dependancies - -Visual Studio Tools and Python are required by node-gyp, which build native node modules. -Since Node.js 16, the official Windows installer can install them. - -If you're using nvm-windows or another headless Node.js install, you can easily install the required packages with [Chocolatey](https://chocolatey.org/install): - -``` -choco install python visualstudio2022-workload-vctools +# For Linux +$ yarn build:linux ``` - -#### 'vue-cli-service' is not recognized - -Please see issue 122 ([vcync/modv-3/issues/122#issuecomment-640100114](https://github.com/vcync/modv-3/issues/122#issuecomment-640100114)) on how to resolve this. - -### Ubuntu/Debian - -libndi is required for NDI sources and must be installed for modV to build. You can find that available to download here: [Palakis/obs-ndi/releases](https://github.com/Palakis/obs-ndi/releases) - -Last successful build was with `libndi4_4.5.1-1_amd64.deb`. - -### Other Linux flavours - -Untested. NDI is provided by grandiose, our fork is here: [vcync/grandiose](https://github.com/vcync/grandiose/) This fork of grandiose has other libndi supported platforms, however even on Ubuntu we needed the above libndi package to be installed. - -Let us know how you get on (good or bad) and we'll update the repo and docs accordingly. - - - -## Contributing - -Contributions, issues and feature requests are welcome! -Feel free to check [issues page](https://github.com/vcync/modV/issues). - - - -## Show your support - -Give a ⭐️ if this project helped you! - - - -## Acknowledgements - -Thank you to: - -- [Tim Pietrusky](https://nerddis.co/) for his continued support, help, mentorship and kindness -- [Live:JS](http://livejs.network/) for inspiration, motivation and advice -- Hugh Rawlinson, Nevo Segal and Jakub Fiala for the incredible audio analysis engine, [meyda](https://github.com/hughrawlinson/meyda) -- [Dario Villanueva](http://alolo.co/) for his advice and introduction to live visuals which inspired this whole project - - - -## License - -Copyright © 2022 [vcync](https://github.com/vcync). -This project is [MIT](https://github.com/vcync/modV/blob/main/LICENSE) licensed. diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist index 07567c8e4..38c887b21 100644 --- a/build/entitlements.mac.plist +++ b/build/entitlements.mac.plist @@ -2,15 +2,11 @@ - com.apple.security.cs.allow-unsigned-executable-memory - com.apple.security.cs.allow-jit - com.apple.security.device.audio-input - - com.apple.security.device.camera + com.apple.security.cs.allow-unsigned-executable-memory - com.apple.security.cs.disable-library-validation + com.apple.security.cs.allow-dyld-environment-variables diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..28644aa9d97942c50008d03bc0f93505f7824737 GIT binary patch literal 85649 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

-KY5Dg)0#~?~rEbJH_9`n*G*i+1Ec#;TIAa-b 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 literal 0 HcmV?d00001 diff --git a/build/icon.ico b/build/icon.ico index 5dbf90407877c14bde627e263e2b2389da0f5021..72c391ebcc280e8c34ec175fabb4337e34804b3d 100644 GIT binary patch literal 123545 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 bqOm^PqC+~3h1FmPI^vm>($fM-eXsuqO4ic& literal 59276 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

(|V7O%OOlsc*vU+P^72X!U7K_eX*CG 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 E`T9pxN5i1O>p>VAy6%lIREYyb! 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 Kl11VsAO0U5dJ04U literal 36777 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 L%#Ifu5O4k;lUXgP diff --git a/dev-app-update.yml b/dev-app-update.yml new file mode 100644 index 000000000..9e5b033cc --- /dev/null +++ b/dev-app-update.yml @@ -0,0 +1,3 @@ +provider: generic +url: https://example.com/auto-updates +updaterCacheDirName: modv-vite-updater diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 000000000..f599850ce --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,42 @@ +appId: com.electron.app +productName: modv-vite +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: modv-vite +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - snap + - deb + maintainer: electronjs.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: https://example.com/auto-updates diff --git a/electron.vite.config.mjs b/electron.vite.config.mjs new file mode 100644 index 000000000..94e2a6f3b --- /dev/null +++ b/electron.vite.config.mjs @@ -0,0 +1,35 @@ +import { resolve } from 'path' +import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import { createVuePlugin as vue } from "vite-plugin-vue2"; +import svgLoader from 'vite-svg-loader' +import { nodePolyfills } from 'vite-plugin-node-polyfills' +import rawPlugin from 'vite-raw-plugin'; + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()] + }, + preload: { + plugins: [externalizeDepsPlugin()] + }, + renderer: { + resolve: { + alias: { + '@renderer': resolve('src/renderer/src') + } + }, + plugins: [ + vue(), + svgLoader(), + nodePolyfills({ + include: ['events'] + }), + rawPlugin({ + fileRegex: /\.(glsl|vert|frag|fs|vs)$/ + }) + ], + worker: { + format: 'es' + } + } +}) diff --git a/out/main/index.js b/out/main/index.js new file mode 100644 index 000000000..f8c5b692f --- /dev/null +++ b/out/main/index.js @@ -0,0 +1,89 @@ +"use strict"; +const electron = require("electron"); +const path = require("path"); +const utils = require("@electron-toolkit/utils"); +const remoteMain = require("@electron/remote/main"); +const os = require("node:os"); +function _interopNamespaceDefault(e) { + const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } }); + if (e) { + for (const k in e) { + if (k !== "default") { + const d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: () => e[k] + }); + } + } + } + n.default = e; + return Object.freeze(n); +} +const remoteMain__namespace = /* @__PURE__ */ _interopNamespaceDefault(remoteMain); +const icon = path.join(__dirname, "../../resources/icon.png"); +remoteMain__namespace.initialize(); +function createWindow() { + const mainWindow = new electron.BrowserWindow({ + width: 900, + height: 670, + show: false, + autoHideMenuBar: true, + ...process.platform === "linux" ? { icon } : {}, + webPreferences: { + preload: path.join(__dirname, "../preload/index.js"), + sandbox: false, + nodeIntegrationInWorker: true, + nativeWindowOpen: true, + // window.open return Window object(like in regular browsers), not BrowserWindowProxy, + affinity: "main-window" + // main window, and addition windows should work in one process, + } + }); + require("@electron/remote/main").enable(mainWindow.webContents); + electron.ipcMain.handle("is-modv-ready", () => modVReady); + mainWindow.setRepresentedFilename(os.homedir()); + mainWindow.setDocumentEdited(true); + mainWindow.setTitle("Untitled"); + mainWindow.on("ready-to-show", () => { + mainWindow.show(); + }); + mainWindow.webContents.on( + "new-window", + (event, url, frameName, disposition, options) => { + if (frameName === "modal") { + event.preventDefault(); + event.newGuest = new electron.BrowserWindow({ + ...options, + autoHideMenuBar: true, + closable: false, + enableLargerThanScreen: true, + title: "" + }); + event.newGuest.removeMenu(); + } + } + ); + if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) { + mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]); + } else { + mainWindow.loadFile(path.join(__dirname, "../renderer/index.html")); + } +} +electron.app.whenReady().then(() => { + utils.electronApp.setAppUserModelId("com.electron"); + electron.app.on("browser-window-created", (_, window) => { + utils.optimizer.watchWindowShortcuts(window); + }); + electron.ipcMain.on("ping", () => console.log("pong")); + createWindow(); + electron.app.on("activate", function() { + if (electron.BrowserWindow.getAllWindows().length === 0) + createWindow(); + }); +}); +electron.app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + electron.app.quit(); + } +}); diff --git a/out/preload/index.js b/out/preload/index.js new file mode 100644 index 000000000..a01e81290 --- /dev/null +++ b/out/preload/index.js @@ -0,0 +1,38 @@ +"use strict"; +const electron = require("electron"); +const preload = require("@electron-toolkit/preload"); +const remote = require("@electron/remote"); +function _interopNamespaceDefault(e) { + const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } }); + if (e) { + for (const k in e) { + if (k !== "default") { + const d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: () => e[k] + }); + } + } + } + n.default = e; + return Object.freeze(n); +} +const remote__namespace = /* @__PURE__ */ _interopNamespaceDefault(remote); +const { vibrate } = require("hapticjs"); +const api = { + vibrate +}; +if (process.contextIsolated) { + try { + electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI); + electron.contextBridge.exposeInMainWorld("api", api); + electron.contextBridge.exposeInMainWorld("remote", remote__namespace); + } catch (error) { + console.error(error); + } +} else { + window.electron = preload.electronAPI; + window.remote = remote__namespace; + window.api = api; +} diff --git a/package.json b/package.json index ce9a87e13..bc3373196 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,31 @@ { - "name": "modv", - "productName": "modV", - "description": "modular audio visualisation powered by JavaScript", - "author": "vcync", - "version": "3.20.0", - "private": true, - "homepage": "https://modv.vcync.gl/", - "repository": { - "type": "git", - "url": "git+https://github.com/vcync/modV.git" - }, - "bugs": { - "url": "https://github.com/vcync/modV/issues" - }, + "name": "modv-vite", + "version": "1.0.0", + "description": "An Electron application with Vue", + "main": "./out/main/index.js", + "author": "example.com", + "homepage": "https://electron-vite.org", "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint", - "electron:build": "vue-cli-service electron:build", - "electron:serve": "vue-cli-service electron:serve", - "postinstall": "electron-builder install-app-deps && patch-package", - "postuninstall": "electron-builder install-app-deps", - "test:e2e": "npx playwright test" + "format": "prettier --write .", + "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", + "start": "electron-vite preview", + "dev": "electron-vite dev", + "build": "electron-vite build", + "postinstall": "electron-builder install-app-deps", + "build:unpack": "npm run build && electron-builder --dir", + "build:win": "npm run build && electron-builder --win", + "build:mac": "npm run build && electron-builder --mac", + "build:linux": "npm run build && electron-builder --linux" }, "dependencies": { + "@electron-toolkit/preload": "^3.0.0", + "@electron-toolkit/utils": "^3.0.0", "@electron/remote": "^2.1.2", - "animated-gif-detector": "^1.2.0", - "animejs": "3.2.1", + "@tupilabs/vue-lumino": "1.1.5", + "animejs": "^3.2.1", "canvas-text-wrapper": "github:cyberj/canvas-text-wrapper#master", "color": "^3.1.2", - "dotenv": "^8.2.0", - "electron-updater": "^4.3.1", - "fluent-ffmpeg": "^2.1.2", + "electron-updater": "^6.1.7", "fuse.js": "^6.2.1", "golden-layout": "^1.5.9", "grandiose": "github:vcync/grandiose#feat/workerCompatibility", @@ -41,73 +35,39 @@ "lodash.set": "^4.3.2", "mathjs": "^7.5.1", "meyda": "^5.6.0", - "mkdirp": "^0.5.1", - "npm": "6.14.6", - "nwjs-menu-browser": "^1.0.0", - "ospath": "^1.2.2", - "patch-package": "^6.2.2", "pex-context": "^2.10.2", "promise-worker": "^2.0.1", "promise-worker-transferable": "^1.0.4", - "recursive-deps": "^1.1.1", "stream-to-blob": "^2.0.0", "tap-tempo": "^0.1.1", "three": "^0.131.3", "uuid": "^9.0.0", - "vue": "^2.7.14", - "vue-class-component": "^7.2.3", + "vue": "^2.7", "vue-color": "^2.7.1", "vue-fragment": "^1.5.1", - "vue-golden-layout": "^2.1.0", - "vue-property-decorator": "^8.3.0", + "vue-golden-layout": "2.1.1", "vue-smooth-dnd": "^0.8.1", "vuex": "^3.6.2", - "vuex-persistedstate": "^4.0.0-beta.3", - "webpack": "^4.43.0", - "webpack-3": "npm:webpack@3.12.0" + "vuex-persistedstate": "^4.0.0-beta.3" }, "devDependencies": { - "@babel/core": "^7.0.0-0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@playwright/test": "^1.31.2", - "@semantic-release/git": "^9.0.0", - "@vue/cli-plugin-babel": "^5.0.8", - "@vue/cli-plugin-eslint": "^3.12.1", - "@vue/cli-service": "^5.0.8", - "@vue/eslint-config-prettier": "^4.0.1", - "@vue/runtime-dom": "^3.2.47", - "babel-eslint": "^10.0.3", - "babel-loader": "^9.1.2", - "core-js": "^3.19.1", - "electron": "29.1.5", - "electron-builder": "^22.9.1", - "electron-notarize": "^1.2.2", - "electron-playwright-helpers": "^1.5.3", - "eslint": "^5.16.0", - "eslint-plugin-no-for-each": "^0.1.14", - "eslint-plugin-vue": "^5.2.3", - "lint-staged": "^8.2.1", - "node-loader": "^0.6.0", - "playwright": "^1.31.2", - "playwright-core": "^1.31.2", - "sass-loader": "^7.3.1", - "text-loader": "0.0.1", - "vue-cli-plugin-electron-builder": "3.0.0-alpha.4", - "vue-template-babel-compiler": "^2.0.0", - "vue-template-compiler": "^2.7.14", - "vuex-localstorage": "^1.0.0" - }, - "gitHooks": { - "pre-commit": "lint-staged" - }, - "lint-staged": { - "*.{js,vue}": [ - "vue-cli-service lint", - "git add" - ] - }, - "resolutions": { - "electron-builder": "23.0.2" + "@electron-toolkit/eslint-config": "^1.0.1", + "@rushstack/eslint-patch": "^1.6.1", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/eslint-config-prettier": "^9.0.0", + "electron": "^28.2.0", + "electron-builder": "^24.9.1", + "electron-vite": "^2.0.0", + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.20.1", + "prettier": "^3.2.4", + "vite": "^5.0.12", + "vite-plugin-node-polyfills": "^0.21.0", + "vite-plugin-vue2": "^2.0.3", + "vite-raw-plugin": "^1.0.2", + "vite-svg-loader": "^5.1.0", + "vue-class-component": "^7.2.6", + "vue-property-decorator": "^9.1.2", + "vue-template-compiler": "^2.7.16" } } diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cf9e8b2c87b5c18ac0b26913af6fd3ed00ec3bfb GIT binary patch literal 35949 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

(|V7O%OOlsc*vU+P^72X!U7K_eX*CG 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 E`T9pxN5i1O>p>VAy6%lIREYyb! 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 Kl11VsAO0U5dJ04U literal 0 HcmV?d00001 diff --git a/src/main/index.js b/src/main/index.js new file mode 100644 index 000000000..2d7ef9d3e --- /dev/null +++ b/src/main/index.js @@ -0,0 +1,107 @@ +import { app, shell, BrowserWindow, ipcMain } from 'electron' +import { join } from 'path' +import { electronApp, optimizer, is } from '@electron-toolkit/utils' +import icon from '../../resources/icon.png?asset' +import * as remoteMain from '@electron/remote/main'; +import os from "node:os"; + +remoteMain.initialize(); + +function createWindow() { + // Create the browser window. + const mainWindow = new BrowserWindow({ + width: 900, + height: 670, + show: false, + autoHideMenuBar: true, + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + sandbox: false, + nodeIntegrationInWorker: true, + nativeWindowOpen: true, // window.open return Window object(like in regular browsers), not BrowserWindowProxy, + affinity: "main-window" // main window, and addition windows should work in one process, + } + }) + + require("@electron/remote/main").enable(mainWindow.webContents); + + ipcMain.handle("is-modv-ready", () => modVReady); + + mainWindow.setRepresentedFilename(os.homedir()); + mainWindow.setDocumentEdited(true); + mainWindow.setTitle("Untitled"); + + mainWindow.on('ready-to-show', () => { + mainWindow.show() + }) + + mainWindow.webContents.on( + "new-window", + (event, url, frameName, disposition, options) => { + if (frameName === "modal") { + event.preventDefault(); + event.newGuest = new BrowserWindow({ + ...options, + autoHideMenuBar: true, + closable: false, + enableLargerThanScreen: true, + title: "" + }); + + event.newGuest.removeMenu(); + } + } + ); + + // mainWindow.webContents.setWindowOpenHandler((details) => { + // shell.openExternal(details.url) + // return { action: 'deny' } + // }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + } +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(() => { + // Set app user model id for windows + electronApp.setAppUserModelId('com.electron') + + // Default open or close DevTools by F12 in development + // and ignore CommandOrControl + R in production. + // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) + + // IPC test + ipcMain.on('ping', () => console.log('pong')) + + createWindow() + + app.on('activate', function () { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) + +// In this file you can include the rest of your app"s specific main process +// code. You can also put them in separate files and require them here. diff --git a/src/main/media-manager/add-read-handler.js b/src/main/media-manager/add-read-handler.js new file mode 100644 index 000000000..3eae43eae --- /dev/null +++ b/src/main/media-manager/add-read-handler.js @@ -0,0 +1,43 @@ +import { log, logError } from "./log"; +import store from "./store"; + +const fs = require("fs"); +const path = require("path"); + +export default async function addReadHandler({ readHandler }) { + let ok = true; + + if (typeof readHandler.init === "function") { + try { + ok = await readHandler.init( + { + binaryPath: this.binaryPath, + + join: (...args) => path.join.call(args), + + exists: file => + new Promise((resolve, reject) => { + fs.access(file, fs.constants.F_OK, err => { + if (err) { + resolve(); + } else { + reject(); + } + }); + }) + }, + { + log + } + ); + } catch (e) { + ok = false; + logError(e); + } + } + + if (ok) { + await store.dispatch("readHandlers/addHandler", { readHandler }); + log(`Added ReadHandler for ${readHandler.folder}`); + } +} diff --git a/src/main/media-manager/add-save-handler.js b/src/main/media-manager/add-save-handler.js new file mode 100644 index 000000000..535d8cc94 --- /dev/null +++ b/src/main/media-manager/add-save-handler.js @@ -0,0 +1,7 @@ +import { log } from "./log"; +import store from "./store"; + +export default async function addReadHandler({ saveHandler }) { + await store.dispatch("saveHandlers/addHandler", { saveHandler }); + log(`Added SaveHandler for ${saveHandler.folder}`); +} diff --git a/src/main/media-manager/create-watcher.js b/src/main/media-manager/create-watcher.js new file mode 100644 index 000000000..9f7a9832a --- /dev/null +++ b/src/main/media-manager/create-watcher.js @@ -0,0 +1,58 @@ +import store from "./store"; +import { log } from "./log"; + +const chokidar = require("chokidar"); +const os = require("os"); + +export default function createWatcher() { + return new Promise(resolve => { + if (this.watcher) { + this.watcher.close(); + } + + const ignored = [ + os.platform() === "darwin" ? /(^|[/\\])\../ : undefined, + /node_modules/, + "**/package.json", + "**/package-lock.json" + ].concat(store.getters["readHandlers/ignored"]); + + this.watcher = chokidar.watch(this.mediaDirectoryPath, { + ignored + }); + + this.watcher + .on("add", filePath => { + log(`➕ File ${filePath} has been added`); + this.readFile(filePath); + }) + .on("change", filePath => { + log(`🔄 File ${filePath} has been changed`); + this.readFile(filePath); + }) + .on("unlink", filePath => { + log(`➖ File ${filePath} has been removed`); + this.removeFile(filePath); + }) + // .on('addDir', (changedPath) => { + // log(`➕ Directory ${changedPath} has been added`); + // const seperated = changedPath.split(path.sep); + + // if (seperated[seperated.length - 2] === this.mediaFolderName) { + // this.addProfile(seperated[seperated.length - 1]); + // } + // }) + // .on('unlinkDir', (changedPath) => { + // log(`➖ Directory ${changedPath} has been removed`); + + // const seperated = changedPath.split(path.sep); + + // if (seperated[seperated.length - 2] === this.mediaFolderName) { + // this.removeProfile(seperated[seperated.length - 1]); + // } + // }) + .on("ready", () => { + resolve(); + }); + }); +} diff --git a/src/main/media-manager/fs-create-profile.js b/src/main/media-manager/fs-create-profile.js new file mode 100644 index 000000000..275cdc683 --- /dev/null +++ b/src/main/media-manager/fs-create-profile.js @@ -0,0 +1,80 @@ +import mkdirpTop from "mkdirp"; +import path from "path"; +import { promisify } from "util"; +import store from "./store"; + +const mkdirp = promisify(mkdirpTop); + +export default async function fsCreateProfile(profileName) { + await mkdirp(path.join(this.mediaDirectoryPath, profileName)); + + const promises = [ + ...store.getters["readHandlers/folders"].map(folder => + mkdirp(path.join(this.mediaDirectoryPath, profileName, folder)) + ) + ]; + + return Promise.all(promises); + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName)); + // } catch (e) { + // throw new Error(`Could not make "${profileName}" profile directory ${e}`); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "image")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile image directory ${e}` + // ); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "video")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile video directory ${e}` + // ); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "palette")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile palette directory ${e}` + // ); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "preset")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile preset directory ${e}` + // ); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "module")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile module directory ${e}` + // ); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "plugin")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile plugin directory ${e}` + // ); + // } + + // try { + // mkdirp.sync(path.join(this.mediaDirectoryPath, profileName, "plugin_data")); + // } catch (e) { + // throw new Error( + // `Could not make "${profileName}" profile plug_data directory ${e}` + // ); + // } +} diff --git a/src/main/media-manager/index.js b/src/main/media-manager/index.js new file mode 100644 index 000000000..97800db1e --- /dev/null +++ b/src/main/media-manager/index.js @@ -0,0 +1,153 @@ +import ospath from "ospath"; + +import store from "./store"; + +import addReadHandler from "./add-read-handler"; +import addSaveHandler from "./add-save-handler"; +import createWatcher from "./create-watcher"; +import readFile from "./read-file"; +import parseMessage from "./parse-message"; +import fsCreateProfile from "./fs-create-profile"; + +import imageReadHandler from "./read-handlers/image"; +import paletteReadHandler from "./read-handlers/palette"; +import presetReadHandler from "./read-handlers/preset"; +import moduleReadHandler from "./read-handlers/module"; +import isfReadHandler from "./read-handlers/isf"; +import videoReadHandler from "./read-handlers/video"; + +import presetSaveHandler from "./save-handlers/preset"; + +import path from "path"; +import fs from "fs"; + +import { promisify } from "util"; +import mkdirpTop from "mkdirp"; + +const mkdirp = promisify(mkdirpTop); + +export default class MediaManager { + get dataPath() { + return path.join(ospath.data(), "modV"); + } + + get mediaDirectoryPath() { + return path.join(this.dataPath, "media"); + } + + constructor(options) { + const defaults = { + mediaFolderName: "media" + }; + + this.addReadHandler = addReadHandler.bind(this); + this.addSaveHandler = addSaveHandler.bind(this); + this.createWatcher = createWatcher.bind(this); + this.readFile = readFile.bind(this); + this.parseMessage = parseMessage.bind(this); + this.fsCreateProfile = fsCreateProfile.bind(this); + this.$store = store; + + this.update = () => { + if (options.update) { + options.update(...arguments); + } + }; + + this.pathChanged = () => { + if (options.pathChanged) { + options.pathChanged(...arguments); + } + }; + + Object.assign(this, defaults, options); + + this.addReadHandler({ readHandler: imageReadHandler }); + this.addReadHandler({ readHandler: paletteReadHandler }); + this.addReadHandler({ readHandler: presetReadHandler }); + this.addReadHandler({ readHandler: moduleReadHandler }); + this.addReadHandler({ readHandler: isfReadHandler }); + this.addReadHandler({ readHandler: videoReadHandler }); + + this.addSaveHandler({ saveHandler: presetSaveHandler }); + + store.subscribe(mutation => { + if (mutation.type.split("/")[0] !== "media") { + return; + } else if (mutation.type === "media/SET_MEDIA_DIRECTORY_PATH") { + this.pathChanged(mutation); + return; + } + + this.update(mutation); + }); + + (async () => { + try { + await fs.promises.access(path.join(this.mediaDirectoryPath)); + } catch (error) { + await mkdirp(this.mediaDirectoryPath); + } + + try { + await fs.promises.access(path.join(this.mediaDirectoryPath, "default")); + } catch (error) { + this.fsCreateProfile("default"); + } + })(); + } + + async start() { + await store.dispatch("media/setMediaDirectoryPath", { + path: this.mediaDirectoryPath + }); + await this.createWatcher(); + } + + async reset() { + await store.dispatch("resetAll"); + await store.dispatch("media/setMediaDirectoryPath", { + path: this.mediaDirectoryPath + }); + } + + async saveFile({ what, name, fileType, project, payload }) { + const { saveHandlers } = store.state; + + if (!name) { + throw new Error("Cannot save without a name"); + } + + if (!fileType) { + throw new Error("Cannot save without a file type"); + } + + if (!project) { + throw new Error("Cannot save without a project"); + } + + if (!saveHandlers[what]) { + throw new Error(`No save handler for "${what}"`); + } + + const handler = saveHandlers[what]; + + if (handler.fileTypes.indexOf(fileType) < -1) { + throw new Error( + `The "${what}" save handler cannot save files with a type of "${fileType}"` + ); + } + + await fs.promises.writeFile( + path.join( + this.mediaDirectoryPath, + project, + handler.folder, + `${name}.${fileType}` + ), + payload + ); + + return true; + } +} diff --git a/src/main/media-manager/log.js b/src/main/media-manager/log.js new file mode 100644 index 000000000..f951b00ba --- /dev/null +++ b/src/main/media-manager/log.js @@ -0,0 +1,27 @@ +/* eslint-disable no-console */ +const consoleLog = console.log; +const consoleError = console.error; + +function log(...argsIn) { + const delta = new Date(); + const args = []; + args.push(`[${delta.toLocaleTimeString()}]:`); + + for (let i = 0; i < argsIn.length; i += 1) { + args.push(argsIn[i]); + } + consoleLog.apply(console, args); +} + +function logError(...argsIn) { + const delta = new Date(); + const args = []; + args.push(`[${delta.toLocaleTimeString()}]:`); + + for (let i = 0; i < argsIn.length; i += 1) { + args.push(argsIn[i]); + } + consoleError.apply(console, args); +} + +export { log, logError }; diff --git a/src/main/media-manager/media-manager-utils/stream-to-string.js b/src/main/media-manager/media-manager-utils/stream-to-string.js new file mode 100644 index 000000000..185441eb2 --- /dev/null +++ b/src/main/media-manager/media-manager-utils/stream-to-string.js @@ -0,0 +1,9 @@ +// https://stackoverflow.com/a/49428486 +module.exports = function streamToString(stream) { + const chunks = []; + return new Promise((resolve, reject) => { + stream.on("data", chunk => chunks.push(Buffer.from(chunk))); + stream.on("error", err => reject(err)); + stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + }); +}; diff --git a/src/main/media-manager/parse-message.js b/src/main/media-manager/parse-message.js new file mode 100644 index 000000000..97ff98f09 --- /dev/null +++ b/src/main/media-manager/parse-message.js @@ -0,0 +1,68 @@ +import store from "./store"; +import { log } from "./log"; + +export default function parseMessage(message, connection) { + const parsed = JSON.parse(message); + log(`Received message from client: ${message}`); + + if ("request" in parsed) { + switch (parsed.request) { + default: + break; + + case "update": + this.broadcast( + JSON.stringify({ + type: "media/UPDATE", + payload: { + media: store.state.media, + plugins: store.state.plugins + } + }) + ); + break; + + case "save-option": + this.writeOptions(parsed.key, parsed.value); + break; + + case "set-folder": + this.changeDirectory(parsed.folder); + break; + + case "make-profile": + if (this.makeProfile(parsed.profileName)) { + this.getOrMakeProfile(parsed.profileName); + this.updateClients(); + } + break; + + case "save-preset": + this.writePreset( + parsed.name, + parsed.payload, + parsed.profile, + connection + ); + break; + + case "save-palette": + this.writePalette( + parsed.name, + parsed.payload, + parsed.profile, + connection + ); + break; + + case "save-plugin": + this.writePlugin( + parsed.name, + parsed.payload, + parsed.profile, + connection + ); + break; + } + } +} diff --git a/src/main/media-manager/read-file.js b/src/main/media-manager/read-file.js new file mode 100644 index 000000000..252ab25bf --- /dev/null +++ b/src/main/media-manager/read-file.js @@ -0,0 +1,64 @@ +import { log } from "./log"; +import store from "./store"; + +const fs = require("fs"); +const path = require("path"); + +export default async function readFile(filePath) { + const relativePath = filePath.replace(this.mediaDirectoryPath, ""); + const parsed = path.parse(relativePath); + + const seperated = relativePath.split(path.sep); + const project = seperated[seperated.length - 3]; + const folder = seperated[seperated.length - 2]; + + const fileType = parsed.ext.replace(".", "").toLowerCase(); + const fileName = parsed.name; + + const handlers = store.getters["readHandlers/forFileType"](folder, fileType); + if (!handlers || !handlers.length) { + return; + } + + for (let i = 0, len = handlers.length; i < len; i++) { + const handler = handlers[i]; + + const file = fs.createReadStream(filePath, { encoding: "utf8" }); + + const processResult = await handler.process( + { + file, + fileName, + fileType, + filePath + }, + { + getStream: () => {}, + log + } + ); + + if (processResult && typeof processResult === "boolean") { + store.dispatch("media/addMedia", { + project, + folder, + item: { + name: fileName, + path: relativePath + } + }); + } else if (processResult && typeof processResult === "object") { + const { filePath: path } = processResult; + const relativePath = path.replace(this.mediaDirectoryPath, ""); + + store.dispatch("media/addMedia", { + project, + folder, + item: { + name: fileName, + path: relativePath + } + }); + } + } +} diff --git a/src/main/media-manager/read-handlers/image.js b/src/main/media-manager/read-handlers/image.js new file mode 100644 index 000000000..ffc4f6a6e --- /dev/null +++ b/src/main/media-manager/read-handlers/image.js @@ -0,0 +1,156 @@ +const animated = require("animated-gif-detector"); +// const ffbinaries = require("ffbinaries"); +const Ffmpeg = require("fluent-ffmpeg"); + +/** + * @typedef {OutputFileContext} + * @property {Stream|String} file Processed file or file path + * @property {String} [folder] Folder in which to save processed file + */ + +/** + * @typedef {ReadHandler} + * @property {String} folder The folder name to watch + * @property {String} identifier An identifier for logging (usually a single emoji) + * @property {Array} folderAccess A list of folder names for Stream writing + * @property {Array} fileTypes File types to match this File Handler against + * @property {Function} init Called before File Hander is added, for setup + * @property {Function} process Function to process matched files + */ +export default { + folder: "image", + identifier: "🖼", + + // Requests stream writing to the video folder + folderAccess: ["video"], + + fileTypes: [ + // @todo regex match + "jpg", + "jpeg", + "png", + "gif" + ], + + ignored: ["processed-gifs"], + + /** + * Takes in a readable stream, processes file accordingly and outputs file location plus stream + * + * @param {Stream} options.file Readable stream of file. + * @param {String} options.fileName The file's name. + * @param {String} options.fileType The file's extension type e.g. .jpeg. + * @param {Function} util.log Log something to the console. + * + * @return {Promise} A Promise resolving with `true` if the file + * needed no modification and can remain in the + * same folder. + * A Promise resolving a `OutputFileContext` if + * the file required processing. + * A Promise rejecting with `Error` if something + * went wrong. + */ + process({ file, fileName, fileType }, { getStream, log }) { + return new Promise((resolve, reject) => { + if (fileType !== "gif" && animated(file)) { + log("Converting animated gif to video"); + + const outputStream = getStream(); + + Ffmpeg(file) + .inputFormat("gif") + .format("mp4") + .noAudio() + .videoCodec("libx264") + .on("error", err => { + reject( + new Error( + `An error occurred converting ${fileName}:`, + err.message + ) + ); + }) + .on("end", () => { + resolve({ + filePath: outputStream, + folder: "video" + }); + }) + .pipe(outputStream, { end: true }); + } else { + resolve(true); + } + + reject(new Error("Unknown error")); + }); + } + + /** + * Called before File Handler is added to the Media Manager. + * Should return a Promise resolving true or a FileOutputContext if the File Handler is ready to + * be added to the Media Manager. + * This function can be omitted if no checks or pre-requisites need to be made or fetched. + * + * @param {String} options.binaryPath Path to a common binaries folder to store binaries the + * handler may require. + * @param {Function} options.exists async Function which returns true if a file or folder + * exists, false otherwise. + * @param {Function} options.join Function to join paths together with the appropriate + * filesystem delimiter. Similar to fs.join. + * @param {Function} util.log Log something to the console. + * + * @return {Promise} + */ + // init({ binaryPath, exists, join }, { log }) { + // return new Promise(resolve => resolve(true)); + // return new Promise((resolve, reject) => { + // const ffmpegCommand = new Ffmpeg(); + + // function gotPath(path) { + // Ffmpeg.setFfmpegPath(path); + // resolve({ + // file: path, + // }); + // } + + // function download(dest, platform, destFfmpeg) { + // ffbinaries.downloadFiles({ components: ['ffmpeg'], destination: dest }, () => { + // log(`Downloaded ffmpeg for ${platform}`); + // gotPath(destFfmpeg); + // }); + // } + + // /* eslint-disable no-underscore-dangle */ + // ffmpegCommand._getFfmpegPath((err, result) => { + // if (err || result.trim() === '') { + // const dest = binaryPath; + // let destFfmpeg = join(dest, 'ffmpeg'); + // const platform = ffbinaries.detectPlatform(); + // const isWin = /^win/.test(process.platform); + + // if (isWin) destFfmpeg += '.exe'; + + // exists(destFfmpeg, (err) => { + // if (err && err.code === 'ENOENT') { + // log('‼️ ffmpeg not found locally, in environment or within path'); + + // yesno.ask(`Do you want to download ffmpeg locally for platform ${platform}? (yes)`, true, (ok) => { + // if (ok) { + // download(dest, platform, destFfmpeg); + // } else { + // reject('ffmpeg is required, user denied ffmpeg download'); + // } + // }); + // } else { + // log('ffmpeg found locally for platform', platform); + // gotPath(destFfmpeg); + // } + // }); + // } else { + // log('ffmpeg found in path or environment'); + // gotPath(result); + // } + // }); + // }); + // } +}; diff --git a/src/main/media-manager/read-handlers/isf.js b/src/main/media-manager/read-handlers/isf.js new file mode 100644 index 000000000..4eb5e3621 --- /dev/null +++ b/src/main/media-manager/read-handlers/isf.js @@ -0,0 +1,170 @@ +const fs = require("fs"); +const path = require("path"); +const util = require("util"); +const webpack = require("webpack-3"); + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); +const mkdir = util.promisify(fs.mkdir); +const streamToString = require("../media-manager-utils/stream-to-string"); + +/** + * @typedef {OutputFileContext} + * @property {Stream|String} file Processed file or file path + * @property {String} [folder] Folder in which to save processed file + */ + +/** + * @typedef {ReadHandler} + * @property {String} folder The folder name to watch + * @property {String} identifier An identifier for logging (usually a single emoji) + * @property {Array} folderAccess A list of folder names for Stream writing + * @property {Array} fileTypes File types to match this File Handler against + * @property {Function} init Called before File Hander is added, for setup + * @property {Function} process Function to process matched files + */ +export default { + folder: "isf", + identifier: "🌈", + + // Requests stream writing to the video folder + folderAccess: ["isf/compiled"], + + fileTypes: [ + // @todo regex match + "fs" + ], + + ignored: [/isf[\\/]compiled/, /isf[\\/]temp/], + + /** + * Takes in a readable stream, processes file accordingly and outputs file location plus stream + * + * @param {Stream} options.file Readable stream of file. + * @param {String} options.fileName The file's name. + * @param {String} options.fileType The file's extension type e.g. .jpeg. + * @param {String} options.filePath The path to the file. + * @param {Function} util.log Log something to the console. + * + * @return {Promise} A Promise resolving with `true` if the file + * needed no modification and can remain in the + * same folder. + * A Promise resolving a `OutputFileContext` if + * the file required processing. + * A Promise rejecting with `Error` if something + * went wrong. + */ + async process({ filePath, fileName, file }) { + return { + filePath: await compileModule({ filePath, fileName, file }), + folder: "isf/compiled" + }; + } +}; + +function compileModule({ filePath, fileName, file }) { + return new Promise(async resolve => { + // Default vertex shader from ISF that is used when the user didn't specify anything + let vertexShader = "void main() {isf_vertShaderInit();}"; + let fragmentShader; + + // Load the vertex shader + try { + vertexShader = await readFile(filePath.replace(".fs", ".vs"), "utf8"); + } catch (err) { + // Don't show warning when error is because the file doesn't exist + if (err.code !== "ENOENT") { + console.warn(err); + } + } + + // Convert the fragment shader stream into a string + try { + fragmentShader = await streamToString(file); + } catch (err) { + console.error(err); + } + + // Create the module so that modV can understand it as this is the default format + const isfModule = { + meta: { + name: fileName.replace(/(\.\/|\.fs)/g, ""), + author: "", + version: "1.0.0", + type: "isf" + }, + fragmentShader, + vertexShader + }; + + const tempFilePath = path.join( + path.dirname(filePath), + "temp", + path.basename(filePath) + ); + + const tempDirectoryPath = path.join(path.dirname(filePath), "temp"); + + try { + await mkdir(tempDirectoryPath); + } catch (err) { + if (err) { + // don't log errors if the folder already exists + if (err.code !== "EEXIST") { + console.error(err); + } + } + } + + const compiledFilePath = path.join( + path.dirname(filePath), + "compiled", + path.basename(filePath) + ); + + const compiledDirectoryPath = path.join(path.dirname(filePath), "compiled"); + + try { + await mkdir(compiledDirectoryPath); + } catch (err) { + if (err) { + // don't log errors if the folder already exists + if (err.code !== "EEXIST") { + console.error(err); + } + } + } + + const json = JSON.stringify(isfModule); + + await writeFile(tempFilePath, `export default ${json}`); + + const webpackConfig = { + entry: tempFilePath, + output: { + path: compiledDirectoryPath, + filename: path.basename(filePath), + libraryTarget: "var" + }, + resolveLoader: { + modules: ["node_modules", __dirname + "/node_modules"] + } + }; + + webpack(webpackConfig, (err, stats) => { + if (err || stats.hasErrors()) { + const statsJson = stats.toJson("minimal"); + const canada = statsJson.errors; + for (let i = 0, len = canada.length; i < len; i++) { + console.log(canada[i]); + } + console.error(err); + } + + // 3. save compiled module to user media directory + + // 4. update modv clients with new file contents (then eval in modv) + resolve(compiledFilePath); + }); + }); +} diff --git a/src/main/media-manager/read-handlers/module.js b/src/main/media-manager/read-handlers/module.js new file mode 100644 index 000000000..00d62df61 --- /dev/null +++ b/src/main/media-manager/read-handlers/module.js @@ -0,0 +1,168 @@ +const recursiveDeps = require("recursive-deps"); +const webpack = require("webpack-3"); +const path = require("path"); +const npm = require("npm"); +const fs = require("fs"); + +/** + * @typedef {OutputFileContext} + * @property {Stream|String} file Processed file or file path + * @property {String} [folder] Folder in which to save processed file + */ + +/** + * @typedef {ReadHandler} + * @property {String} folder The folder name to watch + * @property {String} identifier An identifier for logging (usually a single emoji) + * @property {Array} folderAccess A list of folder names for Stream writing + * @property {Array} fileTypes File types to match this File Handler against + * @property {Function} init Called before File Hander is added, for setup + * @property {Function} process Function to process matched files + */ +export default { + folder: "module", + identifier: "📄", + + // Requests stream writing to the video folder + folderAccess: ["module/compiled"], + + fileTypes: [ + // @todo regex match + "js" + ], + + ignored: [/module[\\/]compiled/], + + /** + * Takes in a readable stream, processes file accordingly and outputs file location plus stream + * + * @param {Stream} options.file Readable stream of file. + * @param {String} options.fileName The file's name. + * @param {String} options.fileType The file's extension type e.g. .jpeg. + * @param {String} options.filePath The path to the file. + * @param {Function} util.log Log something to the console. + * + * @return {Promise} A Promise resolving with `true` if the file + * needed no modification and can remain in the + * same folder. + * A Promise resolving a `OutputFileContext` if + * the file required processing. + * A Promise rejecting with `Error` if something + * went wrong. + */ + async process({ filePath }, { log }) { + return { + filePath: await compileModule(filePath, log), + folder: "module/compiled" + }; + } +}; + +const ensurePackageJson = ({ dirPath }) => { + const packageJsonPath = path.join(dirPath, "package.json"); + + if (!fs.existsSync(packageJsonPath)) { + fs.writeFileSync(packageJsonPath, "{}", { encoding: "utf8" }); + } +}; + +function doWebpack(filePath) { + return new Promise((resolve, reject) => { + // 2. webpack compilation + // --- + // @todo figure out a way to allow modv and local + // classes/libs (three.js/Module/Module2D/ModuleISF etc) + // to be skipped here so extra data doesn't need transferring + + const webpackConfig = { + entry: filePath, + output: { + path: path.join(path.dirname(filePath), "compiled"), + filename: path.basename(filePath), + libraryTarget: "var" + }, + resolveLoader: { + modules: ["node_modules", __dirname + "/node_modules"] + } + }; + + webpack(webpackConfig, (err, stats) => { + if (err || stats.hasErrors()) { + const statsJson = stats.toJson("minimal"); + const canada = statsJson.errors; + for (let i = 0, len = canada.length; i < len; i++) { + console.log(canada[i]); + } + reject(err); + } + + // 3. save compiled module to user media directory + + // 4. update modv clients with new file contents (then eval in modv) + resolve( + path.join(path.dirname(filePath), "compiled", path.basename(filePath)) + ); + }); + }); +} + +async function compileModule(filePath, log) { + return new Promise((resolve, reject) => { + const dirPath = path.dirname(filePath); + + // 1. install file deps + // + // Shameless clone of szymonkaliski's awesome Neutron + // https://github.com/szymonkaliski/Neutron/blob/b8523e0efa3a7cc8bf5fcafc753d3d01b3c5338c/src/index.js#L54 + recursiveDeps(filePath).then(dependencies => { + if (!dependencies.length) { + resolve(doWebpack(filePath)); + } + + ensurePackageJson({ dirPath }); + + npm.load( + { + color: false, + loglevel: "silent", + maxsockets: 1, + parseable: true, + prefix: dirPath, + progress: true, + save: true, + unicode: false + }, + err => { + if (err) { + reject(err); + } + + npm.commands.ls(dependencies, (_, data) => { + const installedDeps = Object.keys(data.dependencies).filter( + key => data.dependencies[key].missing === undefined + ); + + const missingDeps = dependencies.filter( + dep => installedDeps.indexOf(dep) < 0 + ); + + if (missingDeps.length) { + log("🛒 Installing", dependencies.join(", "), "for", filePath); + + npm.commands.install(missingDeps, err => { + if (err) { + reject(err); + } + + log("🛍 Installed!"); + resolve(doWebpack(filePath)); + }); + } else { + resolve(doWebpack(filePath)); + } + }); + } + ); + }); + }); +} diff --git a/src/main/media-manager/read-handlers/palette.js b/src/main/media-manager/read-handlers/palette.js new file mode 100644 index 000000000..f64012055 --- /dev/null +++ b/src/main/media-manager/read-handlers/palette.js @@ -0,0 +1,12 @@ +export default { + folder: "palette", + identifier: "🎨", + + fileTypes: ["json"], + + process() { + return new Promise(resolve => { + resolve(true); + }); + } +}; diff --git a/src/main/media-manager/read-handlers/preset.js b/src/main/media-manager/read-handlers/preset.js new file mode 100644 index 000000000..7d32354f4 --- /dev/null +++ b/src/main/media-manager/read-handlers/preset.js @@ -0,0 +1,12 @@ +export default { + folder: "preset", + identifier: "📜", + + fileTypes: ["json"], + + process() { + return new Promise(resolve => { + resolve(true); + }); + } +}; diff --git a/src/main/media-manager/read-handlers/video.js b/src/main/media-manager/read-handlers/video.js new file mode 100644 index 000000000..c0dca8fcc --- /dev/null +++ b/src/main/media-manager/read-handlers/video.js @@ -0,0 +1,50 @@ +/** + * @typedef {OutputFileContext} + * @property {Stream|String} file Processed file or file path + * @property {String} [folder] Folder in which to save processed file + */ + +/** + * @typedef {ReadHandler} + * @property {String} folder The folder name to watch + * @property {String} identifier An identifier for logging (usually a single emoji) + * @property {Array} folderAccess A list of folder names for Stream writing + * @property {Array} fileTypes File types to match this File Handler against + * @property {Function} init Called before File Hander is added, for setup + * @property {Function} process Function to process matched files + */ +export default { + folder: "video", + identifier: "📹", + + // Requests stream writing to the video folder + folderAccess: ["video"], + + fileTypes: [ + // @todo regex match + "mp4", + "webv" + ], + + /** + * Takes in a readable stream, processes file accordingly and outputs file location plus stream + * + * @param {Stream} options.file Readable stream of file. + * @param {String} options.fileName The file's name. + * @param {String} options.fileType The file's extension type e.g. .jpeg. + * @param {Function} util.log Log something to the console. + * + * @return {Promise} A Promise resolving with `true` if the file + * needed no modification and can remain in the + * same folder. + * A Promise resolving a `OutputFileContext` if + * the file required processing. + * A Promise rejecting with `Error` if something + * went wrong. + */ + process() { + return new Promise(resolve => { + resolve(true); + }); + } +}; diff --git a/src/main/media-manager/save-handlers/preset.js b/src/main/media-manager/save-handlers/preset.js new file mode 100644 index 000000000..7d32354f4 --- /dev/null +++ b/src/main/media-manager/save-handlers/preset.js @@ -0,0 +1,12 @@ +export default { + folder: "preset", + identifier: "📜", + + fileTypes: ["json"], + + process() { + return new Promise(resolve => { + resolve(true); + }); + } +}; diff --git a/src/main/media-manager/store/index.js b/src/main/media-manager/store/index.js new file mode 100644 index 000000000..0340050f9 --- /dev/null +++ b/src/main/media-manager/store/index.js @@ -0,0 +1,34 @@ +import Vue from "vue"; +import Vuex from "vuex"; + +import readHandlers from "./modules/read-handlers"; +import saveHandlers from "./modules/save-handlers"; +import media from "./modules/media"; +import plugins from "./modules/plugins"; + +Vue.use(Vuex); + +const modules = { + readHandlers, + saveHandlers, + media, + plugins +}; + +const store = new Vuex.Store({ + strict: false, + modules, + + actions: { + resetAll({ commit }) { + const moduleKeys = Object.keys(modules); + for (let i = 0, len = moduleKeys.length; i < len; i++) { + const moduleKey = moduleKeys[i]; + + commit(`${moduleKey}/RESET_STATE`); + } + } + } +}); + +export default store; diff --git a/src/main/media-manager/store/modules/media.js b/src/main/media-manager/store/modules/media.js new file mode 100644 index 000000000..552755fb5 --- /dev/null +++ b/src/main/media-manager/store/modules/media.js @@ -0,0 +1,100 @@ +import Vue from "vue"; + +function initialState() { + /** + * Holds processed media + * + * @type {Object} + */ + return { + media: {}, + path: null + }; +} + +const getters = { + projects: state => Object.keys(state.media).sort((a, b) => a.localeCompare(b)) +}; + +const actions = { + async addMedia({ commit }, { project, folder, item }) { + commit("ADD", { project, folder, item }); + }, + + async setState({ commit }, newState) { + const store = require("../index.js").default; + commit("CLEAR_MEDIA_STATE"); + + const projectKeys = Object.keys(newState); + for (let i = 0, len = projectKeys.length; i < len; i++) { + const projectKey = projectKeys[i]; + + const folderKeys = Object.keys(newState[projectKey]); + for (let j = 0, len = folderKeys.length; j < len; j++) { + const folderKey = folderKeys[j]; + + const items = Object.values(newState[projectKey][folderKey]); + for (let k = 0, len = items.length; k < len; k++) { + const item = items[k]; + + await store.dispatch("media/addMedia", { + project: projectKey, + folder: folderKey, + item + }); + } + } + } + + // commit("SET_STATE", newState); + }, + + setMediaDirectoryPath({ commit }, { path }) { + commit("SET_MEDIA_DIRECTORY_PATH", { path }); + } +}; + +const mutations = { + ADD(state, { project, folder, item }) { + if (!state.media[project]) { + Vue.set(state.media, project, {}); + } + + if (!state.media[project][folder]) { + Vue.set(state.media[project], folder, {}); + } + + state.media[project][folder][item.name] = item; + }, + + CLEAR_MEDIA_STATE(state) { + const stateMediaKeys = Object.keys(state.media); + for (let i = 0, len = stateMediaKeys.length; i < len; i++) { + const key = stateMediaKeys[i]; + + Vue.delete(state.media, key); + } + }, + + RESET_STATE(state) { + const s = initialState(); + const stateKeys = Object.keys(s); + for (let i = 0, len = stateKeys.length; i < len; i++) { + const key = stateKeys[i]; + + state[key] = s[key]; + } + }, + + SET_MEDIA_DIRECTORY_PATH(state, { path }) { + state.path = path; + } +}; + +export default { + namespaced: true, + state: initialState, + getters, + actions, + mutations +}; diff --git a/src/main/media-manager/store/modules/plugins.js b/src/main/media-manager/store/modules/plugins.js new file mode 100644 index 000000000..b934f89fb --- /dev/null +++ b/src/main/media-manager/store/modules/plugins.js @@ -0,0 +1,56 @@ +import Vue from "vue"; + +function initialState() { + /** + * Holds Plugins + * + * @type {Object} + */ + return { + plugins: {}, + pluginData: {} + }; +} + +const getters = {}; + +const actions = { + addMedia({ commit }, { project, folder, item }) { + return new Promise(resolve => { + commit("ADD", { project, folder, item }); + resolve(); + }); + } +}; + +const mutations = { + ADD(state, { project, folder, item }) { + if (!state[project]) { + Vue.set(state, project, {}); + } + + if (!state[project][folder]) { + Vue.set(state[project], folder, []); + } + + state[project][folder].push(item); + }, + + RESET_STATE(state) { + const s = initialState(); + const stateKeys = Object.keys(s); + for (let i = 0, len = stateKeys.length; i < len; i++) { + const key = stateKeys[i]; + + state[key] = s[key]; + } + } +}; + +export default { + namespaced: true, + state: initialState, + getters, + actions, + mutations +}; diff --git a/src/main/media-manager/store/modules/read-handlers.js b/src/main/media-manager/store/modules/read-handlers.js new file mode 100644 index 000000000..20e70762b --- /dev/null +++ b/src/main/media-manager/store/modules/read-handlers.js @@ -0,0 +1,64 @@ +import Vue from "vue"; + +function initialState() { + /** + * Holds the ReadHandlers + * + * @type {Object} + * @param {Array} folderName Name of a folder in a project. Array of ReadHandlers + * to use for that folder. + */ + return {}; +} + +const getters = { + ignored: state => + Object.values(state).reduce( + (arr, folder) => + arr.concat(folder.reduce((arr, rh) => arr.concat(rh.ignored), [])), + [] + ), + + forFileType: state => (folder, type) => + state[folder] && + state[folder].filter(rh => rh.fileTypes.indexOf(type) > -1), + + folders: state => Object.keys(state) +}; + +const actions = { + addHandler({ commit }, { readHandler }) { + return new Promise(resolve => { + commit("ADD", { folder: readHandler.folder, readHandler }); + resolve(); + }); + } +}; + +const mutations = { + ADD(state, { folder, readHandler }) { + if (!state[folder]) { + Vue.set(state, folder, []); + } + + state[folder].push(readHandler); + }, + + RESET_STATE(state) { + const s = initialState(); + const stateKeys = Object.keys(s); + for (let i = 0, len = stateKeys.length; i < len; i++) { + const key = stateKeys[i]; + + state[key] = s[key]; + } + } +}; + +export default { + namespaced: true, + state: initialState, + getters, + actions, + mutations +}; diff --git a/src/main/media-manager/store/modules/save-handlers.js b/src/main/media-manager/store/modules/save-handlers.js new file mode 100644 index 000000000..0bc6f9136 --- /dev/null +++ b/src/main/media-manager/store/modules/save-handlers.js @@ -0,0 +1,60 @@ +import Vue from "vue"; + +function initialState() { + /** + * Holds the SaveHandler + * + * @type {Object} + * @param {Array} folderName Name of a folder in a project. Array of SaveHandlers + * to use for that folder. + */ + return {}; +} + +const getters = { + ignored: state => + Object.values(state).reduce( + (arr, folder) => + arr.concat(folder.reduce((arr, sh) => arr.concat(sh.ignored), [])), + [] + ), + + forFileType: state => (folder, type) => + state[folder] && + state[folder].filter(sh => sh.fileTypes.indexOf(type) > -1), + + folders: state => Object.keys(state) +}; + +const actions = { + addHandler({ commit }, { saveHandler }) { + return new Promise(resolve => { + commit("ADD", saveHandler); + resolve(); + }); + } +}; + +const mutations = { + ADD(state, saveHandler) { + Vue.set(state, saveHandler.folder, saveHandler); + }, + + RESET_STATE(state) { + const s = initialState(); + const stateKeys = Object.keys(s); + for (let i = 0, len = stateKeys.length; i < len; i++) { + const key = stateKeys[i]; + + state[key] = s[key]; + } + } +}; + +export default { + namespaced: true, + state: initialState, + getters, + actions, + mutations +}; diff --git a/src/preload/index.js b/src/preload/index.js new file mode 100644 index 000000000..b277e670d --- /dev/null +++ b/src/preload/index.js @@ -0,0 +1,27 @@ +import { contextBridge } from 'electron' +import { electronAPI } from '@electron-toolkit/preload' +import * as remote from "@electron/remote"; +const { vibrate } = require("hapticjs"); + + +// Custom APIs for renderer +const api = { + vibrate +} + +// Use `contextBridge` APIs to expose Electron APIs to +// renderer only if context isolation is enabled, otherwise +// just add to the DOM global. +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('electron', electronAPI) + contextBridge.exposeInMainWorld('api', api) + contextBridge.exposeInMainWorld('remote', remote) + } catch (error) { + console.error(error) + } +} else { + window.electron = electronAPI + window.remote = remote + window.api = api +} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 000000000..777c35cbe --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,18 @@ + + + + + + Electron + + + + + +

+
+ + + + diff --git a/src/renderer/output-window.html b/src/renderer/output-window.html new file mode 100644 index 000000000..6e0066925 --- /dev/null +++ b/src/renderer/output-window.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue new file mode 100644 index 000000000..1be9f2e76 --- /dev/null +++ b/src/renderer/src/App.vue @@ -0,0 +1,504 @@ + + + + + + + + + + + + + diff --git a/src/renderer/src/application/constants.js b/src/renderer/src/application/constants.js new file mode 100644 index 000000000..70bc9eb68 --- /dev/null +++ b/src/renderer/src/application/constants.js @@ -0,0 +1,21 @@ +export default { + get GALLERY_GROUP_NAME() { + return "modV internal Gallery Group"; + }, + + get LAYOUT_STATE_KEY() { + return "layoutState"; + }, + + get LAYOUT_LOAD_ERROR() { + return "layoutLoadError"; + }, + + get AUDIO_BUFFER_SIZE() { + return 512; + } +}; + +export const GROUP_DISABLED = 0; +export const GROUP_ENABLED = 1; +export const GROUP_DRAW_TO_OUTPUT = 2; diff --git a/src/renderer/src/application/createWebcodecVideo.js b/src/renderer/src/application/createWebcodecVideo.js new file mode 100644 index 000000000..8002fc739 --- /dev/null +++ b/src/renderer/src/application/createWebcodecVideo.js @@ -0,0 +1,42 @@ +export function createWebcodecVideo({ id, url, textureDefinition }) { + return new Promise(async (resolve, reject) => { + const video = document.createElement("video"); + video.setAttribute("crossorigin", "anonymous"); + video.setAttribute("loop", true); + video.onerror = reject; + video.muted = true; + + video.onloadedmetadata = async () => { + const stream = video.captureStream(); + const [track] = stream.getVideoTracks(); + + // eslint-disable-next-line + const processor = new MediaStreamTrackProcessor(track); + const frameStream = processor.readable; + + // Transfer the readable stream to the worker. + // NOTE: transferring frameStream and reading it in the worker is more + // efficient than reading frameStream here and transferring VideoFrames individually. + this.store.dispatch( + "videos/assignVideoStream", + { + id, + stream: frameStream, + width: video.videoWidth || 256, + height: video.videoHeight || 256 + }, + [frameStream] + ); + + resolve({ id, video, stream }); + }; + + video.setAttribute("src", url); + video.playbackRate = textureDefinition?.options?.playbackrate; + if (textureDefinition?.options?.paused) { + video.pause(); + } else { + video.play(); + } + }); +} diff --git a/src/renderer/src/application/index.js b/src/renderer/src/application/index.js new file mode 100644 index 000000000..63b536a38 --- /dev/null +++ b/src/renderer/src/application/index.js @@ -0,0 +1,336 @@ +import PromiseWorker from "promise-worker-transferable"; +import Vue from "vue"; +import { createWebcodecVideo } from "./createWebcodecVideo"; + +const { app } = window.remote; +const { ipcRenderer } = window.electron; + + +import { + setupMedia, + enumerateDevices, + getByteFrequencyData, + getByteTimeDomainData +} from "./setup-media"; +import setupBeatDetektor from "./setup-beat-detektor"; +import setupMidi from "./setup-midi"; +import store from "./worker/store"; +import windowHandler from "./window-handler"; +import use from "./use"; +import { GROUP_ENABLED } from "./constants"; +import ModVWorker from './worker/index.worker.js?worker' + +let imageBitmap; +const imageBitmapQueue = []; + +class ModV { + _mediaStream; + _imageCapture; + setupMedia = setupMedia; + enumerateDevices = enumerateDevices; + setupBeatDetektor = setupBeatDetektor; + setupMidi = setupMidi; + windowHandler = windowHandler; + createWebcodecVideo = createWebcodecVideo; + use = use; + debug = false; + features = Vue.observable({ + energy: 0, + rms: 0, + zcr: 0, + spectralCentroid: 0, + spectralFlatness: 0, + spectralSlope: 0, + spectralRolloff: 0, + spectralSpread: 0, + spectralSkewness: 0, + spectralKurtosis: 0, + perceptualSpread: 0, + perceptualSharpness: 0 + }); + videos = {}; + + _store = store; + store = { + state: store.state + }; + + constructor() { + let resolver = null; + this.ready = new Promise(resolve => { + resolver = resolve; + }); + this.$worker = new ModVWorker(); + this.$asyncWorker = new PromiseWorker(this.$worker); + + this.$worker.postMessage({ + type: "__dirname", + payload: app.getAppPath() + }); + + this.$worker.addEventListener("message", async e => { + const message = e.data; + const { type } = message; + + // if ( + // type !== "metrics/SET_FPS_MEASURE" && + // type !== "modules/UPDATE_ACTIVE_MODULE_PROP" && + // type !== "beats/SET_BPM" && + // type !== "beats/SET_KICK" && + // type !== "tick" + // ) { + // console.log(`⚙️%c ${type}`, "color: red"); + // } + + if (type === "createWebcodecVideo") { + const videoContext = await this.createWebcodecVideo(message); + this.videos[videoContext.id] = videoContext; + } + + if (type === "removeWebcodecVideo") { + const { video, stream } = this.videos[message.id]; + video.src = ""; + // eslint-disable-next-line no-for-each/no-for-each + stream.getTracks().forEach(track => track.stop()); + delete this.videos[message.id]; + } + + if (e.data.type === "tick" && this.ready) { + this.tick(e.data.payload); + return; + } + + if (type === "worker-setup-complete") { + // Make the default group + this.store.dispatch("groups/createGroup", { enabled: GROUP_ENABLED }); + resolver(); + ipcRenderer.send("modv-ready"); + return; + } + + if (Array.isArray(message)) { + return; + } + const payload = e.data.payload ? JSON.parse(e.data.payload) : undefined; + + if (type === "commitQueue") { + for (let i = 0; i < payload.length; i++) { + const commit = payload[i]; + store.commit(commit.type, commit.payload); + } + } else { + store.commit(type, payload); + } + }); + + const that = this; + + this.store = { + state: store.state, + getters: store.getters, + + commit(...args) { + return that.$worker.postMessage( + { + type: "commit", + identifier: args[0], + payload: args[1] + }, + args[2] + ); + }, + + dispatch(...args) { + return that.$asyncWorker.postMessage( + { + __async: true, + type: "dispatch", + identifier: args[0], + payload: args[1] + }, + args[2] + ); + } + }; + + + window.addEventListener("beforeunload", () => true); + } + + async setup(canvas = document.createElement("canvas")) { + this.windowHandler(); + + this.enumerateFonts(); + + try { + await this.setupMedia({ useDefaultDevices: true }); + } catch (e) { + console.error(e); + } + + // listen to mediastream device changes + navigator.mediaDevices.ondevicechange = () => { + this.enumerateDevices(); + }; + + try { + await this.setupMidi(); + } catch (e) { + console.error(e); + } + + this.setupBeatDetektor(); + + this.canvas = canvas; + const offscreen = this.canvas.transferControlToOffscreen(); + + this.$worker.postMessage( + { + type: "canvas", + where: "output", + payload: offscreen + }, + [offscreen] + ); + + this.store.dispatch("windows/createWindow"); + + ipcRenderer.on("media-manager-state", (event, message) => { + this.store.dispatch("media/setState", message); + }); + + ipcRenderer.on("media-manager-update", (event, message) => { + this.store.dispatch("media/addMedia", message.payload); + }); + + ipcRenderer.on("media-manager-path-changed", (event, message) => { + this.store.dispatch("media/setMediaDirectoryPath", message.payload); + }); + + ipcRenderer.on("open-preset", (event, message) => { + this.loadPreset(message); + }); + + ipcRenderer.on("generate-preset", async () => { + ipcRenderer.send("preset-data", await this.generatePreset()); + }); + + ipcRenderer.on("set-current-project", async (event, message) => { + await this.store.dispatch("projects/setCurrentProject", message); + ipcRenderer.send( + "current-project", + this.store.state.projects.currentProject + ); + }); + + ipcRenderer.on("create-output-window", () => { + this.store.dispatch("windows/createWindow"); + }); + + ipcRenderer.on("input-update", (event, { moduleId, prop, data }) => { + this.store.dispatch("modules/updateProp", { + moduleId, + prop, + data + }); + }); + + ipcRenderer.send("get-media-manager-state"); + + window.addEventListener("beforeunload", () => { + ipcRenderer.send("modv-destroy"); + }); + } + + async inputLoop() { + if ( + this._imageCapture && + this._imageCapture.track.readyState === "live" && + !this._imageCapture.track.muted + ) { + try { + imageBitmap = await this._imageCapture.grabFrame(); + } catch (e) { + if (e) { + console.error(e, e.message, this._imageCapture.track.readyState); + } + } + + if ( + imageBitmap && + imageBitmap.width && + imageBitmap.height && + !imageBitmapQueue.length + ) { + imageBitmapQueue.push(imageBitmap); + } + + while (imageBitmapQueue.length) { + const bitmap = imageBitmapQueue.splice(0, 1)[0]; + + this.$worker.postMessage({ type: "videoFrame", payload: bitmap }, [ + bitmap + ]); + } + } + } + + loop(delta) { + const { + meyda: { features: featuresToGet } + } = this.store.state; + + const features = this.meyda?.get(featuresToGet); + + if (features) { + this.updateBeatDetektor && this.updateBeatDetektor(delta, features); + features.byteFrequencyData = Array.from(getByteFrequencyData() || []); + features.byteTimeDomainData = Array.from(getByteTimeDomainData() || []); + this.$worker.postMessage({ type: "meyda", payload: features }); + + for (let i = 0; i < featuresToGet.length; i += 1) { + const feature = featuresToGet[i]; + this.features[feature] = features[feature]; + } + } + } + + setSize({ width, height }) { + this.store.dispatch("size/setSize", { width, height }); + } + + tick(delta) { + this.loop(delta); + this.inputLoop(delta); + } + + async generatePreset() { + return await this.$asyncWorker.postMessage({ + __async: true, + type: "generatePreset" + }); + } + + loadPreset(filePathToPreset) { + this.$worker.postMessage({ type: "loadPreset", payload: filePathToPreset }); + } + + async enumerateFonts() { + const localFonts = await window.queryLocalFonts(); + const fonts = []; + + for (let i = 0; i < localFonts.length; i += 1) { + const { family, fullName, postscriptName, style } = localFonts[i]; + + fonts.push({ family, fullName, postscriptName, style }); + + // No need to await here, async loading is fine. + // The user can't use fonts fonts immediately at this stage, so no need to block the thread + document.fonts.load(`14px ${postscriptName}`, fullName); + } + + this.store.commit("fonts/SET_LOCAL_FONTS", fonts); + } +} + +export default new ModV(); diff --git a/src/renderer/src/application/install-plugin.js b/src/renderer/src/application/install-plugin.js new file mode 100644 index 000000000..67be0c4fa --- /dev/null +++ b/src/renderer/src/application/install-plugin.js @@ -0,0 +1,45 @@ +import store from "./worker/store"; +import uiStore from "../ui-store"; +import Vue from "vue"; + +function camelize(str) { + return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => { + if (+match === 0) { + return ""; + } // or if (/\s+/.test(match)) for white spaces + return index === 0 ? match.toLowerCase() : match.toUpperCase(); + }); +} + +export default function installPlugin(plugin) { + if (!("name" in plugin)) { + throw new Error("Plugin requires a name"); + } + + store.dispatch("plugins/add", plugin); + + if ("store" in plugin) { + const storeName = plugin.storeName || camelize(plugin.name); + store.registerModule(storeName, plugin.store); + } + + if ("uiStore" in plugin) { + const uiStoreName = plugin.uiStoreName || camelize(plugin.name); + uiStore.registerModule(uiStoreName, plugin.uiStore); + } + + if ("galleryTabComponent" in plugin) { + Vue.component(plugin.galleryTabComponent.name, plugin.galleryTabComponent); + } + + if ("controlPanelComponent" in plugin) { + Vue.component( + plugin.controlPanelComponent.name, + plugin.controlPanelComponent + ); + } + + if ("install" in plugin) { + plugin.install(Vue, store, uiStore); + } +} diff --git a/src/renderer/src/application/plugins/feature-assignment/index.js b/src/renderer/src/application/plugins/feature-assignment/index.js new file mode 100644 index 000000000..2010bd47a --- /dev/null +++ b/src/renderer/src/application/plugins/feature-assignment/index.js @@ -0,0 +1,38 @@ +import featureStoreModule from "./store"; + +export default { + name: "Feature Assignment", + store: featureStoreModule, + + preProcessFrame({ features, store }) { + if (!features) { + return; + } + + const availableFeatures = Object.keys(features); + const availableFeaturesLength = availableFeatures.length; + + for (let i = 0; i < availableFeaturesLength; ++i) { + const feature = availableFeatures[i]; + if (featureStoreModule.state[feature]) { + const featureData = features[feature]; + const modulesToUpdate = Object.keys(featureStoreModule.state[feature]); + const modulesToUpdateLength = modulesToUpdate.length; + + for (let j = 0; j < modulesToUpdateLength; ++j) { + const moduleId = modulesToUpdate[j]; + const props = featureStoreModule.state[feature][moduleId]; + + for (let k = 0; k < props.length; ++k) { + const prop = props[k]; + store.dispatch("modules/updateProp", { + moduleId, + prop, + data: featureData + }); + } + } + } + } + } +}; diff --git a/src/renderer/src/application/plugins/feature-assignment/store.js b/src/renderer/src/application/plugins/feature-assignment/store.js new file mode 100644 index 000000000..8280e0204 --- /dev/null +++ b/src/renderer/src/application/plugins/feature-assignment/store.js @@ -0,0 +1,36 @@ +const state = { + feature: { + moduleId: ["prop"] + } +}; + +const mutations = { + ADD_FEATURE_ASSIGNMENT(state, { moduleId, prop, feature }) { + if (!state[feature]) { + state[feature] = {}; + } + + if (!state[feature][moduleId]) { + state[feature][moduleId] = []; + } + + state[feature][moduleId].push(prop); + }, + + REMOVE_FEATURE_ASSIGNMENT(state, { moduleId, prop, feature }) { + if (!state[feature] || !state[feature][moduleId]) { + return; + } + + const index = state[feature][moduleId].indexOf(prop); + if (index > -1) { + state[feature][moduleId].splice(index, 1); + } + } +}; + +export default { + namespaced: true, + state, + mutations +}; diff --git a/src/renderer/src/application/plugins/grab-canvas.js b/src/renderer/src/application/plugins/grab-canvas.js new file mode 100644 index 000000000..f1e7d485f --- /dev/null +++ b/src/renderer/src/application/plugins/grab-canvas.js @@ -0,0 +1,186 @@ +const mappingCanvas = new OffscreenCanvas(1, 1); +mappingCanvas.title = "mappingCanvas"; + +let timeout = 0; +let connection = undefined; +let outputContext = null; +let reconnect = false; + +const mappingContext = mappingCanvas.getContext("2d", { + // Boolean that indicates if the canvas contains an alpha channel. + // If set to false, the browser now knows that the backdrop is always opaque, + // which can speed up drawing of transparent content and images. + // (lights don't have an alpha channel, so let's drop it) + alpha: false, + desynchronized: true, + imageSmoothingEnabled: false, + willReadFrequently: true +}); + +export default { + name: "Grab Canvas", + props: { + mappingWidth: { + type: "int", + default: 7, + min: 1, + max: 1024, + step: 1, + abs: true + }, + + mappingHeight: { + type: "int", + default: 7, + min: 1, + max: 1024, + step: 1, + abs: true + }, + + url: { + type: "text", + default: "ws://localhost:3006/modV" + }, + + reconnectAfter: { + type: "int", + default: 4000, + min: 1000, + max: 60000, + step: 1, + abs: true + }, + + shouldReconnect: { + type: "bool", + default: true + } + }, + + async init({ store, props }) { + if (!outputContext) { + outputContext = await store.dispatch("outputs/getAuxillaryOutput", { + name: "Fixture Canvas", + group: "Plugins", + canvas: mappingCanvas, + context: mappingContext, + reactToResize: false + }); + } + + mappingCanvas.width = props.mappingWidth; + mappingCanvas.height = props.mappingHeight; + + reconnect = props.shouldReconnect; + + this.setupSocket(props); + }, + + shutdown() { + this.stopReconnect(); + this.closeConnection(); + }, + + /** + * Create a WebSocket for example to luminave + */ + setupSocket(props) { + const { url, reconnectAfter } = props; + + // Close an old connection + this.closeConnection(); + + // Create a new connection + connection = new WebSocket(url); + + // Listen for errors (e.g. could not connect) + connection.addEventListener("error", event => { + console.error("grab-canvas: WebSocket: Error:", event); + + // Reconnect is allowed + if (reconnect) { + // Reconnect after a specific amount of time + timeout = setTimeout(() => { + this.setupSocket(props); + }, reconnectAfter); + } + }); + + // Connection is opened + connection.addEventListener("open", () => { + console.info("grab-canvas: WebSocket: Opened"); + }); + + connection.addEventListener("close", () => { + console.info("grab-canvas: WebSocket: Closed"); + }); + }, + + /** + * Close the WebSocket connection and stop reconnecting + */ + closeConnection() { + clearTimeout(timeout); + + if (connection !== undefined) { + connection.close(); + } + + connection = undefined; + }, + + /** + * Stop reconnecting to WebSocket + */ + stopReconnect() { + reconnect = false; + clearTimeout(timeout); + }, + + postProcessFrame({ canvas, props }) { + mappingContext.clearRect(0, 0, canvas.width, canvas.height); + mappingContext.drawImage( + canvas, + 0, + 0, + canvas.width, + canvas.height, + 0, + 0, + props.mappingWidth, + props.mappingHeight + ); + + const imageData = mappingContext.getImageData( + 0, + 0, + props.mappingWidth, + props.mappingHeight + ); + const { data } = imageData; + const arrayData = Array.from(data); + const rgbArray = arrayData.filter((value, index) => (index + 1) % 4 !== 0); + + this.send(rgbArray); + }, + + /** + * Send data to WebSocket if connection is established + * @param {Object} data + */ + send(data) { + // Connection is established + if (connection !== undefined && connection.readyState === 1) { + const message = { + _type: "modV", + colors: data + }; + + const messageString = JSON.stringify(message, null, 2); + + // Send JSON message + connection.send(messageString); + } + } +}; diff --git a/src/renderer/src/application/renderers/2d.js b/src/renderer/src/application/renderers/2d.js new file mode 100644 index 000000000..d216a163a --- /dev/null +++ b/src/renderer/src/application/renderers/2d.js @@ -0,0 +1,99 @@ +import store from "../worker/store"; + +const twoDCanvas = new OffscreenCanvas(300, 300); +const twoDContext = twoDCanvas.getContext("2d"); +store.dispatch("outputs/addAuxillaryOutput", { + name: "2d-buffer", + context: twoDContext, + group: "buffer" +}); + +/** + * Called each frame to update the Module + * @param {Object} Module A 2D Module + * @param {HTMLCanvas} canvas The Canvas to draw to + * @param {CanvasRenderingContext2D} context The Context of the Canvas + * @param {HTMLVideoElement} video The video stream requested by modV + * @param {Array} meydaFeatures Requested Meyda features + * @param {Meyda} meyda The Meyda instance + * (for Windowing functions etc.) + * + * @param {DOMHighResTimeStamp} delta Timestamp returned by requestAnimationFrame + * @param {Number} bpm The detected or tapped BPM + * @param {Boolean} kick Indicates if BeatDetektor detected a kick in + * the audio stream + */ +function render({ + module, + canvas, + context, + video, + features, + meyda, + delta, + bpm, + kick, + props, + data +}) { + if ( + twoDCanvas.width !== canvas.width || + twoDCanvas.height !== canvas.height + ) { + twoDCanvas.width = canvas.width; + twoDCanvas.height = canvas.height; + } + + twoDContext.clearRect(0, 0, canvas.width, canvas.height); + twoDContext.drawImage(canvas, 0, 0, canvas.width, canvas.height); + + twoDContext.save(); + module.draw({ + canvas: twoDCanvas, + context: twoDContext, + video, + features, + meyda, + delta, + bpm, + kick, + props, + data + }); + twoDContext.restore(); + + context.drawImage(twoDCanvas, 0, 0, canvas.width, canvas.height); +} + +/** + * Called each frame to update the Module + */ +function updateModule({ + moduleDefinition, + props, + data, + canvas, + context, + delta +}) { + const { data: dataUpdated } = moduleDefinition.update({ + props, + data, + canvas, + context, + delta + }); + + return dataUpdated ?? data; +} + +function resizeModule({ moduleDefinition, canvas, data, props }) { + return moduleDefinition.resize({ canvas, data, props }); +} + +function resize({ width, height }) { + twoDCanvas.width = width; + twoDCanvas.height = height; +} + +export default { render, resize, updateModule, resizeModule }; diff --git a/src/renderer/src/application/renderers/isf.js b/src/renderer/src/application/renderers/isf.js new file mode 100644 index 000000000..1d9bb0d2c --- /dev/null +++ b/src/renderer/src/application/renderers/isf.js @@ -0,0 +1,225 @@ +import store from "../worker/store"; + +import { + Renderer as ISFRenderer, + Parser as ISFParser, + Upgrader as ISFUpgrader +} from "interactive-shader-format/src/main.js"; +import { getFeatures } from "../worker/audio-features"; +import constants from "../constants"; + +const isfCanvas = new OffscreenCanvas(300, 300); +const isfContext = isfCanvas.getContext("webgl2", { + antialias: true, + desynchronized: true, + powerPreference: "high-performance", + premultipliedAlpha: false +}); +store.dispatch("outputs/addAuxillaryOutput", { + name: "isf-buffer", + context: isfContext, + group: "buffer" +}); + +const renderers = {}; +const inputs = {}; + +function resize({ width, height }) { + isfCanvas.width = width; + isfCanvas.height = height; +} + +function render({ module, canvas, context, pipeline, props }) { + const renderer = renderers[module.meta.name]; + const moduleInputs = inputs[module.meta.name]; + + // Only update the audio data if the module has audio inputs to improve performance + // for modules that don't use any audio inputs at all + if (renderer.hasAudio) { + const features = getFeatures(); + const byteFrequencyData = features.byteFrequencyData; + const byteTimeDomainData = features.byteTimeDomainData; + + renderer.audio.setFrequencyValues(byteFrequencyData, byteFrequencyData); + renderer.audio.setTimeDomainValues(byteTimeDomainData, byteTimeDomainData); + } + + if (moduleInputs) { + for (let i = 0, len = moduleInputs.length; i < len; i++) { + const input = moduleInputs[i]; + + if (input.TYPE === "image") { + if (input.NAME in props) { + const resolvedTexture = + (props[input.NAME] && props[input.NAME].value) || canvas; + renderer.setValue(input.NAME, resolvedTexture); + } else { + renderer.setValue(input.NAME, canvas); + } + } else if (input.TYPE === "event") { + renderer.setValue(input.NAME, !!props[input.NAME]); + } else { + renderer.setValue(input.NAME, props[input.NAME]); + } + } + } + + isfContext.clear(isfContext.COLOR_BUFFER_BIT); + renderer.draw(isfCanvas); + + if (pipeline) { + context.clearRect(0, 0, canvas.width, canvas.height); + } + context.drawImage(isfCanvas, 0, 0, canvas.width, canvas.height); +} + +/** + * Called each frame to update the Module + */ +function updateModule({ module, props, data, canvas, context, delta }) { + const { data: dataUpdated } = module.update({ + props, + data, + canvas, + context, + delta + }); + + return dataUpdated ?? data; +} + +function resizeModule({ moduleDefinition, canvas, data, props }) { + return moduleDefinition.resize({ canvas, data, props }); +} + +async function setupModule(moduleDefinition) { + let fragmentShader = moduleDefinition.fragmentShader; + let vertexShader = moduleDefinition.vertexShader; + + const parser = new ISFParser(); + parser.parse(fragmentShader, vertexShader); + if (parser.error) { + throw new Error(`Parsing error: ${parser.error}`); + } + + if (parser.isfVersion < 2) { + fragmentShader = ISFUpgrader.convertFragment(fragmentShader); + if (vertexShader) { + vertexShader = ISFUpgrader.convertVertex(vertexShader); + } + } + + moduleDefinition.meta.isfVersion = parser.isfVersion; + moduleDefinition.meta.author = parser.metadata.CREDIT; + moduleDefinition.meta.description = parser.metadata.DESCRIPTION; + moduleDefinition.meta.version = parser.metadata.VSN; + + const renderer = new ISFRenderer(isfContext, { + useWebAudio: false, + fftSize: constants.AUDIO_BUFFER_SIZE, + hasAudio: parser.hasAudio + }); + renderer.loadSource(fragmentShader, vertexShader); + + if (!renderer.valid) { + throw renderer.errorWithCorrectedLines; + } + + function addProp(name, prop) { + if (!moduleDefinition.props) { + moduleDefinition.props = {}; + } + + moduleDefinition.props[name] = prop; + } + + const moduleInputs = parser.inputs; + for (let i = 0, len = moduleInputs.length; i < len; i++) { + const input = moduleInputs[i]; + + switch (input.TYPE) { + default: + break; + + case "float": + addProp(input.NAME, { + type: "float", + label: input.LABEL || input.NAME, + default: typeof input.DEFAULT !== "undefined" ? input.DEFAULT : 0.0, + min: input.MIN, + max: input.MAX, + step: 0.01 + }); + break; + + case "bool": + addProp(input.NAME, { + type: "bool", + label: input.LABEL || input.NAME, + default: Boolean(input.DEFAULT) + }); + break; + + case "long": + addProp(input.NAME, { + type: "enum", + label: input.NAME, + enum: input.VALUES.map((value, idx) => ({ + label: input.LABELS[idx], + value, + selected: value === input.DEFAULT + })) + }); + break; + + case "color": + addProp(input.NAME, { + type: "vec4", + label: input.LABEL || input.NAME, + default: input.DEFAULT + }); + break; + + case "point2D": + addProp(input.NAME, { + type: "vec2", + label: input.LABEL || input.NAME, + default: input.DEFAULT || [0.0, 0.0], + min: input.MIN, + max: input.MAX + }); + break; + + case "image": + moduleDefinition.meta.previewWithOutput = true; + + addProp(input.NAME, { + type: "texture", + label: input.LABEL || input.NAME + }); + + break; + + case "event": + addProp(input.NAME, { + type: "event", + label: input.LABEL || input.NAME + }); + break; + } + } + + renderers[moduleDefinition.meta.name] = renderer; + inputs[moduleDefinition.meta.name] = moduleInputs; + moduleDefinition.draw = render; + + return moduleDefinition; +} + +export default { + setupModule, + render, + updateModule, + resizeModule, + resize +}; diff --git a/src/renderer/src/application/renderers/shader.js b/src/renderer/src/application/renderers/shader.js new file mode 100644 index 000000000..cf0e60d46 --- /dev/null +++ b/src/renderer/src/application/renderers/shader.js @@ -0,0 +1,2 @@ +import * as shader from "./shader/index"; +export default shader; diff --git a/src/renderer/src/application/renderers/shader/default-shader.js b/src/renderer/src/application/renderers/shader/default-shader.js new file mode 100644 index 000000000..25358556c --- /dev/null +++ b/src/renderer/src/application/renderers/shader/default-shader.js @@ -0,0 +1,83 @@ +export default { + v: ` + precision mediump float; + + attribute vec2 position, a_position, a_texCoord; + + varying vec2 vUv; + varying vec2 fragCoord; + + void main() { + vUv = vec2(1.0 - position.x, position.y); + fragCoord = vec2(1.0 - position.x, position.y); + gl_Position = vec4(1.0 - 2.0 * position, 0, 1); + }`, + + f: ` + precision mediump float; + + uniform vec3 iResolution; // viewport resolution (in pixels) + uniform float iGlobalTime; // shader playback time (in seconds) + uniform float iTimeDelta; // render time (in seconds) + uniform float iTime; // render time (in seconds) + uniform int iFrame; // shader playback frame + uniform float iChannelTime[4]; // channel playback time (in seconds) + uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) + uniform vec4 iDate; // (year, month, day, time in seconds) + uniform sampler2D iChannel0; // Texture #1 + uniform sampler2D iChannel1; // Texture #2 + uniform sampler2D iChannel2; // Texture #3 + uniform sampler2D iChannel3; // Texture #4 + uniform sampler2D u_modVCanvas; // modV's canvas + uniform bool u_kick; // beatdetektor kick + uniform sampler2D u_fft; // fft texture + uniform float u_fftResolution; // fft texture width + + + varying vec2 vUv; + + void main() { + gl_FragColor=vec4(vUv.x,0.5+0.5*cos(iGlobalTime/1.1),0.5+0.5*sin(iGlobalTime),1.0); + }`, + + v300: `#version 300 es + precision mediump float; + in vec2 position; + uniform vec3 iResolution; + uniform float iTime; + out vec2 vUv; + void main() { + vUv = vec2(1.0 - position.x, position.y); + gl_Position = vec4(1.0 - 2.0 * position, 0, 1); + }`, + + fWrap: `#version 300 es + precision mediump float; + uniform vec3 iResolution; // viewport resolution (in pixels) + uniform float iGlobalTime; // shader playback time (in seconds) + uniform float iTimeDelta; // render time (in seconds) + uniform float iTime; // render time (in seconds) + uniform int iFrame; // shader playback frame + uniform float iChannelTime[4]; // channel playback time (in seconds) + uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) + uniform vec4 iDate; // (year, month, day, time in seconds) + uniform sampler2D iChannel0; // Texture #1 + uniform sampler2D iChannel1; // Texture #2 + uniform sampler2D iChannel2; // Texture #3 + uniform sampler2D iChannel3; // Texture #4 + uniform sampler2D u_modVCanvas; // modV's canvas + uniform bool u_kick; // beatdetektor kick + uniform sampler2D u_fft; // fft texture + uniform float u_fftResolution; // fft texture width + + in vec2 vUv; + out vec4 outColor; + + %MAIN_IMAGE_INJECT% + void main() { + vec4 image; + mainImage(image, gl_FragCoord.xy); + image.a = 1.; + outColor = image; + }` +}; diff --git a/src/renderer/src/application/renderers/shader/index.js b/src/renderer/src/application/renderers/shader/index.js new file mode 100644 index 000000000..99511bc63 --- /dev/null +++ b/src/renderer/src/application/renderers/shader/index.js @@ -0,0 +1,231 @@ +import createContext from "pex-context"; +import { frames } from "../../worker/frame-counter"; +import store from "../../worker/store"; +import defaultShader from "./default-shader"; + +const shaderCanvas = new OffscreenCanvas(300, 300); +const shaderContext = shaderCanvas.getContext("webgl2", { + antialias: true, + desynchronized: true, + powerPreference: "high-performance", + premultipliedAlpha: false +}); + +store.dispatch("outputs/addAuxillaryOutput", { + name: "shader-buffer", + context: shaderContext, + group: "buffer" +}); + +const pex = createContext({ gl: shaderContext }); + +const a_position = pex.vertexBuffer([-1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1]); +const indices = pex.indexBuffer([0, 1, 2, 3, 4, 5]); + +const commands = {}; + +let canvasTexture; +let fftTexture; + +const clearCmd = { + pass: pex.pass({ + clearColor: [0, 0, 0, 1], + clearDepth: 1 + }) +}; + +function resize({ width, height }) { + shaderCanvas.width = width; + shaderCanvas.height = height; + + // pex.set({ + // width, + // height + // }); +} + +function generateUniforms(canvasTexture, uniforms, kick = false) { + const { width, height, dpr } = store.state.size; + + const date = new Date(); + const time = performance.now(); + const resolution = [width, height, dpr]; + + const defaults = { + iGlobalTime: time / 1000, + iFrame: frames(), + iDate: [date.getFullYear(), date.getMonth(), date.getDay(), time / 1000], + iTime: time / 1000, + iTimeDelta: time / 1000, + iResolution: resolution, + iChannel0: canvasTexture, + iChannel1: canvasTexture, + iChannel2: canvasTexture, + iChannel3: canvasTexture, + iChannelResolution: [resolution, resolution, resolution, resolution], + u_modVCanvas: canvasTexture, + u_fft: fftTexture, + u_fftResolution: fftTexture ? fftTexture.width : 1, + u_delta: time / 1000, + u_time: time, + u_kick: kick + }; + + return { ...uniforms, ...defaults }; +} + +function makeProgram(moduleDefinition) { + return new Promise(resolve => { + let vert = moduleDefinition.vertexShader; + let frag = moduleDefinition.fragmentShader; + + if (!vert) { + vert = defaultShader.v; + } + + if (!frag) { + frag = defaultShader.f; + } + + if (frag.search("gl_FragColor") < 0) { + frag = defaultShader.fWrap.replace(/(%MAIN_IMAGE_INJECT%)/, frag); + + vert = defaultShader.v300; + } + + const pipeline = pex.pipeline({ + depthTest: true, + vert, + frag + }); + + const shaderUniforms = {}; + + if (moduleDefinition.props) { + const modulePropsKeys = Object.keys(moduleDefinition.props); + const modulePropsKeysLength = modulePropsKeys.length; + + for (let i = 0; i < modulePropsKeysLength; i++) { + const key = modulePropsKeys[i]; + + if (moduleDefinition.props[key].type === "texture") { + shaderUniforms[key] = moduleDefinition.props[key].value; + } else { + shaderUniforms[key] = moduleDefinition.props[key]; + } + } + } + + const uniforms = generateUniforms(canvasTexture, shaderUniforms); + + const command = { + pipeline, + attributes: { + a_position, + position: a_position + }, + indices, + uniforms + }; + + commands[moduleDefinition.meta.name] = command; + + resolve(moduleDefinition); + }); +} + +async function setupModule(moduleDefinition) { + try { + return await makeProgram(moduleDefinition); + } catch (e) { + throw new Error(e); + } +} + +function render({ module, props, canvas, context, pipeline, kick, fftCanvas }) { + resize({ width: canvas.width, height: canvas.height }); + + if (!canvasTexture) { + canvasTexture = pex.texture2D({ + data: canvas.data || canvas, + width: canvas.width, + height: canvas.height, + pixelFormat: pex.PixelFormat.RGBA8, + encoding: pex.Encoding.Linear, + min: pex.Filter.Linear, + mag: pex.Filter.Linear, + wrap: pex.Wrap.Repeat + }); + fftTexture = pex.texture2D({ + data: fftCanvas.data || fftCanvas, + width: fftCanvas.width, + height: 1, + pixelFormat: pex.PixelFormat.RGBA8, + encoding: pex.Encoding.Linear, + wrap: pex.Wrap.Repeat + }); + } else { + pex.update(canvasTexture, { + width: canvas.width, + height: canvas.height, + data: canvas.data || canvas + }); + pex.update(fftTexture, { + width: fftCanvas.width, + height: 1, + data: fftCanvas.data || fftCanvas + }); + } + + const shaderUniforms = {}; + + if (props) { + const modulePropsKeys = Object.keys(props); + const modulePropsKeysLength = modulePropsKeys.length; + + for (let i = 0; i < modulePropsKeysLength; i++) { + const key = modulePropsKeys[i]; + + if (module.props[key].type === "texture") { + shaderUniforms[key] = props[key].value; + } else { + shaderUniforms[key] = props[key]; + } + } + } + + const uniforms = generateUniforms(canvasTexture, shaderUniforms, kick); + + const command = commands[module.meta.name]; + + pex.submit(clearCmd); + pex.submit(command, { + uniforms, + viewport: [0, 0, canvas.width, canvas.height] + }); + + // clear context if we're in pipeline mode + if (pipeline) { + context.clearRect(0, 0, canvas.width, canvas.height); + } + + // Copy Shader Canvas to Main Canvas + context.drawImage(shaderCanvas, 0, 0, canvas.width, canvas.height); +} + +/** + * Called each frame to update the Module + */ +function updateModule({ module, props, data, canvas, context, delta }) { + const { data: dataUpdated } = module.update({ + props, + data, + canvas, + context, + delta + }); + + return dataUpdated ?? data; +} + +export { setupModule, render, resize, updateModule }; diff --git a/src/renderer/src/application/renderers/three.js b/src/renderer/src/application/renderers/three.js new file mode 100644 index 000000000..68ca85629 --- /dev/null +++ b/src/renderer/src/application/renderers/three.js @@ -0,0 +1,198 @@ +import store from "../worker/store"; +import * as THREEimport from "three/build/three.module.js"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; + +const THREE = { ...THREEimport, GLTFLoader }; +const threeModuleData = {}; + +const threeCanvas = new OffscreenCanvas(300, 300); +const threeContext = threeCanvas.getContext("webgl2", { + antialias: true, + desynchronized: true, + powerPreference: "high-performance", + premultipliedAlpha: false +}); + +store.dispatch("outputs/addAuxillaryOutput", { + name: "three-buffer", + context: threeContext, + group: "buffer" +}); + +const renderer = new THREE.WebGLRenderer({ + alpha: true, + antialias: false, + canvas: threeCanvas, + powerPreference: "high-performance", + premultipliedAlpha: false +}); +renderer.setPixelRatio(1); + +const inputTextureCanvas = new OffscreenCanvas(300, 300); +const inputTextureContext = inputTextureCanvas.getContext("2d"); +store.dispatch("outputs/addAuxillaryOutput", { + name: "three-inputTexture-buffer", + context: inputTextureContext, + group: "buffer" +}); + +const inputTexture = new THREE.CanvasTexture(inputTextureCanvas); + +/** + * Called each frame to update the Module + * @param {Object} Module A three Module + * @param {HTMLCanvas} canvas The Canvas to draw to + * @param {WebGL2RenderingContext} context The Context of the Canvas + * @param {HTMLVideoElement} video The video stream requested by modV + * @param {Array} features Requested Meyda features + * @param {Meyda} meyda The Meyda instance + * (for Windowing functions etc.) + * + * @param {DOMHighResTimeStamp} delta Timestamp returned by requestAnimationFrame + * @param {Number} bpm The detected or tapped BPM + * @param {Boolean} kick Indicates if BeatDetektor detected a kick in + * the audio stream + */ +function render({ + module, + canvas, + context, + video, + features, + meyda, + delta, + bpm, + kick, + props, + data, + fftCanvas, + pipeline +}) { + inputTextureContext.drawImage(canvas, 0, 0, canvas.width, canvas.height); + inputTexture.image = inputTextureCanvas.transferToImageBitmap(); + inputTexture.needsUpdate = true; + + const { scene, camera } = threeModuleData[module.meta.name]; + + module.draw({ + THREE, + inputTexture, + canvas, + video, + features, + meyda, + delta, + bpm, + kick, + props, + data: { ...data }, + scene, + camera, + fftCanvas + }); + + renderer.render(scene, camera); + + // clear context if we're in pipeline mode + if (pipeline) { + context.clearRect(0, 0, canvas.width, canvas.height); + } + + // Copy three Canvas to Main Canvas + context.drawImage(threeCanvas, 0, 0, canvas.width, canvas.height); +} + +/** + * Called each frame to update the Module + */ +function updateModule({ + moduleDefinition, + props, + data, + canvas, + context, + delta +}) { + const { scene, camera } = threeModuleData[moduleDefinition.meta.name]; + + const { + data: dataUpdated, + scene: sceneUpdated, + camera: cameraUpdated + } = moduleDefinition.update({ + THREE, + props, + data, + canvas, + context, + delta, + scene, + camera + }); + + threeModuleData[moduleDefinition.meta.name] = { + scene: sceneUpdated ?? scene, + camera: cameraUpdated ?? camera + }; + + return dataUpdated ?? data; +} + +async function setupModule(moduleDefinition) { + const { data, scene, camera } = await moduleDefinition.setupThree({ + THREE, + inputTexture, + data: moduleDefinition.data || {}, + width: renderer.domElement.width, + height: renderer.domElement.height + }); + + threeModuleData[moduleDefinition.meta.name] = { + scene, + camera + }; + + moduleDefinition.data = data; + + return moduleDefinition; +} + +function resizeModule({ moduleDefinition, canvas, data, props }) { + const { scene, camera } = threeModuleData[moduleDefinition.meta.name]; + return moduleDefinition.resize({ canvas, data, props, camera, scene }); +} + +function removeModule(module) { + delete threeModuleData[module.meta.name]; +} + +function createPresetData(module) { + return module.data; +} + +function loadPresetData(module, data) { + threeModuleData[module.meta.name] = data; +} + +function resize({ width, height }) { + inputTextureCanvas.width = width; + inputTextureCanvas.height = height; + renderer.setSize(width, height, false); +} + +function getModuleData(name) { + return threeModuleData[name]; +} + +export default { + render, + updateModule, + resize, + resizeModule, + setupModule, + removeModule, + createPresetData, + loadPresetData, + getModuleData +}; +export { threeModuleData }; diff --git a/src/renderer/src/application/sample-modules/Ball.js b/src/renderer/src/application/sample-modules/Ball.js new file mode 100644 index 000000000..4af71ff22 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Ball.js @@ -0,0 +1,230 @@ +// import Meyda from 'meyda'; + +export default { + meta: { + name: "Ball", + author: "2xAA", + version: "1.0.1", + audioFeatures: ["zcr", "rms"], + type: "2d" + }, + + props: { + amount: { + label: "Amount", + type: "int", + min: 1, + max: 300, + default: 10, + // default: [1, 10, 20], + // random: true, + strict: true + }, + + speed: { + label: "Speed", + type: "float", + min: 0, + max: 20, + step: 0.01, + default: 2 + }, + + wrap: { + label: "Wrap", + type: "bool", + default: false + }, + + size: { + label: "Size", + type: "int", + min: 1, + max: 50, + step: 1, + default: 2, + abs: true + }, + + intensity: { + label: "RMS/ZCR Intensity", + type: "int", + min: 0, + max: 30, + step: 1, + default: 15, + abs: true + }, + + soundType: { + label: "RMS (unchecked) / ZCR (checked)", + type: "bool", + default: false + }, + + color: { + type: "tween", + component: "PaletteControl", + default: { + data: [ + [0, 0, 0], + [255, 255, 255] + ], + duration: 10000, + easing: "linear" + } + } + }, + + data: { + soundType: false, // false RMS, true ZCR + intensity: 1, // Half max + analysed: 0, + baseSize: 1, + size: 2, + color: [255, 0, 0, 1], + speed: 1, + balls: [] + }, + + init({ data, canvas }) { + data.balls = this.setupBalls(canvas); + return data; + }, + + resize({ data, canvas }) { + data.balls = this.setupBalls(canvas); + return data; + }, + + update({ data, props, canvas }) { + const { amount, speed, wrap } = props; + + for (let i = 0; i < amount; i += 1) { + this.updateBall({ + ball: data.balls[i], + speed, + wrap, + canvas, + radius: props.size + }); + } + + return data; + }, + + draw({ data, props, context, features }) { + let analysed; + + if (props.soundType) { + analysed = (features.zcr / 10) * props.intensity; + } else { + analysed = features.rms * 10 * props.intensity; + } + + const color = props.color.value; + for (let i = 0; i < props.amount; i += 1) { + this.drawBall({ ball: data.balls[i], color, context, analysed }); + } + }, + + setupBalls(canvas) { + const balls = []; + for (let i = 0; i < 300; i += 1) { + const ball = this.ballFactory({ + positionX: Math.floor(Math.random() * canvas.width + 1), + positionY: Math.floor(Math.random() * canvas.height + 1), + directionX: Math.round(Math.random()), + directionY: Math.round(Math.random()) + }); + + balls.push(ball); + } + + return balls; + }, + + ballFactory({ + positionX, + positionY, + directionX, + directionY, + speed = 0, + radius = 0 + }) { + return { + radius, + speed, + position: { x: positionX, y: positionY }, + direction: { x: directionX, y: directionY } + }; + }, + + updateBall({ canvas: { width, height }, ball, speed, radius, wrap }) { + const { + position: { x, y }, + direction: { x: directionX, y: directionY } + } = ball; + + ball.radius = radius; + + let dx = speed; + let dy = speed; + + if (directionX) { + dx = -dx; + } + + if (directionY) { + dy = -dy; + } + + if (wrap) { + if (ball.position.x - radius < 1) { + ball.position.x = width - radius; + } + + if (ball.position.y - radius < 1) { + ball.position.y = height - radius; + } + + if (ball.position.x + radius > width) { + ball.position.x = radius; + } + + if (ball.position.y + radius > height) { + ball.position.y = radius; + } + } else { + if (x + dx > width - radius || x + dx < radius) { + ball.direction.x = !ball.direction.x; + } + + if (y + dy > height - radius || y + dy < radius) { + ball.direction.y = !ball.direction.y; + } + } + + ball.position.x += dx; + ball.position.y += dy; + + return ball; + }, + + drawBall({ ball, color, context, analysed }) { + context.beginPath(); + context.fillStyle = `rgb(${Math.round(color[0])},${Math.round( + color[1] + )},${Math.round(color[2])})`; + context.arc( + ball.position.x, + ball.position.y, + Math.round(ball.radius + ball.radius * analysed), + 0, + 2 * Math.PI, + true + ); + context.fill(); + context.closePath(); + } +}; diff --git a/src/renderer/src/application/sample-modules/Bar.js b/src/renderer/src/application/sample-modules/Bar.js new file mode 100644 index 000000000..7e068e311 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Bar.js @@ -0,0 +1,71 @@ +export default { + meta: { + name: "Bar", + author: "2xAA", + type: "2d" + }, + + props: { + rotation: { + type: "float", + default: 0, + min: 0, + max: 360 + }, + + thickness: { + type: "float", + default: 20, + min: 0, + max: 200 + }, + + strobe: { + type: "int", + default: 0, + min: 0, + max: 240 + }, + + color1: { + type: "color", + default: { + r: 0, + g: 0, + b: 0, + a: 1 + } + }, + + color2: { + type: "color", + default: { + r: 1, + g: 1, + b: 1, + a: 1 + } + } + }, + + draw({ canvas: { width, height }, context, delta, props }) { + const { thickness, strobe, color1, color2, rotation } = props; + + context.translate(width / 2, height / 2); + context.rotate((rotation * Math.PI) / 180); + context.translate(-width / 2, -height / 2); + + context.lineWidth = thickness; + context.beginPath(); + context.moveTo(0, height / 2); + context.lineTo(width, height / 2); + if (Math.round(delta) % strobe < Math.round(strobe / 2)) { + context.strokeStyle = `rgba(${color1.r * 255},${color1.g * + 255},${color1.b * 255},${color1.a})`; + } else { + context.strokeStyle = `rgba(${color2.r * 255},${color2.g * + 255},${color2.b * 255},${color2.a})`; + } + context.stroke(); + } +}; diff --git a/src/renderer/src/application/sample-modules/ChromaticAbberation.js b/src/renderer/src/application/sample-modules/ChromaticAbberation.js new file mode 100644 index 000000000..a4470e79b --- /dev/null +++ b/src/renderer/src/application/sample-modules/ChromaticAbberation.js @@ -0,0 +1,40 @@ +import chromaticAbberationFrag from "./ChromaticAbberation/chromaticAbberation.frag"; + +export default { + meta: { + name: "Chromatic Abberation", + author: "2xAA", + version: "1.0.0", + previewWithOutput: true, + meyda: [], // returned variables passed to the shader individually as uniforms + type: "shader" + }, + fragmentShader: chromaticAbberationFrag, + + props: { + rOffset: { + type: "float", + label: "Red Offset", + min: 1.0, + max: 2.0, + step: 0.001, + default: 1.0 + }, + gOffset: { + type: "float", + label: "Green Offset", + min: 1.0, + max: 2.0, + step: 0.001, + default: 1.015 + }, + bOffset: { + type: "float", + label: "Blue Offset", + min: 1.0, + max: 2.0, + step: 0.001, + default: 1.03 + } + } +}; diff --git a/src/renderer/src/application/sample-modules/ChromaticAbberation/chromaticAbberation.frag b/src/renderer/src/application/sample-modules/ChromaticAbberation/chromaticAbberation.frag new file mode 100644 index 000000000..93296caf0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/ChromaticAbberation/chromaticAbberation.frag @@ -0,0 +1,28 @@ +// from here: https://www.shadertoy.com/view/MtXXDr + +precision mediump float; +uniform float rOffset; //1.0 +uniform float gOffset; //1.015 +uniform float bOffset; //1.03 + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec3 refractiveIndex = vec3(rOffset, gOffset, bOffset); + vec2 uv = vUv; + vec2 normalizedTexCoord = vec2(2.0, 2.0) * uv - vec2(1.0, 1.0); // [0, 1] -> [-1, 1] + vec3 texVec = vec3(normalizedTexCoord, 1.0); + vec3 normalVec = vec3(0.0, 0.0, -1.0); + vec3 redRefractionVec = refract(texVec, normalVec, refractiveIndex.r); + vec3 greenRefractionVec = refract(texVec, normalVec, refractiveIndex.g); + vec3 blueRefractionVec = refract(texVec, normalVec, refractiveIndex.b); + vec2 redTexCoord = ((redRefractionVec / redRefractionVec.z).xy + vec2(1.0, 1.0)) / vec2(2.0, 2.0); + vec2 greenTexCoord = ((greenRefractionVec / greenRefractionVec.z).xy + vec2(1.0, 1.0)) / vec2(2.0, 2.0); + vec2 blueTexCoord = ((blueRefractionVec / blueRefractionVec.z).xy + vec2(1.0, 1.0)) / vec2(2.0, 2.0); + + fragColor = vec4 + ( + texture(u_modVCanvas, redTexCoord).r, + texture(u_modVCanvas, greenTexCoord).g, + texture(u_modVCanvas, blueTexCoord).b, + texture(u_modVCanvas, vUv).a + ); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/Concentrics.js b/src/renderer/src/application/sample-modules/Concentrics.js new file mode 100644 index 000000000..86cef54ec --- /dev/null +++ b/src/renderer/src/application/sample-modules/Concentrics.js @@ -0,0 +1,158 @@ +// import Meyda from 'meyda'; + +export default { + meta: { + name: "Concentrics", + author: "2xAA", + version: "1.0.0", + audioFeatures: ["zcr", "rms"], + type: "2d" + }, + + props: { + rms: { + type: "bool", + variable: "", + label: "Use RMS", + default: true + }, + + intensity: { + type: "float", + label: "RMS/ZCR Intensity", + min: 0, + max: 30, + default: 1 + }, + + spacing: { + type: "float", + label: "Circle Spacing", + min: 0, + max: 100, + default: 5 + }, + + objectDistance: { + type: "float", + label: "Object Distance", + min: 0, + max: 200, + default: 40 + }, + + strokeWeight: { + type: "float", + label: "Stroke Weight", + min: 1, + max: 20, + default: 1, + strict: true + } + }, + + data: { + circles: [] + }, + + init({ canvas, data }) { + const circles = []; + const x = canvas.width / 2; + const y = canvas.height / 2; + + circles.push(this.circlesObjectFactory({ x, y })); + circles.push(this.circlesObjectFactory({ x, y })); + + data.circles = circles; + + return data; + }, + + update({ canvas, data, delta, props }) { + const { width, height } = canvas; + const widthHalf = width / 2; + const heightHalf = height / 2; + + this.updateCircles({ + circles: data.circles[0], + x: widthHalf + Math.sin(delta / 1000) * props.objectDistance, + y: heightHalf + Math.cos(delta / 1000) * (props.objectDistance / 2) + }); + + this.updateCircles({ + circles: data.circles[1], + x: widthHalf + -Math.sin(delta / 1000) * props.objectDistance, + y: heightHalf + -Math.cos(delta / 1000) * (props.objectDistance / 2) + }); + + return data; + }, + + draw({ context, features, data, props }) { + const { strokeWeight, spacing, rms, intensity } = props; + let amp = features.zcr; + if (rms) { + amp = features.rms; + } + + amp *= intensity; + if (rms) { + amp *= 50; + } + + this.drawCircles({ + circles: data.circles[0], + context, + amp, + strokeWeight, + spacing + }); + + this.drawCircles({ + circles: data.circles[1], + context, + amp, + strokeWeight, + spacing + }); + }, + + circlesObjectFactory({ x, y }) { + return { + hue: Math.round(Math.random() * 360), + x, + y + }; + }, + + updateCircles({ circles, x, y }) { + if (circles.hue > 360) { + circles.hue = 0; + } else { + circles.hue += 0.2; + } + + circles.x = x; + circles.y = y; + + return circles; + }, + + drawCircles({ circles, context, amp, strokeWeight, spacing }) { + context.lineWidth = strokeWeight; + context.strokeStyle = `hsl(${circles.hue}, 50%, 50%)`; + + for (let i = 0; i < amp; i += 1) { + if (i === amp - 1) { + context.strokeStyle = `hsl(${circles.hue}, 50%, ${(1 - + (amp - Math.round(amp))) * + 50}%)`; + } + + context.beginPath(); + context.arc(circles.x, circles.y, i * spacing, 0, 2 * Math.PI); + context.closePath(); + context.stroke(); + } + } +}; diff --git a/src/renderer/src/application/sample-modules/Counter.js b/src/renderer/src/application/sample-modules/Counter.js new file mode 100644 index 000000000..4e2d041a9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Counter.js @@ -0,0 +1,35 @@ +export default { + meta: { + name: "Counter", + type: "2d" + }, + + props: { + input1: { + default: 0.5, + min: 0, + max: 1, + type: "float" + } + }, + + data: { + counter: 0 + }, + + multiply(value, multiplier) { + return value * multiplier; + }, + + update({ data, props }) { + data.counter += this.multiply(props.input1, 2); + return data; + }, + + draw({ data, context, canvas: { width, height } }) { + context.fillStyle = "white"; + context.fillRect(0, 0, width, height); + context.fillStyle = "black"; + context.fillText(data.counter, width / 2, height / 2); + } +}; diff --git a/src/renderer/src/application/sample-modules/Cube.js b/src/renderer/src/application/sample-modules/Cube.js new file mode 100644 index 000000000..f91184a0e --- /dev/null +++ b/src/renderer/src/application/sample-modules/Cube.js @@ -0,0 +1,119 @@ +export default { + meta: { + name: "Cube", + author: "2xAA", + type: "three" + }, + + props: { + rotation: { + type: "vec3", + default: [0, 0, 0], + min: 0, + max: 1 + }, + + scale: { + type: "vec3", + default: [1, 1, 1], + min: 0, + max: 1 + }, + + position: { + type: "vec3", + default: [0, 0, 0], + min: 0, + max: 1 + }, + + color: { + type: "color", + default: { + r: 1, + g: 1, + b: 1, + a: 1 + } + }, + + useMap: { + type: "bool", + default: false + } + }, + + data: { + cubeMesh: null + }, + + setupThree({ THREE, data, width, height, inputTexture }) { + const camera = new THREE.PerspectiveCamera(40, width / height, 1, 1000); + camera.position.z = 5; + + const scene = new THREE.Scene(); + scene.background = null; + + const light = new THREE.AmbientLight(0xffffff); + scene.add(light); + + const pointLight = new THREE.PointLight(0xffffff, 1, 100); + pointLight.position.set(50, 50, 50); + scene.add(pointLight); + + const geometry = new THREE.BoxGeometry(1, 1, 1); + const material = new THREE.MeshStandardMaterial({ + color: "#ffffff", + roughness: 0.351, + map: inputTexture + }); + const cubeMesh = new THREE.Mesh(geometry, material); + + scene.add(cubeMesh); + data.cubeMesh = cubeMesh; + + return { data, camera, scene }; + }, + + resize({ canvas: { width, height }, camera }) { + camera.aspect = width / height; + camera.updateProjectionMatrix(); + }, + + draw({ + THREE, + data, + props: { + scale, + position, + rotation, + color: { r, g, b }, + useMap + }, + inputTexture + }) { + data.cubeMesh.position.x = position[0]; + data.cubeMesh.position.y = position[1]; + data.cubeMesh.position.z = position[2]; + + data.cubeMesh.scale.x = scale[0]; + data.cubeMesh.scale.y = scale[1]; + data.cubeMesh.scale.z = scale[2]; + + data.cubeMesh.rotation.x = rotation[0] * 360 * THREE.Math.DEG2RAD; + data.cubeMesh.rotation.y = rotation[1] * 360 * THREE.Math.DEG2RAD; + data.cubeMesh.rotation.z = rotation[2] * 360 * THREE.Math.DEG2RAD; + + if (useMap && !data.cubeMesh.material.map) { + data.cubeMesh.material.map = inputTexture; + data.cubeMesh.material.needsUpdate = true; + } else if (!useMap && data.cubeMesh.material.map) { + data.cubeMesh.material.map = undefined; + data.cubeMesh.material.needsUpdate = true; + } + + data.cubeMesh.material.color.r = r; + data.cubeMesh.material.color.g = g; + data.cubeMesh.material.color.b = b; + } +}; diff --git a/src/renderer/src/application/sample-modules/Fisheye.js b/src/renderer/src/application/sample-modules/Fisheye.js new file mode 100644 index 000000000..12e43c2d3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Fisheye.js @@ -0,0 +1,24 @@ +import fragmentShader from "./Fisheye/fisheye.frag"; + +export default { + meta: { + name: "Fisheye", + type: "shader", + version: "1.0.0", + author: "???", + previewWithOutput: true + }, + + fragmentShader, + + props: { + aperture: { + type: "float", + label: "Aperture", + default: 180.0, + min: 1.0, + max: 360.0, + step: 0.5 + } + } +}; diff --git a/src/renderer/src/application/sample-modules/Fisheye/fisheye.frag b/src/renderer/src/application/sample-modules/Fisheye/fisheye.frag new file mode 100644 index 000000000..1e4df4f25 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Fisheye/fisheye.frag @@ -0,0 +1,47 @@ +// Inspired by the "Angular Fisheye à la Bourke" sketch from +// Jonathan Cremieux, as shown in the OpenProcessing website: +// http://openprocessing.org/visuals/?visualID=12140 +// Using the inverse transform of the angular fisheye as +// explained in Paul Bourke's website: +// http://paulbourke.net/miscellaneous/domefisheye/fisheye/ + +precision mediump float; + +uniform sampler2D u_modVCanvas; +uniform float aperture; + +varying vec2 vUv; + +const float PI = 3.1415926535; + +void main(void) { + float apertureHalf = 0.5 * aperture * (PI / 180.0); + + // This factor ajusts the coordinates in the case that + // the aperture angle is less than 180 degrees, in which + // case the area displayed is not the entire half-sphere. + float maxFactor = sin(apertureHalf); + + vec2 pos = 2.0 * vUv - 1.0; + + float l = length(pos); + if (l > 1.0) { + gl_FragColor = vec4(0, 0, 0, 1); + } else { + float x = maxFactor * pos.x; + float y = maxFactor * pos.y; + + float n = length(vec2(x, y)); + + float z = sqrt(1.0 - n * n); + + float r = atan(n, z) / PI; + + float phi = atan(y, x); + + float u = r * cos(phi) + 0.5; + float v = r * sin(phi) + 0.5; + + gl_FragColor = texture2D(u_modVCanvas, vec2(u, v)); + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/GreatBallOfFire.js b/src/renderer/src/application/sample-modules/GreatBallOfFire.js new file mode 100644 index 000000000..55fe65044 --- /dev/null +++ b/src/renderer/src/application/sample-modules/GreatBallOfFire.js @@ -0,0 +1,249 @@ +export default { + meta: { + name: "Great Ball Of Fire", + author: ":)", + version: "1.0.0", + type: "isf" + }, + fragmentShader: `/*{ + "CREDIT": "by mojovideotech", + "CATEGORIES": [ + "generator", + "fire" + ], + "INPUTS": [ + { + "NAME": "offset", + "TYPE": "point2D", + "MAX": [ + 1, + 1 + ], + "MIN": [ + -1, + -1 + ] + }, + { + "NAME": "rotation", + "TYPE": "float", + "DEFAULT": 0, + "MIN": -2, + "MAX": 2 + }, + { + "NAME": "size", + "TYPE": "float", + "DEFAULT": 2.5, + "MIN": 1, + "MAX": 4 + }, + { + "NAME": "depth", + "TYPE": "float", + "DEFAULT": 0.1, + "MIN": 0.03, + "MAX": 0.15 + }, + { + "NAME": "density", + "TYPE": "float", + "DEFAULT": 3.15, + "MIN": 0.1, + "MAX": 4 + }, + { + "NAME": "rateX", + "TYPE": "float", + "DEFAULT": 3, + "MIN": -9, + "MAX": 9 + }, + { + "NAME": "rateY", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": -9, + "MAX": 9 + }, + { + "NAME": "rateZ", + "TYPE": "float", + "DEFAULT": 1, + "MIN": -9, + "MAX": 9 + } + ], + "DESCRIPTION": "GreatBallOfFire" + }*/ + + + /////////////////////////////////////////// + // GreatBallOfFire by mojovideotech + // + // based on : + // + // glslsandbox.com e#30176.0 + // Fireball by @AlexWDunn + // + // Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + /////////////////////////////////////////// + + #define saturate(oo) clamp(oo, 0.0, 1.0) + #define MarchSteps 4 + #define Radius 1.5 + #define NoiseSteps 4 + #define Color1 vec4(1.0, 1.0, 1.0, 1.0) + #define Color2 vec4(1.0, 0.8, 0.2, 1.0) + #define Color3 vec4(1.0, 0.03, 0.0, 1.0) + #define Color4 vec4(0.4, 0.02, 0.02, 1.0) + + + vec3 mod196(vec3 x) { return x - floor(x * (1.0 / 196.0)) * 196.0; } + vec4 mod196(vec4 x) { return x - floor(x * (1.0 / 196.0)) * 196.0; } + vec4 permute(vec4 x) { return mod196(((x*56.0)+1.0)*x); } + vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; } + + float snoise(vec3 v) + { + const vec2 C = vec2(1.0/6.0, 1.0/3.0); + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + i = mod196(i); + vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + + float n_ = 0.142857142857; + vec3 ns = n_ * D.wyz - D.xzx; + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_); + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + + return 35.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3))); + } + + float Turbulence(vec3 position, float minFreq, float maxFreq, float qWidth) + { + float value = 0.0; + float cutoff = clamp(0.5/qWidth, 0.0, maxFreq); + float fade; + float fOut = minFreq; + for(int i=NoiseSteps ; i>=0 ; i--) + { + if(fOut >= 0.5 * cutoff) break; + fOut *= 2.0; + value += abs(snoise(position * fOut))/fOut; + } + fade = clamp(2.0 * (cutoff-fOut)/cutoff, 0.0, 1.0); + value += fade * abs(snoise(position * fOut))/fOut; + + return 1.0-value; + } + + float SphereDist(vec3 position) + { + return length(position) - Radius; + } + + vec4 Shade(float distance) + { + float c1 = saturate(distance*5.0 + 0.5); + float c2 = saturate(distance*5.0); + float c3 = saturate(distance*3.4 - 0.5); + + vec4 a = mix(Color1,Color2, c1); + vec4 b = mix(a, Color3, c2); + return mix(b, Color4, c3); + } + + float RenderScene(vec3 position, out float distance) + { + float noise = Turbulence(position * density + vec3(rateZ, rateX, rateY)*TIME, 0.1, 1.5, 0.03) * depth; + noise = saturate(abs(noise)); + distance = SphereDist(position) - noise; + + return noise; + } + + vec4 March(vec3 rayOrigin, vec3 rayStep) + { + vec3 position = rayOrigin; + float distance; + float displacement; + for(int step = MarchSteps; step >=0 ; --step) + { + displacement = RenderScene(position, distance); + if(distance < 0.05) break; + position += rayStep * distance; + } + + return mix(Shade(displacement), vec4(0.0, 0.0, 0.0, 0.0), float(distance >= 0.5)); + } + + bool IntersectSphere(vec3 ro, vec3 rd, vec3 pos, float radius, out vec3 intersectPoint) + { + vec3 relDistance = (ro - pos); + float b = dot(relDistance, rd); + float c = dot(relDistance, relDistance) - radius*radius; + float d = b*b - c; + intersectPoint = ro + rd*(-b - sqrt(d)); + + return d >= 0.0; + } + + void main(void) + { + vec2 p = (gl_FragCoord.xy / RENDERSIZE.xy) * 2.0 - 1.0; + p += offset; + p.x *= RENDERSIZE.x/RENDERSIZE.y; + + float rotx = rotation* 4.0; + float roty = -rotation * 4.0; + float zoom = 16.0-(size*3.); + vec3 ro = zoom * normalize(vec3(cos(roty), cos(rotx), sin(roty))); + vec3 ww = normalize(vec3(0.0, 0.0, 0.0) - ro); + vec3 uu = normalize(cross( vec3(0.0, 1.0, 0.0), ww)); + vec3 vv = normalize(cross(ww, uu)); + vec3 rd = normalize(p.x*uu + p.y*vv + 1.5*ww); + vec4 col = vec4(0.0); + vec3 origin; + if(IntersectSphere(ro, rd, vec3(0.0), Radius + depth*7.0, origin)) + { + col = March(origin, rd); + } + + gl_FragColor = col; + }`, + vertexShader: "void main() {isf_vertShaderInit();}" +}; diff --git a/src/renderer/src/application/sample-modules/GridStretch.js b/src/renderer/src/application/sample-modules/GridStretch.js new file mode 100644 index 000000000..e7a399008 --- /dev/null +++ b/src/renderer/src/application/sample-modules/GridStretch.js @@ -0,0 +1,99 @@ +export default { + meta: { + name: "Grid Stretch", + author: "2xAA", + version: 0.1, + meyda: ["zcr", "rms"], + previewWithOutput: true, + type: "2d" + }, + + props: { + countX: { + label: "Grid Size X", + type: "int", + min: 1, + max: 20, + step: 1, + default: 10 + }, + + countY: { + label: "Grid Size Y", + type: "int", + min: 1, + max: 20, + step: 1, + default: 10 + }, + + intensity: { + label: "RMS/ZCR Intensity", + type: "float", + min: 0, + max: 30, + default: 15 + }, + + zcr: { + label: "RMS (unchecked) / ZCR (checked)", + type: "bool", + default: false + } + }, + + data: { + newCanvas2: null, + newCtx2: null + }, + + init({ canvas, data }) { + data.newCanvas2 = new OffscreenCanvas(canvas.width, canvas.height); + data.newCtx2 = data.newCanvas2.getContext("2d"); + + data.newCanvas2.width = canvas.width; + data.newCanvas2.height = canvas.height; + + return data; + }, + + resize({ canvas, data }) { + data.newCanvas2.width = canvas.width; + data.newCanvas2.height = canvas.height; + + return data; + }, + + draw({ canvas, context, features, props, data }) { + const sliceWidth = canvas.width / props.countX; + const sliceHeight = canvas.height / props.countY; + + data.newCtx2.clearRect(0, 0, canvas.width, canvas.height); + let analysed; + + if (props.zcr) { + analysed = (features.zcr / 10) * props.intensity; + } else { + analysed = features.rms * 10 * props.intensity; + } + + for (var i = props.countX; i >= 0; i--) { + for (var j = props.countY; j >= 0; j--) { + data.newCtx2.drawImage( + canvas, + i * sliceWidth, + j * sliceHeight, + sliceWidth, + sliceHeight, + + i * sliceWidth - analysed, + j * sliceHeight - analysed, + sliceWidth + analysed * 2, + sliceHeight + analysed * 2 + ); + } + } + + context.drawImage(data.newCanvas2, 0, 0, canvas.width, canvas.height); + } +}; diff --git a/src/renderer/src/application/sample-modules/Line.js b/src/renderer/src/application/sample-modules/Line.js new file mode 100644 index 000000000..94aeaaeff --- /dev/null +++ b/src/renderer/src/application/sample-modules/Line.js @@ -0,0 +1,137 @@ +export default { + meta: { + type: "2d", + name: "Line", + author: "2xAA" + }, + + props: { + linesToShow: { + type: "int", + default: 100, + min: 5, + max: 200, + strict: true + }, + + spacing: { + type: "int", + default: 1, + min: 1, + max: 20, + abs: true + }, + + lineWidth: { + type: "int", + default: 1, + min: 1, + max: 20, + abs: true + }, + + color: { + default: { r: 255, g: 255, b: 255, a: 1 }, + // explicitly define a control + control: { + type: "paletteControl", + + // pass options to the control + options: { + returnFormat: "rgbaString", + colors: [ + { r: 255, g: 255, b: 255, a: 1 }, + { r: 0, g: 0, b: 0, a: 1 }, + { r: 255, g: 0, b: 0, a: 0.5 } + ], + duration: 1000 + } + } + }, + + speed: { + type: "float", + abs: true, + min: 0, + max: 2, + default: 1 + } + }, + + data: { + vector: [ + [0, 0], + [0, 0] + ], + velocity: [ + [1, 1], + [1, 1] + ], + history: [] + }, + + init({ data }) { + data.vector = [ + [Math.random(), Math.random()], + [Math.random(), Math.random()] + ]; + + data.velocity = [ + [Math.random() > 0.5 ? 1 : -1, Math.random() > 0.5 ? 1 : -1], + [Math.random() > 0.5 ? 1 : -1, Math.random() > 0.5 ? 1 : -1] + ]; + + return data; + }, + + update({ data, props, canvas: { width } }) { + const { linesToShow, speed, spacing } = props; + const pixel = (speed / width) * spacing; + + if (data.vector[0][0] >= 1.0 || data.vector[0][0] <= 0.0) { + data.velocity[0][0] = -data.velocity[0][0]; + } + + if (data.vector[0][1] >= 1.0 || data.vector[0][1] <= 0.0) { + data.velocity[0][1] = -data.velocity[0][1]; + } + + if (data.vector[1][0] >= 1.0 || data.vector[1][0] <= 0.0) { + data.velocity[1][0] = -data.velocity[1][0]; + } + + if (data.vector[1][1] >= 1.0 || data.vector[1][1] <= 0.0) { + data.velocity[1][1] = -data.velocity[1][1]; + } + + data.vector[0][0] += data.velocity[0][0] > 0 ? pixel : -pixel; + data.vector[0][1] += data.velocity[0][1] > 0 ? pixel : -pixel; + data.vector[1][0] += data.velocity[1][0] > 0 ? pixel : -pixel; + data.vector[1][1] += data.velocity[1][1] > 0 ? pixel : -pixel; + + data.history.push(JSON.parse(JSON.stringify(data.vector))); + + if (data.history.length > linesToShow) { + data.history.splice(0, data.history.length - linesToShow); + } + + return data; + }, + + draw({ canvas: { width, height }, context, data }) { + const { color, lineWidth } = this; + + context.strokeStyle = "#fff" || color; + context.lineWidth = lineWidth; + const hl = data.history.length; + + for (let i = 0; i < hl; i += 1) { + const [p1, p2] = data.history[i]; + + context.beginPath(); + context.moveTo(Math.round(p1[0] * width), Math.round(p1[1] * height)); + context.lineTo(Math.round(p2[0] * width), Math.round(p2[1] * height)); + context.stroke(); + } + } +}; diff --git a/src/renderer/src/application/sample-modules/MattiasCRT-2.0.js b/src/renderer/src/application/sample-modules/MattiasCRT-2.0.js new file mode 100644 index 000000000..2f9556db5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/MattiasCRT-2.0.js @@ -0,0 +1,12 @@ +import crtFrag from "./MattiasCRT/mattiasCrt.frag"; + +export default { + meta: { + name: "MattiasCRT", + author: "Mattias", + version: "1.0.0", + previewWithOutput: true, + type: "shader" + }, + fragmentShader: crtFrag +}; diff --git a/src/renderer/src/application/sample-modules/MattiasCRT/mattiasCrt.frag b/src/renderer/src/application/sample-modules/MattiasCRT/mattiasCrt.frag new file mode 100644 index 000000000..b17c7596f --- /dev/null +++ b/src/renderer/src/application/sample-modules/MattiasCRT/mattiasCrt.frag @@ -0,0 +1,63 @@ +precision mediump float; +uniform sampler2D u_modVCanvas; +uniform float u_delta; +uniform vec3 iResolution; +varying vec2 vUv; +varying vec2 fragCoord; + +// from here: https://www.shadertoy.com/view/Ms23DR +// Loosely based on postprocessing shader by inigo quilez, License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +vec2 curve(vec2 uv) { + uv = (uv - 0.5) * 2.0; + uv *= 1.1; + uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0); + uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0); + uv = (uv / 2.0) + 0.5; + uv = uv *0.92 + 0.04; + return uv; +} + +void main() { + vec2 uv = vUv; + + uv = curve( uv ); + vec3 oricol = texture2D( u_modVCanvas, vec2(uv.x,uv.y) ).xyz; + vec3 col; + float x = sin(0.3*u_delta+uv.y*21.0)*sin(0.7*u_delta+uv.y*29.0)*sin(0.3+0.33*u_delta+uv.y*31.0)*0.0017; + + col.r = texture2D(u_modVCanvas,vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05; + col.g = texture2D(u_modVCanvas,vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05; + col.b = texture2D(u_modVCanvas,vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05; + col.r += 0.08*texture2D(u_modVCanvas,0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x; + col.g += 0.05*texture2D(u_modVCanvas,0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y; + col.b += 0.08*texture2D(u_modVCanvas,0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z; + + col = clamp(col*0.6+0.4*col*col*1.0,0.0,1.0); + + float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y)); + col *= vec3(pow(vig,0.3)); + + col *= vec3(0.95,1.05,0.95); + col *= 2.8; + + float scans = clamp( 0.35+0.35*sin(3.5*u_delta+uv.y*iResolution.y*1.5), 0.0, 1.0); + + float s = pow(scans,1.7); + col = col*vec3( 0.4+0.7*s) ; + + col *= 1.0+0.01*sin(110.0*u_delta); + if (uv.x < 0.0 || uv.x > 1.0) + col *= 0.0; + if (uv.y < 0.0 || uv.y > 1.0) + col *= 0.0; + + col*=1.0-0.65*vec3(clamp((mod(fragCoord.x, 2.0)-1.0)*2.0,0.0,1.0)); + + float comp = smoothstep( 0.1, 0.9, sin(u_delta) ); + + // Remove the next line to stop cross-fade between original and postprocess + // col = mix( col, oricol, comp ); + + gl_FragColor = vec4(col,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/Pixelate.js b/src/renderer/src/application/sample-modules/Pixelate.js new file mode 100644 index 000000000..4fbc0b46c --- /dev/null +++ b/src/renderer/src/application/sample-modules/Pixelate.js @@ -0,0 +1,107 @@ +export default { + meta: { + name: "Pixelate", + author: "2xAA", + version: "1.0.0", + audioFeatures: ["zcr", "rms"], + type: "2d", + previewWithOutput: true + }, + + props: { + pixelAmount: { + type: "int", + label: "Amount", + min: 2, + max: 30, + step: 1, + default: 5 + }, + + soundReactive: { + type: "bool", + label: "Sound Reactive", + default: false + }, + + intensity: { + type: "int", + label: "RMS/ZCR Intensity", + min: 0, + max: 30, + step: 1, + default: 15 + }, + + soundType: { + type: "bool", + label: "RMS (unchecked) / ZCR (checked)", + default: false + } + }, + + init({ canvas, data }) { + data.newCanvas2 = new OffscreenCanvas(300, 300); + data.newCtx2 = data.newCanvas2.getContext("2d"); + data.newCtx2.imageSmoothingEnabled = false; + + data.newCanvas2.width = canvas.width; + data.newCanvas2.height = canvas.height; + + return data; + }, + + resize({ canvas, data }) { + data.newCanvas2.width = canvas.width; + data.newCanvas2.height = canvas.height; + + return data; + }, + + draw({ canvas, context, features, data, props }) { + let w; + let h; + let analysed; + + if (props.soundReactive) { + if (props.soundType) { + analysed = features.zcr / (10 * props.intensity); + } else { + analysed = features.rms * 10 * props.intensity; + } + + w = canvas.width / analysed; + h = canvas.height / analysed; + } else { + w = canvas.width / props.pixelAmount; + h = canvas.height / props.pixelAmount; + } + + context.save(); + data.newCtx2.clearRect(0, 0, data.newCanvas2.width, data.newCanvas2.height); + context.imageSmoothingEnabled = false; + data.newCtx2.drawImage( + canvas, + 0, + 0, + canvas.width, + canvas.height, + 0, + 0, + w, + h + ); + context.drawImage( + data.newCanvas2, + 0, + 0, + w, + h, + 0, + 0, + canvas.width, + canvas.height + ); + context.restore(); + } +}; diff --git a/src/renderer/src/application/sample-modules/Plasma.js b/src/renderer/src/application/sample-modules/Plasma.js new file mode 100644 index 000000000..d3ab08876 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Plasma.js @@ -0,0 +1,40 @@ +import plasmaFrag from "./Plasma/plasma.frag"; + +export default { + meta: { + name: "Plasma", + author: "2xAA", + version: 0.1, + meyda: [], // returned variables passed to the shader individually as uniforms + type: "shader" + }, + fragmentShader: plasmaFrag, + props: { + u_scaleX: { + type: "float", + label: "Scale X", + min: 1.0, + max: 150.0, + step: 1.0, + default: 50.0 + }, + + u_scaleY: { + type: "float", + label: "Scale Y", + min: 1.0, + max: 150.0, + step: 1.0, + default: 50.0 + }, + + u_timeScale: { + type: "float", + label: "Time Scale", + min: 1.0, + max: 1000.0, + step: 1.0, + default: 100.0 + } + } +}; diff --git a/src/renderer/src/application/sample-modules/Plasma/plasma.frag b/src/renderer/src/application/sample-modules/Plasma/plasma.frag new file mode 100644 index 000000000..577775c36 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Plasma/plasma.frag @@ -0,0 +1,26 @@ +/* spec: webgl */ +precision mediump float; + +#define PI 3.1415926535897932384626433832795 + +uniform float u_time; +uniform float u_scaleX; +uniform float u_scaleY; +uniform float u_timeScale; +uniform vec3 iResolution; +varying vec2 vUv; + +void main() { + float time = u_time / u_timeScale; + vec2 u_scale = vec2(iResolution.x / u_scaleX, iResolution.y / u_scaleY); + float v = 0.0; + vec2 c = vUv * u_scale - u_scale/2.0; + v += sin((c.x+time)); + v += sin((c.y+time)/2.0); + v += sin((c.x+c.y+time)/2.0); + c += u_scale/2.0 * vec2(sin(time/3.0), cos(time/2.0)); + v += sin(sqrt(c.x*c.x+c.y*c.y+1.0)+time); + v = v/2.0; + vec3 col = vec3(1, sin(PI*v), cos(PI*v)); + gl_FragColor = vec4(col*.5 + .5, 1); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/Polygon.js b/src/renderer/src/application/sample-modules/Polygon.js new file mode 100644 index 000000000..207b42e2d --- /dev/null +++ b/src/renderer/src/application/sample-modules/Polygon.js @@ -0,0 +1,173 @@ +export default { + meta: { + type: "2d", + name: "Polygon", + author: "2xAA", + audioFeatures: ["rms", "zcr"], + version: "0.2.0" + }, + + props: { + intensity: { + type: "int", + label: "RMS/ZCR Intensity", + min: 0, + max: 30, + step: 1, + default: 15 + }, + + shapeSize: { + type: "int", + label: "Shape Size", + min: 0, + max: 300, + step: 1, + default: 60 + }, + + strokeWeight: { + type: "int", + label: "Stroke Weight", + min: 1, + max: 20, + step: 1, + default: 1, + strict: true + }, + + fill: { + type: "bool", + label: "Fill", + default: false + }, + + rotateToggle: { + type: "bool", + label: "Rotate", + default: false + }, + + rotateSpeed: { + type: "float", + label: "Rotate Speed", + min: 0.1, + max: 10.0, + step: 0.1, + default: 5.0 + }, + + color: { + type: "tween", + component: "PaletteControl", + default: { + data: [ + [199, 64, 163], + [97, 214, 199], + [222, 60, 75], + [101, 151, 220], + [213, 158, 151], + [100, 132, 129], + [154, 94, 218], + [194, 211, 205], + [201, 107, 152], + [119, 98, 169], + [214, 175, 208], + [218, 57, 123], + [196, 96, 98], + [218, 74, 219], + [138, 100, 121], + [96, 118, 225], + [132, 195, 223], + [82, 127, 162], + [209, 121, 211], + [181, 152, 220] + ], + duration: 500, + easing: "linear" + } + } + }, + + data: { + rotation: 0 + }, + + update({ data, props }) { + data.rotation += props.rotateSpeed; + + if (data.rotation > 360) { + data.rotation = 0; + } + + return data; + }, + + draw({ canvas, context, features, data, props }) { + const { + color: { value: color }, + rotateToggle, + soundType, + intensity, + shapeSize, + fill, + strokeWeight + } = props; + let analysed; + let rotate = 0; + + if (rotateToggle) { + rotate = data.rotation; + } + + if (soundType) { + analysed = (features.zcr / 10) * intensity; + } else { + analysed = features.rms * 10 * intensity; + } + + context.strokeStyle = `rgb(${Math.round(color[0])},${Math.round( + color[1] + )},${Math.round(color[2])})`; + context.fillStyle = `rgb(${Math.round(color[0])},${Math.round( + color[1] + )},${Math.round(color[2])})`; + context.lineWidth = strokeWeight; + + context.beginPath(); + this.polygon( + context, + Math.round(canvas.width / 2), + Math.round(canvas.height / 2), + analysed + shapeSize, + 3 + Math.round(analysed / 10), + rotate * 0.0174533 + ); + context.closePath(); + context.stroke(); + if (fill) { + context.fill(); + } + }, + + polygon(ctx, x, y, radius, sides, startAngle, anticlockwise) { + if (sides < 3) { + return; + } + + let a = (Math.PI * 2) / sides; + a = anticlockwise ? -a : a; + + ctx.save(); + ctx.translate(x, y); + ctx.rotate(startAngle); + ctx.moveTo(radius, 0); + + for (let i = 1; i < sides; i += 1) { + ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i)); + } + + ctx.closePath(); + ctx.restore(); + } +}; diff --git a/src/renderer/src/application/sample-modules/Smear.js b/src/renderer/src/application/sample-modules/Smear.js new file mode 100644 index 000000000..518e70682 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Smear.js @@ -0,0 +1,41 @@ +export default { + meta: { + name: "Smear", + type: "2d", + audioFeatures: ["rms", "energy"] + }, + + props: { + speedX: { + type: "int", + min: -20, + max: 20, + default: 0 + }, + speedY: { + type: "int", + min: -20, + max: 20, + default: 10 + }, + sizeX: { + type: "int", + min: -20, + max: 20, + default: 0 + }, + sizeY: { + type: "int", + min: -20, + max: 20, + default: 0 + } + }, + + draw({ context, canvas, props }) { + const { width, height } = canvas; + const { speedX, speedY, sizeX, sizeY } = props; + + context.drawImage(canvas, -speedX, -speedY, width + sizeX, height + sizeY); + } +}; diff --git a/src/renderer/src/application/sample-modules/Text.js b/src/renderer/src/application/sample-modules/Text.js new file mode 100644 index 000000000..4aa7984d3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Text.js @@ -0,0 +1,185 @@ +import ctw from "canvas-text-wrapper"; + +const CanvasTextWrapper = ctw.CanvasTextWrapper; + +export default { + meta: { + name: "Text", + type: "2d" + }, + + props: { + text: { + type: "text", + default: "", + set(args) { + this.drawText(args); + } + }, + + size: { + type: "int", + min: 0, + max: 1000, + default: 16, + set(args) { + this.drawText(args); + } + }, + + offsetX: { + type: "float", + default: 0, + max: 100, + min: -100, + step: 1, + set(args) { + this.drawText(args); + } + }, + + offsetY: { + type: "float", + default: 0, + max: 100, + min: -100, + step: 1, + set(args) { + this.drawText(args); + } + }, + + strokeSize: { + type: "float", + default: 1, + max: 50, + min: 0, + abs: true, + set(args) { + this.drawText(args); + } + }, + + font: { + type: "text", + component: "FontControl", + default: "sans-serif", + set(args) { + this.drawText(args); + } + }, + + weight: { + type: "text", + default: "bold", + set(args) { + this.drawText(args); + } + }, + + fill: { + type: "bool", + default: true, + set(args) { + this.drawText(args); + } + }, + + fillColor: { + type: "color", + default: { + r: 1, + g: 1, + b: 1, + a: 1 + }, + set(args) { + this.drawText(args); + } + }, + + stroke: { + type: "bool", + default: false, + set(args) { + this.drawText(args); + } + }, + + strokeColor: { + type: "color", + default: { + r: 1, + g: 0, + b: 0, + a: 1 + }, + set(args) { + this.drawText(args); + } + } + }, + + init({ canvas: { width, height }, data, props }) { + data.canvas = new OffscreenCanvas(width, height); + data.context = data.canvas.getContext("2d"); + this.drawText({ props, data }); + return data; + }, + + resize({ canvas: { width, height }, props, data }) { + data.canvas.width = width; + data.canvas.height = height; + this.drawText({ props, data }); + return data; + }, + + draw({ context, data }) { + context.drawImage(data.canvas, 0, 0); + }, + + drawText({ props, data }) { + const { + size, + text, + strokeSize, + font, + weight, + stroke, + strokeColor, + fillColor, + fill, + offsetX, + offsetY + } = props; + + const { + canvas, + canvas: { width, height }, + context + } = data; + + if (fill) { + context.fillStyle = `rgba(${fillColor.r * 255},${fillColor.g * + 255},${fillColor.b * 255},${fillColor.a})`; + } else { + context.fillStyle = "rgba(0,0,0,0)"; + } + context.strokeStyle = `rgba(${strokeColor.r * 255},${strokeColor.g * + 255},${strokeColor.b * 255},${strokeColor.a})`; + context.lineWidth = strokeSize; + context.clearRect(0, 0, width, height); + + const calculatedOffsetX = (width / 100) * offsetX; + const calculatedOffsetY = (height / 100) * offsetY; + + CanvasTextWrapper(canvas, text, { + font: `${weight} ${size}px ${font}`, + verticalAlign: "middle", + textAlign: "center", + strokeText: stroke, + offsetX: calculatedOffsetX, + offsetY: calculatedOffsetY + }); + } +}; diff --git a/src/renderer/src/application/sample-modules/Texture2d.js b/src/renderer/src/application/sample-modules/Texture2d.js new file mode 100644 index 000000000..c35da9d41 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Texture2d.js @@ -0,0 +1,107 @@ +export default { + meta: { + name: "Texture 2D", + type: "2d", + version: "1.0.0", + author: "NERDDISCO" + }, + props: { + texture: { + type: "texture" + }, + scale: { + label: "Scale", + type: "float", + default: 1, + min: 0, + max: 5, + step: 0.001 + }, + offsetX: { + label: "Offset X in %", + type: "float", + default: 0, + min: -100, + max: 100, + step: 1 + }, + offsetY: { + label: "Offset Y in %", + type: "float", + default: 0, + min: -100, + max: 100, + step: 1 + }, + constrain: { + label: "Constrain", + type: "enum", + default: "none", + enum: [ + { label: "None (Scale)", value: "none" }, + { label: "Contain", value: "contain" }, + { label: "Cover", value: "cover" } + ] + } + }, + draw({ canvas: { width, height }, context, props }) { + const { constrain, offsetX, offsetY, scale, texture } = props; + + if (texture.value) { + let { width: imageWidth, height: imageHeight } = texture.value; + + let x; + let y; + + if (constrain === "contain") { + imageHeight = (imageHeight / imageWidth) * width; + imageWidth = width; + + y = (height - imageHeight) / 2; + x = 0; + + if (imageHeight > height) { + imageWidth = (imageWidth / imageHeight) * height; + imageHeight = height; + + y = 0; + x = (width - imageWidth) / 2; + } + } else if (constrain === "cover") { + const imageRatio = imageHeight / imageWidth; + const canvasRatio = height / width; + + if (imageRatio < canvasRatio) { + imageWidth = (width * canvasRatio) / imageRatio; + imageHeight = height; + + x = (width - imageWidth) / 2; + y = 0; + } else { + imageHeight = width * imageRatio; + imageWidth = width; + + x = 0; + y = (height - imageHeight * scale) / 2; + } + } else { + imageWidth = imageWidth * scale; + imageHeight = imageHeight * scale; + + x = (width - imageWidth) / 2; + y = (height - imageHeight) / 2; + } + + const calculatedOffsetX = (width / 100) * offsetX; + const calculatedOffsetY = (height / 100) * offsetY; + + context.drawImage( + texture.value, + x + calculatedOffsetX, + y + calculatedOffsetY, + imageWidth, + imageHeight + ); + } + } +}; diff --git a/src/renderer/src/application/sample-modules/Waveform.js b/src/renderer/src/application/sample-modules/Waveform.js new file mode 100644 index 000000000..7d6d6e0e4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Waveform.js @@ -0,0 +1,102 @@ +// import Meyda from 'meyda'; + +export default { + meta: { + name: "Waveform", + author: "2xAA", + version: "1.0.0", + audioFeatures: ["buffer"], + type: "2d" + }, + + props: { + strokeWeight: { + type: "int", + label: "Stroke", + min: 1, + max: 30, + default: 1, + abs: true + }, + + maxHeight: { + type: "float", + label: "Height", + min: 1.0, + max: 100.0, + default: 100.0 + }, + + maxWidth: { + type: "float", + label: "Width", + min: 0, + max: 1.0, + default: 1.0 + }, + + windowing: { + type: "enum", + label: "Windowing", + default: "hanning", + enum: [ + { label: "Rectangular (no window)", value: "rect" }, + { label: "Hanning", value: "hanning" }, + { label: "Hamming", value: "hamming" }, + { label: "Blackman", value: "blackman" }, + { label: "Sine", value: "sine" } + ] + }, + + color: { + type: "tween", + component: "PaletteControl", + default: { + data: [ + [255, 255, 255], + [255, 255, 255] + ], + duration: 10000, + easing: "linear" + } + } + }, + + draw({ canvas, context, features, meyda, props }) { + const { + color: { value: color }, + maxWidth, + maxHeight, + strokeWeight, + windowing + } = props; + const { width, height } = canvas; + const bufferLength = features.buffer.length; + const buffer = meyda.windowing(features.buffer, windowing); + + context.lineWidth = strokeWeight; + context.strokeStyle = `rgb(${Math.round(color[0])},${Math.round( + color[1] + )},${Math.round(color[2])})`; + context.beginPath(); + + const sliceWidth = (width * maxWidth) / bufferLength; + let x = 0; + + for (let i = 0; i < bufferLength; i += 1) { + const v = (buffer[i] / 100) * maxHeight; + const y = height / 2 + (height / 2) * v; + + if (i === 0) { + context.moveTo(x, y); + } else { + context.lineTo(x, y); + } + + x += sliceWidth; + } + + context.lineTo(width * maxWidth, height / 2); + context.stroke(); + } +}; diff --git a/src/renderer/src/application/sample-modules/Webcam.js b/src/renderer/src/application/sample-modules/Webcam.js new file mode 100644 index 000000000..23c1d2e7c --- /dev/null +++ b/src/renderer/src/application/sample-modules/Webcam.js @@ -0,0 +1,45 @@ +export default { + meta: { + type: "2d", + name: "Webcam", + author: "2xAA", + version: "1.0.0" + }, + + props: { + scale: { + type: "float", + min: 0, + max: 10, + default: 1 + }, + + position: { + type: "vec2", + default: [0.5, 0.5], + min: 0, + max: 1 + }, + + imageSmoothing: { + type: "bool", + default: true + } + }, + + draw({ canvas, context, video: { canvas: video }, props }) { + const { position, scale, imageSmoothing } = props; + const { width: videoWidth, height: videoHeight } = video; + const { width, height } = canvas; + + context.imageSmoothingEnabled = imageSmoothing; + + context.drawImage( + video, + width * position[0] - (videoWidth * scale) / 2, + height * (1 - position[1]) - (videoHeight * scale) / 2, + videoWidth * scale, + videoHeight * scale + ); + } +}; diff --git a/src/renderer/src/application/sample-modules/Wobble.js b/src/renderer/src/application/sample-modules/Wobble.js new file mode 100644 index 000000000..364104db0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Wobble.js @@ -0,0 +1,33 @@ +import fragmentShader from "./Wobble/wobble.frag"; + +export default { + meta: { + type: "shader", + name: "Wobble", + author: "2xAA", + version: "1.0.0", + previewWithOutput: true + }, + + fragmentShader, + + props: { + strength: { + type: "float", + label: "Float", + min: 0.0, + max: 0.05, + step: 0.001, + default: 0.001 + }, + + size: { + type: "float", + label: "Size", + min: 1.0, + max: 50.0, + step: 1.0, + default: 1.0 + } + } +}; diff --git a/src/renderer/src/application/sample-modules/Wobble/wobble.frag b/src/renderer/src/application/sample-modules/Wobble/wobble.frag new file mode 100644 index 000000000..ff7ca96d7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/Wobble/wobble.frag @@ -0,0 +1,22 @@ +/* spec: webgl */ +// from here: https://getmosh.io/ + +precision mediump float; + +uniform sampler2D u_modVCanvas; +uniform float iGlobalTime; +uniform float strength; +uniform float size; +float speed = 1.0; +varying vec2 vUv; + +void main() { + vec2 p = -1.0 + 2.0 * vUv; + gl_FragColor = texture2D( + u_modVCanvas, + vUv + strength * vec2( + cos(iGlobalTime * speed + length(p * size)), + sin(iGlobalTime * speed + length(p * size)) + ) + ); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/X-Drips.js b/src/renderer/src/application/sample-modules/X-Drips.js new file mode 100644 index 000000000..bc604683e --- /dev/null +++ b/src/renderer/src/application/sample-modules/X-Drips.js @@ -0,0 +1,156 @@ +export default { + meta: { + type: "2d", + name: "X Drips" + }, + + props: { + dripBaseHeight: { + default: 70, + type: "int", + min: 0, + max: 70 + }, + + insetHeight: { + default: 15, + type: "int", + min: 0, + max: 70 + }, + + maxDripHeight: { + default: 1, + type: "float", + min: 0, + max: 1, + abs: true + } + }, + + data: { + spacings: [50, 30], + widths: [30, 50, 80], + heights: [80, 145, 220], + colors: ["#15216b", "#f33a58", "#f76272", "#ff9fda", "#e10079", "#a231ef"], + color: "#000", + pattern: [] + }, + + generatePattern({ canvas, data }) { + const { widths, heights, spacings } = data; + const { width } = canvas; + const pattern = []; + let overallWidth = 0; + let selectedWidth = 0; + let flip = false; + + while (overallWidth < width) { + const selectedHeight = + heights[Math.floor(Math.random() * heights.length)]; + + const random = Math.random(); + + if (flip) { + selectedWidth = widths[Math.floor(Math.random() * widths.length)]; + pattern.push({ + type: "width", + width: selectedWidth, + height: selectedHeight, + random + }); + } else { + selectedWidth = spacings[Math.floor(Math.random() * spacings.length)]; + pattern.push({ + type: "space", + width: selectedWidth, + height: selectedHeight, + random + }); + } + + overallWidth += selectedWidth; + flip = !flip; + selectedWidth = 0; + } + + return pattern; + }, + + clearCircle(context, x, y, radius) { + context.save(); + context.beginPath(); + context.arc(x, y, radius, 0, 2 * Math.PI, true); + context.clip(); + context.clearRect(x - radius, y - radius, radius * 2, radius * 2); + context.restore(); + }, + + init({ canvas, data }) { + data.color = data.colors[Math.floor(Math.random() * data.colors.length)]; + data.pattern = this.generatePattern({ canvas, data }); + return data; + }, + + resize({ canvas, data }) { + data.pattern = this.generatePattern({ canvas, data }); + return data; + }, + + draw({ context, delta, data, props }) { + const { dripBaseHeight, insetHeight, maxDripHeight } = props; + const { color, pattern } = data; + const { width, height } = context.canvas; + + context.clearRect(0, 0, width, height); + context.fillStyle = color; + context.fillRect(0, 0, width, dripBaseHeight + insetHeight); + + const patternLength = pattern.length; + let x = 0; + + for (let i = 0; i < patternLength; i += 1) { + const piece = pattern[i]; + const h = + (piece.width / 2 + Math.sin((delta / 500) * piece.random) * 30) * + maxDripHeight; + + if (piece.type === "width") { + context.fillStyle = color; + context.fillRect(x, insetHeight, piece.width, piece.height + h); + context.beginPath(); + context.arc( + x + piece.width / 2, + insetHeight + piece.height + h, + piece.width / 2, + 0, + Math.PI * 2 + ); + context.fill(); + } + + if (piece.type === "space") { + context.clearRect( + x, + dripBaseHeight - h / 3, + piece.width, + insetHeight + piece.height + ); + this.clearCircle( + context, + x + piece.width / 2, + dripBaseHeight - h / 3, + piece.width / 2 + ); + this.clearCircle( + context, + x + piece.width / 2, + insetHeight + piece.height + dripBaseHeight - h / 3, + piece.width / 2 + ); + } + + x += piece.width; + } + } +}; diff --git a/src/renderer/src/application/sample-modules/isf/ASCII Art.fs b/src/renderer/src/application/sample-modules/isf/ASCII Art.fs new file mode 100644 index 000000000..c908511da --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/ASCII Art.fs @@ -0,0 +1,54 @@ +/*{ + "DESCRIPTION": "ASCII Art", + "CREDIT": "by IMIMOT (Ported from https://www.shadertoy.com/view/lssGDj)", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "amount", + "TYPE": "float", + "DEFAULT": 0.0 + } + ] + +}*/ + +float character(float n, vec2 p) // some compilers have the word "char" reserved +{ + p = floor(p*vec2(4.0, -4.0) + 2.5); + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) + { + if (int(mod(n/exp2(p.x + 5.0*p.y), 2.0)) == 1) return 1.0; + } + return 0.0; +} + + + +void main() { + float _amount = amount*36.0+8.0; + vec2 uv = gl_FragCoord.xy; + vec3 col = IMG_NORM_PIXEL(inputImage, (floor(uv/_amount)*_amount/RENDERSIZE.xy)).rgb; + + + float gray = (col.r + col.g + col.b)/3.0; + + float n = 65536.0; // . + if (gray > 0.2) n = 65600.0; // : + if (gray > 0.3) n = 332772.0; // * + if (gray > 0.4) n = 15255086.0; // o + if (gray > 0.5) n = 23385164.0; // & + if (gray > 0.6) n = 15252014.0; // 8 + if (gray > 0.7) n = 13199452.0; // @ + if (gray > 0.8) n = 11512810.0; // # + + vec2 p = mod(uv/(_amount/2.0), 2.0) - vec2(1.0); + col = col*character(n, p); + gl_FragColor = vec4(col, 1.0); + +} diff --git a/src/renderer/src/application/sample-modules/isf/Angular.fs b/src/renderer/src/application/sample-modules/isf/Angular.fs new file mode 100644 index 000000000..982b2cbf8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Angular.fs @@ -0,0 +1,71 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/angular.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 90, + "MAX": 360, + "MIN": 0, + "NAME": "startingAngle", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Fernando Kuteken +// License: MIT + +#define PI 3.141592653589 + + +vec4 transition (vec2 uv) { + + float offset = startingAngle * PI / 180.0; + float angle = atan(uv.y - 0.5, uv.x - 0.5) + offset; + float normalizedAngle = (angle + PI) / (2.0 * PI); + + normalizedAngle = normalizedAngle - floor(normalizedAngle); + + return mix( + getFromColor(uv), + getToColor(uv), + step(normalizedAngle, progress) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Auto Color Tone.fs b/src/renderer/src/application/sample-modules/isf/Auto Color Tone.fs new file mode 100644 index 000000000..04d414cd2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Auto Color Tone.fs @@ -0,0 +1,347 @@ +/*{ + "CATEGORIES": [ + "Color Effect" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Creates variations on a base color using a given algorithm.", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "LABEL": "Sample Mode", + "LABELS": [ + "Base Color", + "Pixel Follow", + "Color Average" + ], + "NAME": "sampleMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + }, + { + "DEFAULT": 1, + "LABEL": "Color Mode", + "LABELS": [ + "Basic Complementary", + "Split Complementary", + "Compound Complementary", + "Spectrum", + "Shades", + "Analogous", + "Compound Analogous" + ], + "NAME": "colorModeOverride", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + { + "DEFAULT": 7, + "LABEL": "Color Count", + "LABELS": [ + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16" + ], + "NAME": "colorCount", + "TYPE": "long", + "VALUES": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ] + }, + { + "DEFAULT": [ + 0.25, + 0.59, + 0.9, + 1 + ], + "LABEL": "Base Color", + "NAME": "baseColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "LABEL": "Pixel Point", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pixelFollowLocation", + "TYPE": "point2D" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "HEIGHT": "$HEIGHT / 100.0", + "TARGET": "bufferPassA", + "WIDTH": "$WIDTH / 100.0" + }, + { + "HEIGHT": "1.0", + "TARGET": "autoColorBuffer", + "WIDTH": "1.0", + "persistent": true + }, + { + } + ] +} +*/ + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float gray(vec4 c) { + return (c.r + c.g + c.b) * c.a / 3.0; +} + +void main() +{ + if (PASSINDEX == 0) { + vec4 inputColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + gl_FragColor = inputColor; + } + else if (PASSINDEX == 1) { + vec4 inputColor = IMG_NORM_PIXEL(bufferPassA, isf_FragNormCoord); + vec4 oldColor = IMG_NORM_PIXEL(autoColorBuffer, vec2(0.5,0.5)); + gl_FragColor = mix(inputColor, oldColor, 0.8); + } + else if (PASSINDEX == 2) { + vec4 inputColor = IMG_THIS_PIXEL(inputImage); + vec4 inColor = baseColor; + float index = floor(gray(inputColor) * float(colorCount)); + //float index = floor(isf_FragNormCoord.x * float(colorCount)); + float variation = 0.3236; // 1/5 the golden ratio + int colorMode = colorModeOverride; + + if (sampleMode == 0) { + inColor = baseColor; + inColor.rgb = rgb2hsv(inColor.rgb); + } + else if (sampleMode == 1) { + inColor = IMG_NORM_PIXEL(inputImage, pixelFollowLocation); + inColor.rgb = rgb2hsv(inColor.rgb); + } + else if (sampleMode == 2) { + inColor = IMG_NORM_PIXEL(autoColorBuffer, vec2(0.5,0.5)); + inColor.rgb = rgb2hsv(inColor.rgb); + if (inColor.b < 0.1) { + inColor = inColor * 1.5; + } + } + + vec4 outColor = inColor; + + // Basic complimentary saturation and brightness variations on two fixed 180 degree opposite hues + if (colorMode == 0) { + if (mod(index, 2.0) >= 1.0) { + outColor.r = outColor.r + 0.5; + outColor.r = outColor.r - floor(outColor.r); + } + + outColor.g = outColor.g - variation * floor(index / 2.0); + + if (outColor.g < 0.1) { + outColor.g = outColor.g + variation * floor(index / 2.0); + outColor.g = outColor.g - floor(outColor.g); + } + + outColor.b = outColor.b - variation * floor(index / 4.0); + if (outColor.b < 0.2) { + outColor.b = outColor.b + variation * floor(index / 4.0); + outColor.b = outColor.b - floor(outColor.b); + } + } + // Split complimentary saturation and brightness variations on a 3 fixed 120 degree hues + else if (colorMode == 1) { + float divisor = 3.0; + float ratio = 0.45; + if (mod(index, 3.0) >= 2.0) { + outColor.r = outColor.r - ratio; + } + else if (mod(index, 3.0) >= 1.0) { + outColor.r = outColor.r + ratio; + } + + //outColor.g = outColor.g + variation * floor(index / divisor); + + if (mod(index, 5.0) >= 3.0) { + outColor.g = outColor.g - variation; + outColor.g = outColor.g - floor(outColor.g); + } + outColor.b = outColor.b - variation * floor(index / (divisor)); + if (outColor.b < 0.1) { + outColor.b = outColor.b + variation * floor(index / (divisor)); + outColor.b = outColor.b - floor(outColor.b); + } + } + // Compound complimentary a combination of shades, complimentary and analogous colors with slight shifts + else if (colorMode == 2) { + if (mod(index, 3.0) >= 2.0) { + outColor.r = outColor.r + 0.5; + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1) / 4.0; + } + else { + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1); + } + outColor.r = outColor.r - floor(outColor.r); + + + if (mod(index, 2.0) >= 1.0) { + outColor.g = outColor.g + index * variation / 2.0; + } + else if (mod(index, 3.0) >= 2.0) { + outColor.g = outColor.g - variation / 2.0; + } + else { + outColor.g = outColor.g - index * variation / float(colorCount - 1); + } + if (outColor.g > 1.0) { + outColor.g = outColor.g - floor(outColor.g); + } + } + // Spectrum hue shifts based on number of colors with minor saturation shifts + else if (colorMode == 3) { + outColor.r = outColor.r + index * 1.0 / float(colorCount); + if (mod(index, 3.0) >= 2.0) { + outColor.g = outColor.g - variation / 2.0; + outColor.g = outColor.g - floor(outColor.g); + } + else if (mod(index, 4.0) >= 3.0) { + outColor.g = outColor.g + variation / 2.0; + //outColor.g = outColor.g - floor(outColor.g); + } + } + // Shades saturation and brightness variations on a single fixed hue + else if (colorMode == 4) { + if (mod(index, 2.0) >= 1.0) { + outColor.b = outColor.b - (index * variation) / float(colorCount-1); + } + else { + outColor.b = outColor.b + (index * variation) / float(colorCount-1); + outColor.b = outColor.b - floor(outColor.b); + } + if (outColor.b < 0.075) { + outColor.b = 1.0 - outColor.b * variation; + } + + if (mod(index, 3.0) >= 2.0) { + outColor.g = outColor.g - (index * variation) / 2.0; + } + else if (mod(index, 4.0) >= 3.0) { + outColor.g = outColor.g + (index * variation) / 2.0; + } + + if ((outColor.g > 1.0) || (outColor.g < 0.05)) { + outColor.g = outColor.g - floor(outColor.g); + } + } + // Analogous small hue and saturation shifts + else if (colorMode == 5) { + + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1); + + if (mod(index, 3.0) >= 1.0) { + outColor.g = outColor.g - variation / 2.0; + if (outColor.g < 0.0) { + outColor.g = outColor.g + variation / 2.0; + } + if (outColor.g > 1.0) { + outColor.g = outColor.g - floor(outColor.g); + } + } + } + // Compound Analogous similar to analogous but with negative hue shifts + else if (colorMode == 6) { + if (mod(index, 3.0) >= 1.0) { + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1); + } + else { + outColor.r = outColor.r - variation * index * 0.5 / float(colorCount - 1); + } + if (mod(index, 3.0) >= 1.0) { + outColor.g = outColor.g - variation / 2.0; + if (outColor.g < 0.0) { + outColor.g = outColor.g + variation; + } + if (outColor.g > 1.0) { + outColor.g = outColor.g - floor(outColor.g); + } + } + if (mod(index, 4.0) >= 2.0) { + if (outColor.b < variation) { + outColor.b = outColor.b + variation; + } + } + } + + gl_FragColor = vec4(hsv2rgb(outColor.rgb), inColor.a); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Auto Levels.fs b/src/renderer/src/application/sample-modules/isf/Auto Levels.fs new file mode 100644 index 000000000..02ea12824 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Auto Levels.fs @@ -0,0 +1,322 @@ +/*{ + "CATEGORIES": [ + "Color Effect" + ], + "CREDIT": "by Carter Rosenberg", + "DESCRIPTION": "Auto Levels", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "min_threshold", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "mid_point", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "max_threshold", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "adapt_rate", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "HEIGHT": "$HEIGHT / 2.0", + "TARGET": "bufferPassA", + "WIDTH": "$WIDTH / 2.0" + }, + { + "HEIGHT": "max($HEIGHT / 27.0, 1.0)", + "TARGET": "bufferPassB", + "WIDTH": "max($WIDTH / 27.0, 1.0)" + }, + { + "HEIGHT": "max($HEIGHT / 3.0, 1.0)", + "TARGET": "bufferPassC", + "WIDTH": "max($WIDTH / 3.0, 1.0)" + }, + { + "HEIGHT": "max($HEIGHT / 243.0, 1.0)", + "TARGET": "bufferPassD", + "WIDTH": "max($WIDTH / 243.0, 1.0)" + }, + { + "HEIGHT": "max($HEIGHT/486.0,1.0)", + "PERSISTENT": true, + "TARGET": "bufferVariableNameA", + "WIDTH": "max($WIDTH/486.0,1.0)" + }, + { + } + ] +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +// packed as min, mid, max +vec3 minmaxmean(vec4 n, vec3 old) +{ + float val = gray(n); + vec3 returnMe = old; + if (val < returnMe.x) { + returnMe.x = val; + } + if (val > returnMe.z) { + returnMe.z = val; + } + returnMe.y = returnMe.y + val / 9.0; + return returnMe; +} + +vec3 minmaxcompare(vec4 n, vec3 old) { + vec3 returnMe = old; + if (n.r < returnMe.x) { + returnMe.x = n.r; + } + if (n.g > returnMe.z) { + returnMe.z = n.g; + } + returnMe.y = returnMe.y + n.b / 9.0; + return returnMe; +} + + +void main() +{ + vec3 vals = vec3(1.0,0.0,0.0); + + // on this first pass we find the local mins, mid, and max for each pixel + if (PASSINDEX == 0) { + vec4 min_color = IMG_THIS_PIXEL(inputImage); + vec4 min_colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 min_colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 min_colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 min_colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 min_colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 min_colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 min_colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 min_colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vals = minmaxmean(min_color,vals); + + vals = minmaxmean(min_colorL,vals); + vals = minmaxmean(min_colorR,vals); + vals = minmaxmean(min_colorA,vals); + vals = minmaxmean(min_colorB,vals); + + vals = minmaxmean(min_colorLA,vals); + vals = minmaxmean(min_colorRA,vals); + vals = minmaxmean(min_colorLB,vals); + vals = minmaxmean(min_colorRB,vals); + gl_FragColor = vec4(vals.x,vals.y,vals.z,min_color.a); + } + // second pass: read from "bufferPassA", write to "bufferPassB" + else if (PASSINDEX == 1) { + vec4 min_color = IMG_THIS_PIXEL(bufferPassA); + vec4 min_colorL = IMG_NORM_PIXEL(bufferPassA, left_coord); + vec4 min_colorR = IMG_NORM_PIXEL(bufferPassA, right_coord); + vec4 min_colorA = IMG_NORM_PIXEL(bufferPassA, above_coord); + vec4 min_colorB = IMG_NORM_PIXEL(bufferPassA, below_coord); + + vec4 min_colorLA = IMG_NORM_PIXEL(bufferPassA, lefta_coord); + vec4 min_colorRA = IMG_NORM_PIXEL(bufferPassA, righta_coord); + vec4 min_colorLB = IMG_NORM_PIXEL(bufferPassA, leftb_coord); + vec4 min_colorRB = IMG_NORM_PIXEL(bufferPassA, rightb_coord); + + vals = minmaxcompare(min_color,vals); + + vals = minmaxcompare(min_colorL,vals); + vals = minmaxcompare(min_colorR,vals); + vals = minmaxcompare(min_colorA,vals); + vals = minmaxcompare(min_colorB,vals); + + vals = minmaxcompare(min_colorLA,vals); + vals = minmaxcompare(min_colorRA,vals); + vals = minmaxcompare(min_colorLB,vals); + vals = minmaxcompare(min_colorRB,vals); + gl_FragColor = vec4(vals.x,vals.y,vals.z,min_color.a); + } + // read from "bufferPassB", write to "bufferPassC" + else if (PASSINDEX == 2) { + vec4 min_color = IMG_THIS_PIXEL(bufferPassB); + vec4 min_colorL = IMG_NORM_PIXEL(bufferPassB, left_coord); + vec4 min_colorR = IMG_NORM_PIXEL(bufferPassB, right_coord); + vec4 min_colorA = IMG_NORM_PIXEL(bufferPassB, above_coord); + vec4 min_colorB = IMG_NORM_PIXEL(bufferPassB, below_coord); + + vec4 min_colorLA = IMG_NORM_PIXEL(bufferPassB, lefta_coord); + vec4 min_colorRA = IMG_NORM_PIXEL(bufferPassB, righta_coord); + vec4 min_colorLB = IMG_NORM_PIXEL(bufferPassB, leftb_coord); + vec4 min_colorRB = IMG_NORM_PIXEL(bufferPassB, rightb_coord); + + vals = minmaxcompare(min_color,vals); + + vals = minmaxcompare(min_colorL,vals); + vals = minmaxcompare(min_colorR,vals); + vals = minmaxcompare(min_colorA,vals); + vals = minmaxcompare(min_colorB,vals); + + vals = minmaxcompare(min_colorLA,vals); + vals = minmaxcompare(min_colorRA,vals); + vals = minmaxcompare(min_colorLB,vals); + vals = minmaxcompare(min_colorRB,vals); + gl_FragColor = vec4(vals.x,vals.y,vals.z,min_color.a); + } + // read from "bufferPassC", write to "bufferPassD" + else if (PASSINDEX == 3) { + vec4 min_color = IMG_THIS_PIXEL(bufferPassC); + vec4 min_colorL = IMG_NORM_PIXEL(bufferPassC, left_coord); + vec4 min_colorR = IMG_NORM_PIXEL(bufferPassC, right_coord); + vec4 min_colorA = IMG_NORM_PIXEL(bufferPassC, above_coord); + vec4 min_colorB = IMG_NORM_PIXEL(bufferPassC, below_coord); + + vec4 min_colorLA = IMG_NORM_PIXEL(bufferPassC, lefta_coord); + vec4 min_colorRA = IMG_NORM_PIXEL(bufferPassC, righta_coord); + vec4 min_colorLB = IMG_NORM_PIXEL(bufferPassC, leftb_coord); + vec4 min_colorRB = IMG_NORM_PIXEL(bufferPassC, rightb_coord); + + vals = minmaxcompare(min_color,vals); + + vals = minmaxcompare(min_colorL,vals); + vals = minmaxcompare(min_colorR,vals); + vals = minmaxcompare(min_colorA,vals); + vals = minmaxcompare(min_colorB,vals); + + vals = minmaxcompare(min_colorLA,vals); + vals = minmaxcompare(min_colorRA,vals); + vals = minmaxcompare(min_colorLB,vals); + vals = minmaxcompare(min_colorRB,vals); + gl_FragColor = vec4(vals.x,vals.y,vals.z,min_color.a); + } + // read from "bufferPassD", write to "bufferVariableNameA" + else if (PASSINDEX == 4) { + vec4 min_color = IMG_THIS_PIXEL(bufferPassD); + vec4 stalePixel = IMG_THIS_PIXEL(bufferVariableNameA); + vec4 min_colorL = IMG_NORM_PIXEL(bufferPassD, left_coord); + vec4 min_colorR = IMG_NORM_PIXEL(bufferPassD, right_coord); + vec4 min_colorA = IMG_NORM_PIXEL(bufferPassD, above_coord); + vec4 min_colorB = IMG_NORM_PIXEL(bufferPassD, below_coord); + + vec4 min_colorLA = IMG_NORM_PIXEL(bufferPassD, lefta_coord); + vec4 min_colorRA = IMG_NORM_PIXEL(bufferPassD, righta_coord); + vec4 min_colorLB = IMG_NORM_PIXEL(bufferPassD, leftb_coord); + vec4 min_colorRB = IMG_NORM_PIXEL(bufferPassD, rightb_coord); + + vals = minmaxcompare(min_color,vals); + + vals = minmaxcompare(min_colorL,vals); + vals = minmaxcompare(min_colorR,vals); + vals = minmaxcompare(min_colorA,vals); + vals = minmaxcompare(min_colorB,vals); + + vals = minmaxcompare(min_colorLA,vals); + vals = minmaxcompare(min_colorRA,vals); + vals = minmaxcompare(min_colorLB,vals); + vals = minmaxcompare(min_colorRB,vals); + gl_FragColor = mix(vec4(vals.x,vals.y,vals.z,min_color.a),stalePixel,adapt_rate);; + } + // read from "bufferVariableNameA", write to output + else if (PASSINDEX == 5) { + vec4 min_color = IMG_THIS_PIXEL(bufferVariableNameA); + vec4 min_colorL = IMG_NORM_PIXEL(bufferVariableNameA, left_coord); + vec4 min_colorR = IMG_NORM_PIXEL(bufferVariableNameA, right_coord); + vec4 min_colorA = IMG_NORM_PIXEL(bufferVariableNameA, above_coord); + vec4 min_colorB = IMG_NORM_PIXEL(bufferVariableNameA, below_coord); + + vec4 min_colorLA = IMG_NORM_PIXEL(bufferVariableNameA, lefta_coord); + vec4 min_colorRA = IMG_NORM_PIXEL(bufferVariableNameA, righta_coord); + vec4 min_colorLB = IMG_NORM_PIXEL(bufferVariableNameA, leftb_coord); + vec4 min_colorRB = IMG_NORM_PIXEL(bufferVariableNameA, rightb_coord); + + vals = minmaxcompare(min_color,vals); + + vals = minmaxcompare(min_colorL,vals); + vals = minmaxcompare(min_colorR,vals); + vals = minmaxcompare(min_colorA,vals); + vals = minmaxcompare(min_colorB,vals); + + vals = minmaxcompare(min_colorLA,vals); + vals = minmaxcompare(min_colorRA,vals); + vals = minmaxcompare(min_colorLB,vals); + vals = minmaxcompare(min_colorRB,vals); + + vec4 final_pixel = IMG_THIS_PIXEL(inputImage); + + + float val = gray (final_pixel); + if (vals.x < min_threshold) { + vals.x = min_threshold; + } + if (val < min_threshold) { + val = min_threshold; + } + if (vals.z > max_threshold) { + vals.z = max_threshold; + } + if (val > max_threshold) { + val = max_threshold; + } + //final_pixel.rgb = (final_pixel.rgb - vals.x) / (vals.z - vals.x); + // shift by the absdiff of the midpoint + //final_pixel.rgb = final_pixel.rgb + (mid_point - vals.y); + final_pixel.rgb = final_pixel.rgb - (0.5 - mid_point); + // by contrast adjustments + float diff = vals.z-vals.x; + if (diff < 0.01) { + diff = 0.01; + } + final_pixel.rgb = (final_pixel.rgb - vals.x) / diff; + //float contrast = 1.0/diff; + //final_pixel.rgb = ((vec3(2.0) * (final_pixel.rgb - vec3(vals.x))) * vec3(contrast) / vec3(2.0)) + vec3(0.5); + + gl_FragColor = final_pixel; + //gl_FragColor = vec4(vec3(vals.z),1.0); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Auto Levels.vs b/src/renderer/src/application/sample-modules/isf/Auto Levels.vs new file mode 100644 index 000000000..57a15704f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Auto Levels.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + d = 1.0/RENDERSIZE; + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Bloom.fs b/src/renderer/src/application/sample-modules/isf/Bloom.fs new file mode 100644 index 000000000..dbe5eb84d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bloom.fs @@ -0,0 +1,134 @@ +/*{ + "CATEGORIES": [ + "Blur", + "Film" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 12, + "MAX": 12, + "MIN": 0, + "NAME": "blurAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 5, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "DESCRIPTION": "Pass 0", + "HEIGHT": "floor($HEIGHT/2.0)", + "TARGET": "halfSizeBaseRender", + "WIDTH": "floor($WIDTH/2.0)" + }, + { + "DESCRIPTION": "Pass 1", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterSizeBaseRender", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 2", + "HEIGHT": "floor($HEIGHT/8.0)", + "TARGET": "eighthSizeBaseRender", + "WIDTH": "floor($WIDTH/8.0)" + }, + { + "DESCRIPTION": "Pass 3", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 4", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 5", + "TARGET": "fullGaussA" + }, + { + "DESCRIPTION": "Pass 6", + "TARGET": "fullGaussB" + } + ] +} +*/ + +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +in vec2 texOffsets[5]; +#endif + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first three passes are just copying the input image into the buffer at varying sizes + if (PASSINDEX==0) { + gl_FragColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + } + else if (PASSINDEX==1) { + gl_FragColor = IMG_NORM_PIXEL(halfSizeBaseRender, isf_FragNormCoord); + } + else if (PASSINDEX==2) { + gl_FragColor = IMG_NORM_PIXEL(quarterSizeBaseRender, isf_FragNormCoord); + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 3) { + vec4 sample0 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + //gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + //gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + // ...writes into the full-size + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + else if (PASSINDEX == 6) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + //vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgba / (4.0)); + vec4 originalImg = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord); + if (blurLevel == 0) + blurredImg = mix(originalImg, blurredImg, (blurLevelModulus/6.0)); + + gl_FragColor = max(mix(originalImg,blurredImg*1.2,intensity), originalImg); + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Bloom.vs b/src/renderer/src/application/sample-modules/isf/Bloom.vs new file mode 100644 index 000000000..3c9a24ee7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bloom.vs @@ -0,0 +1,70 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +out vec2 texOffsets[5]; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first three passes are just drawing the texture- do nothing + + // the next four passes do gaussion blurs on the various levels + if (PASSINDEX==3 || PASSINDEX==5) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 3 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==3) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 5 is sampling 1/4 and writing to full-size + else if (PASSINDEX==5) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==3) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==4 || PASSINDEX==6) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 4 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==4) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 6 is sampling 1/4 and writing to full-size + else if (PASSINDEX==6) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Bounce.fs b/src/renderer/src/application/sample-modules/isf/Bounce.fs new file mode 100644 index 000000000..daafb805d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bounce.fs @@ -0,0 +1,95 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/Bounce.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": 0, + "NAME": "bounces", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "shadow_height", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "shadow_colour", + "TYPE": "color" + } + ], + "ISFVSN": "2", + "VSN": "" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Adrian Purser +// License: MIT + + +const float PI = 3.14159265358; + +vec4 transition (vec2 uv) { + float time = progress; + float stime = sin(time * PI / 2.); + float phase = time * PI * bounces; + float y = (abs(cos(phase))) * (1.0 - stime); + float d = uv.y - y; + return mix( + mix( + getToColor(uv), + shadow_colour, + step(d, shadow_height) * (1. - mix( + ((d / shadow_height) * shadow_colour.a) + (1.0 - shadow_colour.a), + 1.0, + smoothstep(0.95, 1., progress) // fade-out the shadow at the end + )) + ), + getFromColor(vec2(uv.x, uv.y + (1.0 - y))), + step(d, 0.0) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Bow Tie Horizontal.fs b/src/renderer/src/application/sample-modules/isf/Bow Tie Horizontal.fs new file mode 100644 index 000000000..d558837af --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bow Tie Horizontal.fs @@ -0,0 +1,165 @@ +/* +{ + "CATEGORIES" : [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/BowTieHorizontal.glsl", + "DESCRIPTION" : "Automatically converted from https://gl-transitions.com/", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "startImage", + "TYPE" : "image" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "MIN" : 0, + "TYPE" : "float", + "NAME" : "progress", + "MAX" : 1, + "DEFAULT" : 0 + } + ] +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: huynx +// License: MIT + +vec2 bottom_left = vec2(0.0, 1.0); +vec2 bottom_right = vec2(1.0, 1.0); +vec2 top_left = vec2(0.0, 0.0); +vec2 top_right = vec2(1.0, 0.0); +vec2 center = vec2(0.5, 0.5); + +float check(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle (vec2 pt, vec2 p1, vec2 p2, vec2 p3) +{ + bool b1, b2, b3; + b1 = check(pt, p1, p2) < 0.0; + b2 = check(pt, p2, p3) < 0.0; + b3 = check(pt, p3, p1) < 0.0; + return ((b1 == b2) && (b2 == b3)); +} + +bool in_left_triangle(vec2 p){ + vec2 vertex1, vertex2, vertex3; + vertex1 = vec2(progress, 0.5); + vertex2 = vec2(0.0, 0.5-progress); + vertex3 = vec2(0.0, 0.5+progress); + if (PointInTriangle(p, vertex1, vertex2, vertex3)) + { + return true; + } + return false; +} + +bool in_right_triangle(vec2 p){ + vec2 vertex1, vertex2, vertex3; + vertex1 = vec2(1.0-progress, 0.5); + vertex2 = vec2(1.0, 0.5-progress); + vertex3 = vec2(1.0, 0.5+progress); + if (PointInTriangle(p, vertex1, vertex2, vertex3)) + { + return true; + } + return false; +} + +float blur_edge(vec2 bot1, vec2 bot2, vec2 top, vec2 testPt) +{ + vec2 lineDir = bot1 - top; + vec2 perpDir = vec2(lineDir.y, -lineDir.x); + vec2 dirToPt1 = bot1 - testPt; + float dist1 = abs(dot(normalize(perpDir), dirToPt1)); + + lineDir = bot2 - top; + perpDir = vec2(lineDir.y, -lineDir.x); + dirToPt1 = bot2 - testPt; + float min_dist = min(abs(dot(normalize(perpDir), dirToPt1)), dist1); + + if (min_dist < 0.005) { + return min_dist / 0.005; + } + else { + return 1.0; + }; +} + + +vec4 transition (vec2 uv) { + if (in_left_triangle(uv)) + { + if (progress < 0.1) + { + return getFromColor(uv); + } + if (uv.x < 0.5) + { + vec2 vertex1 = vec2(progress, 0.5); + vec2 vertex2 = vec2(0.0, 0.5-progress); + vec2 vertex3 = vec2(0.0, 0.5+progress); + return mix( + getFromColor(uv), + getToColor(uv), + blur_edge(vertex2, vertex3, vertex1, uv) + ); + } + else + { + if (progress > 0.0) + { + return getToColor(uv); + } + else + { + return getFromColor(uv); + } + } + } + else if (in_right_triangle(uv)) + { + if (uv.x >= 0.5) + { + vec2 vertex1 = vec2(1.0-progress, 0.5); + vec2 vertex2 = vec2(1.0, 0.5-progress); + vec2 vertex3 = vec2(1.0, 0.5+progress); + return mix( + getFromColor(uv), + getToColor(uv), + blur_edge(vertex2, vertex3, vertex1, uv) + ); + } + else + { + return getFromColor(uv); + } + } + else { + return getFromColor(uv); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Bow Tie Vertical.fs b/src/renderer/src/application/sample-modules/isf/Bow Tie Vertical.fs new file mode 100644 index 000000000..fa96a4409 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bow Tie Vertical.fs @@ -0,0 +1,158 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/BowTieVertical.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: huynx +// License: MIT + +float check(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle (vec2 pt, vec2 p1, vec2 p2, vec2 p3) +{ + bool b1, b2, b3; + b1 = check(pt, p1, p2) < 0.0; + b2 = check(pt, p2, p3) < 0.0; + b3 = check(pt, p3, p1) < 0.0; + return ((b1 == b2) && (b2 == b3)); +} + +bool in_top_triangle(vec2 p){ + vec2 vertex1, vertex2, vertex3; + vertex1 = vec2(0.5, progress); + vertex2 = vec2(0.5-progress, 0.0); + vertex3 = vec2(0.5+progress, 0.0); + if (PointInTriangle(p, vertex1, vertex2, vertex3)) + { + return true; + } + return false; +} + +bool in_bottom_triangle(vec2 p){ + vec2 vertex1, vertex2, vertex3; + vertex1 = vec2(0.5, 1.0 - progress); + vertex2 = vec2(0.5-progress, 1.0); + vertex3 = vec2(0.5+progress, 1.0); + if (PointInTriangle(p, vertex1, vertex2, vertex3)) + { + return true; + } + return false; +} + +float blur_edge(vec2 bot1, vec2 bot2, vec2 top, vec2 testPt) +{ + vec2 lineDir = bot1 - top; + vec2 perpDir = vec2(lineDir.y, -lineDir.x); + vec2 dirToPt1 = bot1 - testPt; + float dist1 = abs(dot(normalize(perpDir), dirToPt1)); + + lineDir = bot2 - top; + perpDir = vec2(lineDir.y, -lineDir.x); + dirToPt1 = bot2 - testPt; + float min_dist = min(abs(dot(normalize(perpDir), dirToPt1)), dist1); + + if (min_dist < 0.005) { + return min_dist / 0.005; + } + else { + return 1.0; + }; +} + + +vec4 transition (vec2 uv) { + if (in_top_triangle(uv)) + { + if (progress < 0.1) + { + return getFromColor(uv); + } + if (uv.y < 0.5) + { + vec2 vertex1 = vec2(0.5, progress); + vec2 vertex2 = vec2(0.5-progress, 0.0); + vec2 vertex3 = vec2(0.5+progress, 0.0); + return mix( + getFromColor(uv), + getToColor(uv), + blur_edge(vertex2, vertex3, vertex1, uv) + ); + } + else + { + if (progress > 0.0) + { + return getToColor(uv); + } + else + { + return getFromColor(uv); + } + } + } + else if (in_bottom_triangle(uv)) + { + if (uv.y >= 0.5) + { + vec2 vertex1 = vec2(0.5, 1.0-progress); + vec2 vertex2 = vec2(0.5-progress, 1.0); + vec2 vertex3 = vec2(0.5+progress, 1.0); + return mix( + getFromColor(uv), + getToColor(uv), + blur_edge(vertex2, vertex3, vertex1, uv) + ); + } + else + { + return getFromColor(uv); + } + } + else { + return getFromColor(uv); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Boxinator.fs b/src/renderer/src/application/sample-modules/isf/Boxinator.fs new file mode 100644 index 000000000..32f9d0cdc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Boxinator.fs @@ -0,0 +1,146 @@ +/*{ + "CREDIT": "by mojovideotech", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "rate", + "TYPE": "float", + "DEFAULT": 2.5, + "MIN": 0.0, + "MAX": 10.0 + }, + { + "NAME": "edge", + "TYPE": "float", + "DEFAULT": 0.001, + "MIN": 0.0, + "MAX": 0.01 + }, + { + "NAME": "blend", + "TYPE": "float", + "DEFAULT": 0.95, + "MIN": -1.0, + "MAX": 1.0 + }, + { + "NAME": "randomize", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME": "gamma", + "TYPE": "float", + "DEFAULT": -0.3, + "MIN": -0.5, + "MAX": 0.2 + }, + { + "NAME": "grid", + "TYPE": "point2D", + "DEFAULT": [ 64.0, 36.0 ], + "MIN": [ 1.5, 1.5 ], + "MAX": [ 900.0, 600.0 ] + } + ] +}*/ + +//////////////////////////////////////////////////////////////////// +// Boxinator by mojovideotech +// +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +//////////////////////////////////////////////////////////////////// + + +#ifdef GL_ES +precision mediump float; +#endif + + +//------------------------------------------------------------------ +// simplex noise function +// by : Ian McEwan, Ashima Arts +// © 2011 Ashima Arts, MIT License + +vec4 permute(vec4 x) { return mod(((x*34.0)+1.0)*x, 289.0); } + +vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } + +float snoise(vec3 v) { + const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + vec3 i = floor(v + dot(v, C.yyy) ); + vec3 x0 = v - i + dot(i, C.xxx) ; + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min( g.xyz, l.zxy ); + vec3 i2 = max( g.xyz, l.zxy ); + vec3 x1 = x0 - i1 + 1.0 * C.xxx; + vec3 x2 = x0 - i2 + 2.0 * C.xxx; + vec3 x3 = x0 - 1. + 3.0 * C.xxx; + i = mod(i, 289.0 ); + vec4 p = permute( permute( permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + float n_ = 1.0/7.0; // N=7 + vec3 ns = n_ * D.wyz - D.xzx; + vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + vec4 b0 = vec4( x.xy, y.xy ); + vec4 b1 = vec4( x.zw, y.zw ); + vec4 s0 = floor(b0)*2.0 + 1.0; + vec4 s1 = floor(b1)*2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; + vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; + vec3 p0 = vec3(a0.xy,h.x); + vec3 p1 = vec3(a0.zw,h.y); + vec3 p2 = vec3(a1.xy,h.z); + vec3 p3 = vec3(a1.zw,h.w); + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), + dot(p2,x2), dot(p3,x3) ) ); +} +//------------------------------------------------------------------ + +float hash(float h) { return fract(sin(h) * 43758.5453123); } + +vec2 tile(vec2 cell, vec2 size) { return fract(cell*size); } + +float box(vec2 a, vec2 b){ vec2 o = step(b,a); return o.x*o.y; } + +void main(void){ + float T = TIME*rate; + vec2 uv = gl_FragCoord.xy/RENDERSIZE.xy; + vec2 g = floor(grid.xy); + float C = g.x*g.y ; + float I = 1.0 + floor(uv.x * g.x) + g.y * floor(uv.y * g.y) + g.x; + vec2 st = tile(uv, g); + float S = I / C * box(st, vec2(edge*g.xy)); + S = mix(S,hash(S),randomize); + vec3 color = vec3(S*T); + float n = snoise(color+IMG_NORM_PIXEL(inputImage, uv.xy).xyz*blend); + + gl_FragColor = sqrt(max(vec4(vec3(n, n, n ),1.0)+IMG_NORM_PIXEL(inputImage, uv.xy),0.0)+gamma); +} + diff --git a/src/renderer/src/application/sample-modules/isf/Brick Pattern.fs b/src/renderer/src/application/sample-modules/isf/Brick Pattern.fs new file mode 100644 index 000000000..118264990 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Brick Pattern.fs @@ -0,0 +1,102 @@ +/* +{ + "CATEGORIES" : [ + "Pattern", "Color" + ], + "DESCRIPTION" : "Generates a basic brick pattern", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "brickSize", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.2, + "MIN" : 0 + }, + { + "NAME" : "fillSize", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.9, + "MIN" : 0 + }, + { + "NAME" : "brickOffset", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0, + 0 + ], + "MIN" : [ + 0, + 0 + ] + }, + { + "NAME" : "fillColor", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 0, + 0 + ] + }, + { + "NAME" : "brickColor", + "TYPE" : "color", + "DEFAULT" : [ + 1, + 1, + 1, + 1 + ] + } + ], + "CREDIT" : "patriciogv" +} +*/ + +// Based on Brick example from https://thebookofshaders.com/09/ +// Author @patriciogv ( patriciogonzalezvivo.com ) - 2015 + + + +vec2 brickTile(vec2 _st, float _zoom){ + _st *= _zoom; + + // Here is where the offset is happening + _st.x += step(1., mod(_st.y,2.0)) * 0.5; + + return fract(_st); +} + +float box(vec2 _st, vec2 _size){ + _size = vec2(0.5)-_size*0.5; + vec2 uv = smoothstep(_size,_size+vec2(1e-4),_st); + uv *= smoothstep(_size,_size+vec2(1e-4),vec2(1.0)-_st); + return uv.x*uv.y; +} + +void main(void){ + vec2 st = isf_FragNormCoord; + vec4 color = fillColor; + float brickCount = (brickSize == 0.0) ? max(RENDERSIZE.x,RENDERSIZE.y) : 1.0 / brickSize; + // Apply the offset + st += brickOffset; + // Apply the brick tiling + st = brickTile(st,brickCount); + + float val = box(st,vec2(fillSize)); + color = mix(fillColor,brickColor,val); + + // Uncomment to see the space coordinates + //color.rgb = vec3(st,0.0); + + gl_FragColor = color; +} + diff --git a/src/renderer/src/application/sample-modules/isf/Bright.fs b/src/renderer/src/application/sample-modules/isf/Bright.fs new file mode 100644 index 000000000..c9d3e0317 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bright.fs @@ -0,0 +1,24 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "bright", + "TYPE": "float", + "MIN": -1.0, + "MAX": 1.0, + "DEFAULT": 0.0 + } + ] +}*/ + +void main() { + gl_FragColor = clamp(IMG_THIS_PIXEL(inputImage) + vec4(bright,bright,bright,0.0), 0.0, 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/BrightnessContrast.fs b/src/renderer/src/application/sample-modules/isf/BrightnessContrast.fs new file mode 100644 index 000000000..b2207c629 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/BrightnessContrast.fs @@ -0,0 +1,34 @@ +/* +{ + "DESCRIPTION" : "Brightness\/Contrast adjustment", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "brightness", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : -1 + }, + { + "NAME" : "contrast", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 1, + "MIN" : 0 + } + ], + "CREDIT" : "" +} +*/ + +void main() { + vec3 color = IMG_PIXEL(inputImage, gl_FragCoord.xy).rgb; + vec3 colorContrasted = (color) * contrast; + vec3 bright = colorContrasted + vec3(brightness,brightness,brightness); + gl_FragColor = vec4(bright, IMG_PIXEL(inputImage, gl_FragCoord.xy).a); +} diff --git a/src/renderer/src/application/sample-modules/isf/Broken LCD.fs b/src/renderer/src/application/sample-modules/isf/Broken LCD.fs new file mode 100644 index 000000000..ead59312f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Broken LCD.fs @@ -0,0 +1,660 @@ +/* +{ + "CATEGORIES" : [ + "Glitch", + "Stylize" + ], + "DESCRIPTION" : "Broken LCD using value noise", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "flickerLevel", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "VALUES" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "LABELS" : [ + "Random1", + "Random2", + "Random3", + "Nothing", + "Noise", + "Lines", + "Vertical", + "Horizontal", + "Stripes", + "Checkerboard" + ], + "IDENTITY" : 0, + "DEFAULT" : 0, + "LABEL" : "Pattern Style", + "TYPE" : "long", + "NAME" : "patternStyle" + }, + { + "NAME" : "glitchBrightness", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 3, + "MIN" : 0, + "LABEL" : "Glitch Thickness" + }, + { + "NAME" : "glitchBrightnessCurve", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 2, + "LABEL" : "Glitch Shape", + "MIN" : 1 + }, + { + "NAME" : "glitchRadius", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "LABEL" : "Glitch Spread", + "MIN" : 0 + }, + { + "NAME" : "glitchSmoothness", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "MIN" : 0, + "LABEL" : "Glitch Edge Smoothness" + }, + { + "NAME" : "glitchScale", + "TYPE" : "float", + "MAX" : 100, + "DEFAULT" : 4.1013007164001465, + "MIN" : 0, + "LABEL" : "Glitch Size" + }, + { + "NAME" : "noiseSeed", + "TYPE" : "float", + "MAX" : 10, + "DEFAULT" : 5.0863485336303711, + "MIN" : 0, + "LABEL" : "Glitch Seed" + }, + { + "NAME" : "patternSeed", + "TYPE" : "float", + "MAX" : 10, + "DEFAULT" : 2.3372085094451904, + "LABEL" : "Pattern Seed", + "MIN" : 0 + }, + { + "NAME" : "tintBrightness", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 4, + "MIN" : 0 + }, + { + "NAME" : "tintRadius", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.22620739042758942, + "MIN" : 0 + }, + { + "NAME" : "tintBrightnessCurve", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 1.2606533765792847, + "MIN" : 1 + }, + { + "NAME" : "tintSeed", + "TYPE" : "float", + "MAX" : 20, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "tintColor1", + "TYPE" : "color", + "DEFAULT" : [ + 0.90196079015731812, + 0.25098040699958801, + 0.25098040699958801, + 1 + ] + }, + { + "NAME" : "tintColor2", + "TYPE" : "color", + "DEFAULT" : [ + 0.25098040699958801, + 0.50196081399917603, + 0.90196079015731812, + 1 + ] + }, + { + "NAME" : "tintScale", + "TYPE" : "float", + "MAX" : 100, + "DEFAULT" : 4, + "MIN" : 0 + }, + { + "NAME" : "rowGlitchLevel", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.0099999997764825821, + "MIN" : 0 + }, + { + "NAME" : "rowFlickerLevel", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "rowGlitchSeed", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.34099999070167542, + "MIN" : 0 + }, + { + "NAME" : "hardEdge", + "TYPE" : "bool", + "DEFAULT" : true, + "LABEL" : "Hard Edge" + }, + { + "VALUES" : [ + 0, + 1, + 2 + ], + "LABELS" : [ + "Original", + "Multiply", + "Replace" + ], + "IDENTITY" : 0, + "DEFAULT" : 0, + "LABEL" : "Alpha Mode", + "TYPE" : "long", + "NAME" : "alphaMode" + } + ], + "ISFVSN" : "2", + "CREDIT" : "VIDVOX" +} +*/ + + +const float pi = 3.1415926535897932384626433832795; + + +vec2 rotatePoint(vec2 pt, float angle, vec2 center) +{ + vec2 returnMe; + float s = sin(angle * pi); + float c = cos(angle * pi); + + returnMe = pt; + + // translate point back to origin: + returnMe.x -= center.x; + returnMe.y -= center.y; + + // rotate point + float xnew = returnMe.x * c - returnMe.y * s; + float ynew = returnMe.x * s + returnMe.y * c; + + // translate point back: + returnMe.x = xnew + center.x; + returnMe.y = ynew + center.y; + return returnMe; +} + +float sign(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3) +{ + bool b1, b2, b3; + + b1 = sign(pt, v1, v2) < 0.0; + b2 = sign(pt, v2, v3) < 0.0; + b3 = sign(pt, v3, v1) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + +bool RotatedPointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3, vec2 center) +{ + bool b1, b2, b3; + + vec2 v1r = v1; + vec2 v2r = v2; + vec2 v3r = v3; + + b1 = sign(pt, v1r, v2r) < 0.0; + b2 = sign(pt, v2r, v3r) < 0.0; + b3 = sign(pt, v3r, v1r) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + + +float isPointInShape(vec2 pt, int shape, vec4 shapeCoordinates) { + float returnMe = 0.0; + + // rectangle + if (shape == 0) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + // soft edge if needed + if ((pt.x > shapeCoordinates.x) && (pt.x < shapeCoordinates.x)) { + returnMe = clamp(((pt.x - shapeCoordinates.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + else if ((pt.x > shapeCoordinates.x + shapeCoordinates.z) && (pt.x < shapeCoordinates.x + shapeCoordinates.z)) { + returnMe = clamp(((shapeCoordinates.x + shapeCoordinates.z - pt.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + } + else if (RotatedPointInTriangle(pt, shapeCoordinates.xy + shapeCoordinates.zw, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + // soft edge if needed + if ((pt.x > shapeCoordinates.x) && (pt.x < shapeCoordinates.x)) { + returnMe = clamp(((pt.x - shapeCoordinates.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + else if ((pt.x > shapeCoordinates.x + shapeCoordinates.z) && (pt.x < shapeCoordinates.x + shapeCoordinates.z)) { + returnMe = clamp(((shapeCoordinates.x + shapeCoordinates.z - pt.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + } + } + // triangle + else if (shape == 1) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy, shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + } + // oval + else if (shape == 2) { + returnMe = distance(pt, vec2(shapeCoordinates.xy + shapeCoordinates.zw / 2.0)); + if (returnMe < min(shapeCoordinates.z,shapeCoordinates.w) / 2.0) { + returnMe = 1.0; + } + else { + returnMe = 0.0; + } + } + // diamond + else if (shape == 3) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w / 2.0), shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, shapeCoordinates.w / 2.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + else if (RotatedPointInTriangle(pt, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w / 2.0), shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, 0.0), shapeCoordinates.xy + vec2(shapeCoordinates.z, shapeCoordinates.w / 2.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + } + + return returnMe; +} + +// Value Noise code by Inigo Quilez ported by @colin_movecraft + +// Value Noise (http://en.wikipedia.org/wiki/Value_noise), not to be confused with Perlin's +// Noise, is probably the simplest way to generate noise (a random smooth signal with +// mostly all its energy in the low frequencies) suitable for procedural texturing/shading, +// modeling and animation. +// +// It produces lowe quality noise than Gradient Noise (https://www.shadertoy.com/view/XdXGW8) +// but it is slightly faster to compute. When used in a fractal construction, the blockyness +// of Value Noise gets qcuikly hidden, making it a very popular alternative to Gradient Noise. +// +// The princpiple is to create a virtual grid/latice all over the plane, and assign one +// random value to every vertex in the grid. When querying/requesting a noise value at +// an arbitrary point in the plane, the grid cell in which the query is performed is +// determined (line 30), the four vertices of the grid are determined and their random +// value fetched (lines 35 to 38) and then bilinearly interpolated (lines 35 to 38 again) +// with a smooth interpolant (line 31 and 33). + + +// Value Noise 2D, Derivatives: https://www.shadertoy.com/view/4dXBRH +// Gradient Noise 2D, Derivatives: https://www.shadertoy.com/view/XdXBRH +// Value Noise 3D, Derivatives: https://www.shadertoy.com/view/XsXfRH +// Gradient Noise 3D, Derivatives: https://www.shadertoy.com/view/4dffRH +// Value Noise 2D : https://www.shadertoy.com/view/lsf3WH +// Value Noise 3D : https://www.shadertoy.com/view/4sfGzS +// Gradient Noise 2D : https://www.shadertoy.com/view/XdXGW8 +// Gradient Noise 3D : https://www.shadertoy.com/view/Xsl3Dl +// Simplex Noise 2D : https://www.shadertoy.com/view/Msf3WH + + +float hash(vec2 p, float seed) // replace this by something better +{ + p = 50.0*fract(seed*1.17921+p*0.3183099 + vec2(0.71,0.113)); + return -1.0+2.0*fract( p.x*p.y*(p.x+p.y) ); +} + +float noise( in vec2 p , in float seed) +{ + vec2 i = floor( p ); + vec2 f = fract( p ); + + vec2 u = f*f*(3.0-2.0*f); + + float v1 = mix( mix( hash( i + vec2(0.0,0.0) , floor(seed) ), + hash( i + vec2(1.0,0.0) , floor(seed) ), u.x), + mix( hash( i + vec2(0.0,1.0) , floor(seed) ), + hash( i + vec2(1.0,1.0) , floor(seed) ), u.x), u.y); + float v2 = mix( mix( hash( i + vec2(0.0,0.0) , ceil(seed) ), + hash( i + vec2(1.0,0.0) , ceil(seed) ), u.x), + mix( hash( i + vec2(0.0,1.0) , ceil(seed) ), + hash( i + vec2(1.0,1.0) , ceil(seed) ), u.x), u.y); + return mix(v1,v2,fract(seed)); +} + +float map(float n, float i1, float i2, float o1, float o2){ + return o1 + (o2-o1) * (n-i1)/(i2-i1); + +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 rand4(vec4 co) { + vec4 returnMe = vec4(0.0); + returnMe.r = rand(co.rg); + returnMe.g = rand(co.gb); + returnMe.b = rand(co.ba); + returnMe.a = rand(co.rb); + return returnMe; +} + +vec4 patternForType(vec2 coord, int pType, vec2 seed) { + vec4 returnMe = vec4(0.0); + int pt = pType; + if (pt <= 2) { + vec2 p = gl_FragCoord.xy / RENDERSIZE; + vec2 uv = p*vec2(RENDERSIZE.x/RENDERSIZE.y,1.0); + float f = 0.0; + + if (glitchSmoothness < 1.0) { + float blockness = rand(noiseSeed*(floor(uv*(1.0+glitchScale*50.0))/(1.0+glitchScale*50.0))); + if (blockness > glitchSmoothness) + uv = floor(uv*(1.0+glitchScale*20.0))/(1.0+glitchScale*20.0); + } + + //fbm - fractal noise (4 octaves) + { + uv *= glitchScale; + mat2 m = mat2( 1.6, 1.2, -1.2, 1.6 ); + f = 0.5000*noise( uv , patternSeed ); uv = m*uv; + f += 0.2500*noise( uv , patternSeed ); uv = m*uv; + f += 0.1250*noise( uv , patternSeed ); uv = m*uv; + f += 0.0625*noise( uv , patternSeed ); uv = m*uv; + } + + f = pow(f,0.5); + + float d = distance(isf_FragNormCoord,vec2(0.5)); + if (d > glitchRadius) { + f = f + (d-glitchRadius); + } + if (pt == 0) { + pt = 5 + int(min(f,1.0) * 3.99); + } + else if (pt == 1) { + pt = 3 + int(min(f,1.0) * 3.99); + } + else if (pt == 2) { + pt = 4 + int(f * 5.99); + } + /* + if (patternSeed < 0.25) + pt = 2 + int(4.999 * rand(seed)); + else if (patternSeed < 0.5) + pt = 2 + int(4.999 * rand(vec2(seed.x*floor(coord.x/(RENDERSIZE.x/4.0)),seed.x*floor(coord.y/(RENDERSIZE.y/4.0))))); + else if (patternSeed < 0.75) + pt = 2 + int(4.999 * rand(vec2(seed.x*floor(coord.x/(RENDERSIZE.x/10.0)),seed.x*floor(coord.y/(RENDERSIZE.y/10.0))))); + else + pt = 2 + int(4.999 * rand(vec2(seed.x*floor(coord.x/(RENDERSIZE.x/100.0)),seed.x*floor(coord.y/(RENDERSIZE.y/100.0))))); + */ + } + + vec2 flickCoord = coord; + if (pt == 5) { + flickCoord = vec2(coord.x,floor(coord.y/(1.0+patternSeed))*(1.0+patternSeed)); + } + else if (pt == 6) { + flickCoord = vec2(floor(coord.x/(1.0+patternSeed))*(1.0+patternSeed),coord.y); + } + else if (pt > 6) { + flickCoord = floor(coord/(1.0+patternSeed))*(1.0+patternSeed); + } + float flickRand = (flickerLevel > 0.0) ? rand(TIME * flickCoord + patternSeed) : 0.0; + bool doFlick = false; + if (flickRand < flickerLevel) { + doFlick = true; + } + //nothing + if (pt == 3) { + returnMe = vec4(0.0); + } + //noise + else if (pt == 4) { + if (doFlick) { + returnMe = IMG_THIS_PIXEL(inputImage); + } + else { + vec2 modCoord = RENDERSIZE*vec2(rand(patternSeed * coord),rand(patternSeed * coord.yx)); + modCoord *= max(1.0+patternSeed,1.00001); + returnMe.r = (mod(modCoord.x+modCoord.y,4.0)<2.0) ? 1.0 : 0.0; + returnMe.g = (mod(modCoord.x+modCoord.y,5.0)<2.0) ? 1.0 : 0.0; + returnMe.b = (mod(modCoord.x+modCoord.y,7.0)<2.0) ? 1.0 : 0.0; + returnMe.a = 1.0; + } + } + //lines + else if (pt == 5) { + vec2 modCoord = (rand(vec2(patternSeed,coord.y)) < 0.25) ? coord : coord.yx; + modCoord *= (1.0+patternSeed); + if (doFlick) { + returnMe = mix(IMG_PIXEL(inputImage,vec2(modCoord.x,0.0)),IMG_PIXEL(inputImage,vec2(modCoord.x,RENDERSIZE.y)),modCoord.y/RENDERSIZE.y); + } + else { + returnMe.r = (mod(modCoord.x,4.0)<1.0) ? 1.0 : 0.0; + returnMe.g = (mod(modCoord.x,5.0)<1.0) ? 1.0 : 0.0; + returnMe.b = (mod(modCoord.x,7.0)<1.0) ? 1.0 : 0.0; + returnMe.a = 1.0; + } + } + //vertical lines + else if (pt == 6) { + vec2 modCoord = coord; + //modCoord.x += rand(vec2(patternSeed,modCoord.x)); + modCoord.x *= (1.0+patternSeed); + modCoord *= (1.0+patternSeed); + modCoord = mod(modCoord,RENDERSIZE); + if (doFlick) { + returnMe = mix(IMG_PIXEL(inputImage,vec2(modCoord.x,0.0)),IMG_PIXEL(inputImage,vec2(modCoord.x,RENDERSIZE.y)),modCoord.y/RENDERSIZE.y); + } + else { + returnMe.r = (mod(modCoord.x,4.0)<1.0) ? 1.0 : 0.0; + returnMe.g = (mod(modCoord.x,5.0)<1.0) ? 1.0 : 0.0; + returnMe.b = (mod(modCoord.x,7.0)<1.0) ? 1.0 : 0.0; + returnMe.a = 1.0; + } + } + //horizontal lines + else if (pt == 7) { + vec2 modCoord = coord; + //modCoord.y += rand(vec2(patternSeed,modCoord.y)); + modCoord.y *= (1.0+patternSeed); + modCoord = mod(modCoord,RENDERSIZE); + if (doFlick) { + returnMe = mix(IMG_PIXEL(inputImage,vec2(0.0,modCoord.y)),IMG_PIXEL(inputImage,vec2(RENDERSIZE.x,modCoord.y)),modCoord.x/RENDERSIZE.x); + } + else { + returnMe.r = (mod(modCoord.y,4.0)<1.0) ? 1.0 : 0.0; + returnMe.g = (mod(modCoord.y,5.0)<1.0) ? 1.0 : 0.0; + returnMe.b = (mod(modCoord.y,7.0)<1.0) ? 1.0 : 0.0; + returnMe.a = 1.0; + } + } + //stripes + else if (pt == 8) { + vec2 modCoord = mod(coord*(1.0+patternSeed),10.0); + if (doFlick) { + returnMe = (modCoord.x<5.0) ? IMG_PIXEL(inputImage,vec2(modCoord.x,0.0)) : IMG_PIXEL(inputImage,vec2(modCoord.x,RENDERSIZE.y)); + } + else { + returnMe = ((modCoord.x<5.0)) ? vec4(1.0) : vec4(0.0,0.0,0.0,1.0); + } + } + //checkerboard + else { + vec2 modCoord = mod(coord*(1.0+patternSeed),80.0); + returnMe = vec4(0.9,0.9,0.9,1.0); + if (doFlick) + returnMe = IMG_PIXEL(inputImage,floor(coord/(1.0+patternSeed))*(1.0+patternSeed)); + else if ((modCoord.x<40.0)&&(modCoord.y<40.0)) + returnMe = vec4(0.5,0.5,0.5,1.0); + else if ((modCoord.x>40.0)&&(modCoord.y>40.0)) + returnMe = vec4(0.5,0.5,0.5,1.0); + + } + return returnMe; +} + +void main(){ + vec2 p = gl_FragCoord.xy / RENDERSIZE; + vec4 returnMe = IMG_THIS_PIXEL(inputImage); + float f = 1.0; + bool didRowGlitch = false; + + if (rowGlitchLevel > 0.0) { + float rowGlitch = rand(vec2(p.y+patternSeed+0.239,noiseSeed+0.821)); + if (rowGlitch < rowGlitchLevel) { + rowGlitch = rand(vec2(p.y+1.287*patternSeed+2.239,6.12*noiseSeed+3.477)); + if (p.x < rowGlitch) { + returnMe = IMG_NORM_PIXEL(inputImage,vec2(rowGlitch,p.y)); + vec4 bgColor = rand4(vec4(rowGlitch, 1.28*rowGlitchSeed+p.y, rowGlitchSeed+floor(10.0*p.x/rowGlitch), rowGlitchSeed)); + //patternForType(vec2(rowGlitch,p.y)*RENDERSIZE,patternStyle,vec2(noiseSeed,rowGlitchSeed)); + returnMe = mix(returnMe,bgColor,1.0-rowFlickerLevel*rand(vec2(TIME+p.y,rowGlitch+rowGlitchSeed))); + didRowGlitch = true; + } + } + } + { + vec2 uv = p*vec2(RENDERSIZE.x/RENDERSIZE.y,1.0); + f = 0.0; + if (glitchSmoothness < 1.0) { + float blockness = rand(noiseSeed*(floor(uv*(1.0+glitchScale*100.0))/(1.0+glitchScale*100.0))); + if (blockness > glitchSmoothness) + uv = floor(uv*(1.0+glitchScale*25.0))/(1.0+glitchScale*25.0); + } + //fbm - fractal noise (4 octaves) + { + uv *= glitchScale; + mat2 m = mat2( 1.6, 1.2, -1.2, 1.6 ); + f = 0.5000*noise( uv , noiseSeed ); uv = m*uv; + f += 0.2500*noise( uv , noiseSeed ); uv = m*uv; + f += 0.1250*noise( uv , noiseSeed ); uv = m*uv; + f += 0.0625*noise( uv , noiseSeed ); uv = m*uv; + } + + f = 1.0-pow(f,(5.0-glitchBrightnessCurve))*glitchBrightness; + + float d = distance(isf_FragNormCoord,vec2(0.5)); + if (d > glitchRadius) { + f = f + (d-glitchRadius); + } + + f = (f > 1.0) ? 1.0 : f; + + + //vec4 returnMe = vec4(f,f,f,1.0); + if (hardEdge) { + f = (f > 0.9) ? 1.0 : 0.0; + } + if (f < 1.0) { + vec4 bgColor = patternForType(gl_FragCoord.xy,patternStyle,vec2(patternSeed,0.2941)); + returnMe = mix(returnMe,bgColor,1.0-f); + } + } + + { + vec2 uv = p*vec2(RENDERSIZE.x/RENDERSIZE.y,1.0); + f = 0.0; + //fbm - fractal noise (4 octaves) + { + uv *= tintScale; + mat2 m = mat2( 1.6, 1.2, -1.2, 1.6 ); + f = 0.5000*noise( uv , tintSeed ); uv = m*uv; + f += 0.2500*noise( uv , tintSeed ); uv = m*uv; + f += 0.1250*noise( uv , tintSeed ); uv = m*uv; + f += 0.0625*noise( uv , tintSeed ); uv = m*uv; + } + + f = 1.0-pow(f,(5.0-tintBrightnessCurve))*tintBrightness; + + float d = distance(isf_FragNormCoord,vec2(0.5)); + if (d > tintRadius) { + f = f + (d-tintRadius); + } + + f = (f > 1.0) ? 1.0 : f; + + + //vec4 returnMe = vec4(f,f,f,1.0); + if (f < 1.0) { + vec4 bgColor = mix(tintColor1,tintColor2,f+rand(vec2(tintSeed,0.231))); + if (hardEdge) { + f = (f > 0.9) ? 1.0 : 0.0; + } + returnMe = mix(returnMe,bgColor*returnMe,1.0-f); + } + } + + if (alphaMode == 1) + returnMe.a *= f; + else if (alphaMode == 2) + returnMe.a = f; + + gl_FragColor = returnMe; +} + + + + +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/renderer/src/application/sample-modules/isf/Bump Distortion.fs b/src/renderer/src/application/sample-modules/isf/Bump Distortion.fs new file mode 100644 index 000000000..88a381501 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Bump Distortion.fs @@ -0,0 +1,90 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "by carter rosenberg", + "DESCRIPTION": "Bends and distorts the image", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": -1, + "NAME": "level", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "radius", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + +const float pi = 3.14159265359; + +#ifndef GL_ES +float distance (vec2 inCenter, vec2 pt) +{ + float tmp = pow(inCenter.x-pt.x,2.0)+pow(inCenter.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +void main() { + vec2 uv = isf_FragNormCoord.xy; + vec2 texSize = RENDERSIZE.xy; + vec2 tc = uv * texSize; + vec2 modifiedCenter = center * texSize; + float r = distance(modifiedCenter, tc); + float a = atan ((tc.y-modifiedCenter.y),(tc.x-modifiedCenter.x)); + float radius_sized = radius * length(RENDERSIZE); + + tc -= modifiedCenter; + + if (r < radius_sized) { + float percent = 1.0-(radius_sized - r) / radius_sized; + if (level>=0.0) { + percent = percent * percent; + tc.x = r*pow(percent,level) * cos(a); + tc.y = r*pow(percent,level) * sin(a); + } + else { + float adjustedLevel = level/2.0; + tc.x = r*pow(percent,adjustedLevel) * cos(a); + tc.y = r*pow(percent,adjustedLevel) * sin(a); + } + } + tc += modifiedCenter; + vec2 loc = tc / texSize; + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage, loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Burn.fs b/src/renderer/src/application/sample-modules/isf/Burn.fs new file mode 100644 index 000000000..af6b88c8b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Burn.fs @@ -0,0 +1,63 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/burn.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "NAME": "color", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: gre +// License: MIT +vec4 transition (vec2 uv) { + return mix( + getFromColor(uv) + progress*color, + getToColor(uv) + (1.0-progress)*color, + progress + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Butterfly Wave Scrawler.fs b/src/renderer/src/application/sample-modules/isf/Butterfly Wave Scrawler.fs new file mode 100644 index 000000000..766196a1d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Butterfly Wave Scrawler.fs @@ -0,0 +1,90 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/ButterflyWaveScrawler.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 5, + "MIN": 0, + "NAME": "amplitude", + "TYPE": "float" + }, + { + "DEFAULT": 30, + "MAX": 50, + "MIN": 0, + "NAME": "waves", + "TYPE": "float" + }, + { + "DEFAULT": 0.3, + "MAX": 1, + "MIN": 0, + "NAME": "colorSeparation", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: mandubian +// License: MIT +float PI = 3.14159265358979323846264; +float compute(vec2 p, float progress, vec2 center) { +vec2 o = p*sin(progress * amplitude)-center; +// horizontal vector +vec2 h = vec2(1., 0.); +// butterfly polar function (don't ask me why this one :)) +float theta = acos(dot(o, h)) * waves; +return (exp(cos(theta)) - 2.*cos(4.*theta) + pow(sin((2.*theta - PI) / 24.), 5.)) / 10.; +} +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy; + float inv = 1. - progress; + vec2 dir = p - vec2(.5); + float dist = length(dir); + float disp = compute(p, progress, vec2(0.5, 0.5)) ; + vec4 texTo = getToColor(p + inv*disp); + vec4 texFrom = vec4( + getFromColor(p + progress*disp*(1.0 - colorSeparation)).r, + getFromColor(p + progress*disp).g, + getFromColor(p + progress*disp*(1.0 + colorSeparation)).b, + 1.0); + return texTo*progress + texFrom*inv; +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/CMYK Halftone-Lookaround.fs b/src/renderer/src/application/sample-modules/isf/CMYK Halftone-Lookaround.fs new file mode 100644 index 000000000..9f9f62086 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/CMYK Halftone-Lookaround.fs @@ -0,0 +1,137 @@ +/*{ + "CATEGORIES": [ + "Halftone Effect", + "Retro" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 45, + "MAX": 256, + "MIN": 1, + "NAME": "gridSize", + "TYPE": "float" + }, + { + "DEFAULT": 0.15, + "MAX": 1, + "MIN": 0, + "NAME": "smoothing", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +vec4 gridRot = vec4(15.0, 45.0, 0.0, 75.0); +//vec4 gridRot = vec4(15.0, 75.0, 0.0, 45.0); +//vec4 gridRot = vec4(0.0, 0.0, 0.0, 0.0); + + + +// during calculation we find the closest dot to a frag, determine its size, and then determine the size of the four dots above/below/right/left of it. this array of offsets move "one left", "one up", "one right", and "one down"... +vec2 originOffsets[4]; + + + +vec4 RGBAtoCMYK(vec4 inColor) +{ + /* + vec4 cmyk; + cmyk.xyz = 1.0 - inColor.xyz; + cmyk.w = min(cmyk.x, min(cmyk.y, cmyk.z)); // Create K + cmyk.xyz -= cmyk.w; // Subtract K equivalent from CMY + return cmyk; + */ + vec4 ret; + ret.w = 1.0 - max(max(inColor.x, inColor.y), inColor.z); + ret.x = (1.0-inColor.x-ret.w)/(1.0-ret.w); + ret.y = (1.0-inColor.y-ret.w)/(1.0-ret.w); + ret.z = (1.0-inColor.z-ret.w)/(1.0-ret.w); + //ret.w = min(min(ret.x, ret.y), min(ret.z, ret.w)); + return ret; + +} +vec4 CMYKtoRGBA(vec4 inColor) +{ + vec4 ret; + ret.xyz = (1.0-inColor.xyz)*(1.0-inColor.w); + ret.w = 1.0; + return ret; +} + +void main() { + // a halftone is an overlapping series of grids of dots + // each grid of dots is rotated by a different amount + // the size of the dots determines the colors. the shape of the dot should never change (always be a dot with regular edges) + originOffsets[0] = vec2(-1.0, 0.0); + originOffsets[1] = vec2(0.0, 1.0); + originOffsets[2] = vec2(1.0, 0.0); + originOffsets[3] = vec2(0.0, -1.0); + + vec4 cmykAmounts = vec4(0.0); + + // for each of the channels (i) of CMYK... + for (int i=0; i<4; ++i) { + // figure out the rotation of the grid in radians + float rotRad = radians(gridRot[i]); + // the grids are rotated counter-clockwise- to find the nearest dot, take the fragment pixel loc, + // rotate it clockwise, and split by the grid to find the center of the dot. then rotate this + // coord counter-clockwise to yield the location of the center of the dot in pixel coords local to the render space + mat2 ccTrans = mat2(vec2(cos(rotRad), sin(rotRad)), vec2(-1.0*sin(rotRad), cos(rotRad))); + mat2 cTrans = mat2(vec2(cos(rotRad), -1.0*sin(rotRad)), vec2(sin(rotRad), cos(rotRad))); + + // find the location of the frag in the grid (prior to rotating it) + vec2 gridFragLoc = cTrans * gl_FragCoord.xy; + // find the center of the dot closest to the frag- there's no "round" in GLSL 1.2, so do a "floor" to find the dot to the bottom-left of the frag, then figure out if the frag would be in the top and right halves of that square to find the closest dot to the frag + vec2 gridOriginLoc = vec2(floor(gridFragLoc.x/gridSize), floor(gridFragLoc.y/gridSize)); + + vec2 tmpGridCoords = gridFragLoc/vec2(gridSize); + bool fragAtTopOfGrid = ((tmpGridCoords.y-floor(tmpGridCoords.y)) > (gridSize/2.0)) ? true : false; + bool fragAtRightOfGrid = ((tmpGridCoords.x-floor(tmpGridCoords.x)) > (gridSize/2.0)) ? true : false; + if (fragAtTopOfGrid) + gridOriginLoc.y = gridOriginLoc.y + 1.0; + if (fragAtRightOfGrid) + gridOriginLoc.x = gridOriginLoc.x + 1.0; + // ...at this point, "gridOriginLoc" contains the grid coords of the nearest dot to the fragment being rendered + // convert the location of the center of the dot from grid coords to pixel coords + vec2 gridDotLoc = vec2(gridOriginLoc.x*gridSize, gridOriginLoc.y*gridSize) + vec2(gridSize/2.0); + // rotate the pixel coords of the center of the dot so they become relative to the rendering space + vec2 renderDotLoc = ccTrans * gridDotLoc; + // get the color of the pixel of the input image under this dot (the color will ultimately determine the size of the dot) + vec4 renderDotImageColorRGB = IMG_PIXEL(inputImage, renderDotLoc); + // convert the color from RGB to CMYK + vec4 renderDotImageColorCMYK = RGBAtoCMYK(renderDotImageColorRGB); + + // the amount of this channel is taken from the same channel of the color of the pixel of the input image under this halftone dot + float imageChannelAmount = renderDotImageColorCMYK[i]; + // the size of the dot is determined by the value of the channel + float dotRadius = imageChannelAmount * (gridSize*1.50/2.0); + float fragDistanceToDotCenter = distance(gl_FragCoord.xy, renderDotLoc); + if (fragDistanceToDotCenter < dotRadius) { + cmykAmounts[i] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToDotCenter); + } + + // calcluate the size of the dots abov/below/to the left/right to see if they're overlapping + for (int j=0; j<4; ++j) { + gridDotLoc = vec2((gridOriginLoc.x+originOffsets[j].x)*gridSize, (gridOriginLoc.y+originOffsets[j].y)*gridSize) + vec2(gridSize/2.0); + renderDotLoc = ccTrans * gridDotLoc; + renderDotImageColorRGB = IMG_PIXEL(inputImage, renderDotLoc); + renderDotImageColorCMYK = RGBAtoCMYK(renderDotImageColorRGB); + + imageChannelAmount = renderDotImageColorCMYK[i]; + dotRadius = imageChannelAmount * (gridSize*1.50/2.0); + fragDistanceToDotCenter = distance(gl_FragCoord.xy, renderDotLoc); + if (fragDistanceToDotCenter < dotRadius) { + cmykAmounts[i] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToDotCenter); + } + } + } + + gl_FragColor = CMYKtoRGBA(cmykAmounts); +} diff --git a/src/renderer/src/application/sample-modules/isf/CMYK Halftone.fs b/src/renderer/src/application/sample-modules/isf/CMYK Halftone.fs new file mode 100644 index 000000000..16d9200e5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/CMYK Halftone.fs @@ -0,0 +1,87 @@ +/*{ + "CATEGORIES": [ + "Halftone Effect", + "Retro" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 45, + "MAX": 256, + "MIN": 1, + "NAME": "gridSize", + "TYPE": "float" + }, + { + "DEFAULT": 0.15, + "MAX": 1, + "MIN": 0, + "NAME": "smoothing", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +const vec4 gridRot = vec4(15.0, 45.0, 0.0, 75.0); + + + + +vec4 RGBAtoCMYK(vec4 inColor) +{ + vec4 ret; + ret.w = 1.0 - max(max(inColor.x, inColor.y), inColor.z); + ret.x = (1.0-inColor.x-ret.w)/(1.0-ret.w); + ret.y = (1.0-inColor.y-ret.w)/(1.0-ret.w); + ret.z = (1.0-inColor.z-ret.w)/(1.0-ret.w); + return ret; +} +vec4 CMYKtoRGBA(vec4 inColor) +{ + vec4 ret; + ret.xyz = (1.0-inColor.xyz)*(1.0-inColor.w); + ret.w = 1.0; + return ret; +} + +void main() { + // a halftone is an overlapping series of grids of dots + // each grid of dots is rotated by a different amount + // the size of the dots determines the colors. the shape of the dot should never change (always be a dot with regular edges) + + vec4 cmykAmounts = vec4(0.0); + + // for each of the channels (i) of CMYK... + for (int i=0; i<4; ++i) { + // figure out the rotation of the grid in radians + float rotRad = radians(gridRot[i]); + // the grids are rotated counter-clockwise- to find the nearest dot, take the fragment pixel loc, + // rotate it clockwise, and split by the grid to find the center of the dot. then rotate this + // coord counter-clockwise to yield the location of the center of the dot in pixel coords local to the render space + mat2 ccTrans = mat2(vec2(cos(rotRad), sin(rotRad)), vec2(-1.0*sin(rotRad), cos(rotRad))); + mat2 cTrans = mat2(vec2(cos(rotRad), -1.0*sin(rotRad)), vec2(sin(rotRad), cos(rotRad))); + + // render loc -> grid loc -> grid dot loc -> grid dot loc in render coords -> pixel color under grid dot loc + vec2 gridFragLoc = cTrans * gl_FragCoord.xy; + vec2 gridDotLoc = vec2(floor(gridFragLoc.x/gridSize)*gridSize, floor(gridFragLoc.y/gridSize)*gridSize); + gridDotLoc = gridDotLoc + vec2(gridSize/2.0); + vec2 renderDotLoc = ccTrans * gridDotLoc; + vec4 renderDotImageColorRGB = IMG_PIXEL(inputImage, renderDotLoc); + vec4 renderDotImageColorCMYK = RGBAtoCMYK(renderDotImageColorRGB); + + float channelAmount = renderDotImageColorCMYK[i]; + float dotRadius = channelAmount * (gridSize/2.0); + float fragDistanceToGridCenter = distance(gl_FragCoord.xy, renderDotLoc); + // the amount of the channel depends on the distance to the center of the grid, the size of the dot, and smoothing + float smoothDist = smoothing * (gridSize/6.0); + cmykAmounts[i] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToGridCenter); + } + + gl_FragColor = CMYKtoRGBA(cmykAmounts); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Channel Slide.fs b/src/renderer/src/application/sample-modules/isf/Channel Slide.fs new file mode 100644 index 000000000..a26fac725 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Channel Slide.fs @@ -0,0 +1,50 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "slideAmt", + "LABEL": "slide amount", + "TYPE": "color", + "DEFAULT": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + { + "NAME": "reflection", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ] +}*/ + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + if (reflection == true) { + vec4 outPixel; + outPixel.rgb = srcPixel.rgb - slideAmt.rgb; + outPixel.a = srcPixel.a + slideAmt.a; // alpha behaves the same in both modes (just easier to work with) + gl_FragColor.x = (outPixel.x<0.0) ? outPixel.x+1.0 : outPixel.x; + gl_FragColor.y = (outPixel.y<0.0) ? outPixel.y+1.0 : outPixel.y; + gl_FragColor.z = (outPixel.z<0.0) ? outPixel.z+1.0 : outPixel.z; + //gl_FragColor.a = (outPixel.a<0.0) ? outPixel.a+1.0 : outPixel.a; + gl_FragColor.a = (outPixel.a>1.0) ? outPixel.a-1.0 : outPixel.a; + } + else { + vec4 outPixel = srcPixel+slideAmt; + gl_FragColor.x = (outPixel.x>1.0) ? outPixel.x-1.0 : outPixel.x; + gl_FragColor.y = (outPixel.y>1.0) ? outPixel.y-1.0 : outPixel.y; + gl_FragColor.z = (outPixel.z>1.0) ? outPixel.z-1.0 : outPixel.z; + gl_FragColor.a = (outPixel.a>1.0) ? outPixel.a-1.0 : outPixel.a; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Checkerboard.fs b/src/renderer/src/application/sample-modules/isf/Checkerboard.fs new file mode 100644 index 000000000..f4b7f99d1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Checkerboard.fs @@ -0,0 +1,92 @@ +/* +{ + "CATEGORIES" : [ + "Pattern", "Color" + ], + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "width", + "TYPE" : "float", + "DEFAULT" : 0.25 + }, + { + "NAME" : "offset", + "TYPE" : "point2D", + "DEFAULT" : [ + 0, + 0 + ], + "MIN" : [ + 0, + 0 + ], + "MAX" : [ + 1, + 1 + ] + }, + { + "NAME" : "color1", + "TYPE" : "color", + "DEFAULT" : [ + 1, + 1, + 1, + 1 + ] + }, + { + "NAME" : "color2", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 0, + 1 + ] + }, + { + "NAME" : "splitPos", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + + + +void main() { + // determine if we are on an even or odd line + // math goes like.. + // mod(((coord+offset) / width),2) + + + vec4 out_color = color2; + float size = width * RENDERSIZE.x; + + if (size == 0.0) { + out_color = color1; + } + else if ((mod(((gl_FragCoord.x+(offset.x*RENDERSIZE.x)) / size),2.0) < 2.0 * splitPos.x)&&(mod(((gl_FragCoord.y+(offset.y*RENDERSIZE.y)) / size),2.0) > 2.0 * splitPos.y)) { + out_color = color1; + } + else if ((mod(((gl_FragCoord.x+(offset.x*RENDERSIZE.x)) / size),2.0) > 2.0 * splitPos.x)&&(mod(((gl_FragCoord.y+(offset.y*RENDERSIZE.y)) / size),2.0) < 2.0 * splitPos.y)) { + out_color = color1; + } + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Chroma Zoom.fs b/src/renderer/src/application/sample-modules/isf/Chroma Zoom.fs new file mode 100644 index 000000000..0f846db9b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Chroma Zoom.fs @@ -0,0 +1,83 @@ +/* +{ + "CATEGORIES" : [ + "Color Effect", + "Stylize" + ], + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "master_zoom", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 1, + "MIN" : 0.1 + }, + { + "NAME" : "red_zoom", + "TYPE" : "float", + "MAX" : 1.5, + "DEFAULT" : 1, + "MIN" : 1 + }, + { + "NAME" : "green_zoom", + "TYPE" : "float", + "MAX" : 1.5, + "DEFAULT" : 1, + "MIN" : 1 + }, + { + "NAME" : "blue_zoom", + "TYPE" : "float", + "MAX" : 1.5, + "DEFAULT" : 1, + "MIN" : 1 + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + } + ], + "CREDIT" : "by toneburst" +} +*/ + +void main() { + vec2 loc; + vec2 modifiedCenter; + + loc = isf_FragNormCoord; + modifiedCenter = center; + + vec2 locR = (loc - modifiedCenter)*(1.0/(red_zoom*master_zoom)) + modifiedCenter; + vec2 locG = (loc - modifiedCenter)*(1.0/(green_zoom*master_zoom)) + modifiedCenter; + vec2 locB = (loc - modifiedCenter)*(1.0/(blue_zoom*master_zoom)) + modifiedCenter; + + vec4 outPix; + outPix.r = IMG_NORM_PIXEL(inputImage,locR).r; + outPix.g = IMG_NORM_PIXEL(inputImage,locG).g; + outPix.b = IMG_NORM_PIXEL(inputImage,locB).b; + + loc.x = (loc.x - modifiedCenter.x)*(1.0/master_zoom) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/master_zoom) + modifiedCenter.y; + + outPix.a = IMG_NORM_PIXEL(inputImage,loc).a; + gl_FragColor = outPix; +} diff --git a/src/renderer/src/application/sample-modules/isf/Circle Crop.fs b/src/renderer/src/application/sample-modules/isf/Circle Crop.fs new file mode 100644 index 000000000..e0f5fec32 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle Crop.fs @@ -0,0 +1,70 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/CircleCrop.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "bgcolor", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: fkuteken +// ported by gre from https://gist.github.com/fkuteken/f63e3009c1143950dee9063c3b83fb88 + +float ratio = RENDERSIZE.x/RENDERSIZE.y; +vec2 ratio2 = vec2(1.0, 1.0 / ratio); +float s = pow(2.0 * abs(progress - 0.5), 3.0); + +vec4 transition(vec2 p) { + float dist = length((vec2(p) - 0.5) * ratio2); + return mix( + progress < 0.5 ? getFromColor(p) : getToColor(p), // branching is ok here as we statically depend on progress uniform (branching won't change over pixels) + bgcolor, + step(s, dist) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Circle Open.fs b/src/renderer/src/application/sample-modules/isf/Circle Open.fs new file mode 100644 index 000000000..6c9145074 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle Open.fs @@ -0,0 +1,67 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/circleopen.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.3, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + }, + { + "DEFAULT": true, + "NAME": "opening", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: gre +// License: MIT + +const vec2 center = vec2(0.5, 0.5); +const float SQRT_2 = 1.414213562373; + +vec4 transition (vec2 uv) { + float x = opening ? progress : 1.-progress; + float m = smoothstep(-smoothness, 0.0, SQRT_2*distance(center, uv) - x*(1.+smoothness)); + return mix(getFromColor(uv), getToColor(uv), opening ? 1.-m : m); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Circle Splash Distortion.fs b/src/renderer/src/application/sample-modules/isf/Circle Splash Distortion.fs new file mode 100644 index 000000000..beb4366f2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle Splash Distortion.fs @@ -0,0 +1,102 @@ +/* +{ + "CATEGORIES" : [ + "Distortion Effect" + ], + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "radius", + "TYPE" : "float", + "MAX" : 0.75, + "DEFAULT" : 0.125, + "MIN" : 0 + }, + { + "NAME" : "streaks", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + + + + +// Pretty simple ‚Äì¬ if we're inside the radius, draw as normal +// If we're outside the circle grab the last color along the angle + +#ifndef GL_ES +float distance (vec2 inCenter, vec2 pt) +{ + float tmp = pow(inCenter.x-pt.x,2.0)+pow(inCenter.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +void main() { + vec2 uv = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 texSize = RENDERSIZE; + vec2 tc = uv * texSize; + vec2 tc2 = uv * texSize; + vec2 modifiedCenter = center * texSize; + float r = distance(modifiedCenter, tc); + float render_length = length(RENDERSIZE); + float a = atan ((tc.y-modifiedCenter.y),(tc.x-modifiedCenter.x)); + float radius_sized = clamp(radius * render_length, 1.0, render_length); + + tc -= modifiedCenter; + tc2 -= modifiedCenter; + + if (r < radius_sized) { + tc.x = r * cos(a); + tc.y = r * sin(a); + tc2 = tc; + } + else { + tc.x = radius_sized * cos(a); + tc.y = radius_sized * sin(a); + tc2.x = (radius_sized + streaks * render_length) * cos(a); + tc2.y = (radius_sized + streaks * render_length) * sin(a); + } + tc += modifiedCenter; + tc2 += modifiedCenter; + vec2 loc = tc / texSize; + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + vec4 result = IMG_NORM_PIXEL(inputImage, loc); + if (streaks > 0.0) { + vec2 loc2 = tc2 / texSize; + vec4 mixColor = IMG_NORM_PIXEL(inputImage, loc2); + result = mix(result, mixColor, clamp(2.0*((r - radius_sized)/(render_length))*streaks,0.0,1.0)); + } + gl_FragColor = result; + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Circle Trails.fs b/src/renderer/src/application/sample-modules/isf/Circle Trails.fs new file mode 100644 index 000000000..3d70ad2de --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle Trails.fs @@ -0,0 +1,160 @@ +/*{ + "CATEGORIES": [ + "Drawing" + ], + "DESCRIPTION": "", + "INPUTS": [ + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pointInput", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0.9596965909004211, + 0.5817266273340564, + 0.17662368847084817, + 1 + ], + "NAME": "startColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.9623754620552063, + 0.9023552320837249, + 0.28796788144265206, + 1 + ], + "NAME": "endColor", + "TYPE": "color" + }, + { + "DEFAULT": 0.1, + "MAX": 0.5, + "MIN": 0, + "NAME": "startSize", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 0.5, + "MIN": 0, + "NAME": "endSize", + "TYPE": "float" + }, + { + "DEFAULT": 16, + "MAX": 32, + "MIN": 1, + "NAME": "trailCount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 0.1, + "MIN": 0, + "NAME": "strokeSize", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "strokeColor", + "TYPE": "color" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "HEIGHT": "1", + "PERSISTENT": true, + "TARGET": "pointsBuffer", + "WIDTH": "$trailCount" + }, + { + "DESCRIPTION": "this empty pass is rendered at the same rez as whatever you are running the ISF filter at- the previous step rendered an image at one-sixteenth the res, so this step ensures that the output is full-size" + } + ], + "VSN": "" +} +*/ + +float drawCircle(vec2 pt, vec2 circlePosition, float circleSize){ + if (distance(pt, circlePosition) 0.0) + inCircle += drawCircle(loc,ptInfo.xy*aspectRatio,cSize); + + if (inCircle > 0.0) { + //inCircle = 1.0; + j = i; + break; + } + + if (i == int(trailCount)) { + j = i; + break; + } + + } + + if (inCircle > 0.0) { + returnMe = mix(startColor,endColor,float(j)/floor(trailCount)); + + if ((strokeSize > 0.0)&&(inCircle > 1.0 - strokeSize)) { + returnMe = strokeColor; + } + + } + + } + + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Circle Warp.fs b/src/renderer/src/application/sample-modules/isf/Circle Warp.fs new file mode 100644 index 000000000..9462fa5b8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle Warp.fs @@ -0,0 +1,78 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Warps an image to fit in a circle by fitting the height of the image to the height of a circle", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 0.5, + "MIN": 0, + "NAME": "radius", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": 0, + "NAME": "width", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "resultRotation", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +const float pi = 3.1415926535897932384626433832795; + +vec2 rotatePointNorm(vec2 pt, float rot) { + vec2 returnMe = pt; + + float r = distance(vec2(0.50), returnMe); + float a = atan((returnMe.y-0.5),(returnMe.x-0.5)); + + returnMe.x = r * cos(a + 2.0 * pi * rot - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * rot - pi) + 0.5; + + returnMe = returnMe; + + return returnMe; +} + +void main() { + vec4 inputPixelColor; + vec2 pt = isf_FragNormCoord; + vec2 ct = vec2(0.5,0.5); + + pt -= ct; + pt.x /= width; + pt += ct; + + pt = mix(vec2((pt.x*RENDERSIZE.x/RENDERSIZE.y)-(RENDERSIZE.x*.5-RENDERSIZE.y*.5)/RENDERSIZE.y,pt.y), + vec2(pt.x,pt.y*(RENDERSIZE.y/RENDERSIZE.x)-(RENDERSIZE.y*.5-RENDERSIZE.x*.5)/RENDERSIZE.x), + step(RENDERSIZE.x,RENDERSIZE.y)); + pt = rotatePointNorm(pt,resultRotation+0.5); + if (distance(pt,ct) < radius) { + float a = (atan(ct.y-pt.y,ct.x-pt.x) + pi) / (2.0*pi); + //inputPixelColor = IMG_NORM_PIXEL(inputImage,pt); + pt.y -= 0.5; + pt.y /= 2.0*sqrt(pow(radius,2.0)-pow((pt.x-0.5),2.0)); + pt.y += 0.5; + inputPixelColor = IMG_NORM_PIXEL(inputImage,pt); + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Circle Wrap Distortion.fs b/src/renderer/src/application/sample-modules/isf/Circle Wrap Distortion.fs new file mode 100644 index 000000000..56b750783 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle Wrap Distortion.fs @@ -0,0 +1,97 @@ +/* +{ + "CATEGORIES" : [ + "Distortion Effect" + ], + "DESCRIPTION" : "Wraps the video into a circular shape", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "inputAngle", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "MIN" : 0 + }, + { + "NAME" : "inputRadius", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 1, + "MIN" : 0 + }, + { + "NAME" : "inputCenter", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + }, + { + "NAME" : "mirror", + "TYPE" : "bool", + "DEFAULT" : false + }, + { + "NAME" : "correctAspect", + "TYPE" : "bool", + "DEFAULT" : true + } + ], + "CREDIT" : "VIDVOX" +} +*/ + + +const float tau = 6.28318530718; +const float pi = 3.14159265359; +const float halfpi = 1.57079632679; + + +void main() { + vec4 inputPixelColor; + if (inputRadius > 0.0) { + + vec2 loc = isf_FragNormCoord.xy; + vec2 center = inputCenter; + + // account for aspect ratio so we stay circle + if (correctAspect) { + loc.x = loc.x * RENDERSIZE.x/RENDERSIZE.y; + center.x = center.x * RENDERSIZE.x/RENDERSIZE.y; + } + // translate from polar coords back to cart for this point + // the effect translates (x,y) to (r,theta) + float r = distance(loc,center); + float theta = (inputAngle * tau + pi + atan(loc.x-center.x,loc.y-center.y))/tau; + + theta = mod(theta, 1.0); + + //loc = vec2((r * 2.0)/inputRadius, theta / pi); + loc = vec2(theta, (r * 2.0)/inputRadius); + + if ((loc.x < 0.0)||(loc.x > 1.0)||(loc.y < 0.0)||(loc.y > 1.0)) + inputPixelColor = vec4(0.0); + else { + loc.y = 1.0 - loc.y; + if (mirror) { + loc.x = (loc.x < 0.5) ? loc.x * 2.0 : 2.0 - loc.x * 2.0; + } + inputPixelColor = IMG_NORM_PIXEL(inputImage, loc); + } + } + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Circle.fs b/src/renderer/src/application/sample-modules/isf/Circle.fs new file mode 100644 index 000000000..33928f39d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circle.fs @@ -0,0 +1,87 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/circle.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "backColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Fernando Kuteken +// License: MIT + + +vec4 transition (vec2 uv) { + + float distance = length(uv - center); + float radius = sqrt(8.0) * abs(progress - 0.5); + + if (distance > radius) { + return backColor; + } + else { + if (progress < 0.5) return getFromColor(uv); + else return getToColor(uv); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Circuits.fs b/src/renderer/src/application/sample-modules/isf/Circuits.fs new file mode 100644 index 000000000..91b5cd824 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circuits.fs @@ -0,0 +1,189 @@ +/*{ + "CREDIT": "by thedantheman", + "DESCRIPTION": "", + "CATEGORIES": [ + ], + "INPUTS": [ + { + "NAME": "inputA", + "TYPE": "float", + "MAX" : 10.0, + "MIN" : 0.0, + "DEFAULT":0.002 + }, + { + "NAME": "inputF", + "TYPE": "float", + "MAX" : 10.0, + "MIN" : 0.2, + "DEFAULT":1.0 + }, + { + "NAME": "timeScale", + "TYPE": "float", + "MAX" : 10.0, + "MIN" : 0.2, + "DEFAULT":1.0 + }, + { + "NAME": "timeOffset", + "TYPE": "float", + "MAX" : 1.0, + "MIN" : 0.0, + "DEFAULT":0.4 + }, + { + "NAME": "slide", + "TYPE": "point2D", + "DEFAULT": [0,0], + "MIN": [-1,-1], + "MAX": [1,1] + }, + { + "NAME": "slidePos", + "TYPE": "float", + "MAX" : 1000.0, + "MIN" : 0.0, + "DEFAULT":0.0 + } +] +} +*/ + + +#ifdef GL_ES +precision mediump float; +#endif + + +// rotate position around axis +vec2 rotate(vec2 p, float a) +{ + return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); +} + +// 1D random numbers +float rand1(float n) +{ + return fract(sin(n) * 58.5453123); +} + +// 2D random numbers +vec2 rand2(in vec2 p) +{ + return fract(vec2(sin(p.x * 591.32 + p.y * 154.077 + TIME), cos(p.x * 391.32 + p.y * 49.077 + TIME))); +} + +// 1D noise +float noiseF(float p) +{ + float fl = floor(p); + float fc = fract(p); + return mix(rand1(fl), rand1(fl + 1.0), fc); +} + +// voronoi distance noise, based on iq's articles +float voronoi(in vec2 x) +{ + vec2 p = floor(x); + vec2 f = fract(x); + + vec2 res = vec2(8.0); + for(int j = -1; j <= 1; j ++) + { + for(int i = -1; i <= 1; i ++) + { + vec2 b = vec2(i, j); + vec2 r = vec2(b) - f + rand2(p + b); + + // chebyshev distance, one of many ways to do this + float d = max(abs(r.x), abs(r.y)); + + if(d < res.x) + { + res.y = res.x; + res.x = d; + } + else if(d < res.y) + { + res.y = d; + } + } + } + return res.y - res.x; +} + + + + +void main(void) +{ + float flicker = noiseF(TIME * timeScale) * (1.0-timeOffset) + timeOffset; + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy; + uv = (uv - 0.5) * 2.0; + vec2 suv = uv; + uv.x *= RENDERSIZE.x / RENDERSIZE.y; + + + float v = 0.0; + + // that looks highly interesting: + //v = 1.0 - length(uv) * 1.3; + + + // a bit of camera movement + //uv *= 0.6 + sin(TIME * 0.1) * 0.4; + //uv = rotate(uv, sin(0.0 * 0.3) * 1.0); + uv += slide*slidePos; + + + // add some noise octaves + float a = inputA, f = inputF; + + for(int i = 0; i < 3; i ++) // 4 octaves also look nice, its getting a bit slow though + { + float v1 = voronoi(uv * f + 1.0); + float v2 = 0.0; + + // make the moving electrons-effect for higher octaves + if(i > 0) + { + // of course everything based on voronoi + v2 = voronoi(uv * f * 0.5 + 50.0 + TIME); + + float va = 0.0, vb = 0.0; + va = 1.0 - smoothstep(0.0, 0.1, v1); + vb = 1.0 - smoothstep(0.0, 0.08, v2); + v += a * pow(va * (0.5 + vb), 2.0); + } + + // make sharp edges + v1 = 1.0 - smoothstep(0.0, 0.3, v1); + + // noise is used as intensity map + v2 = a * (noiseF(v1 * 10.0 + 0.1)); + + // octave 0's intensity changes a bit + if(i == 0) + v += v2*flicker; + else + v += v2; + + f *= inputF; + a *= inputA; + } + + // slight vignetting + v *= exp(-0.6 * length(suv)) * 1.2; + + // use texture channel0 for color? why not. + //vec3 cexp = IMG_NORM_PIXEL(iChannel0,uv * 0.001).xyz * 3.0 + IMG_NORM_PIXEL(iChannel0,uv * 0.01).xyz;//vec3(1.0, 2.0, 4.0); + + // old blueish color set + vec3 cexp = vec3(3.0, 2.0, 4.0); + cexp *= 1.3; + + vec3 col = vec3(pow(v, cexp.x), pow(v, cexp.y), pow(v, cexp.z)) * 1.0; + + gl_FragColor = vec4(col, 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Circular Feedback Mask.fs b/src/renderer/src/application/sample-modules/isf/Circular Feedback Mask.fs new file mode 100644 index 000000000..baab2494e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circular Feedback Mask.fs @@ -0,0 +1,158 @@ +/* +{ + "CATEGORIES" : [ + "Feedback" + ], + "DESCRIPTION" : "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "maskRadius", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.25, + "MIN" : 0 + }, + { + "NAME" : "feedbackRate", + "TYPE" : "float", + "MAX" : 16, + "DEFAULT" : 1, + "MIN" : 0 + }, + { + "NAME" : "twirlAmount", + "TYPE" : "float", + "MAX" : 0.25, + "DEFAULT" : 0, + "MIN" : -0.25 + }, + { + "NAME" : "fadeRate", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "centerFeedback", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "feedbackCenter", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + }, + { + "LABELS" : [ + "Mask", + "CenteredMask", + "Scaled", + "Wrap", + "MirrorWrap", + "InvertedMask" + ], + "NAME" : "styleMode", + "TYPE" : "long", + "DEFAULT" : 2, + "VALUES" : [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + { + "NAME" : "clearBuffer", + "TYPE" : "event" + } + ], + "PASSES" : [ + { + "TARGET" : "feedbackBuffer", + "PERSISTENT" : true + } + ], + "CREDIT" : "VIDVOX" +} +*/ + +const float pi = 3.1415926535897932384626433832795; +const float tau = 6.2831853071795864769252867665590; + + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 loc = gl_FragCoord.xy; + vec2 locCenter = feedbackCenter * RENDERSIZE; + float scaledRadius = maskRadius * min(RENDERSIZE.x,RENDERSIZE.y); + float dist = distance(locCenter,loc); + bool invertMask = (styleMode == 5); + // if within the shape, just do the shape + if (((dist>scaledRadius)&&(invertMask))||((dist 0.0)&&(clearBuffer == false)) { + inputPixelColor = mix(inputPixelColor,IMG_THIS_PIXEL(feedbackBuffer),centerFeedback); + } + //inputPixelColor = vec4(loc.x,loc.y,0.0,1.0); + } + else if (clearBuffer == false) { + //float r = distance(RENDERSIZE/2.0,loc); + float a = atan((loc.y-locCenter.y),(loc.x-locCenter.x)); + float shiftAmount = -1.0 * feedbackRate; + vec2 shift = shiftAmount * vec2(cos(a + twirlAmount * tau), sin(a + twirlAmount * tau)); + loc = (invertMask) ? loc - shift : loc + shift; + inputPixelColor = IMG_PIXEL(feedbackBuffer,loc); + inputPixelColor.a -= fadeRate / 50.0; + //inputPixelColor = vec4((a+pi)/(2.0*pi),2.0*r/RENDERSIZE.x,0.0,1.0); + //inputPixelColor = vec4(shift.x*2.0-1.0,shift.y*2.0-1.0,0.0,1.0); + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Circular Screen.fs b/src/renderer/src/application/sample-modules/isf/Circular Screen.fs new file mode 100644 index 000000000..f295b3459 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circular Screen.fs @@ -0,0 +1,111 @@ +/*{ + "CATEGORIES": [ + "Halftone Effect" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "sharpness", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "offset", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": 0, + "NAME": "scale", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "colorize", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +const float tau = 6.28318530718; + +float pattern() { + float s = 0.0; + float c = 1.0; + vec2 tex = isf_FragNormCoord * RENDERSIZE; + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ); + float d = distance(point, center*RENDERSIZE) * max(scale,0.001); + return ( sin(d + offset * tau) ) * 4.0; +} + +void main() { + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = color + sharpness * (8.0*color - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + + float average = ( final.r + final.g + final.b ) / 3.0; + final = vec4( vec3( average * 10.0 - 5.0 + pattern() ), color.a ); + final = mix (color * final, final, 1.0-colorize); + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Circular Screen.vs b/src/renderer/src/application/sample-modules/isf/Circular Screen.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Circular Screen.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/City Lights.fs b/src/renderer/src/application/sample-modules/isf/City Lights.fs new file mode 100644 index 000000000..209bba5b6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/City Lights.fs @@ -0,0 +1,289 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "blurAmount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 12.0, + "DEFAULT": 4.0 + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "edgeIntensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 100.0, + "DEFAULT": 15.0 + }, + { + "NAME": "edgeThreshold", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "erodeIntensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.25 + } + ], + "PASSES": [ + { + "TARGET": "edges", + "DESCRIPTION": "PASSINDEX is 0" + }, + { + "TARGET": "halfSize", + "WIDTH": "floor($WIDTH/2.0)", + "HEIGHT": "floor($HEIGHT/2.0)", + "DESCRIPTION": "pass 1, 1x horiz sampling (3 wide total) to prevent data loss" + }, + { + "TARGET": "quarterSizePassA", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "pass 2, vert sampling, use erodeRadius (size is being reduced, halve coords deltas when sampling)" + }, + { + "TARGET": "quarterSizePassB", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "pass 3, horiz sampling, use erodeRadius" + }, + { + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 4" + }, + { + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 5" + }, + { + "TARGET": "fullGaussA", + "DESCRIPTION": "Pass 6" + }, + { + "TARGET": "fullGaussB", + "DESCRIPTION": "Pass 7" + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + +varying vec2 texOffsets[5]; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; + +in vec2 texOffsets[5]; +#endif + +vec3 rgb2hsv(vec3 c); +float gray(vec4 n); + + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first pass draws edges + if (PASSINDEX==0) { + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + float gx = (-1.0 * gray(colorLA)) + (-2.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (2.0 * gray(colorR)) + (1.0 * gray(colorRB)); + float gy = (1.0 * gray(colorLA)) + (2.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-2.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + + float bright = pow(gx*gx + gy*gy,0.5); + vec4 final = color * bright; + + // if the brightness is below the threshold draw black + if (bright < edgeThreshold) { + final = vec4(0.0, 0.0, 0.0, 1.0); + } + else { + final = final * edgeIntensity; + final.a = 1.0; + } + + gl_FragColor = final; + } + // next three passes are doing an erode. the first and third are doing horizontal sampling, the second is doing vert sampling + else if (PASSINDEX==1 || PASSINDEX==2 || PASSINDEX==3) { + // generally speaking, sample a bunch of pixels, for each sample convert color val to HSV, if luma is greater than max luma, that's the new color + vec2 tmpCoord; + float maxLuma; + vec4 maxLumaRGBColor = vec4(0.0); + vec4 sampleColorRGB; + vec4 sampleColorHSV; + vec2 pixelWidth = 1.0/RENDERSIZE.xy; + const float localBlurRadius = 1.0; + bool vertFlag = false; + + // pass 0 and 2 are doing horizontal erosion + if (PASSINDEX==2) + vertFlag = true; + // the first pass should have a blur radius of 1.0 simply to prevent the loss of information while reducing resolution + if (PASSINDEX==1) + //localBlurRadius = 1.0; + // other passes go by the blur radius! +// else + //localBlurRadius = 1.0; + // sample pixels as per the blur radius... + for (float i=0.; i<=localBlurRadius; ++i) { + if (PASSINDEX==1) { + tmpCoord = vec2(clamp(isf_FragNormCoord.x+(i*pixelWidth.x), 0., 1.), isf_FragNormCoord.y); + sampleColorRGB = IMG_NORM_PIXEL(edges, tmpCoord); + } + else if (PASSINDEX==2) { + tmpCoord = vec2(isf_FragNormCoord.x, clamp(isf_FragNormCoord.y+(i*pixelWidth.y), 0., 1.)); + sampleColorRGB = IMG_NORM_PIXEL(halfSize, tmpCoord); + } + else if (PASSINDEX==3) { + tmpCoord = vec2(clamp(isf_FragNormCoord.x+(i*pixelWidth.x), 0., 1.), isf_FragNormCoord.y); + sampleColorRGB = IMG_NORM_PIXEL(quarterSizePassA, tmpCoord); + } + // if this is the first sample for this fragment, don't bother comparing- just set the max luma stuff + if (i == 0.) { + maxLuma = rgb2hsv(sampleColorRGB.rgb).b; + maxLumaRGBColor = sampleColorRGB; + } + // else this isn't the first sample... + else { + // compare, determine if it's the max luma + sampleColorRGB = mix(maxLumaRGBColor, sampleColorRGB, 1.*erodeIntensity/i); + sampleColorHSV.rgb = rgb2hsv(sampleColorRGB.rgb); + if (sampleColorHSV.b > maxLuma) { + maxLuma = sampleColorHSV.b; + maxLumaRGBColor = sampleColorRGB; + } + // do another sample for the negative coordinate + if (PASSINDEX==1) { + tmpCoord = vec2(clamp(isf_FragNormCoord.x-(i*pixelWidth.x), 0., 1.), isf_FragNormCoord.y); + sampleColorRGB = IMG_NORM_PIXEL(edges, tmpCoord); + } + else if (PASSINDEX==2) { + tmpCoord = vec2(isf_FragNormCoord.x, clamp(isf_FragNormCoord.y-(i*pixelWidth.y), 0., 1.)); + sampleColorRGB = IMG_NORM_PIXEL(halfSize, tmpCoord); + } + else if (PASSINDEX==3) { + tmpCoord = vec2(clamp(isf_FragNormCoord.x-(i*pixelWidth.x), 0., 1.), isf_FragNormCoord.y); + sampleColorRGB = IMG_NORM_PIXEL(quarterSizePassA, tmpCoord); + } + sampleColorRGB = mix(maxLumaRGBColor, sampleColorRGB, 1.*erodeIntensity/i); + sampleColorHSV.rgb = rgb2hsv(sampleColorRGB.rgb); + if (sampleColorHSV.b > maxLuma) { + maxLuma = sampleColorHSV.b; + maxLumaRGBColor = sampleColorRGB; + } + } + } + gl_FragColor = maxLumaRGBColor; + + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(quarterSizePassB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterSizePassB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterSizePassB,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterSizePassB,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterSizePassB,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + } + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + } + // ...writes into the full-size + else if (PASSINDEX == 6) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + } + else if (PASSINDEX == 7) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + vec4 originalImg = IMG_NORM_PIXEL(edges,isf_FragNormCoord); + if (blurLevel == 0) + blurredImg = mix(originalImg, blurredImg, (blurLevelModulus/6.0)); + + gl_FragColor = max(mix(originalImg,blurredImg*1.9,intensity), originalImg); + } + +} + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} diff --git a/src/renderer/src/application/sample-modules/isf/City Lights.vs b/src/renderer/src/application/sample-modules/isf/City Lights.vs new file mode 100644 index 000000000..29a749be5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/City Lights.vs @@ -0,0 +1,105 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + +varying vec2 texOffsets[5]; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; + +out vec2 texOffsets[5]; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first pass draws edges + if (PASSINDEX==0) { + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); + } + // next three passes are just drawing the texture- do nothing + + // the next four passes do gaussion blurs on the various levels + else if (PASSINDEX==4 || PASSINDEX==6) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 4 is sampling 1/8, but writing to 1/4 + if (PASSINDEX==4) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 6 is sampling 1/4 and writing to full-size + else if (PASSINDEX==6) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==5 || PASSINDEX==7) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 5 is sampling 1/14, but writing to 1/4 + if (PASSINDEX==5) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 7 is sampling 1/4 and writing to full-size + else if (PASSINDEX==7) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==5) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Collage.fs b/src/renderer/src/application/sample-modules/isf/Collage.fs new file mode 100644 index 000000000..2b9df9da8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Collage.fs @@ -0,0 +1,113 @@ + +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment", "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "seed", + "TYPE": "float", + "MIN": 0.01, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "cell_size", + "TYPE": "float", + "MIN": 0.01, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "allow_flips_h", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "allow_flips_v", + "TYPE": "bool", + "DEFAULT": 0.0 + } + + ] +}*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + // At 0.0 just do a pass-thru + if (cell_size == 0.0) { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // Position of current pixel + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + + // Left and right of tile + float CellWidth = cell_size; + float CellHeight = cell_size; + + /* + float x1 = floor(xy.x / CellWidth)*CellWidth; + float x2 = clamp((ceil(xy.x / CellWidth)*CellWidth), 0.0, 1.0); + // Top and bottom of tile + float y1 = floor(xy.y / CellHeight)*CellHeight; + float y2 = clamp((ceil(xy.y / CellHeight)*CellHeight), 0.0, 1.0); + */ + + // divide 1 by the cell width and cell height to determine the count + float rows = floor(1.0/CellHeight); + float cols = floor(1.0/CellWidth); + float count = floor(rows * cols); + + // figure out the ID # of the region + float region = cols*floor(xy.x / CellWidth) + floor(xy.y / CellHeight); + + // use this to draw the gradient of the regions as gray colors.. + //gl_FragColor = vec4(vec3(region/count),1.0); + + // now translate this region to another random region using our seed and region + float translated = clamp(rand(vec2(region/count, seed)),0.0,1.0); + //translated = region/count; + //gl_FragColor = vec4(vec3(translated),1.0); + + // quantize the translated! + translated = floor(count * translated); + //gl_FragColor = vec4(vec3(translated),1.0); + // now convert the translated region back to an xy location + // get the relative position within the original block and then add on the translated amount + xy.x = (xy.x - floor(xy.x / CellWidth)*CellWidth) + CellWidth * floor(translated / rows); + //xy.x = (xy.x - floor(xy.x / CellWidth)*CellWidth); + xy.y = xy.y - floor(xy.y / CellHeight)*CellHeight + CellHeight * floor(mod(translated , cols)); + + // lastly if flips are allowed, randomly flip h + if (allow_flips_h) { + float flipx = rand(vec2(translated, seed)); + if (flipx > 0.5) { + xy.x = 1.0-xy.x; + } + } + if (allow_flips_v) { + float flipy = rand(vec2(translated, seed)); + if (flipy > 0.5) { + xy.y = 1.0-xy.y; + } + } + + gl_FragColor = IMG_NORM_PIXEL(inputImage, xy); + + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/CollapsingArchitecture.fs b/src/renderer/src/application/sample-modules/isf/CollapsingArchitecture.fs new file mode 100644 index 000000000..e7f121880 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/CollapsingArchitecture.fs @@ -0,0 +1,239 @@ +/*{ + "CREDIT": "by mojovideotech", + "CATEGORIES" : [ + "3d", + "raymarch", + "distortion", + "architecture", + "twist" + ], + "DESCRIPTION" : "mod of https:\/\/www.shadertoy.com\/view\/XltXWN by s23b", + "INPUTS" : [ + { + "NAME": "rate", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": -1.0, + "MAX": 1.0 + }, + { + "NAME": "FOV", + "TYPE": "float", + "DEFAULT": 1.25, + "MIN": -2.5, + "MAX": 2.5 + }, + { + "NAME": "twist", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": -1.0, + "MAX": 1.0 + }, + { + "NAME": "ambient", + "TYPE": "float", + "DEFAULT": 0.1, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME" : "gamma", + "TYPE" : "float", + "DEFAULT" : 1.5, + "MIN" : 0.5, + "MAX" : 3.0 + }, + { + "NAME" : "fog", + "TYPE" : "float", + "DEFAULT" : 0.025, + "MIN" : 0.0025, + "MAX" : 0.05 + } + ] +} +*/ + +//////////////////////////////////////////////////////////// +// CollapsingArchitecture by mojovideotech +// +// mod of shadertoy.com\/XltXWN by s23b +// +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +//////////////////////////////////////////////////////////// + +#define MAX_STEPS 80 +#define EPS .0001 +#define RENDER_DIST 16. +#define AO_SAMPLES 4. +#define AO_RANGE 10. +#define PI 3.141592653589793 // pi +#define saturate(x) clamp(x, 0., 1.) + +float hash(vec3 uv) { + float f = fract(sin(dot(uv ,vec3(.009123898,.00231233, .00532234)))* 111111.5452313); + return f; +} + +float noise(vec3 uv) { + vec3 fuv = floor(uv); + vec4 cell0 = vec4( + hash(fuv + vec3(0, 0, 0)), + hash(fuv + vec3(0, 1, 0)), + hash(fuv + vec3(1, 0, 0)), + hash(fuv + vec3(1, 1, 0)) + ); + vec2 axis0 = mix(cell0.xz, cell0.yw, fract(uv.y)); + float val0 = mix(axis0.x, axis0.y, fract(uv.x)); + vec4 cell1 = vec4( + hash(fuv + vec3(0, 0, 1)), + hash(fuv + vec3(0, 1, 1)), + hash(fuv + vec3(1, 0, 1)), + hash(fuv + vec3(1, 1, 1)) + ); + vec2 axis1 = mix(cell1.xz, cell1.yw, fract(uv.y)); + float val1 = mix(axis1.x, axis1.y, fract(uv.x)); + return mix(val0, val1, fract(uv.z)); +} + +float fbm(vec3 uv) { + float f = 0.; + float r = 1.; + for (int i = 0; i < 5; ++i) { + f += noise((uv + 10.) * r) / (r *= 2.); + } + return f / (1. - 1. / r); +} + +void tRotate(inout vec2 p, float angel) { + float s = sin(angel), c = cos(angel); + p *= mat2(c, -s, s, c); +} + +void tTwist(inout vec3 p, float a) { + tRotate(p.xy, p.z * a); +} + +float tRepeat1(inout float p, float r) { + float id = floor((p + r * .5) / r); + p = mod(p + r * .5, r) - r * .5; + return id; +} + +vec2 tRepeat2(inout vec2 p, vec2 r) { + vec2 id = floor((p + r * .5) / r); + p = mod(p + r * .5, r) - r * .5; + return id; +} + +float sdRect(vec2 p, vec2 r) { + p = abs(p) - r; + return min(max(p.x, p.y), 0.) + length(max(p, 0.)); +} + +float sdCircle(vec2 p, float r) { + return length(p) - r; +} + +float opU(float a, float b) { + return min(a, b); +} + +float opS(float a, float b) { + return max(a, -b); +} + +float map(vec3 p) { + tTwist(p, twist); + tRepeat2(p.xz, vec2(.7, 1.)); + p.x = abs(p.x); + p.y += .5; + float d = abs(p.z) - .15 + abs(twist*0.1); + float w = opU(sdCircle(p.xy - vec2(0, .75), .25), sdRect(p.xy - vec2(0, .375), vec2(.15, .375))); + d = opS(d, w); + d = opS(d, sdRect(p.xy - vec2(0,.35), vec2(.45,.3))); + d = opU(d, sdCircle(p.xz - vec2(.35, 0.), .067)); + p.z = abs(p.z); + d = opS(d, sdRect(p.yz - vec2(.5, .5), vec2(.6,.4 ))); + d = opU(d, -abs(p.y - .5) + .8); + return d; +} + +float trace(vec3 ro, vec3 rd, float maxDist, out float steps) { + float total = 0.; + steps = 0.; + for (int i = 0; i < MAX_STEPS; ++i) { + ++steps; + float d = map(ro + rd * total); + total += d; + if (d < EPS || maxDist < total) break; + } + return total; +} + +vec3 getNormal(vec3 p) { + vec2 e = vec2(.0001, 0); + return normalize(vec3( + map(p + e.xyy) - map(p - e.xyy), + map(p + e.yxy) - map(p - e.yxy), + map(p + e.yyx) - map(p - e.yyx) + )); +} + +float calculateAO(vec3 p, vec3 n) { + float r = 0., w = 1., d; + for (float i = 1.; i <= AO_SAMPLES; i++){ + d = i / AO_SAMPLES / AO_RANGE; + r += w * (d - map(p + n * d)); + w *= .5; + } + return 1.-saturate(r * AO_RANGE); +} + +bool isWall(vec3 p) { + p.x += .35; + tRepeat2(p.xz, vec2(.7, 1)); + return .375 < abs(p.y + .15) + length(p.xz); +} + +vec3 texture(vec3 p) { + vec3 t; + tTwist(p, twist); + bool wall = isWall(p); + t = fbm((p + (wall ? 0. : .1 + .9 * fbm(p * 5.))) * vec3(5., 20., 5.)) * vec3(1., .7, .4) * .75 + + fbm(p * vec3(2., 10., 2.)) * vec3(1., .8, .5) * .25; + if (wall) t = mix(t, vec3(1), .5); + return saturate(t); +} + +void main() { + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy * 2. - 1.; + uv.x *= RENDERSIZE.x / RENDERSIZE.y; + float time = TIME * rate; + vec3 ro = vec3(sin(time * PI / 2. + 1.) * 1., 0, time); + vec3 rd = normalize(vec3(uv, FOV)); + time += 2.; + vec3 light = vec3(sin(time * PI / 2. + 1.) * 1., 0, time); + time -= 2.; + tRotate(rd.xz, -cos(time * PI / 2. + 1.) * .5); + tRotate(ro.xy, -ro.z * twist); + tRotate(light.xy, -light.z * twist); + tRotate(rd.xy, -ro.z * twist); + float steps, dist = trace(ro, rd, RENDER_DIST, steps); + vec3 p = ro + rd * dist; + vec3 normal = getNormal(p); + vec3 l = normalize(light - p); + vec3 shadowStart = p + normal * EPS * 10.; + float shadowDistance = distance(shadowStart,light); + float shadowSteps, shadow = float(trace(shadowStart, l, shadowDistance, shadowSteps) > shadowDistance); + shadow *= 1. - sqrt(shadowSteps / float(MAX_STEPS)); + float diffuse = max(0., dot(l, normal)); + float specular = pow(max(0., dot(reflect(-l, normal), -rd)), 8.); + float ao = calculateAO(p, normal); + gl_FragColor.rgb = (ao * texture(p)) * (ambient + (specular + diffuse) * shadow); + gl_FragColor = mix(gl_FragColor, vec4(.6, .5, .7, 1.), saturate(dist * dist * fog )); + gl_FragColor = pow(gl_FragColor, vec4(1. / gamma)); + gl_FragColor.a = 1.0; +} + diff --git a/src/renderer/src/application/sample-modules/isf/Color Blowout.fs b/src/renderer/src/application/sample-modules/isf/Color Blowout.fs new file mode 100644 index 000000000..c279bf355 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Blowout.fs @@ -0,0 +1,114 @@ +/*{ + "CATEGORIES": [ + "Color Effect" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": -1, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "TARGET": "pass1" + }, + { + "TARGET": "pass2" + }, + { + "TARGET": "pass3" + } + ] +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + vec4 final = vec4(0.0); + + if (PASSINDEX == 0) { + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = (IMG_NORM_PIXEL(inputImage, left_coord)); + vec4 colorR = (IMG_NORM_PIXEL(inputImage, right_coord)); + vec4 colorA = (IMG_NORM_PIXEL(inputImage, above_coord)); + vec4 colorB = (IMG_NORM_PIXEL(inputImage, below_coord)); + + vec4 colorLA = (IMG_NORM_PIXEL(inputImage, lefta_coord)); + vec4 colorRA = (IMG_NORM_PIXEL(inputImage, righta_coord)); + vec4 colorLB = (IMG_NORM_PIXEL(inputImage, leftb_coord)); + vec4 colorRB = (IMG_NORM_PIXEL(inputImage, rightb_coord)); + + final = color - color * intensity * (8.0*gray(color) - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + final.a = color.a; + } + else if (PASSINDEX == 1) { + vec4 color = IMG_THIS_PIXEL(pass1); + vec4 colorL = (IMG_NORM_PIXEL(pass1, left_coord)); + vec4 colorR = (IMG_NORM_PIXEL(pass1, right_coord)); + vec4 colorA = (IMG_NORM_PIXEL(pass1, above_coord)); + vec4 colorB = (IMG_NORM_PIXEL(pass1, below_coord)); + + vec4 colorLA = (IMG_NORM_PIXEL(pass1, lefta_coord)); + vec4 colorRA = (IMG_NORM_PIXEL(pass1, righta_coord)); + vec4 colorLB = (IMG_NORM_PIXEL(pass1, leftb_coord)); + vec4 colorRB = (IMG_NORM_PIXEL(pass1, rightb_coord)); + + final = (1.0 - abs(intensity)) * color + abs(intensity) * (colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 8.0; + final.a = color.a; + } + else if (PASSINDEX == 2) { + vec4 color = IMG_THIS_PIXEL(pass2); + vec4 colorL = (IMG_NORM_PIXEL(pass2, left_coord)); + vec4 colorR = (IMG_NORM_PIXEL(pass2, right_coord)); + vec4 colorA = (IMG_NORM_PIXEL(pass2, above_coord)); + vec4 colorB = (IMG_NORM_PIXEL(pass2, below_coord)); + + vec4 colorLA = (IMG_NORM_PIXEL(pass2, lefta_coord)); + vec4 colorRA = (IMG_NORM_PIXEL(pass2, righta_coord)); + vec4 colorLB = (IMG_NORM_PIXEL(pass2, leftb_coord)); + vec4 colorRB = (IMG_NORM_PIXEL(pass2, rightb_coord)); + + final = color - color * intensity * (8.0*gray(color) - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + final.a = color.a; + } + + + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Color Blowout.vs b/src/renderer/src/application/sample-modules/isf/Color Blowout.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Blowout.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Color Controls.fs b/src/renderer/src/application/sample-modules/isf/Color Controls.fs new file mode 100644 index 000000000..dd2945df9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Controls.fs @@ -0,0 +1,92 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment", "Utility" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "bright", + "TYPE": "float", + "MIN": -1.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "contrast", + "TYPE": "float", + "MIN": -4.0, + "MAX": 4.0, + "DEFAULT": 1.0 + }, + { + "NAME": "hue", + "TYPE": "float", + "MIN": -1.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "saturation", + "TYPE": "float", + "MIN": 0.0, + "MAX": 4.0, + "DEFAULT": 1.0 + } + ] +}*/ + + +vec3 rgb2hsv(vec3 c); +vec3 hsv2rgb(vec3 c); + +void main() { + vec4 tmpColorA = IMG_THIS_PIXEL(inputImage); + vec4 tmpColorB; + // bright + tmpColorB = tmpColorA + vec4(bright, bright, bright, 0.0); + // contrast + tmpColorA.rgb = ((vec3(2.0) * (tmpColorB.rgb - vec3(0.5))) * vec3(contrast) / vec3(2.0)) + vec3(0.5); + tmpColorA.a = ((2.0 * (tmpColorB.a - 0.5)) * abs(contrast) / 2.0) + 0.5; + + // convert RGB to HSV + tmpColorB.xyz = rgb2hsv(clamp(tmpColorA.rgb, 0.0, 1.0)); + tmpColorB.a = tmpColorA.a; + + + // hue + tmpColorB.x = mod((tmpColorB.x + hue), 1.0); + // saturation + tmpColorB.y = tmpColorB.y * saturation; + + + // convert HSV back to RGB + tmpColorA.rgb = hsv2rgb(clamp(tmpColorB.xyz, 0.0, 1.0)); + tmpColorA.a = tmpColorB.a; + + + gl_FragColor = clamp(tmpColorA, 0.0, 1.0); +} + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} diff --git a/src/renderer/src/application/sample-modules/isf/Color History.fs b/src/renderer/src/application/sample-modules/isf/Color History.fs new file mode 100644 index 000000000..7d24210f0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color History.fs @@ -0,0 +1,88 @@ +/* +{ + "CATEGORIES" : [ + "Color" + ], + "DESCRIPTION" : "Displays Data Over Time", + "ISFVSN" : "2", + "INPUTS" : [ + { + "LABELS" : [ + "Color", + "Lines" + ], + "NAME" : "displayMode", + "TYPE" : "long", + "DEFAULT" : 0, + "VALUES" : [ + 0, + 1 + ] + }, + { + "NAME" : "data", + "TYPE" : "color", + "DEFAULT" : [ + 0.95, + 0.25, + 0, + 1 + ] + } + ], + "PASSES" : [ + { + "PERSISTENT" : true, + "WIDTH" : "$WIDTH", + "HEIGHT" : "1", + "TARGET" : "dataHistory", + "FLOAT" : true + }, + { + + } + ], + "CREDIT" : "VIDVOX" +} +*/ + +void main() { + vec4 inputPixelColor = vec4(0.0); + if (PASSINDEX == 0) { + vec2 loc = gl_FragCoord.xy; + if (floor(loc.x) == 0.0) { + inputPixelColor = data; + } + else { + loc.x = loc.x - 1.0; + inputPixelColor = IMG_PIXEL(dataHistory,loc); + } + } + else { + vec2 loc = gl_FragCoord.xy; + vec4 val = IMG_PIXEL(dataHistory,loc); + if (displayMode == 0) { + inputPixelColor = val; + } + else if (displayMode == 1) { + float tmp = floor(val.r * RENDERSIZE.y); + inputPixelColor.a = val.a; + if (floor(loc.y) == tmp) { + inputPixelColor.r = 1.0; + inputPixelColor.a = 1.0; + } + tmp = floor(val.g * RENDERSIZE.y); + if (floor(loc.y) == tmp) { + inputPixelColor.g = 1.0; + inputPixelColor.a = 1.0; + } + tmp = floor(val.b * RENDERSIZE.y); + if (floor(loc.y) == tmp) { + inputPixelColor.b = 1.0; + inputPixelColor.a = 1.0; + } + } + + } + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Color Invert.fs b/src/renderer/src/application/sample-modules/isf/Color Invert.fs new file mode 100644 index 000000000..bb6025d35 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Invert.fs @@ -0,0 +1,19 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "DESCRIPTION": "Inverts the RGB channels of the input", + "CATEGORIES": [ + "Color Effect", "Utility" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ] +}*/ + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + gl_FragColor = vec4(1.0-srcPixel.r, 1.0-srcPixel.g, 1.0-srcPixel.b, srcPixel.a); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Color Levels.fs b/src/renderer/src/application/sample-modules/isf/Color Levels.fs new file mode 100644 index 000000000..928cbc979 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Levels.fs @@ -0,0 +1,156 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "minLevel", + "LABEL": "Minimum Point", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.2 + }, + { + "NAME": "midLevel", + "LABEL": "Mid Point", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "maxLevel", + "LABEL": "Maximum Point", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.8 + }, + { + "NAME": "offset1", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "NAME": "offset2", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "NAME": "offset3", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "NAME": "offset4", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "NAME": "offset5", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "NAME": "offset6", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 0.5 + ] + }, + { + "NAME": "levelsMode", + "LABEL": "Levels Mode", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ] +}*/ + + + +void main() { + vec4 tmpColor = IMG_THIS_PIXEL(inputImage); + float brightness = (tmpColor.r + tmpColor.g + tmpColor.b) * tmpColor.a / 3.0; + + // all adjustments + if (brightness <= minLevel) { + tmpColor = tmpColor + offset1 - 0.5; + } + else if (brightness <= (minLevel + midLevel)/2.0) { + tmpColor = tmpColor + offset2 - 0.5; + } + else if (brightness <= midLevel) { + tmpColor = tmpColor + offset3 - 0.5; + } + else if (brightness <= (maxLevel + midLevel)/2.0) { + tmpColor = tmpColor + offset4 - 0.5; + } + else if (brightness <= maxLevel) { + tmpColor = tmpColor + offset5 - 0.5; + } + else { + tmpColor = tmpColor + offset6 - 0.5; + } + + if (levelsMode) { + // all adjustments + tmpColor.rgb = vec3(1.0); + + if (brightness <= minLevel) { + tmpColor.a = 0.0; + } + else if (brightness <= (minLevel + midLevel)/2.0) { + tmpColor.a = 1.0/5.0; + } + else if (brightness <= midLevel) { + tmpColor.a = 2.0/5.0; + } + else if (brightness <= (maxLevel + midLevel)/2.0) { + tmpColor.a = 3.0/5.0; + } + else if (brightness <= maxLevel) { + tmpColor.a = 4.0/5.0; + } + else { + tmpColor.a = 1.0; + } + } + gl_FragColor = clamp(tmpColor, 0.0, 1.0); +} + diff --git a/src/renderer/src/application/sample-modules/isf/Color Monochrome.fs b/src/renderer/src/application/sample-modules/isf/Color Monochrome.fs new file mode 100644 index 000000000..0ec0b10e1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Monochrome.fs @@ -0,0 +1,46 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Retro" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.6, + 0.45, + 0.3, + 1 + ], + "NAME": "color", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + +//const vec4 lumcoeff = vec4(0.299, 0.587, 0.114, 0.0); +const vec4 lumcoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + float luminance = dot(srcPixel,lumcoeff); + //float luminance = (srcPixel.r + srcPixel.g + srcPixel.b)/3.0; + vec4 dstPixel = (luminance >= 0.50) + ? mix(color, vec4(1,1,1,srcPixel.a), (luminance-0.5)*2.0) + : mix(vec4(0,0,0,srcPixel.a), color, luminance*2.0); + gl_FragColor = mix(srcPixel, dstPixel, intensity); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Color Phase.fs b/src/renderer/src/application/sample-modules/isf/Color Phase.fs new file mode 100644 index 000000000..61dabc9ed --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Phase.fs @@ -0,0 +1,76 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/colorphase.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0.19607843137254902, + 0.39215686274509803, + 0 + ], + "NAME": "fromStep", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.6, + 0.8, + 1, + 1 + ], + "NAME": "toStep", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + +// Usage: fromStep and toStep must be in [0.0, 1.0] range +// and all(fromStep) must be < all(toStep) + + +vec4 transition (vec2 uv) { + vec4 a = getFromColor(uv); + vec4 b = getToColor(uv); + return mix(a, b, smoothstep(fromStep, toStep, vec4(progress))); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Color Posterize.fs b/src/renderer/src/application/sample-modules/isf/Color Posterize.fs new file mode 100644 index 000000000..a76a0925e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Posterize.fs @@ -0,0 +1,32 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Retro" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 30, + "MAX": 30, + "MIN": 2, + "NAME": "levels", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + // get the src pixel, convert to HSL, posterize the 'L', convert back to RGB + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + vec4 amountPerLevel = vec4(1.0/levels); + vec4 numOfLevels = floor(srcPixel/amountPerLevel); + vec4 outColor = numOfLevels * (vec4(1.0) / (vec4(levels) - vec4(1.0))); + outColor.a = srcPixel.a; + gl_FragColor = outColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Color Relookup.fs b/src/renderer/src/application/sample-modules/isf/Color Relookup.fs new file mode 100644 index 000000000..f6d4bb3dc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Relookup.fs @@ -0,0 +1,38 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "lookupImage", + "TYPE": "image" + }, + { + "NAME": "mix_amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + } + ] +}*/ + +void main() { + vec4 thisColor = IMG_THIS_PIXEL(inputImage); + vec2 lookupCoord; + lookupCoord.x = mix (thisColor.r, thisColor.g, mix_amount); + lookupCoord.x = mix (lookupCoord.x, thisColor.b, mix_amount); + lookupCoord.x = RENDERSIZE.x * lookupCoord.x / 255.0; + + lookupCoord.y = mix (thisColor.r, thisColor.g, mix_amount); + lookupCoord.y = mix (lookupCoord.y, thisColor.b, mix_amount); + lookupCoord.y = RENDERSIZE.y * lookupCoord.y / 255.0; + + gl_FragColor = IMG_NORM_PIXEL(lookupImage, lookupCoord); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Color Scales.fs b/src/renderer/src/application/sample-modules/isf/Color Scales.fs new file mode 100644 index 000000000..5f387fbd9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Scales.fs @@ -0,0 +1,505 @@ +/* +{ + "CATEGORIES" : [ + "Color" + ], + "DESCRIPTION" : "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "LABELS" : [ + "Comparison Mode", + "Newton", + "Castel", + "Field", + "Jameson", + "Seemann", + "Rimington", + "Bishop", + "Helmholtz", + "Scriabin", + "Klein", + "Aeppli", + "Belmont", + "Zieverink" + ], + "NAME" : "iAuthor", + "TYPE" : "long", + "DEFAULT" : 0, + "LABEL" : "Author", + "VALUES" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + }, + { + "NAME" : "iNote", + "TYPE" : "float", + "MAX" : 11, + "DEFAULT" : 0, + "MIN" : 0, + "LABEL" : "Note" + }, + { + "NAME" : "iPreviewMode", + "TYPE" : "bool", + "DEFAULT" : 0 + } + ], + "CREDIT" : "VIDVOX" +} +*/ + + +/* +// Color Scales via http://rhythmiclight.com/archives/ideas/colorscales.html +C C# D D# E F F# G G# A A# B +Newton • red • orange • yellow • green • blue • indigo • violet (Gerstner, p.167) +Castel • blue • blue-green • green • olive green • yellow • yellow-orange • orange • red • crimson • violet • agate • indigo (Peacock, p.400) +Field • blue • purple • red • orange • yellow • yellow green • green (Klein, p.69) +Jameson • red • red-orange • orange • orange-yellow • yellow • green • green-blue • blue • blue-purple • purple • purple-violet • violet (Jameson, p.12) +Seemann • carmine • scarlet • orange • yellow-orange • yellow • green • green blue • blue • indigo • violet • brown • black (Klein, p.86) +Rimington • deep red • crimson • orange-crimson • orange • yellow • yellow-green • green • blueish green • blue-green • indigo • deep blue • violet (Peacock, p.402) +Bishop • red • orange-red or scarlet • orange • gold or yellow-orange • yellow or green-gold • yellow-green • green • greenish-blue or aquamarine • blue • indigo or violet-blue • violet • violet-red • red (Bishop, p.11) +Helmholtz • yellow • green • greenish blue • cayan-blue • indigo blue • violet • end of red • red • red • red • red orange • orange (Helmholtz, p.22) +Scriabin • red • violet • yellow • steely with the glint of metal • pearly blue the shimmer of moonshine • dark red • bright blue • rosy orange • purple • green • steely with a glint of metal • pearly blue the shimmer of moonshine (Jones, p.104) +Klein • dark red • red • red orange • orange • yellow • yellow green • green • blue-green • blue • blue violet • violet • dark violet (Klein, p.209) +Aeppli • red • orange • yellow • green • blue-green • ultramarine blue • violet • purple (Gerstner, p.169) +Belmont • red • red-orange • orange • yellow-orange • yellow • yellow-green • green • blue-green • blue • blue-violet • violet • red-violet (Belmont, p.226) +Zieverink • yellow/green • green • blue/green • blue • indigo • violet • ultra violet • infra red • red • orange • yellow/white • yellow (Cincinnati Contemporary Art Center) +*/ + +const vec3 red = vec3(231.0/255.0,52.0/255.0,39.0/255.0); +const vec3 orange = vec3(234.0/255.0,134.0/255.0,54.0/255.0); +const vec3 yellow = vec3(245.0/255.0,243.0/255.0,98.0/255.0); +const vec3 green = vec3(64.0/255.0,141.0/255.0,64.0/255.0); +const vec3 blue = vec3(25.0/255.0,19.0/255.0,125.0/255.0); + +const vec3 indigo = vec3(117.0/255.0,28.0/255.0,120.0/255.0); +const vec3 violet = vec3(199.0/255.0,49.0/255.0,132.0/255.0); + +const vec3 bluegreen = vec3(66.0/255.0,142.0/255.0,129.0/255.0); +const vec3 olivegreen = vec3(119.0/255.0,144.0/255.0,57.0/255.0); +const vec3 yelloworange = vec3(240.0/255.0,210.0/255.0,90.0/255.0); +const vec3 crimson = vec3(148.0/255.0,33.0/255.0,23.0/255.0); +const vec3 agate = vec3(69.0/255.0,23.0/255.0,120.0/255.0); + +const vec3 purple = vec3(117.0/255.0,28.0/255.0,120.0/255.0); +const vec3 yellowgreen = vec3(119.0/255.0,144.0/255.0,57.0/255.0); +const vec3 redorange = vec3(227.0/255.0,85.0/255.0,44.0/255.0); +const vec3 orangeyellow = vec3(240.0/255.0,210.0/255.0,90.0/255.0); +const vec3 greenblue = vec3(66.0/255.0,142.0/255.0,129.0/255.0); +const vec3 bluepurple = vec3(69.0/255.0,23.0/255.0,120.0/255.0); +const vec3 purpleviolet = vec3(153.0/255.0,41.0/255.0,130.0/255.0); +const vec3 carmine = vec3(98.0/255.0,34.0/255.0,31.0/255.0); +const vec3 scarlet = vec3(231.0/255.0,52.0/255.0,39.0/255.0); +const vec3 brown = vec3(98.0/255.0,34.0/255.0,31.0/255.0); +const vec3 black = vec3(7.0/255.0); +const vec3 deepred = vec3(231.0/255.0,52.0/255.0,39.0/255.0); +const vec3 orangecrimson = vec3(227.0/255.0,85.0/255.0,44.0/255.0); +const vec3 blueishgreen = vec3(79.0/255.0,161.0/255.0,131.0/255.0); +const vec3 deepblue = vec3(25.0/255.0,19.0/255.0,125.0/255.0); + +const vec3 greenishblue = vec3(66.0/255.0,142.0/255.0,129.0/255.0); +const vec3 violetred = vec3(201.0/255.0,51.0/255.0,83.0/255.0); +const vec3 darkviolet = vec3(117.0/255.0,28.0/255.0,120.0/255.0); +const vec3 cayanblue = vec3(45.0/255.0,91.0/255.0,155.0/255.0); +const vec3 indigoblue = purple; + +const vec3 endofred = vec3(145.0/255.0,34.0/255.0,84.0/255.0); +const vec3 steely = vec3(89.0/255.0,87.0/255.0,130.0/255.0); +const vec3 pearlyblue = vec3(45.0/255.0,91.0/255.0,155.0/255.0); +const vec3 darkred = vec3(148.0/255.0,33.0/255.0,23.0/255.0); + +const vec3 brightblue = vec3(25.0/255.0,19.0/255.0,125.0/255.0); +const vec3 rosyorange = vec3(234.0,134.0,54.0); +const vec3 ultramarineblue = vec3(45.0/255.0,91.0/255.0,155.0/255.0); +const vec3 blueviolet = vec3(69.0/255.0,23.0/255.0,120.0/255.0); + +const vec3 ultraviolet = vec3(102.0/255.0,25.0/255.0,68.0/255.0); +const vec3 infrared = vec3(148.0/255.0,33.0/255.0,23.0/255.0); +const vec3 yellowwhite = vec3(238.0/255.0,239.0/255.0,149.0/255.0); + + +vec3 colorForAuthorAndNote(int author, int note) { + vec3 inputPixelColor = vec3(0.0); + // newton + if (author == 0) { + if (note == 0) + inputPixelColor.rgb = red; + else if (note == 1) + inputPixelColor.rgb = red; + else if (note == 2) + inputPixelColor.rgb = orange; + else if (note == 3) + inputPixelColor.rgb = orange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = green; + else if (note == 6) + inputPixelColor.rgb = green; + else if (note == 7) + inputPixelColor.rgb = blue; + else if (note == 8) + inputPixelColor.rgb = blue; + else if (note == 9) + inputPixelColor.rgb = indigo; + else if (note == 10) + inputPixelColor.rgb = indigo; + else if (note == 11) + inputPixelColor.rgb = violet; + } + // castel + else if (author == 1) { + if (note == 0) + inputPixelColor.rgb = blue; + else if (note == 1) + inputPixelColor.rgb = bluegreen; + else if (note == 2) + inputPixelColor.rgb = green; + else if (note == 3) + inputPixelColor.rgb = olivegreen; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = yelloworange; + else if (note == 6) + inputPixelColor.rgb = orange; + else if (note == 7) + inputPixelColor.rgb = red; + else if (note == 8) + inputPixelColor.rgb = crimson; + else if (note == 9) + inputPixelColor.rgb = violet; + else if (note == 10) + inputPixelColor.rgb = agate; + else if (note == 11) + inputPixelColor.rgb = indigo; + } + // field + else if (author == 2) { + if (note == 0) + inputPixelColor.rgb = blue; + else if (note == 1) + inputPixelColor.rgb = blue; + else if (note == 2) + inputPixelColor.rgb = purple; + else if (note == 3) + inputPixelColor.rgb = purple; + else if (note == 4) + inputPixelColor.rgb = red; + else if (note == 5) + inputPixelColor.rgb = orange; + else if (note == 6) + inputPixelColor.rgb = orange; + else if (note == 7) + inputPixelColor.rgb = yellow; + else if (note == 8) + inputPixelColor.rgb = yellow; + else if (note == 9) + inputPixelColor.rgb = yellowgreen; + else if (note == 10) + inputPixelColor.rgb = yellowgreen; + else if (note == 11) + inputPixelColor.rgb = green; + } + // jameson + else if (author == 3) { + if (note == 0) + inputPixelColor.rgb = red; + else if (note == 1) + inputPixelColor.rgb = redorange; + else if (note == 2) + inputPixelColor.rgb = orange; + else if (note == 3) + inputPixelColor.rgb = orangeyellow; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = green; + else if (note == 6) + inputPixelColor.rgb = greenblue; + else if (note == 7) + inputPixelColor.rgb = blue; + else if (note == 8) + inputPixelColor.rgb = bluepurple; + else if (note == 9) + inputPixelColor.rgb = purple; + else if (note == 10) + inputPixelColor.rgb = purpleviolet; + else if (note == 11) + inputPixelColor.rgb = violet; + } + // seemann + else if (author == 4) { + if (note == 0) + inputPixelColor.rgb = carmine; + else if (note == 1) + inputPixelColor.rgb = scarlet; + else if (note == 2) + inputPixelColor.rgb = orange; + else if (note == 3) + inputPixelColor.rgb = yelloworange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = green; + else if (note == 6) + inputPixelColor.rgb = greenblue; + else if (note == 7) + inputPixelColor.rgb = blue; + else if (note == 8) + inputPixelColor.rgb = indigo; + else if (note == 9) + inputPixelColor.rgb = violet; + else if (note == 10) + inputPixelColor.rgb = brown; + else if (note == 11) + inputPixelColor.rgb = black; + } + // rimington + else if (author == 5) { + if (note == 0) + inputPixelColor.rgb = deepred; + else if (note == 1) + inputPixelColor.rgb = crimson; + else if (note == 2) + inputPixelColor.rgb = orangecrimson; + else if (note == 3) + inputPixelColor.rgb = orange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = yellowgreen; + else if (note == 6) + inputPixelColor.rgb = green; + else if (note == 7) + inputPixelColor.rgb = blueishgreen; + else if (note == 8) + inputPixelColor.rgb = bluegreen; + else if (note == 9) + inputPixelColor.rgb = indigo; + else if (note == 10) + inputPixelColor.rgb = deepblue; + else if (note == 11) + inputPixelColor.rgb = violet; + } + // bishop + else if (author == 6) { + if (note == 0) + inputPixelColor.rgb = red; + else if (note == 1) + inputPixelColor.rgb = scarlet; + else if (note == 2) + inputPixelColor.rgb = orange; + else if (note == 3) + inputPixelColor.rgb = yelloworange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = yellowgreen; + else if (note == 6) + inputPixelColor.rgb = green; + else if (note == 7) + inputPixelColor.rgb = greenishblue; + else if (note == 8) + inputPixelColor.rgb = blue; + else if (note == 9) + inputPixelColor.rgb = indigo; + else if (note == 10) + inputPixelColor.rgb = violet; + else if (note == 11) + inputPixelColor.rgb = violetred; + } + // helmholtz + else if (author == 7) { + if (note == 0) + inputPixelColor.rgb = yellow; + else if (note == 1) + inputPixelColor.rgb = green; + else if (note == 2) + inputPixelColor.rgb = greenishblue; + else if (note == 3) + inputPixelColor.rgb = cayanblue; + else if (note == 4) + inputPixelColor.rgb = indigoblue; + else if (note == 5) + inputPixelColor.rgb = violet; + else if (note == 6) + inputPixelColor.rgb = endofred; + else if (note == 7) + inputPixelColor.rgb = red; + else if (note == 8) + inputPixelColor.rgb = red; + else if (note == 9) + inputPixelColor.rgb = red; + else if (note == 10) + inputPixelColor.rgb = redorange; + else if (note == 11) + inputPixelColor.rgb = orange; + } + // scriabin + else if (author == 8) { + if (note == 0) + inputPixelColor.rgb = red; + else if (note == 1) + inputPixelColor.rgb = violet; + else if (note == 2) + inputPixelColor.rgb = yellow; + else if (note == 3) + inputPixelColor.rgb = steely; + else if (note == 4) + inputPixelColor.rgb = pearlyblue; + else if (note == 5) + inputPixelColor.rgb = darkred; + else if (note == 6) + inputPixelColor.rgb = brightblue; + else if (note == 7) + inputPixelColor.rgb = rosyorange; + else if (note == 8) + inputPixelColor.rgb = purple; + else if (note == 9) + inputPixelColor.rgb = green; + else if (note == 10) + inputPixelColor.rgb = steely; + else if (note == 11) + inputPixelColor.rgb = pearlyblue; + } + // klein + else if (author == 9) { + if (note == 0) + inputPixelColor.rgb = darkred; + else if (note == 1) + inputPixelColor.rgb = red; + else if (note == 2) + inputPixelColor.rgb = redorange; + else if (note == 3) + inputPixelColor.rgb = orange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = yellowgreen; + else if (note == 6) + inputPixelColor.rgb = green; + else if (note == 7) + inputPixelColor.rgb = bluegreen; + else if (note == 8) + inputPixelColor.rgb = blue; + else if (note == 9) + inputPixelColor.rgb = blueviolet; + else if (note == 10) + inputPixelColor.rgb = violet; + else if (note == 11) + inputPixelColor.rgb = darkviolet; + } + // aeppli + else if (author == 10) { + if (note == 0) + inputPixelColor.rgb = red; + else if (note == 1) + inputPixelColor.rgb = red; + else if (note == 2) + inputPixelColor.rgb = orange; + else if (note == 3) + inputPixelColor.rgb = orange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = yellow; + else if (note == 6) + inputPixelColor.rgb = green; + else if (note == 7) + inputPixelColor.rgb = bluegreen; + else if (note == 8) + inputPixelColor.rgb = bluegreen; + else if (note == 9) + inputPixelColor.rgb = ultramarineblue; + else if (note == 10) + inputPixelColor.rgb = violet; + else if (note == 11) + inputPixelColor.rgb = purple; + } + // belmont + else if (author == 11) { + if (note == 0) + inputPixelColor.rgb = red; + else if (note == 1) + inputPixelColor.rgb = redorange; + else if (note == 2) + inputPixelColor.rgb = orange; + else if (note == 3) + inputPixelColor.rgb = yelloworange; + else if (note == 4) + inputPixelColor.rgb = yellow; + else if (note == 5) + inputPixelColor.rgb = yellowgreen; + else if (note == 6) + inputPixelColor.rgb = green; + else if (note == 7) + inputPixelColor.rgb = bluegreen; + else if (note == 8) + inputPixelColor.rgb = blue; + else if (note == 9) + inputPixelColor.rgb = blueviolet; + else if (note == 10) + inputPixelColor.rgb = violet; + else if (note == 11) + inputPixelColor.rgb = violetred; + } + // zieverink + else if (author == 12) { + if (note == 0) + inputPixelColor.rgb = yellowgreen; + else if (note == 1) + inputPixelColor.rgb = green; + else if (note == 2) + inputPixelColor.rgb = bluegreen; + else if (note == 3) + inputPixelColor.rgb = blue; + else if (note == 4) + inputPixelColor.rgb = indigo; + else if (note == 5) + inputPixelColor.rgb = violet; + else if (note == 6) + inputPixelColor.rgb = ultraviolet; + else if (note == 7) + inputPixelColor.rgb = infrared; + else if (note == 8) + inputPixelColor.rgb = red; + else if (note == 9) + inputPixelColor.rgb = orange; + else if (note == 10) + inputPixelColor.rgb = yellowwhite; + else if (note == 11) + inputPixelColor.rgb = yellow; + } + return inputPixelColor; +} + +void main() { + vec4 inputPixelColor = vec4(1.0); + float mixPoint = (iPreviewMode) ? 0.0 : fract(iNote); + int note1 = (iPreviewMode) ? int(floor((12.0*isf_FragNormCoord.x))) : int(iNote); + int note2 = (mixPoint == 0.0) ? note1 : note1 + 1; + int author = (iAuthor == 0) ? int(floor((13.0*(1.0-isf_FragNormCoord.y)))) : iAuthor - 1; + + vec3 c1 = colorForAuthorAndNote(author,note1); + vec3 c2 = colorForAuthorAndNote(author,note2); + + inputPixelColor.rgb = mix(c1,c2,mixPoint); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Color Schemes.fs b/src/renderer/src/application/sample-modules/isf/Color Schemes.fs new file mode 100644 index 000000000..5cc8f3881 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Schemes.fs @@ -0,0 +1,267 @@ +/*{ + "DESCRIPTION": "Creates variations on a base color using a given algorithm.", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color" + ], + "INPUTS": [ + { + "LABEL": "Base Color", + "NAME": "baseColor", + "TYPE": "color", + "DEFAULT": [ + 0.25, + 0.59, + 0.9, + 1.0 + ] + }, + { + "LABEL": "Color Mode", + "NAME": "colorModeOverride", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "LABELS": [ + "Overview", + "Basic Complementary", + "Split Complementary", + "Compound Complementary", + "Spectrum", + "Shades", + "Analogous", + "Compound Analogous" + ], + "DEFAULT": 1 + }, + { + "LABEL": "Color Count", + "NAME": "colorCount", + "TYPE": "long", + "VALUES": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "LABELS": [ + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15" + ], + "DEFAULT": 5 + } + ] +}*/ + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + + +void main() +{ + vec4 inColor = baseColor; + float index = floor(isf_FragNormCoord.x * float(colorCount)); + float variation = 0.3236; // 1/5 the golden ratio + int colorMode = 0; + if (colorModeOverride == 0) { + colorMode = int(floor((1.0-isf_FragNormCoord.y) * 6.99)); + } + else { + colorMode = colorModeOverride - 1; + } + + inColor.rgb = rgb2hsv(inColor.rgb); + + vec4 outColor = inColor; + + // Basic complimentary – saturation and brightness variations on two fixed 180 degree opposite hues + if (colorMode == 0) { + if (mod(index, 2.0) >= 1.0) { + outColor.r = outColor.r + 0.5; + outColor.r = outColor.r - floor(outColor.r); + } + + outColor.g = outColor.g - variation * floor(index / 2.0); + + if (outColor.g < 0.1) { + outColor.g = outColor.g + variation * floor(index / 2.0); + outColor.g = outColor.g - floor(outColor.g); + } + + outColor.b = outColor.b - variation * floor(index / 4.0); + if (outColor.b < 0.2) { + outColor.b = outColor.b + variation * floor(index / 4.0); + outColor.b = outColor.b - floor(outColor.b); + } + } + // Split complimentary – saturation and brightness variations on a 3 fixed 120 degree hues + else if (colorMode == 1) { + float divisor = 3.0; + float ratio = 0.45; + if (mod(index, 3.0) >= 2.0) { + outColor.r = outColor.r - ratio; + } + else if (mod(index, 3.0) >= 1.0) { + outColor.r = outColor.r + ratio; + } + + //outColor.g = outColor.g + variation * floor(index / divisor); + + if (mod(index, 5.0) >= 3.0) { + outColor.g = outColor.g - variation; + outColor.g = outColor.g - floor(outColor.g); + } + outColor.b = outColor.b - variation * floor(index / (divisor)); + if (outColor.b < 0.1) { + outColor.b = outColor.b + variation * floor(index / (divisor)); + outColor.b = outColor.b - floor(outColor.b); + } + } + // Compound complimentary – a combination of shades, complimentary and analogous colors with slight shifts + else if (colorMode == 2) { + if (mod(index, 3.0) >= 2.0) { + outColor.r = outColor.r + 0.5; + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1) / 4.0; + } + else { + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1); + } + outColor.r = outColor.r - floor(outColor.r); + + + if (mod(index, 2.0) >= 1.0) { + outColor.g = outColor.g + index * variation / 2.0; + } + else if (mod(index, 3.0) >= 2.0) { + outColor.g = outColor.g - variation / 2.0; + } + else { + outColor.g = outColor.g - index * variation / float(colorCount - 1); + } + if (outColor.g > 1.0) { + outColor.g = outColor.g - floor(outColor.g); + } + } + // Spectrum – hue shifts based on number of colors with minor saturation shifts + else if (colorMode == 3) { + outColor.r = outColor.r + index * 1.0 / float(colorCount); + if (mod(index, 3.0) >= 2.0) { + outColor.g = outColor.g - variation / 2.0; + outColor.g = outColor.g - floor(outColor.g); + } + else if (mod(index, 4.0) >= 3.0) { + outColor.g = outColor.g + variation / 2.0; + //outColor.g = outColor.g - floor(outColor.g); + } + } + // Shades – saturation and brightness variations on a single fixed hue + else if (colorMode == 4) { + if (mod(index, 2.0) >= 1.0) { + outColor.b = outColor.b - (index * variation) / float(colorCount-1); + } + else { + outColor.b = outColor.b + (index * variation) / float(colorCount-1); + outColor.b = outColor.b - floor(outColor.b); + } + if (outColor.b < 0.075) { + outColor.b = 1.0 - outColor.b * variation; + } + + if (mod(index, 3.0) >= 2.0) { + outColor.g = outColor.g - (index * variation) / 2.0; + } + else if (mod(index, 4.0) >= 3.0) { + outColor.g = outColor.g + (index * variation) / 2.0; + } + + if ((outColor.g > 1.0) || (outColor.g < 0.05)) { + outColor.g = outColor.g - floor(outColor.g); + } + } + // Analogous – small hue and saturation shifts + else if (colorMode == 5) { + + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1); + + if (mod(index, 3.0) >= 1.0) { + outColor.g = outColor.g - variation / 2.0; + if (outColor.g < 0.0) { + outColor.g = outColor.g + variation / 2.0; + } + if (outColor.g > 1.0) { + outColor.g = outColor.g - floor(outColor.g); + } + } + } + // Compound Analogous – similar to analogous but with negative hue shifts + else if (colorMode == 6) { + if (mod(index, 3.0) >= 1.0) { + outColor.r = outColor.r + variation * index * 1.0 / float(colorCount - 1); + } + else { + outColor.r = outColor.r - variation * index * 0.5 / float(colorCount - 1); + } + if (mod(index, 3.0) >= 1.0) { + outColor.g = outColor.g - variation / 2.0; + if (outColor.g < 0.0) { + outColor.g = outColor.g + variation / 2.0; + } + if (outColor.g > 1.0) { + outColor.g = outColor.g - floor(outColor.g); + } + } + } + + gl_FragColor = vec4(hsv2rgb(outColor.rgb), inColor.a); +} diff --git a/src/renderer/src/application/sample-modules/isf/Color Test Grid.fs b/src/renderer/src/application/sample-modules/isf/Color Test Grid.fs new file mode 100644 index 000000000..1e24f2066 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Color Test Grid.fs @@ -0,0 +1,88 @@ +/*{ + "CATEGORIES": [ + "Color", + "Utility" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "DEFAULT": 16, + "LABEL": "Grid Columns", + "MAX": 32, + "MIN": 1, + "NAME": "gridCols", + "TYPE": "float" + }, + { + "DEFAULT": 9, + "LABEL": "Grid Rows", + "MAX": 32, + "MIN": 1, + "NAME": "gridRows", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Color Shift", + "MAX": 1, + "MIN": 0, + "NAME": "colorShift", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Color Range", + "MAX": 1, + "MIN": 0, + "NAME": "colorRange", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + + +void main() { + vec4 outputPixelColor = vec4(1.0); + vec2 loc = isf_FragNormCoord.xy; + vec2 gridLoc = loc * vec2(gridCols, gridRows); + + // figure out which grid square we are in, normalized, and use it for the hue + outputPixelColor.r = (floor(gridLoc.x) + gridCols * floor(mod(gridLoc.y, gridRows))) / (gridCols * gridRows - 1.0); + + // scale to the hue range + outputPixelColor.r = outputPixelColor.r * colorRange; + + // apply the hue shift + outputPixelColor.r = mod(outputPixelColor.r + colorShift, 1.0); + + // finally convert to RGB + outputPixelColor.rgb = hsv2rgb(outputPixelColor.rgb); + + gl_FragColor = outputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Colour Distance.fs b/src/renderer/src/application/sample-modules/isf/Colour Distance.fs new file mode 100644 index 000000000..6b9784684 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Colour Distance.fs @@ -0,0 +1,67 @@ +/*{ + "CATEGORIES": [ + "Dissolve", + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/ColourDistance.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 5, + "MAX": 10, + "MIN": 0, + "NAME": "power", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: P-Seebauer +// ported by gre from https://gist.github.com/P-Seebauer/2a5fa2f77c883dd661f9 + + +vec4 transition(vec2 p) { + vec4 fTex = getFromColor(p); + vec4 tTex = getToColor(p); + float m = step(distance(fTex, tTex), progress); + return mix( + mix(fTex, tTex, m), + tTex, + pow(progress, power) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Comet Tails.fs b/src/renderer/src/application/sample-modules/isf/Comet Tails.fs new file mode 100644 index 000000000..26f5990a0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Comet Tails.fs @@ -0,0 +1,99 @@ +/*{ + "CATEGORIES": [ + "Feedback", + "Glitch" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "This does a feedback motion blur based on the brightness of pixels to create an analog recording comet trails effect", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "absorptionRate", + "TYPE": "float" + }, + { + "DEFAULT": 0.02, + "MAX": 1, + "MIN": 0, + "NAME": "dischargeRate", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "FLOAT": true, + "PERSISTENT": true, + "TARGET": "feedbackBuffer" + } + ] +} +*/ + + + +// Comet Tails is a specific type of Image Lag +// From https://bavc.github.io/avaa/artifacts/image_lag.html +/* +Image lag occurs in video recorded or displayed using certain types of pick-up devices and cameras, including the Vidicon picture tube, among others. +This type of camera tube captures light radiating from a scene through a lens and projects it onto a photoconductive target, creating a charge-density pattern which is scanned using low-velocity electrons. +The resulting image can be amplified and recorded to tape or output to a video monitor. +The electrical charge remains present on the target until it is re-scanned or the charge dissipates. +*/ + + + +const vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0); +const vec4 kRGBToI = vec4 (0.596, -0.275, -0.321, 0.0); +const vec4 kRGBToQ = vec4 (0.212, -0.523, 0.311, 0.0); + +const vec4 kYIQToR = vec4 (1.0, 0.956, 0.621, 0.0); +const vec4 kYIQToG = vec4 (1.0, -0.272, -0.647, 0.0); +const vec4 kYIQToB = vec4 (1.0, -1.107, 1.704, 0.0); + + +vec3 rgb2yiq(vec3 c) { + float YPrime = dot (c, kRGBToYPrime.rgb); + float I = dot (c, kRGBToI.rgb); + float Q = dot (c, kRGBToQ.rgb); + return vec3(YPrime,I,Q); +} + +vec3 yiq2rgb(vec3 c) { + vec3 yIQ = vec3 (c.r, c.g, c.b); + return vec3(dot (yIQ, kYIQToR.rgb),dot (yIQ, kYIQToG.rgb),dot (yIQ, kYIQToB.rgb)); +} + + + + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + vec4 stalePixel = IMG_PIXEL(feedbackBuffer,gl_FragCoord.xy); + // the input gamma range is 0.0-1.0 (normalized). the actual gamma range i want to use is 0.0 - 5.0. + // however, actual gamma 0.0-1.0 is just as interesting as actual gamma 1.0-5.0, so we scale the normalized input to match... + float realGamma = (absorptionRate<=0.5) ? (absorptionRate * 2.0) : (((absorptionRate-0.5) * 2.0 * 4.0) + 1.0); + vec4 tmpColorA = stalePixel; + vec4 tmpColorB; + tmpColorB.rgb = pow(tmpColorA.rgb, vec3(1.0/realGamma)); + tmpColorB.a = tmpColorA.a; + + float feedbackLevel = rgb2yiq(tmpColorB.rgb).r; + // if the new pixel is brighter, it immediately fills up the buffer to its level + if (rgb2yiq(freshPixel.rgb).r > feedbackLevel) { + feedbackLevel = 0.0; + } + // otherwise, discharge + else { + feedbackLevel *= (1.0 - dischargeRate); + } + + gl_FragColor = mix(freshPixel,stalePixel,feedbackLevel); +} diff --git a/src/renderer/src/application/sample-modules/isf/Convergence.fs b/src/renderer/src/application/sample-modules/isf/Convergence.fs new file mode 100644 index 000000000..bf097573a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Convergence.fs @@ -0,0 +1,108 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "horizontal_magnitude", + "TYPE": "float", + "MIN": 0.00, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "vertical_magnitude", + "TYPE": "float", + "MIN": 0.00, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "color_magnitude", + "TYPE": "float", + "MIN": 0.00, + "MAX": 2.0, + "DEFAULT": 1.0 + }, + { + "NAME": "mode", + "VALUES": [ + 0, + 1, + 2, + 3 + ], + "LABELS": [ + "add", + "add mod", + "multiply", + "difference" + ], + "DEFAULT": 0, + "TYPE": "long" + } + ] + +}*/ + + + +// adapted from maxilla inc's https://github.com/maxillacult/ofxPostGlitch/ + + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + + +void main() +{ + + vec2 texCoord = isf_FragNormCoord; + + vec4 col = IMG_NORM_PIXEL(inputImage,texCoord); + + vec4 col_r = vec4(0.0); + vec4 col_l = vec4(0.0); + vec4 col_g = vec4(0.0); + + vec2 rand_offset = texCoord + vec2((horizontal_magnitude * rand(vec2(TIME,0.213))-horizontal_magnitude/2.0), (vertical_magnitude * rand(vec2(TIME,0.463467))) - vertical_magnitude / 2.0); + col_r = IMG_NORM_PIXEL(inputImage,rand_offset); + rand_offset = texCoord + vec2((horizontal_magnitude * rand(vec2(TIME,0.5345))-horizontal_magnitude/2.0), (vertical_magnitude * rand(vec2(TIME,0.7875))) - vertical_magnitude / 2.0); + col_l = IMG_NORM_PIXEL(inputImage,rand_offset); + rand_offset = texCoord + vec2((horizontal_magnitude * rand(vec2(TIME,0.456345))-horizontal_magnitude/2.0), (vertical_magnitude * rand(vec2(TIME,0.9432))) - vertical_magnitude / 2.0); + col_g = IMG_NORM_PIXEL(inputImage,rand_offset); + + vec4 color_shift; + color_shift.b = color_magnitude*col_r.b*max(1.0,sin(texCoord.y*1.2)*2.5)*rand(vec2(TIME,0.0342)); + color_shift.r = color_magnitude*col_l.r*max(1.0,sin(texCoord.y*1.2)*2.5)*rand(vec2(TIME,0.5253)); + color_shift.g = color_magnitude*col_g.g*max(1.0,sin(texCoord.y*1.2)*2.5)*rand(vec2(TIME,0.1943)); + + // if doing add maths + if (mode == 0) { + col = col + color_shift; + } + // add mod + else if (mode == 1) { + col = mod(col + color_shift,1.001); + } + // multiply + else if (mode == 2) { + col = mix(col, col * (color_magnitude + color_shift),0.9); + } + // difference + else if (mode == 3) { + col = abs(color_shift - col); + } + + gl_FragColor = col; +} diff --git a/src/renderer/src/application/sample-modules/isf/Corner Color Tint.fs b/src/renderer/src/application/sample-modules/isf/Corner Color Tint.fs new file mode 100644 index 000000000..550f26a5b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Corner Color Tint.fs @@ -0,0 +1,98 @@ +/* +{ + "CATEGORIES" : [ + "Color Effect" + ], + "DESCRIPTION" : "Tints the corners of the image in different colors.", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "color1", + "TYPE" : "color", + "DEFAULT" : [ + 1, + 0, + 0, + 1 + ] + }, + { + "NAME" : "color2", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 1, + 0, + 1 + ] + }, + { + "NAME" : "color3", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 1, + 1 + ] + }, + { + "NAME" : "color4", + "TYPE" : "color", + "DEFAULT" : [ + 1, + 1, + 1, + 1 + ] + }, + { + "NAME" : "rotationAngle", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + } + ], + "CREDIT" : "VIDVOX" +} +*/ + +const float pi = 3.1415926535897932384626433832795; + +vec2 rotatePointNorm(vec2 pt, float rot) { + vec2 returnMe = pt; + + float r = distance(vec2(0.50), returnMe); + float a = atan((returnMe.y-0.5),(returnMe.x-0.5)); + + returnMe.x = r * cos(a + 2.0 * pi * rot - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * rot - pi) + 0.5; + + returnMe = returnMe; + + return returnMe; +} + +void main() { + vec2 pt = isf_FragNormCoord; + vec4 srcPixel = IMG_NORM_PIXEL(inputImage,pt); + vec4 dist = vec4(0.0); + pt = rotatePointNorm(pt,rotationAngle+0.5); + dist.r = max(1.0-distance(vec2(0.0,0.0),pt),0.0); + dist.g = max(1.0-distance(vec2(1.0,0.0),pt),0.0); + dist.b = max(1.0-distance(vec2(0.0,1.0),pt),0.0); + dist.a = max(1.0-distance(vec2(1.0,1.0),pt),0.0); + + float luma1 = (srcPixel.r+srcPixel.g+srcPixel.b)/3.0; + vec4 resultPixel = (color1 * dist.r + color2 * dist.g + color3 * dist.b + color4 * dist.a) / (dist.r + dist.g + dist.b + dist.a); + float luma2 = (resultPixel.r+resultPixel.g+resultPixel.b)/3.0; + resultPixel.rgb *= luma1 / luma2; + resultPixel.a *= srcPixel.a; + + gl_FragColor = resultPixel; +} diff --git a/src/renderer/src/application/sample-modules/isf/Corner Colors.fs b/src/renderer/src/application/sample-modules/isf/Corner Colors.fs new file mode 100644 index 000000000..7f662671c --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Corner Colors.fs @@ -0,0 +1,89 @@ +/*{ + "CATEGORIES": [ + "Color" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Generates a gradient that fades between four different colors.", + "INPUTS": [ + { + "DEFAULT": [ + 1, + 0, + 0, + 1 + ], + "NAME": "color1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 1, + 0, + 1 + ], + "NAME": "color2", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 1, + 1 + ], + "NAME": "color3", + "TYPE": "color" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "NAME": "color4", + "TYPE": "color" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "rotationAngle", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +const float pi = 3.1415926535897932384626433832795; + +vec2 rotatePointNorm(vec2 pt, float rot) { + vec2 returnMe = pt; + + float r = distance(vec2(0.50), returnMe); + float a = atan((returnMe.y-0.5),(returnMe.x-0.5)); + + returnMe.x = r * cos(a + 2.0 * pi * rot - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * rot - pi) + 0.5; + + returnMe = returnMe; + + return returnMe; +} + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec4 dist = vec4(0.0); + vec2 pt = isf_FragNormCoord; + pt = rotatePointNorm(pt,rotationAngle+0.5); + dist.r = max(1.0-distance(vec2(0.0,0.0),pt),0.0); + dist.g = max(1.0-distance(vec2(1.0,0.0),pt),0.0); + dist.b = max(1.0-distance(vec2(0.0,1.0),pt),0.0); + dist.a = max(1.0-distance(vec2(1.0,1.0),pt),0.0); + + inputPixelColor = (color1 * dist.r + color2 * dist.g + color3 * dist.b + color4 * dist.a) / (dist.r + dist.g + dist.b + dist.a); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Crazy Parametric Fun.fs b/src/renderer/src/application/sample-modules/isf/Crazy Parametric Fun.fs new file mode 100644 index 000000000..3607ff3f2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Crazy Parametric Fun.fs @@ -0,0 +1,82 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/CrazyParametricFun.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "b", + "TYPE": "float" + }, + { + "NAME": "smoothness", + "TYPE": "float" + }, + { + "DEFAULT": 120, + "MAX": 360, + "MIN": 0, + "NAME": "amplitude", + "TYPE": "float" + }, + { + "DEFAULT": 4, + "MAX": 10, + "MIN": 0, + "NAME": "a", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: mandubian +// License: MIT + + +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy; + vec2 dir = p - vec2(.5); + float dist = length(dir); + float x = (a - b) * cos(progress) + b * cos(progress * ((a / b) - 1.) ); + float y = (a - b) * sin(progress) - b * sin(progress * ((a / b) - 1.)); + vec2 offset = dir * vec2(sin(progress * dist * amplitude * x), sin(progress * dist * amplitude * y)) / smoothness; + return mix(getFromColor(p + offset), getToColor(p), smoothstep(0.2, 1.0, progress)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/CrossZoom.fs b/src/renderer/src/application/sample-modules/isf/CrossZoom.fs new file mode 100644 index 000000000..18f1199b4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/CrossZoom.fs @@ -0,0 +1,111 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/CrossZoom.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "NAME": "strength", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: rectalogic +// ported by gre from https://gist.github.com/rectalogic/b86b90161503a0023231 + +// Converted from https://github.com/rectalogic/rendermix-basic-effects/blob/master/assets/com/rendermix/CrossZoom/CrossZoom.frag +// Which is based on https://github.com/evanw/glfx.js/blob/master/src/filters/blur/zoomblur.js +// With additional easing functions from https://github.com/rectalogic/rendermix-basic-effects/blob/master/assets/com/rendermix/Easing/Easing.glsllib + + +const float PI = 3.141592653589793; + +float Linear_ease(in float begin, in float change, in float duration, in float time) { + return change * time / duration + begin; +} + +float Exponential_easeInOut(in float begin, in float change, in float duration, in float time) { + if (time == 0.0) + return begin; + else if (time == duration) + return begin + change; + time = time / (duration / 2.0); + if (time < 1.0) + return change / 2.0 * pow(2.0, 10.0 * (time - 1.0)) + begin; + return change / 2.0 * (-pow(2.0, -10.0 * (time - 1.0)) + 2.0) + begin; +} + +float Sinusoidal_easeInOut(in float begin, in float change, in float duration, in float time) { + return -change / 2.0 * (cos(PI * time / duration) - 1.0) + begin; +} + +float rand (vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec3 crossFade(in vec2 uv, in float dissolve) { + return mix(getFromColor(uv).rgb, getToColor(uv).rgb, dissolve); +} + +vec4 transition(vec2 uv) { + vec2 texCoord = uv.xy / vec2(1.0).xy; + + // Linear interpolate center across center half of the image + vec2 center = vec2(Linear_ease(0.25, 0.5, 1.0, progress), 0.5); + float dissolve = Exponential_easeInOut(0.0, 1.0, 1.0, progress); + + // Mirrored sinusoidal loop. 0->strength then strength->0 + float strength = Sinusoidal_easeInOut(0.0, strength, 0.5, progress); + + vec3 color = vec3(0.0); + float total = 0.0; + vec2 toCenter = center - texCoord; + + /* randomize the lookup values to hide the fixed number of samples */ + float offset = rand(uv); + + for (float t = 0.0; t <= 40.0; t++) { + float percent = (t + offset) / 40.0; + float weight = 4.0 * (percent - percent * percent); + color += crossFade(texCoord + toCenter * percent * strength, dissolve) * weight; + total += weight; + } + return vec4(color / total, 1.0); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Crosshatch.fs b/src/renderer/src/application/sample-modules/isf/Crosshatch.fs new file mode 100644 index 000000000..ad5922152 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Crosshatch.fs @@ -0,0 +1,85 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/crosshatch.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "NAME": "fadeEdge", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "DEFAULT": 3, + "MAX": 10, + "MIN": 0, + "NAME": "threshold", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "VSN": "" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: pthrasher +// adapted by gre from https://gist.github.com/pthrasher/04fd9a7de4012cbb03f6 + + +float rand(vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} +vec4 transition(vec2 p) { + float dist = distance(center, p) / threshold; + float r = progress - min(rand(vec2(p.y, 0.0)), rand(vec2(0.0, p.x))); + return mix(getFromColor(p), getToColor(p), mix(0.0, mix(step(dist, r), 1.0, smoothstep(1.0-fadeEdge, 1.0, progress)), smoothstep(0.0, fadeEdge, progress))); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Crosswarp.fs b/src/renderer/src/application/sample-modules/isf/Crosswarp.fs new file mode 100644 index 000000000..8035d19e9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Crosswarp.fs @@ -0,0 +1,51 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/crosswarp.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Eke Péter +// License: MIT +vec4 transition(vec2 p) { + float x = progress; + x=smoothstep(.0,1.0,(x*2.0+p.x-1.0)); + return mix(getFromColor((p-.5)*(1.-x)+.5), getToColor((p-.5)*x+.5), x); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Cubic Warp.fs b/src/renderer/src/application/sample-modules/isf/Cubic Warp.fs new file mode 100644 index 000000000..fbab80542 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Cubic Warp.fs @@ -0,0 +1,82 @@ +/*{ + "CREDIT": "by carter rosenberg", + "CATEGORIES": [ + "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "level", + "TYPE": "float", + "MIN": 0, + "MAX": 100, + "DEFAULT": 1 + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + } + ] +}*/ + +const float pi = 3.14159265359; + + +#ifndef GL_ES +float distance (vec2 center, vec2 pt) +{ + float tmp = pow(center.x-pt.x,2.0)+pow(center.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + + +void main() { + vec2 loc; + vec2 modifiedCenter; + + loc = isf_FragNormCoord; + modifiedCenter = center; + + // lens distortion coefficient + float k = -0.15; + + float r2 = (loc.x-modifiedCenter.x) * (loc.x-modifiedCenter.x) + (loc.y-modifiedCenter.y) * (loc.y-modifiedCenter.y); + float f = 0.0; + + //only compute the cubic distortion if necessary + if(level == 0.0){ + f = 1.0 + r2 * k; + } + else { + f = 1.0 + r2 * (k + level * sqrt(r2)); + }; + + float zoom = max(sqrt(level),1.0); + + // get the right pixel for the current position + loc.x = f*(loc.x-modifiedCenter.x)/zoom+modifiedCenter.x; + loc.y = f*(loc.y-modifiedCenter.y)/zoom+modifiedCenter.y; + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Deinterlace.fs b/src/renderer/src/application/sample-modules/isf/Deinterlace.fs new file mode 100644 index 000000000..62532ab53 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Deinterlace.fs @@ -0,0 +1,32 @@ +/*{ + "CATEGORIES": [ + "Utility" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ], + "ISFVSN": "2" +} +*/ + +const vec2 pointOffset = vec2(0.0, 1.0); + +void main() { + vec4 outColor; + // "upper" (a.k.a. "the top row") + if (fract((gl_FragCoord.y+0.5)/2.0) == 0.0) { + //gl_FragColor = vec4(1,0,0,1); + outColor = (IMG_PIXEL(inputImage, gl_FragCoord.xy) + IMG_PIXEL(inputImage, gl_FragCoord.xy - pointOffset))/2.0; + gl_FragColor = outColor; + } + // "lower" (a.k.a. "the bottom row") + else { + //gl_FragColor = vec4(0,1,0,1); + outColor = (IMG_PIXEL(inputImage, gl_FragCoord.xy) + IMG_PIXEL(inputImage, gl_FragCoord.xy + pointOffset))/2.0; + gl_FragColor = outColor; + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Diagonal Blur.fs b/src/renderer/src/application/sample-modules/isf/Diagonal Blur.fs new file mode 100644 index 000000000..7c7a7c563 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Diagonal Blur.fs @@ -0,0 +1,82 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "width", + "LABEL": "Width", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "angle", + "LABEL": "Angle", + "TYPE": "float", + "MIN": -1.0, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "quality", + "LABEL": "Quality", + "VALUES": [ + 12, + 8, + 4, + 2 + ], + "LABELS": [ + "Low", + "Mid", + "High", + "Best" + ], + "DEFAULT": 4, + "TYPE": "long" + } + ] +}*/ + + +const float pi = 3.14159265359; + + +void main() { + vec2 loc = isf_FragNormCoord * RENDERSIZE; + + vec2 p1 = vec2(0.0); + vec2 p2 = vec2(1.0); + vec2 vector = vec2(cos(pi * angle),sin(pi * angle)); + + vec4 returnMe; + + if (width > 0.0) { + p1 = loc - width * RENDERSIZE * vector; + p2 = loc + width * RENDERSIZE * vector; + + // now we have the two points to smear between, + //float i; + float count = clamp(width * max(RENDERSIZE.x,RENDERSIZE.y) / float(quality), 5.0, 125.0); + //float count = 10.0; + vec2 diff = p2 - p1; + for (float i = 0.0; i < 125.0; ++i) { + if (i > float(count)) + break; + float tmp = (i / (count - 1.0)); + returnMe = returnMe + IMG_PIXEL(inputImage, p1 + diff * tmp) / count; + } + } + else { + returnMe = IMG_THIS_PIXEL(inputImage); + } + gl_FragColor = returnMe; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Diagonalize.fs b/src/renderer/src/application/sample-modules/isf/Diagonalize.fs new file mode 100644 index 000000000..3df5d3a47 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Diagonalize.fs @@ -0,0 +1,108 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize", "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "width", + "LABEL": "Width", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "angle", + "LABEL": "Angle", + "TYPE": "float", + "MIN": -0.5, + "MAX": 0.5, + "DEFAULT": 0.125 + } + ] +}*/ + + +const float pi = 3.14159265359; + + +void main() { + vec2 loc = isf_FragNormCoord * RENDERSIZE; + + // for starters let's just do y = x * tan(angle) + b + + float b = 0.0; + float tanVal = 0.0; + vec2 p1 = vec2(0.0); + vec2 p2 = vec2(1.0); + + if (abs(angle) != 0.5) { + tanVal = tan(pi * angle); + b = (loc.y - loc.x * tanVal)/RENDERSIZE.y; + + if (width > 0.0) { + float w = width * (1.0+abs(tanVal)); + b = w * floor(b/w); + } + b = b * RENDERSIZE.y; + + // if p1 is offscreen, adjust to an onscreen point + p1 = vec2(0.0, b); + + // in this case instead of using where it hits the left edge, use where it hits the bottom, solving x for y = 0.0 (x = (y - b) / tanVal) + if (p1.y < 0.0) { + p1 = vec2(0.0-b / tanVal, 0.0); + } + // in this case instead of using where it hits the left edge, use where it hits the top, solving x for y = 1.0 (x = (y - b) / tanVal) + else if (p1.y > RENDERSIZE.y) { + p1 = vec2((RENDERSIZE.x - b) / tanVal, RENDERSIZE.y); + } + + // get the right side edge + p2 = vec2(RENDERSIZE.x, RENDERSIZE.x * tanVal + b); + + // if p2 is offscreen, adjust to an onscreen point + // in this case instead of using where it hits the right edge, use where it hits the bottom, solving x for y = 0.0 (x = (y - b) / tanVal) + if (p2.y < 0.0) { + p2 = vec2(- b / tanVal, 0.0); + } + // in this case instead of using where it hits the right edge, use where it hits the top, solving x for y = 1.0 (x = (y - b) / tanVal) + else if (p2.y > RENDERSIZE.y) { + p2 = vec2((RENDERSIZE.y - b) / tanVal, RENDERSIZE.y); + } + + } + // vertical lines! set p1 & p2 to fixed x with y = 0.0 and 1.0 + else { + if (angle > 0.0) { + p1 = vec2(loc.x, 0.0); + p2 = vec2(loc.x, RENDERSIZE.y); + } + else { + p2 = vec2(loc.x, 0.0); + p1 = vec2(loc.x, RENDERSIZE.y); + } + if (width > 0.0) { + p1.x = RENDERSIZE.x * width * floor((loc.x/RENDERSIZE.x) / width); + p2.x = p1.x; + } + } + + // now average 5 points on the line, p1, p2, their midpoint, and the mid points of those + // midpoint of the line + vec2 mid = (p1 + p2) / 2.0; + + vec4 returnMe = (IMG_PIXEL(inputImage, p1) + IMG_PIXEL(inputImage, (p1 + mid) / 2.0) + IMG_PIXEL(inputImage, mid) + IMG_PIXEL(inputImage, (p2 + mid) / 2.0) + IMG_PIXEL(inputImage, p2)) / 5.0; + //vec4 returnMe = IMG_PIXEL(inputImage, p2); + + gl_FragColor = returnMe; +} + + + diff --git a/src/renderer/src/application/sample-modules/isf/Digital Clock.fs b/src/renderer/src/application/sample-modules/isf/Digital Clock.fs new file mode 100644 index 000000000..a4d18ccbc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Digital Clock.fs @@ -0,0 +1,184 @@ +/*{ + "CATEGORIES": [ + "Utility" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Shows the current time of day or time since the composition started", + "INPUTS": [ + { + "DEFAULT": [ + 1, + 0.5899132640831176, + 0, + 1 + ], + "LABEL": "Color", + "NAME": "colorInput", + "TYPE": "color" + }, + { + "DEFAULT": 0, + "LABEL": "Clock Mode", + "LABELS": [ + "Time", + "Countdown", + "Counter" + ], + "NAME": "clockMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + }, + { + "DEFAULT": 0, + "LABEL": "Y Offset", + "MAX": 1, + "MIN": -1, + "NAME": "yOffset", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Blink", + "NAME": "blinkingColons", + "TYPE": "bool" + }, + { + "LABEL": "24 Hour", + "NAME": "twentyFourHourStyle", + "TYPE": "bool" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + } + ] +} +*/ + + + +float segment(vec2 uv, bool On) { + return (On) ? (1.0 - smoothstep(0.05,0.15,abs(uv.x))) * + (1.-smoothstep(0.35,0.55,abs(uv.y)+abs(uv.x))) + : 0.; +} + +float digit(vec2 uv,int num) { + float seg= 0.; + seg += segment(uv.yx+vec2(-1., 0.),num!=-1 && num!=1 && num!=4 ); + seg += segment(uv.xy+vec2(-.5,-.5),num!=-1 && num!=1 && num!=2 && num!=3 && num!=7); + seg += segment(uv.xy+vec2( .5,-.5),num!=-1 && num!=5 && num!=6 ); + seg += segment(uv.yx+vec2( 0., 0.),num!=-1 && num!=0 && num!=1 && num!=7 ); + seg += segment(uv.xy+vec2(-.5, .5),num==0 || num==2 || num==6 || num==8 ); + seg += segment(uv.xy+vec2( .5, .5),num!=-1 && num!=2 ); + seg += segment(uv.yx+vec2( 1., 0.),num!=-1 && num!=1 && num!=4 && num!=7 ); + return seg; +} + +float showNum(vec2 uv,int nr, bool zeroTrim) { // nr: 2 digits + sgn . zeroTrim: trim leading "0" + if (abs(uv.x)>2.*1.5 || abs(uv.y)>1.2) return 0.; + + if (nr<0) { + nr = -nr; + if (uv.x>1.5) { + uv.x -= 2.; + return segment(uv.yx,true); // minus sign. + } + } + + if (uv.x>0.) { + nr /= 10; if (nr==0 && zeroTrim) nr = -1; + uv -= vec2(.75,0.); + } else { + uv += vec2(.75,0.); + nr = int(mod(float(nr),10.)); + } + + return digit(uv,nr); +} + +float colon(vec2 uv, vec2 cCenter, float cRadius) { + float returnMe = distance(uv,cCenter); + if (returnMe > cRadius) + returnMe = 0.0; + else + returnMe = 1.0 - pow(returnMe / cRadius,4.0); + return returnMe; +} + + + +// a simplfied version of the number drawing from http://www.interactiveshaderformat.com/sketches/120 + + + + +void main() { + vec4 returnMe = vec4(0.0); + vec2 uv = isf_FragNormCoord; + float adjustedOffset = (yOffset*1.2*(RENDERSIZE.y/RENDERSIZE.x)); + vec2 loc = uv; + loc.y = loc.y - adjustedOffset; + + // The first element of the vector is the year, the second element is the month, + // the third element is the day, and the fourth element is the time (in seconds) within the day. + vec4 currentDate = DATE; + if (clockMode == 1) + currentDate.a = 86400.0 - currentDate.a; + else if (clockMode == 2) + currentDate = vec4(TIME); + + float tmpVal = currentDate.a; + float h = 0.0; + float m = 0.0; + float s = 0.0; + + s = mod(tmpVal,60.0); + tmpVal = tmpVal / 60.0; + m = mod(tmpVal,60.0); + tmpVal = tmpVal / 60.0; + h = mod(tmpVal,60.0); + if ((!twentyFourHourStyle)&&(clockMode == 0)) { + h = mod(h,12.0); + if (h < 1.0) + h = 12.0; + } + + float seg = 0.0; + int displayTime = 0; + + if (loc.x < 0.3) { + loc.x = 1.0 - (loc.x + 0.37); + loc = (loc * 3.0 - 1.5) * 4.0; + displayTime = int(h); + } + else if (loc.x < 0.6) { + loc.x = 1.0 - (loc.x+0.05); + loc = (loc * 3.0 - 1.5) * 4.0; + displayTime = int(m); + } + else { + loc.x = 1.0 - (loc.x - 0.3); + loc = (loc * 3.0 - 1.5) * 4.0; + displayTime = int(s); + } + + seg = showNum(loc,displayTime,false); + + if ((!blinkingColons)||(mod(currentDate.a,1.0)<0.5)) { + seg += colon(uv,vec2(0.293,0.53+adjustedOffset),0.015); + seg += colon(uv,vec2(0.293,0.47+adjustedOffset),0.015); + + seg += colon(uv,vec2(0.633,0.53+adjustedOffset),0.015); + seg += colon(uv,vec2(0.633,0.47+adjustedOffset),0.015); + } + if (seg > 0.0) + returnMe = colorInput * seg; + + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Directional Warp.fs b/src/renderer/src/application/sample-modules/isf/Directional Warp.fs new file mode 100644 index 000000000..8ae600c7a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Directional Warp.fs @@ -0,0 +1,74 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/directionalwarp.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + -1, + 1 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + -1, + -1 + ], + "NAME": "direction", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: pschroen +// License: MIT + + +const float smoothness = 0.5; +const vec2 center = vec2(0.5, 0.5); + +vec4 transition (vec2 uv) { + vec2 v = normalize(direction); + v /= abs(v.x) + abs(v.y); + float d = v.x * center.x + v.y * center.y; + float m = 1.0 - smoothstep(-smoothness, 0.0, v.x * uv.x + v.y * uv.y - (d - 0.5 + progress * (1.0 + smoothness))); + return mix(getFromColor((uv - 0.5) * (1.0 - m) + 0.5), getToColor((uv - 0.5) * m + 0.5), m); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Directional Wipe.fs b/src/renderer/src/application/sample-modules/isf/Directional Wipe.fs new file mode 100644 index 000000000..c1e764e45 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Directional Wipe.fs @@ -0,0 +1,82 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/directionalwipe.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + -1 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + -1, + -1 + ], + "NAME": "direction", + "TYPE": "point2D" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +const vec2 center = vec2(0.5, 0.5); + +vec4 transition (vec2 uv) { + vec2 v = normalize(direction); + v /= abs(v.x)+abs(v.y); + float d = v.x * center.x + v.y * center.y; + float m = + (1.0-step(progress, 0.0)) * // there is something wrong with our formula that makes m not equals 0.0 with progress is 0.0 + (1.0 - smoothstep(-smoothness, 0.0, v.x * uv.x + v.y * uv.y - (d-0.5+progress*(1.+smoothness)))); + return mix(getFromColor(uv), getToColor(uv), m); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Directional.fs b/src/renderer/src/application/sample-modules/isf/Directional.fs new file mode 100644 index 000000000..1269aee81 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Directional.fs @@ -0,0 +1,73 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/Directional.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 1 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + -1, + -1 + ], + "NAME": "direction", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Gaëtan Renaudeau +// License: MIT + + +vec4 transition (vec2 uv) { + vec2 p = uv + progress * sign(direction); + vec2 f = fract(p); + return mix( + getToColor(f), + getFromColor(f), + step(0.0, p.y) * step(p.y, 1.0) * step(0.0, p.x) * step(p.x, 1.0) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Dirty Lens.fs b/src/renderer/src/application/sample-modules/isf/Dirty Lens.fs new file mode 100644 index 000000000..e0d987fab --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dirty Lens.fs @@ -0,0 +1,166 @@ +/* +{ + "CATEGORIES" : [ + "Film", + "Masking" + ], + "DESCRIPTION" : "Value Noise", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "scale", + "TYPE" : "float", + "MAX" : 100, + "DEFAULT" : 9, + "MIN" : 0, + "LABEL" : "Dirt Size" + }, + { + "NAME" : "brightness", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 0.5, + "MIN" : 0, + "LABEL" : "Dirt Thickness" + }, + { + "NAME" : "brightnessCurve", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 1, + "LABEL" : "Dirt Shape", + "MIN" : 1 + }, + { + "NAME" : "radius", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "LABEL" : "Dirt Spread", + "MIN" : 0 + }, + { + "NAME" : "noiseSeed", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0, + "LABEL" : "Dirt Seed" + }, + { + "LABELS" : [ + "Original", + "Multiply", + "Replace" + ], + "NAME" : "alphaMode", + "TYPE" : "long", + "IDENTITY" : 0, + "LABEL" : "Alpha Mode", + "VALUES" : [ + 0, + 1, + 2 + ] + } + ], + "ISFVSN" : "2", + "CREDIT" : "Inigo Quilez ported by @colin_movecraft and VIDVOX" +} +*/ + + + +// Value Noise (http://en.wikipedia.org/wiki/Value_noise), not to be confused with Perlin's +// Noise, is probably the simplest way to generate noise (a random smooth signal with +// mostly all its energy in the low frequencies) suitable for procedural texturing/shading, +// modeling and animation. +// +// It produces lowe quality noise than Gradient Noise (https://www.shadertoy.com/view/XdXGW8) +// but it is slightly faster to compute. When used in a fractal construction, the blockyness +// of Value Noise gets qcuikly hidden, making it a very popular alternative to Gradient Noise. +// +// The princpiple is to create a virtual grid/latice all over the plane, and assign one +// random value to every vertex in the grid. When querying/requesting a noise value at +// an arbitrary point in the plane, the grid cell in which the query is performed is +// determined (line 30), the four vertices of the grid are determined and their random +// value fetched (lines 35 to 38) and then bilinearly interpolated (lines 35 to 38 again) +// with a smooth interpolant (line 31 and 33). + + +// Value Noise 2D, Derivatives: https://www.shadertoy.com/view/4dXBRH +// Gradient Noise 2D, Derivatives: https://www.shadertoy.com/view/XdXBRH +// Value Noise 3D, Derivatives: https://www.shadertoy.com/view/XsXfRH +// Gradient Noise 3D, Derivatives: https://www.shadertoy.com/view/4dffRH +// Value Noise 2D : https://www.shadertoy.com/view/lsf3WH +// Value Noise 3D : https://www.shadertoy.com/view/4sfGzS +// Gradient Noise 2D : https://www.shadertoy.com/view/XdXGW8 +// Gradient Noise 3D : https://www.shadertoy.com/view/Xsl3Dl +// Simplex Noise 2D : https://www.shadertoy.com/view/Msf3WH + + +float hash(vec2 p) // replace this by something better +{ + p = 50.0*fract(noiseSeed + p*0.3183099 + vec2(0.71,0.113)); + return -1.0+2.0*fract( p.x*p.y*(p.x+p.y) ); +} + +float noise( in vec2 p ) +{ + vec2 i = floor( p ); + vec2 f = fract( p ); + + vec2 u = f*f*(3.0-2.0*f); + + return mix( mix( hash( i + vec2(0.0,0.0) ), + hash( i + vec2(1.0,0.0) ), u.x), + mix( hash( i + vec2(0.0,1.0) ), + hash( i + vec2(1.0,1.0) ), u.x), u.y); +} + +float map(float n, float i1, float i2, float o1, float o2){ + return o1 + (o2-o1) * (n-i1)/(i2-i1); + +} + +void main(){ + vec2 p = gl_FragCoord.xy / RENDERSIZE; + + vec2 uv = p*vec2(RENDERSIZE.x/RENDERSIZE.y,1.0); + float f = 0.0; + + //fbm - fractal noise (4 octaves) + { + uv *= scale; + mat2 m = mat2( 1.6, 1.2, -1.2, 1.6 ); + f = 0.5000*noise( uv ); uv = m*uv; + f += 0.2500*noise( uv ); uv = m*uv; + f += 0.1250*noise( uv ); uv = m*uv; + f += 0.0625*noise( uv ); uv = m*uv; + } + + f = 1.0-pow(f,(5.0-brightnessCurve))*brightness; + + float d = distance(isf_FragNormCoord,vec2(0.5)); + if (d > radius) { + f = f + (d-radius); + } + + f = (f > 1.0) ? 1.0 : f; + + vec4 returnMe = IMG_THIS_PIXEL(inputImage) * vec4( f, f, f, 1.0 ); + if (alphaMode == 1) + returnMe.a *= f; + else if (alphaMode == 2) + returnMe.a = f; + gl_FragColor = returnMe; +} + + + +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/renderer/src/application/sample-modules/isf/Displace.fs b/src/renderer/src/application/sample-modules/isf/Displace.fs new file mode 100644 index 000000000..7c31d07f6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Displace.fs @@ -0,0 +1,108 @@ +/* +{ + "CATEGORIES" : [ + "Distortion Effect" + ], + "DESCRIPTION" : "Simple Displace", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "displaceImage", + "TYPE" : "image", + "LABEL" : "displace image" + }, + { + "NAME" : "uDisplaceAmt", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 0, + "MIN" : -2 + }, + { + "LABELS" : [ + "Luma", + "R", + "G", + "B", + "A" + ], + "NAME" : "xComponent", + "TYPE" : "long", + "DEFAULT" : 1, + "VALUES" : [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "LABELS" : [ + "Luma", + "R", + "G", + "B", + "A" + ], + "NAME" : "yComponent", + "TYPE" : "long", + "DEFAULT" : 2, + "VALUES" : [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "NAME" : "relativeShift", + "TYPE" : "bool", + "DEFAULT" : 0 + } + ], + "CREDIT" : "by @colin_movecraft" +} +*/ + + + +void main(){ + vec2 p = isf_FragNormCoord.xy; + vec4 displacePixel = IMG_NORM_PIXEL(displaceImage, p); + + float r = (displacePixel.r); + float g = (displacePixel.g); + float b = (displacePixel.b); + float a = (displacePixel.a); + float avg = (r+g+b)/3.0; + + vec2 displace = vec2(avg,avg); + if (xComponent==1) + displace.x = r; + else if (xComponent==2) + displace.x = g; + else if (xComponent==3) + displace.x = b; + else if (xComponent==4) + displace.x = a; + + if (yComponent==1) + displace.y = r; + else if (yComponent==2) + displace.y = g; + else if (yComponent==3) + displace.y = b; + else if (yComponent==4) + displace.y = a; + + displace = (relativeShift) ? 2.0 * (displace - vec2(0.5)) : displace; + displace *= uDisplaceAmt; + + gl_FragColor = IMG_NORM_PIXEL(inputImage,p+displace); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Displacement.fs b/src/renderer/src/application/sample-modules/isf/Displacement.fs new file mode 100644 index 000000000..77446248d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Displacement.fs @@ -0,0 +1,75 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/displacement.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "NAME": "displacementMap", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "strength", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Travis Fischer +// License: MIT +// +// Adapted from a Codrops article by Robin Delaporte +// https://tympanus.net/Development/DistortionHoverEffect + + + +vec4 transition (vec2 uv) { + float displacement = IMG_NORM_PIXEL(displacementMap, uv).r * strength; + + vec2 uvFrom = vec2(uv.x + progress * displacement, uv.y); + vec2 uvTo = vec2(uv.x - (1.0 - progress) * displacement, uv.y); + + return mix( + getFromColor(uvFrom), + getToColor(uvTo), + progress + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Dither-Bayer.fs b/src/renderer/src/application/sample-modules/isf/Dither-Bayer.fs new file mode 100644 index 000000000..a06d34e0b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dither-Bayer.fs @@ -0,0 +1,216 @@ +/*{ + "DESCRIPTION": "Bayer style dithering", + "CREDIT": "Hugh Kennedy, adapted by David Lublin", + "CATEGORIES": [ + "Utility", "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "colorize", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME": "matrixMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ], + "LABELS": [ + "2x2", + "4x4", + "8x8" + ], + "DEFAULT": 2 + } + ] + +}*/ + + + +// based on https://github.com/hughsk/glsl-dither + +float luma(vec3 color) { + return (color.r + color.g + color.b) / 3.0; +} + +float luma(vec4 color) { + return color.a * (color.r + color.g + color.b) / 3.0; +} + +float dither8x8(vec2 position, float brightness) { + int x = int(mod(position.x, 8.0)); + int y = int(mod(position.y, 8.0)); + int index = x + y * 8; + float limit = 0.0; + + if (x < 8) { + if (index == 0) limit = 0.015625; + if (index == 1) limit = 0.515625; + if (index == 2) limit = 0.140625; + if (index == 3) limit = 0.640625; + if (index == 4) limit = 0.046875; + if (index == 5) limit = 0.546875; + if (index == 6) limit = 0.171875; + if (index == 7) limit = 0.671875; + if (index == 8) limit = 0.765625; + if (index == 9) limit = 0.265625; + if (index == 10) limit = 0.890625; + if (index == 11) limit = 0.390625; + if (index == 12) limit = 0.796875; + if (index == 13) limit = 0.296875; + if (index == 14) limit = 0.921875; + if (index == 15) limit = 0.421875; + if (index == 16) limit = 0.203125; + if (index == 17) limit = 0.703125; + if (index == 18) limit = 0.078125; + if (index == 19) limit = 0.578125; + if (index == 20) limit = 0.234375; + if (index == 21) limit = 0.734375; + if (index == 22) limit = 0.109375; + if (index == 23) limit = 0.609375; + if (index == 24) limit = 0.953125; + if (index == 25) limit = 0.453125; + if (index == 26) limit = 0.828125; + if (index == 27) limit = 0.328125; + if (index == 28) limit = 0.984375; + if (index == 29) limit = 0.484375; + if (index == 30) limit = 0.859375; + if (index == 31) limit = 0.359375; + if (index == 32) limit = 0.0625; + if (index == 33) limit = 0.5625; + if (index == 34) limit = 0.1875; + if (index == 35) limit = 0.6875; + if (index == 36) limit = 0.03125; + if (index == 37) limit = 0.53125; + if (index == 38) limit = 0.15625; + if (index == 39) limit = 0.65625; + if (index == 40) limit = 0.8125; + if (index == 41) limit = 0.3125; + if (index == 42) limit = 0.9375; + if (index == 43) limit = 0.4375; + if (index == 44) limit = 0.78125; + if (index == 45) limit = 0.28125; + if (index == 46) limit = 0.90625; + if (index == 47) limit = 0.40625; + if (index == 48) limit = 0.25; + if (index == 49) limit = 0.75; + if (index == 50) limit = 0.125; + if (index == 51) limit = 0.625; + if (index == 52) limit = 0.21875; + if (index == 53) limit = 0.71875; + if (index == 54) limit = 0.09375; + if (index == 55) limit = 0.59375; + if (index == 56) limit = 1.0; + if (index == 57) limit = 0.5; + if (index == 58) limit = 0.875; + if (index == 59) limit = 0.375; + if (index == 60) limit = 0.96875; + if (index == 61) limit = 0.46875; + if (index == 62) limit = 0.84375; + if (index == 63) limit = 0.34375; + } + + return brightness < limit ? 0.0 : 1.0; +} + +vec3 dither8x8(vec2 position, vec3 color) { + return color * dither8x8(position, luma(color)); +} + +vec4 dither8x8(vec2 position, vec4 color) { + return vec4(color.rgb * dither8x8(position, luma(color)), 1.0); +} + +float dither4x4(vec2 position, float brightness) { + int x = int(mod(position.x, 4.0)); + int y = int(mod(position.y, 4.0)); + int index = x + y * 4; + float limit = 0.0; + + if (x < 8) { + if (index == 0) limit = 0.0625; + if (index == 1) limit = 0.5625; + if (index == 2) limit = 0.1875; + if (index == 3) limit = 0.6875; + if (index == 4) limit = 0.8125; + if (index == 5) limit = 0.3125; + if (index == 6) limit = 0.9375; + if (index == 7) limit = 0.4375; + if (index == 8) limit = 0.25; + if (index == 9) limit = 0.75; + if (index == 10) limit = 0.125; + if (index == 11) limit = 0.625; + if (index == 12) limit = 1.0; + if (index == 13) limit = 0.5; + if (index == 14) limit = 0.875; + if (index == 15) limit = 0.375; + } + + return brightness < limit ? 0.0 : 1.0; +} + +vec3 dither4x4(vec2 position, vec3 color) { + return color * dither4x4(position, luma(color)); +} + +vec4 dither4x4(vec2 position, vec4 color) { + return vec4(color.rgb * dither4x4(position, luma(color)), 1.0); +} + +float dither2x2(vec2 position, float brightness) { + int x = int(mod(position.x, 2.0)); + int y = int(mod(position.y, 2.0)); + int index = x + y * 2; + float limit = 0.0; + + if (x < 8) { + if (index == 0) limit = 0.25; + if (index == 1) limit = 0.75; + if (index == 2) limit = 1.00; + if (index == 3) limit = 0.50; + } + + return brightness < limit ? 0.0 : 1.0; +} + +vec3 dither2x2(vec2 position, vec3 color) { + return color * dither2x2(position, luma(color)); +} + +vec4 dither2x2(vec2 position, vec4 color) { + return vec4(color.rgb * dither2x2(position, luma(color)), 1.0); +} + + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 loc = gl_FragCoord.xy; + // both of these are the same + inputPixelColor = IMG_THIS_PIXEL(inputImage); + + float val = 1.0; + + if (matrixMode == 0) { + val = dither2x2(loc, luma(inputPixelColor)); + } + else if (matrixMode == 1) { + val = dither4x4(loc, luma(inputPixelColor)); + } + else if (matrixMode == 2) { + val = dither8x8(loc, luma(inputPixelColor)); + } + + inputPixelColor = inputPixelColor * vec4(val,val,val,1.0); + + gl_FragColor = mix(vec4(val,val,val,inputPixelColor.a),inputPixelColor,colorize); +} diff --git a/src/renderer/src/application/sample-modules/isf/Doom Screen Transition.fs b/src/renderer/src/application/sample-modules/isf/Doom Screen Transition.fs new file mode 100644 index 000000000..8931fa982 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Doom Screen Transition.fs @@ -0,0 +1,130 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/DoomScreenTransition.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "frequency", + "TYPE": "float" + }, + { + "NAME": "noise", + "TYPE": "float" + }, + { + "DEFAULT": 50, + "MAX": 100, + "MIN": 0, + "NAME": "bars", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "dripScale", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": 0, + "NAME": "amplitude", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Zeh Fernando +// License: MIT + + +// Transition parameters -------- + +// Number of total bars/columns + +// Multiplier for speed ratio. 0 = no variation when going down, higher = some elements go much faster + +// Further variations in speed. 0 = no noise, 1 = super noisy (ignore frequency) + +// Speed variation horizontally. the bigger the value, the shorter the waves + +// How much the bars seem to "run" from the middle of the screen first (sticking to the sides). 0 = no drip, 1 = curved drip + + +// The code proper -------- + +float rand(int num) { + return fract(mod(float(num) * 67123.313, 12.0) * sin(float(num) * 10.3) * cos(float(num))); +} + +float wave(int num) { + float fn = float(num) * frequency * 0.1 * float(bars); + return cos(fn * 0.5) * cos(fn * 0.13) * sin((fn+10.0) * 0.3) / 2.0 + 0.5; +} + +float drip(int num) { + return sin(float(num) / float(bars - 1.0) * 3.141592) * dripScale; +} + +float pos(int num) { + return (noise == 0.0 ? wave(num) : mix(wave(num), rand(num), noise)) + (dripScale == 0.0 ? 0.0 : drip(num)); +} + +vec4 transition(vec2 uv) { + int bar = int(uv.x * (float(bars))); + float scale = 1.0 + pos(bar) * amplitude; + float phase = progress * scale; + float posY = uv.y / vec2(1.0).y; + vec2 p; + vec4 c; + if (phase + posY < 1.0) { + p = vec2(uv.x, uv.y + mix(0.0, vec2(1.0).y, phase)) / vec2(1.0).xy; + c = getFromColor(p); + } else { + p = uv.xy / vec2(1.0).xy; + c = getToColor(p); + } + + // Finally, apply the color + return c; +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} diff --git a/src/renderer/src/application/sample-modules/isf/Doorway.fs b/src/renderer/src/application/sample-modules/isf/Doorway.fs new file mode 100644 index 000000000..7b11d2bdd --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Doorway.fs @@ -0,0 +1,112 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/doorway.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.4, + "MAX": 1, + "MIN": 0, + "NAME": "reflection", + "TYPE": "float" + }, + { + "DEFAULT": 0.4, + "MAX": 1, + "MIN": 0, + "NAME": "perspective", + "TYPE": "float" + }, + { + "DEFAULT": 3, + "MAX": 10, + "MIN": 0, + "NAME": "depth", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: gre +// License: MIT + +const vec4 black = vec4(0.0, 0.0, 0.0, 1.0); +const vec2 boundMin = vec2(0.0, 0.0); +const vec2 boundMax = vec2(1.0, 1.0); + +bool inBounds (vec2 p) { + return all(lessThan(boundMin, p)) && all(lessThan(p, boundMax)); +} + +vec2 project (vec2 p) { + return p * vec2(1.0, -1.2) + vec2(0.0, -0.02); +} + +vec4 bgColor (vec2 p, vec2 pto) { + vec4 c = black; + pto = project(pto); + if (inBounds(pto)) { + c += mix(black, getToColor(pto), reflection * mix(1.0, 0.0, pto.y)); + } + return c; +} + + +vec4 transition (vec2 p) { + vec2 pfr = vec2(-1.), pto = vec2(-1.); + float middleSlit = 2.0 * abs(p.x-0.5) - progress; + if (middleSlit > 0.0) { + pfr = p + (p.x > 0.5 ? -1.0 : 1.0) * vec2(0.5*progress, 0.0); + float d = 1.0/(1.0+perspective*progress*(1.0-middleSlit)); + pfr.y -= d/2.; + pfr.y *= d; + pfr.y += d/2.; + } + float size = mix(1.0, depth, 1.-progress); + pto = (p + vec2(-0.5, -0.5)) * vec2(size, size) + vec2(0.5, 0.5); + if (inBounds(pfr)) { + return getFromColor(pfr); + } + else if (inBounds(pto)) { + return getToColor(pto); + } + else { + return bgColor(p, pto); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Dot Screen.fs b/src/renderer/src/application/sample-modules/isf/Dot Screen.fs new file mode 100644 index 000000000..0f63caba7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dot Screen.fs @@ -0,0 +1,110 @@ +/*{ + "CATEGORIES": [ + "Halftone Effect", + "Retro" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "sharpness", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": 0, + "NAME": "scale", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "colorize", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +const float tau = 6.28318530718; + +float pattern() { + float s = sin( angle * tau ), c = cos( angle * tau ); + vec2 tex = (isf_FragNormCoord - center) * RENDERSIZE; + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * max(scale,0.001); + return ( sin( point.x ) * sin( point.y ) ) * 4.0; +} + +void main() { + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = color + sharpness * (8.0*color - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + + float average = ( final.r + final.g + final.b ) / 3.0; + final = vec4( vec3( average * 10.0 - 5.0 + pattern() ), color.a ); + final = mix (color * final, final, 1.0-colorize); + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Dot Screen.vs b/src/renderer/src/application/sample-modules/isf/Dot Screen.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dot Screen.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Double Vision.fs b/src/renderer/src/application/sample-modules/isf/Double Vision.fs new file mode 100644 index 000000000..758dc3d48 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Double Vision.fs @@ -0,0 +1,72 @@ +/*{ + "CATEGORIES": [ + "Stylize" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "LABEL": "H Shift", + "MAX": 0.05, + "MIN": -0.05, + "NAME": "hShift", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "V Shift", + "MAX": 0.05, + "MIN": -0.05, + "NAME": "vShift", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "LABEL": "Shift Mix", + "MAX": 1, + "MIN": 0, + "NAME": "mixAmount1", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "LABEL": "Original Mix", + "MAX": 1, + "MIN": 0, + "NAME": "mixAmount2", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +void main() +{ + vec2 loc = isf_FragNormCoord; + vec2 shift = vec2(hShift, vShift); + + // zoom slightly so that there aren't out of range pixels + float zoomAmount = 1.0 + 2.0 * max(hShift, vShift); + vec2 modifiedCenter = vec2(0.5,0.5); + loc.x = (loc.x - modifiedCenter.x)*(1.0/zoomAmount) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/zoomAmount) + modifiedCenter.y; + + vec2 shiftLeft = clamp(loc - shift, .0, 1.0); + vec2 shiftRight = clamp(loc + shift, .0, 1.0); + + vec4 color = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + vec4 colorL = IMG_NORM_PIXEL(inputImage, shiftLeft); + vec4 colorR = IMG_NORM_PIXEL(inputImage, shiftRight); + + vec4 outColor = mix(min(colorL, colorR), max(colorL, colorR), mixAmount1); + outColor = mix(min(outColor, color), max(outColor, color), mixAmount2); + + gl_FragColor = outColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Dreamy Zoom.fs b/src/renderer/src/application/sample-modules/isf/Dreamy Zoom.fs new file mode 100644 index 000000000..e537a748d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dreamy Zoom.fs @@ -0,0 +1,98 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/DreamyZoom.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 6, + "MAX": 10, + "MIN": 0, + "NAME": "rotation", + "TYPE": "float" + }, + { + "DEFAULT": 1.2, + "MAX": 10, + "MIN": 0, + "NAME": "scale", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Zeh Fernando +// License: MIT + +// Definitions -------- +#define DEG2RAD 0.03926990816987241548078304229099 // 1/180*PI + + +// Transition parameters -------- + +// In degrees + +// Multiplier + + +// The code proper -------- + +float ratio = RENDERSIZE.x/RENDERSIZE.y; + +vec4 transition(vec2 uv) { + // Massage parameters + float phase = progress < 0.5 ? progress * 2.0 : (progress - 0.5) * 2.0; + float angleOffset = progress < 0.5 ? mix(0.0, rotation * DEG2RAD, phase) : mix(-rotation * DEG2RAD, 0.0, phase); + float newScale = progress < 0.5 ? mix(1.0, scale, phase) : mix(scale, 1.0, phase); + + vec2 center = vec2(0, 0); + + // Calculate the source point + vec2 assumedCenter = vec2(0.5, 0.5); + vec2 p = (uv.xy - vec2(0.5, 0.5)) / newScale * vec2(ratio, 1.0); + + // This can probably be optimized (with distance()) + float angle = atan(p.y, p.x) + angleOffset; + float dist = distance(center, p); + p.x = cos(angle) * dist / ratio + 0.5; + p.y = sin(angle) * dist + 0.5; + vec4 c = progress < 0.5 ? getFromColor(p) : getToColor(p); + + // Finally, apply the color + return c + (progress < 0.5 ? mix(0.0, 1.0, phase) : mix(1.0, 0.0, phase)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Dreamy.fs b/src/renderer/src/application/sample-modules/isf/Dreamy.fs new file mode 100644 index 000000000..e0cc9b8dc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dreamy.fs @@ -0,0 +1,56 @@ +/* +{ + "ISFVSN" : "2", + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "TYPE" : "float", + "NAME" : "progress", + "MIN" : 0, + "MAX" : 1, + "DEFAULT" : 0 + } + ], + "CATEGORIES" : [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/Dreamy.glsl", + "DESCRIPTION" : "Automatically converted from https://gl-transitions.com/" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: mikolalysenko +// License: MIT + +vec2 offset(float progress, float x, float theta) { + float phase = progress*progress + progress + theta; + float shifty = 0.03*progress*cos(10.0*(progress+x)); + return vec2(0, shifty); +} +vec4 transition(vec2 p) { + return mix(getFromColor(p + offset(progress, p.x, 0.0)), getToColor(p + offset(1.0-progress, p.x, 3.14)), progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Dual Side Scroller And Flip.fs b/src/renderer/src/application/sample-modules/isf/Dual Side Scroller And Flip.fs new file mode 100644 index 000000000..564de45e3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Dual Side Scroller And Flip.fs @@ -0,0 +1,91 @@ +/*{ + "CREDIT": "Inspired by Side Scroller and Flip by BrianChasalow", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "slidetop", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.0 + }, + { + "NAME": "shifttop", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.0 + }, + { + "NAME": "mirrorHorizontaltop", + "TYPE": "bool", + "MIN": false, + "MAX": true, + "DEFAULT": true + }, + { + "NAME": "mirrorVerticaltop", + "TYPE": "bool", + "MIN": false, + "MAX": true, + "DEFAULT": true + }, + { + "NAME": "slidebot", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.0 + }, + { + "NAME": "shiftbot", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.0 + }, + { + "NAME": "mirrorHorizontalbot", + "TYPE": "bool", + "MIN": false, + "MAX": true, + "DEFAULT": true + }, + { + "NAME": "mirrorVerticalbot", + "TYPE": "bool", + "MIN": false, + "MAX": true, + "DEFAULT": true + } + + ] + }*/ + +void main(void) +{ + vec2 pt = isf_FragNormCoord; + float slide = (isf_FragNormCoord.y > 0.5) ? slidetop : slidebot; + float shift = (isf_FragNormCoord.x < 0.5) ? shifttop : shiftbot; + + bool mirrorHorizontal = (isf_FragNormCoord.y > 0.5) ? mirrorHorizontaltop : mirrorHorizontalbot; + bool mirrorVertical = (isf_FragNormCoord.x < 0.5) ? mirrorVerticaltop : mirrorVerticalbot; + pt.x += slide; + pt.y += shift; + vec2 moddedRetard = mod(pt,1.0); + + if(mirrorHorizontal && pt.x >= 1.0 && pt.x <= 2.0) + moddedRetard = vec2(1.0-moddedRetard.x, moddedRetard.y); + if(mirrorVertical && pt.y >= 1.0 && pt.y <= 2.0) + moddedRetard = vec2(moddedRetard.x, 1.0-moddedRetard.y); + + vec4 pixel = IMG_NORM_PIXEL(inputImage, moddedRetard); + gl_FragColor = pixel; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Duotone.fs b/src/renderer/src/application/sample-modules/isf/Duotone.fs new file mode 100644 index 000000000..088c96a96 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Duotone.fs @@ -0,0 +1,85 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "threshold", + "TYPE": "float", + "DEFAULT": 0.50 + }, + { + "NAME": "softness", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1, + "DEFAULT": 0.0 + }, + { + "NAME": "brightColor", + "TYPE": "color", + "DEFAULT": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + { + "NAME": "darkColor", + "TYPE": "color", + "DEFAULT": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ] +}*/ + +//const vec4 lumcoeff = vec4(0.299, 0.587, 0.114, 0.0); +const vec4 lumcoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + float luminance = dot(srcPixel,lumcoeff); + //gl_FragColor = (luminance>=threshold) ? (brightColor) : (darkColor); + + // if i'm doing hard edges, it's either one color or the other + if (softness<=0.0) { + gl_FragColor = (luminance>=threshold) ? vec4(brightColor.rgb, srcPixel.a) : vec4(darkColor.rgb, srcPixel.a); + } + // else i'm doing soft edges... + else { + // 'softness' is normalized proportion of luminance on either side of threshold to be interpolated + // e.g.: 'softness' is 0.5 and 'threshold' is 0.5: vals < 0.25 are "dark", vals from 0.25-0.75 are "interpolated", and vals > 0.75 are "light" + vec4 midColor = (brightColor+darkColor)/vec4(2.0); + vec4 dstPixel; + if (luminance>=threshold) { + gl_FragColor = mix(midColor, brightColor, smoothstep(threshold, threshold+((1.0-threshold)*softness), luminance)); + } + else { + gl_FragColor = mix(darkColor, midColor, smoothstep(threshold-((1.0-threshold)*softness), threshold, luminance)); + } + + /* + // 'softness' is the absolute width (in luminance) on either side of the threshold to be interpolated + // e.g.: if softness is 0.25 and threshold is 0.5, vals < 0.25 are "dark", vals from 0.25-0.75 are "smoothed", and vals > 0.75 are "light" + vec4 midColor = (brightColor+darkColor)/vec4(2.0); + vec4 dstPixel; + if (luminance>=threshold) { + gl_FragColor = mix(midColor, brightColor, smoothstep(threshold, threshold+softness, luminance)); + } + else { + gl_FragColor = mix(darkColor, midColor, smoothstep(threshold-softness, threshold, luminance)); + } + */ + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Echo Trace.fs b/src/renderer/src/application/sample-modules/isf/Echo Trace.fs new file mode 100644 index 000000000..8342a0570 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Echo Trace.fs @@ -0,0 +1,65 @@ +/*{ + "DESCRIPTION": "Pixel with brightness levels below the threshold do not update.", + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "thresh", + "LABEL": "Threshold", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.25 + }, + { + "NAME": "gain", + "LABEL": "Gain", + "TYPE": "float", + "MIN": 0, + "MAX": 2, + "DEFAULT": 1 + }, + { + "NAME": "hardCutoff", + "LABEL": "Hard Cutoff", + "TYPE": "bool", + "DEFAULT": true + }, + { + "NAME": "invert", + "LABEL": "Invert", + "TYPE": "bool", + "DEFAULT": false + } + ], + "PASSES": [ + { + "TARGET": "bufferVariableNameA", + "persistent": true + }, + {} + ] +}*/ + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + vec4 stalePixel = IMG_PIXEL(bufferVariableNameA,gl_FragCoord.xy); + float brightLevel = (freshPixel.r + freshPixel.b + freshPixel.g) / 3.0; + if (invert) + brightLevel = 1.0 - brightLevel; + brightLevel = brightLevel * gain; + if (hardCutoff) { + if (brightLevel < thresh) + brightLevel = 1.0; + else + brightLevel = 0.0; + } + gl_FragColor = mix(freshPixel,stalePixel, brightLevel); +} diff --git a/src/renderer/src/application/sample-modules/isf/Edge Blowout.fs b/src/renderer/src/application/sample-modules/isf/Edge Blowout.fs new file mode 100644 index 000000000..4857d094d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Blowout.fs @@ -0,0 +1,230 @@ +/*{ + "CATEGORIES": [ + "Stylize" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Stretches the edges out a region of the video", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.25, + "IDENTITY": 0, + "LABEL": "Left Edge", + "MAX": 1, + "MIN": 0, + "NAME": "leftEdge", + "TYPE": "float" + }, + { + "DEFAULT": 0.75, + "IDENTITY": 1, + "LABEL": "Right Edge", + "MAX": 1, + "MIN": 0, + "NAME": "rightEdge", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "IDENTITY": 0, + "LABEL": "Bottom Edge", + "MAX": 1, + "MIN": 0, + "NAME": "bottomEdge", + "TYPE": "float" + }, + { + "DEFAULT": 0.75, + "IDENTITY": 1, + "LABEL": "Top Edge", + "MAX": 1, + "MIN": 0, + "NAME": "topEdge", + "TYPE": "float" + }, + { + "DEFAULT": true, + "LABEL": "Horizontal Bleed", + "NAME": "doHorizontal", + "TYPE": "bool" + }, + { + "DEFAULT": true, + "LABEL": "Vertical Bleed", + "NAME": "doVertical", + "TYPE": "bool" + }, + { + "DEFAULT": true, + "LABEL": "Inside Bleed", + "NAME": "insideBleed", + "TYPE": "bool" + }, + { + "DEFAULT": true, + "LABEL": "Outside Bleed", + "NAME": "outsideBleed", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 outputPixelColor; + float realLeftEdge = (leftEdge < rightEdge) ? leftEdge : rightEdge; + float realRightEdge = (leftEdge > rightEdge) ? leftEdge : rightEdge; + + float realBottomEdge = (bottomEdge < topEdge) ? bottomEdge : topEdge; + float realTopEdge = (bottomEdge > topEdge) ? bottomEdge : topEdge; + + vec2 sampleCoord = isf_FragNormCoord.xy; + + bool insideBox = false; + + vec2 region = vec2(0.0,0.0); + + if (sampleCoord.x < realLeftEdge) { + region.x = 0.0; + } + else if (sampleCoord.x > realRightEdge) { + region.x = 2.0; + } + else { + region.x = 1.0; + } + + if (sampleCoord.y < realBottomEdge) { + region.y = 0.0; + } + else if (sampleCoord.y > realTopEdge) { + region.y = 2.0; + } + else { + region.y = 1.0; + } + + if ((region.x == 1.0) && (region.y == 1.0) && (insideBleed == true)) { + insideBox = true; + } + else if (outsideBleed == true) { + // if we are in the bottom left... + if ((region.x == 0.0) && (region.y == 0.0)) { + if ((doHorizontal) && (doVertical)) { + insideBox = true; + realRightEdge = realLeftEdge; + realLeftEdge = 0.0; + realTopEdge = realBottomEdge; + realBottomEdge = 0.0; + } + } + else if ((region.x == 1.0) && (region.y == 0.0)) { + if ((doHorizontal) && (doVertical)) { + insideBox = true; + realTopEdge = realBottomEdge; + realBottomEdge = 0.0; + } + else if (doVertical) { + insideBox = true; + realTopEdge = realBottomEdge; + realBottomEdge = 0.0; + } + } + else if ((region.x == 2.0) && (region.y == 0.0)) { + if ((doHorizontal) && (doVertical)) { + insideBox = true; + realLeftEdge = realRightEdge; + realRightEdge = 1.0; + realTopEdge = realBottomEdge; + realBottomEdge = 0.0; + } + } + else if ((region.x == 0.0) && (region.y == 1.0)) { + if (doHorizontal) { + insideBox = true; + realRightEdge = realLeftEdge; + realLeftEdge = 0.0; + } + } + else if ((region.x == 2.0) && (region.y == 1.0)) { + if (doHorizontal) { + insideBox = true; + realLeftEdge = realRightEdge; + realRightEdge = 1.0; + } + } + else if ((region.x == 0.0) && (region.y == 2.0)) { + if ((doHorizontal) && (doVertical)) { + insideBox = true; + realRightEdge = realLeftEdge; + realLeftEdge = 0.0; + realBottomEdge = realTopEdge; + realTopEdge = 1.0; + } + } + else if ((region.x == 1.0) && (region.y == 2.0)) { + if (doVertical) { + insideBox = true; + realBottomEdge = realTopEdge; + realTopEdge = 1.0; + } + } + else if ((region.x == 2.0) && (region.y == 2.0)) { + if ((doHorizontal) && (doVertical)) { + insideBox = true; + realLeftEdge = realRightEdge; + realRightEdge = 1.0; + realBottomEdge = realTopEdge; + realTopEdge = 1.0; + } + } + } + + // if we're doing inside bleed + if (insideBox) { + + // how close are we to each edge? + if ((doHorizontal) && (doVertical)) { + float leftDistance = sampleCoord.x - realLeftEdge; + float rightDistance = realRightEdge - sampleCoord.x; + float bottomDistance = sampleCoord.y - realBottomEdge; + float topDistance = realTopEdge - sampleCoord.y; + float totalDistance = (leftDistance + rightDistance + bottomDistance + topDistance); + + vec4 leftPixel = IMG_NORM_PIXEL(inputImage, vec2(realLeftEdge, sampleCoord.y)); + vec4 rightPixel = IMG_NORM_PIXEL(inputImage, vec2(realRightEdge, sampleCoord.y)); + vec4 bottomPixel = IMG_NORM_PIXEL(inputImage, vec2(sampleCoord.x, realBottomEdge)); + vec4 topPixel = IMG_NORM_PIXEL(inputImage, vec2(sampleCoord.x, realTopEdge)); + + outputPixelColor = (rightDistance * leftPixel + leftDistance * rightPixel + topDistance * bottomPixel + bottomDistance * topPixel) / totalDistance; + } + else if (doHorizontal) { + float leftDistance = sampleCoord.x - realLeftEdge; + float rightDistance = realRightEdge - sampleCoord.x; + float totalDistance = leftDistance + rightDistance; + vec4 leftPixel = IMG_NORM_PIXEL(inputImage, vec2(realLeftEdge, sampleCoord.y)); + vec4 rightPixel = IMG_NORM_PIXEL(inputImage, vec2(realRightEdge, sampleCoord.y)); + outputPixelColor = (rightDistance * leftPixel + leftDistance * rightPixel) / totalDistance; + } + else if (doVertical) { + float bottomDistance = sampleCoord.y - realBottomEdge; + float topDistance = realTopEdge - sampleCoord.y; + float totalDistance = bottomDistance + topDistance; + vec4 bottomPixel = IMG_NORM_PIXEL(inputImage, vec2(sampleCoord.x, realBottomEdge)); + vec4 topPixel = IMG_NORM_PIXEL(inputImage, vec2(sampleCoord.x, realTopEdge)); + outputPixelColor = (topDistance * bottomPixel + bottomDistance * topPixel) / totalDistance; + } + else { + outputPixelColor = IMG_NORM_PIXEL(inputImage, sampleCoord); + } + } + else { + outputPixelColor = IMG_NORM_PIXEL(inputImage, sampleCoord); + } + + gl_FragColor = outputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Edge Blur.fs b/src/renderer/src/application/sample-modules/isf/Edge Blur.fs new file mode 100644 index 000000000..5c6528f72 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Blur.fs @@ -0,0 +1,184 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 20.0, + "DEFAULT": 5.0 + }, + { + "NAME": "blurAmount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 24.0, + "DEFAULT": 10.0 + }, + { + "NAME": "invert_map", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET": "halfSizeBaseRender", + "WIDTH": "floor($WIDTH/2.0)", + "HEIGHT": "floor($HEIGHT/2.0)", + "DESCRIPTION": "Pass 0" + }, + { + "TARGET": "quarterSizeBaseRender", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 1" + }, + { + "TARGET": "eighthSizeBaseRender", + "WIDTH": "floor($WIDTH/8.0)", + "HEIGHT": "floor($HEIGHT/8.0)", + "DESCRIPTION": "Pass 2" + }, + { + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 3" + }, + { + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 4" + }, + { + "TARGET": "fullGaussA", + "DESCRIPTION": "Pass 5" + }, + { + "TARGET": "fullGaussB", + "DESCRIPTION": "Pass 6" + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; + +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 texOffsets[5]; +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first three passes are just copying the input image into the buffer at varying sizes + if (PASSINDEX==0) { + gl_FragColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + } + else if (PASSINDEX==1) { + gl_FragColor = IMG_NORM_PIXEL(halfSizeBaseRender, isf_FragNormCoord); + } + else if (PASSINDEX==2) { + gl_FragColor = IMG_NORM_PIXEL(quarterSizeBaseRender, isf_FragNormCoord); + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 3) { + vec4 sample0 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + // ...writes into the full-size + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + else if (PASSINDEX == 6) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + vec4 origImg = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord); + + + + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + float gx = (-1.0 * gray(colorLA)) + (-2.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (2.0 * gray(colorR)) + (1.0 * gray(colorRB)); + float gy = (1.0 * gray(colorLA)) + (2.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-2.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + float edge = 0.0; + edge = intensity * pow(gx*gx + gy*gy,0.5); + if (invert_map) { + edge = 1.0 - edge; + } + + blurredImg = mix(blurredImg, origImg, edge); + + + + if (blurLevel == 0) + gl_FragColor = mix(origImg, blurredImg, (blurLevelModulus/6.1)); + else + gl_FragColor = blurredImg; + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Edge Blur.vs b/src/renderer/src/application/sample-modules/isf/Edge Blur.vs new file mode 100644 index 000000000..e6a0f26dd --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Blur.vs @@ -0,0 +1,103 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; + +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 texOffsets[5]; + +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first three passes are just drawing the texture- do nothing + + // the next four passes do gaussion blurs on the various levels + if (PASSINDEX==3 || PASSINDEX==5) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 3 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==3) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 5 is sampling 1/4 and writing to full-size + else if (PASSINDEX==5) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==3) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==4 || PASSINDEX==6) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 4 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==4) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 6 is sampling 1/4 and writing to full-size + else if (PASSINDEX==6) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Edge Distort.fs b/src/renderer/src/application/sample-modules/isf/Edge Distort.fs new file mode 100644 index 000000000..7f769541e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Distort.fs @@ -0,0 +1,85 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 0.2 + }, + { + "NAME": "invert_map", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ] +}*/ + + +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +const float tau = 6.28318530718; + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + + + float gx = (-1.0 * gray(colorLA)) + (-1.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (1.0 * gray(colorR)) + (1.0 * gray(colorRB)); + float gy = (1.0 * gray(colorLA)) + (1.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-1.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + + float edge = pow(gx*gx + gy*gy,0.5); + float brightness = (color.r + color.g + color.b) / 3.0; + + vec2 tc = isf_FragNormCoord; + vec2 modifiedCenter = vec2(0.5,0.5); + float r = distance(modifiedCenter, tc); + float a = atan ((tc.y-modifiedCenter.y),(tc.x-modifiedCenter.x)); + + // adjust the angle and radius based on the brightness and edge level + if (invert_map) { + edge = 1.0 - edge; + } + r = r + intensity * (1.0-edge) * (brightness - 0.5); + //a = a + intensity * pow(1.0+edge,brightness); + + tc.x = r * cos(a) + 0.5; + tc.y = r * sin(a) + 0.5; + + vec4 final = IMG_NORM_PIXEL(inputImage, tc); + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Edge Distort.vs b/src/renderer/src/application/sample-modules/isf/Edge Distort.vs new file mode 100644 index 000000000..c8b60f73f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Distort.vs @@ -0,0 +1,27 @@ +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Edge Trace.fs b/src/renderer/src/application/sample-modules/isf/Edge Trace.fs new file mode 100644 index 000000000..f06df9088 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Trace.fs @@ -0,0 +1,86 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 50.0, + "DEFAULT": 2.5 + }, + { + "NAME": "spread", + "TYPE": "float", + "MIN": 1.0, + "MAX": 20.0, + "DEFAULT": 5.0 + }, + { + "NAME": "invert_lines", + "TYPE": "bool", + "DEFAULT": 0 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + float gx = (-1.0 * gray(colorLA)) + (-1.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (1.0 * gray(colorR)) + (1.0 * gray(colorRB)); + float gy = (1.0 * gray(colorLA)) + (1.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-1.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + + vec4 blurred = (colorL + colorR + colorA + colorLA + colorLB + colorRB) / 8.0; + float bright = pow(gx*gx + gy*gy,0.5); + vec4 final = color * bright; + + final.rgb = (invert_lines) ? blurred.rgb + final.rgb * intensity : blurred.rgb - final.rgb * intensity; + final.a = color.a; + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Edge Trace.vs b/src/renderer/src/application/sample-modules/isf/Edge Trace.vs new file mode 100644 index 000000000..0c87959a1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edge Trace.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = spread/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Edges.fs b/src/renderer/src/application/sample-modules/isf/Edges.fs new file mode 100644 index 000000000..3138b89b2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edges.fs @@ -0,0 +1,110 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 50.0, + "DEFAULT": 50.0 + }, + { + "NAME": "threshold", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "sobel", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "opaque", + "TYPE": "bool", + "DEFAULT": true + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + float gx = (0.0); + float gy = (0.0); + if (sobel) { + gx = (-1.0 * gray(colorLA)) + (-2.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (2.0 * gray(colorR)) + (1.0 * gray(colorRB)); + gy = (1.0 * gray(colorLA)) + (2.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-2.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + } + else { + gx = (-1.0 * gray(colorLA)) + (-1.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (1.0 * gray(colorR)) + (1.0 * gray(colorRB)); + gy = (1.0 * gray(colorLA)) + (1.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-1.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + } + + float bright = pow(gx*gx + gy*gy,0.5); + vec4 final = color * bright; + + // if the brightness is below the threshold draw black + if (bright < threshold) { + if (opaque) + final = vec4(0.0, 0.0, 0.0, 1.0); + else + final = vec4(0.0, 0.0, 0.0, 0.0); + } + else { + final.rgb = final.rgb * intensity; + if (opaque) + final.a = 1.0; + else + final.a = color.a; + } + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Edges.vs b/src/renderer/src/application/sample-modules/isf/Edges.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Edges.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Emboss.fs b/src/renderer/src/application/sample-modules/isf/Emboss.fs new file mode 100644 index 000000000..ee07b693d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Emboss.fs @@ -0,0 +1,64 @@ +/*{ + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0, + "MAX": 10, + "DEFAULT": 2 + }, + { + "NAME": "colorize", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 1 + }, + { + "NAME": "brightness", + "TYPE": "float", + "MIN": 0.25, + "MAX": 0.75, + "DEFAULT": 0.5 + } + ] +}*/ + + +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 rightb_coord; + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = intensity * (colorR + colorB + colorRB - colorL - colorA - colorLA) + brightness; + float grayscale = gray(final); + final = mix(vec4(grayscale,grayscale,grayscale,final.a),final,colorize); + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Emboss.vs b/src/renderer/src/application/sample-modules/isf/Emboss.vs new file mode 100644 index 000000000..e56f564a6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Emboss.vs @@ -0,0 +1,23 @@ +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 rightb_coord; + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Etch-a-Sketch.fs b/src/renderer/src/application/sample-modules/isf/Etch-a-Sketch.fs new file mode 100644 index 000000000..3ca8a5b03 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Etch-a-Sketch.fs @@ -0,0 +1,138 @@ +/*{ + "CATEGORIES": [ + "Drawing" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Draw images one pixel at a time", + "INPUTS": [ + { + "NAME": "moveUp", + "TYPE": "event" + }, + { + "NAME": "moveDown", + "TYPE": "event" + }, + { + "NAME": "moveLeft", + "TYPE": "event" + }, + { + "NAME": "moveRight", + "TYPE": "event" + }, + { + "DEFAULT": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "NAME": "penColor", + "TYPE": "color" + }, + { + "DEFAULT": 0.05, + "MAX": 1, + "MIN": 0, + "NAME": "penSize", + "TYPE": "float" + }, + { + "NAME": "resetPosition", + "TYPE": "event" + }, + { + "NAME": "clearBuffer", + "TYPE": "event" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 0 + ], + "NAME": "clearColor", + "TYPE": "color" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "FLOAT": true, + "HEIGHT": "1", + "PERSISTENT": true, + "TARGET": "currentPosition", + "WIDTH": "1" + }, + { + "PERSISTENT": true, + "TARGET": "lastState" + } + ] +} +*/ + + +float round (float x) { + if (fract(x) < 0.5) + return floor(x); + else + return ceil(x); +} + + +void main() { + vec4 inputPixelColor = vec4(0.0); + + if (PASSINDEX == 0) { + if ((FRAMEINDEX==0)||(resetPosition)) + inputPixelColor = vec4(0.5,0.5,0.0,1.0); + else + inputPixelColor = IMG_THIS_PIXEL(currentPosition); + + vec2 pos = inputPixelColor.rg; + vec2 outputSize = IMG_SIZE(lastState); + vec2 penSizeInPixels = vec2(penSize * min(outputSize.x,outputSize.y)); + penSizeInPixels.x = (penSizeInPixels.x < 1.0) ? (1.0) : (penSizeInPixels.x); + penSizeInPixels.y = (penSizeInPixels.y < 1.0) ? (1.0) : (penSizeInPixels.y); + vec2 normalizedPenSize = penSizeInPixels / outputSize; + + if ((moveUp)&&(pos.y<1.0-normalizedPenSize.y)) { + pos.y = pos.y + normalizedPenSize.y; + } + else if ((moveDown)&&(pos.y>0.0)) { + pos.y = pos.y - normalizedPenSize.y; + } + else if ((moveLeft)&&(pos.x>0.0)) { + pos.x = pos.x - normalizedPenSize.x; + } + else if ((moveRight)&&(pos.x<1.0-normalizedPenSize.x)) { + pos.x = pos.x + normalizedPenSize.x; + } + pos = min(pos,vec2(1.0)-normalizedPenSize); + pos = max(pos,vec2(0.0)); + inputPixelColor.rg = pos; + } + else if (PASSINDEX == 1) { + if ((FRAMEINDEX==0)||(clearBuffer)) { + inputPixelColor = clearColor; + } + else { + vec4 posPixel = IMG_PIXEL(currentPosition,vec2(0.0)); + vec2 pos = posPixel.rg * RENDERSIZE; + vec2 penSizeInPixels = vec2(penSize * min(RENDERSIZE.x,RENDERSIZE.y)); + penSizeInPixels.x = (penSizeInPixels.x < 1.0) ? (1.0) : (penSizeInPixels.x); + penSizeInPixels.y = (penSizeInPixels.y < 1.0) ? (1.0) : (penSizeInPixels.y); + //pos.x = round(pos.x); + //pos.y = round(pos.y); + if (((gl_FragCoord.x) >= pos.x)&&((gl_FragCoord.y) >= pos.y)&&((gl_FragCoord.x) < pos.x + penSizeInPixels.x)&&((gl_FragCoord.y) < pos.y + penSizeInPixels.y)) + inputPixelColor = penColor; + else + inputPixelColor = IMG_THIS_PIXEL(lastState); + } + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Exposure Adjust.fs b/src/renderer/src/application/sample-modules/isf/Exposure Adjust.fs new file mode 100644 index 000000000..32133f09e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Exposure Adjust.fs @@ -0,0 +1,30 @@ +/*{ + "CREDIT": "by carter rosenberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "inputEV", + "TYPE": "float", + "MIN": -10.0, + "MAX": 10.0, + "DEFAULT": 0.5 + } + ] +}*/ + + + +void main() { + // based on + // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CoreImageFilterReference/Reference/reference.html#//apple_ref/doc/filter/ci/CIExposureAdjust + vec4 tmpColorA = IMG_THIS_PIXEL(inputImage); + tmpColorA.rgb = tmpColorA.rgb * pow(2.0, inputEV); + gl_FragColor = tmpColorA; +} diff --git a/src/renderer/src/application/sample-modules/isf/Fade Color.fs b/src/renderer/src/application/sample-modules/isf/Fade Color.fs new file mode 100644 index 000000000..ff264cea0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Fade Color.fs @@ -0,0 +1,69 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/fadecolor.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "colorPhase", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "color", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: gre +// License: MIT +vec4 transition (vec2 uv) { + return mix( + mix(color, getFromColor(uv), smoothstep(1.0-colorPhase, 0.0, progress)), + mix(color, getToColor(uv), smoothstep( colorPhase, 1.0, progress)), + progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Fade Gray Scale.fs b/src/renderer/src/application/sample-modules/isf/Fade Gray Scale.fs new file mode 100644 index 000000000..a0f67d1de --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Fade Gray Scale.fs @@ -0,0 +1,67 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/fadegrayscale.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.3, + "MAX": 1, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +vec3 grayscale (vec3 color) { + return vec3(0.2126*color.r + 0.7152*color.g + 0.0722*color.b); +} + +vec4 transition (vec2 uv) { + vec4 fc = getFromColor(uv); + vec4 tc = getToColor(uv); + return mix( + mix(vec4(grayscale(fc.rgb), 1.0), fc, smoothstep(1.0-intensity, 0.0, progress)), + mix(vec4(grayscale(tc.rgb), 1.0), tc, smoothstep( intensity, 1.0, progress)), + progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Fade.fs b/src/renderer/src/application/sample-modules/isf/Fade.fs new file mode 100644 index 000000000..a731bd92a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Fade.fs @@ -0,0 +1,55 @@ +/* +{ + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "MAX" : 1, + "TYPE" : "float", + "MIN" : 0, + "NAME" : "progress", + "DEFAULT" : 0 + } + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/fade.glsl", + "DESCRIPTION": "", + "CATEGORIES" : [ + "Dissolve" + ], + "ISFVSN" : "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: gre +// license: MIT + +vec4 transition (vec2 uv) { + return mix( + getFromColor(uv), + getToColor(uv), + progress + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/False Color.fs b/src/renderer/src/application/sample-modules/isf/False Color.fs new file mode 100644 index 000000000..ec23b2ac5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/False Color.fs @@ -0,0 +1,42 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "brightColor", + "TYPE": "color", + "DEFAULT": [ + 1.0, + 0.9, + 0.8, + 1.0 + ] + }, + { + "NAME": "darkColor", + "TYPE": "color", + "DEFAULT": [ + 0.3, + 0.0, + 0.0, + 1.0 + ] + } + ] +}*/ + +//const vec4 lumcoeff = vec4(0.299, 0.587, 0.114, 0.0); +const vec4 lumcoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + float luminance = dot(srcPixel,lumcoeff); + gl_FragColor = mix(vec4(darkColor.rgb, srcPixel.a), vec4(brightColor.rgb, srcPixel.a), luminance); +} diff --git a/src/renderer/src/application/sample-modules/isf/Fast Blur.fs b/src/renderer/src/application/sample-modules/isf/Fast Blur.fs new file mode 100644 index 000000000..d6a166dee --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Fast Blur.fs @@ -0,0 +1,122 @@ +/*{ + "CREDIT": "by zoidberg WOOP WOOP WOOP WOOP WOOP", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "blurAmount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 12.0, + "DEFAULT": 12.0 + } + ], + "PASSES": [ + { + "TARGET": "halfSizeBaseRender", + "WIDTH": "floor($WIDTH/2.0)", + "HEIGHT": "floor($HEIGHT/2.0)", + "DESCRIPTION": "Pass 0" + }, + { + "TARGET": "quarterSizeBaseRender", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 1" + }, + { + "TARGET": "eighthSizeBaseRender", + "WIDTH": "floor($WIDTH/8.0)", + "HEIGHT": "floor($HEIGHT/8.0)", + "DESCRIPTION": "Pass 2" + }, + { + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 3" + }, + { + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/4.0)", + "HEIGHT": "floor($HEIGHT/4.0)", + "DESCRIPTION": "Pass 4" + }, + { + "TARGET": "fullGaussA", + "DESCRIPTION": "Pass 5" + }, + { + "TARGET": "fullGaussB", + "DESCRIPTION": "Pass 6" + } + ] +}*/ + + +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +in vec2 texOffsets[5]; +#endif + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first three passes are just copying the input image into the buffer at varying sizes + if (PASSINDEX==0) { + gl_FragColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + } + else if (PASSINDEX==1) { + gl_FragColor = IMG_NORM_PIXEL(halfSizeBaseRender, isf_FragNormCoord); + } + else if (PASSINDEX==2) { + gl_FragColor = IMG_NORM_PIXEL(quarterSizeBaseRender, isf_FragNormCoord); + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 3) { + vec4 sample0 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + // ...writes into the full-size + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + else if (PASSINDEX == 6) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + if (blurLevel == 0) + gl_FragColor = mix(IMG_NORM_PIXEL(inputImage,isf_FragNormCoord), blurredImg, (blurLevelModulus/6.1)); + else + gl_FragColor = blurredImg; + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Fast Blur.vs b/src/renderer/src/application/sample-modules/isf/Fast Blur.vs new file mode 100644 index 000000000..3c9a24ee7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Fast Blur.vs @@ -0,0 +1,70 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +out vec2 texOffsets[5]; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first three passes are just drawing the texture- do nothing + + // the next four passes do gaussion blurs on the various levels + if (PASSINDEX==3 || PASSINDEX==5) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 3 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==3) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 5 is sampling 1/4 and writing to full-size + else if (PASSINDEX==5) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==3) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==4 || PASSINDEX==6) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 4 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==4) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 6 is sampling 1/4 and writing to full-size + else if (PASSINDEX==6) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/FastMosh.fs b/src/renderer/src/application/sample-modules/isf/FastMosh.fs new file mode 100644 index 000000000..f1f527f32 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/FastMosh.fs @@ -0,0 +1,160 @@ +/*{ + "CATEGORIES": [ + "Feedback", + "Glitch" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Does a fast faked data-mosh style", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "NAME": "update_keyframe", + "TYPE": "bool" + }, + { + "DEFAULT": 0.95, + "MAX": 1, + "MIN": 0.01, + "NAME": "update_rate", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "sharpen", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 2, + "MIN": 0, + "NAME": "blur", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "posterize", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "relative", + "absolute", + "difference" + ], + "NAME": "mode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "HEIGHT": "$HEIGHT/16.0", + "PERSISTENT": true, + "TARGET": "keyFrameBuffer1", + "WIDTH": "$WIDTH/16.0" + }, + { + "HEIGHT": "$HEIGHT", + "PERSISTENT": true, + "TARGET": "keyFrameBuffer2", + "WIDTH": "$WIDTH" + } + ] +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +void main() +{ + + // first pass: read the "inputImage"- remember, we're drawing to the persistent buffer "keyFrameBuffer1" on the first pass + // store it if we're taking in a new keyframe + if (PASSINDEX == 0) { + vec4 stalePixel = IMG_THIS_NORM_PIXEL(keyFrameBuffer1); + vec4 freshPixel = IMG_THIS_NORM_PIXEL(inputImage); + if (update_keyframe==true) { + gl_FragColor = freshPixel; + } + else { + gl_FragColor = stalePixel; + } + } + // second pass: read from "bufferVariableNameA". output looks chunky and low-res. + else if (PASSINDEX == 1) { + vec4 stalePixel1 = IMG_THIS_NORM_PIXEL(keyFrameBuffer1); + vec4 stalePixel2 = IMG_THIS_NORM_PIXEL(keyFrameBuffer2); + vec4 color = IMG_THIS_NORM_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = color; + vec4 diff = (color - stalePixel2); + if (blur > 0.0) { + float blurAmount = blur / 8.0; + diff = diff * (1.0 - blur) + (colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) * blurAmount; + } + + if (mode == 0) { + final = mix(stalePixel2,(diff + stalePixel1),update_rate); + } + else if (mode == 1) { + final = mix(stalePixel2,abs(diff) + stalePixel1,update_rate); + } + else if (mode == 2) { + final = mix(stalePixel2,abs(diff + stalePixel2) * stalePixel1,update_rate); + } + + if (posterize > 0.0) { + float qLevel = 128.0 - posterize * 126.0; + final = floor(final*qLevel)/qLevel; + } + + if (sharpen > 0.0) { + final = final + sharpen * (8.0*color - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + } + + gl_FragColor = final; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/FastMosh.vs b/src/renderer/src/application/sample-modules/isf/FastMosh.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/FastMosh.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Film Burn.fs b/src/renderer/src/application/sample-modules/isf/Film Burn.fs new file mode 100644 index 000000000..c3693ca1a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Film Burn.fs @@ -0,0 +1,109 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/FilmBurn.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 2.31, + "MAX": 10, + "MIN": 0, + "NAME": "Seed", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: Anastasia Dunbar +// license: MIT +float sigmoid(float x, float a) { + float b = pow(x*2.,a)/2.; + if (x > .5) { + b = 1.-pow(2.-(x*2.),a)/2.; + } + return b; +} +float rand(float co){ + return fract(sin((co*24.9898)+Seed)*43758.5453); +} +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} +float apow(float a,float b) { return pow(abs(a),b)*sign(b); } +vec3 pow3(vec3 a,vec3 b) { return vec3(apow(a.r,b.r),apow(a.g,b.g),apow(a.b,b.b)); } +float smooth_mix(float a,float b,float c) { return mix(a,b,sigmoid(c,2.)); } +float random(vec2 co, float shft){ + co += 10.; + return smooth_mix(fract(sin(dot(co.xy ,vec2(12.9898+(floor(shft)*.5),78.233+Seed))) * 43758.5453),fract(sin(dot(co.xy ,vec2(12.9898+(floor(shft+1.)*.5),78.233+Seed))) * 43758.5453),fract(shft)); +} +float smooth_random(vec2 co, float shft) { + return smooth_mix(smooth_mix(random(floor(co),shft),random(floor(co+vec2(1.,0.)),shft),fract(co.x)),smooth_mix(random(floor(co+vec2(0.,1.)),shft),random(floor(co+vec2(1.,1.)),shft),fract(co.x)),fract(co.y)); +} +vec4 texture(vec2 p) { + return mix(getFromColor(p), getToColor(p), sigmoid(progress,10.)); +} +#define pi 3.14159265358979323 +#define clamps(x) clamp(x,0.,1.) + +vec4 transition(vec2 p) { + vec3 f = vec3(0.); + for (float i = 0.; i < 13.; i++) { + f += sin(((p.x*rand(i)*6.)+(progress*8.))+rand(i+1.43))*sin(((p.y*rand(i+4.4)*6.)+(progress*6.))+rand(i+2.4)); + f += 1.-clamps(length(p-vec2(smooth_random(vec2(progress*1.3),i+1.),smooth_random(vec2(progress*.5),i+6.25)))*mix(20.,70.,rand(i))); + } + f += 4.; + f /= 11.; + f = pow3(f*vec3(1.,0.7,0.6),vec3(1.,2.-sin(progress*pi),1.3)); + f *= sin(progress*pi); + + p -= .5; + p *= 1.+(smooth_random(vec2(progress*5.),6.3)*sin(progress*pi)*.05); + p += .5; + + vec4 blurred_image = vec4(0.); + float bluramount = sin(progress*pi)*.03; + #define repeats 50. + for (float i = 0.; i < repeats; i++) { + vec2 q = vec2(cos(degrees((i/repeats)*360.)),sin(degrees((i/repeats)*360.))) * (rand(vec2(i,p.x+p.y))+bluramount); + vec2 uv2 = p+(q*bluramount); + blurred_image += texture(uv2); + } + blurred_image /= repeats; + + return blurred_image+vec4(f,0.); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Flip H.fs b/src/renderer/src/application/sample-modules/isf/Flip H.fs new file mode 100644 index 000000000..ca74d68b4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Flip H.fs @@ -0,0 +1,24 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ] +}*/ + +void main() { + vec2 normSrcCoord; + + normSrcCoord.x = isf_FragNormCoord[0]; + normSrcCoord.y = isf_FragNormCoord[1]; + + normSrcCoord.x = (1.0-normSrcCoord.x); + + gl_FragColor = IMG_NORM_PIXEL(inputImage, normSrcCoord); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Flip V.fs b/src/renderer/src/application/sample-modules/isf/Flip V.fs new file mode 100644 index 000000000..95ba34ed0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Flip V.fs @@ -0,0 +1,24 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ] +}*/ + +void main() { + vec2 normSrcCoord; + + normSrcCoord.x = isf_FragNormCoord[0]; + normSrcCoord.y = isf_FragNormCoord[1]; + + normSrcCoord.y = (1.0-normSrcCoord.y); + + gl_FragColor = IMG_NORM_PIXEL(inputImage, normSrcCoord); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Flipbook.fs b/src/renderer/src/application/sample-modules/isf/Flipbook.fs new file mode 100644 index 000000000..c3620f553 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Flipbook.fs @@ -0,0 +1,142 @@ +/* +{ + "CATEGORIES" : [ + "Stylize" + ], + "DESCRIPTION" : "Creates a flipbook like effect", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "flipRate", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.1, + "MIN" : 0 + }, + { + "NAME" : "stretchMode", + "TYPE" : "bool", + "DEFAULT" : 1 + }, + { + "LABELS" : [ + "Left", + "Right", + "Up", + "Down" + ], + "NAME" : "flipDirection", + "TYPE" : "long", + "DEFAULT" : 3, + "VALUES" : [ + 0, + 1, + 2, + 3 + ] + }, + { + "NAME" : "holdTime", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + } + ], + "PASSES" : [ + { + "FLOAT" : true, + "WIDTH" : "1", + "HEIGHT" : "1", + "TARGET" : "writePos", + "PERSISTENT" : true + }, + { + "TARGET" : "frameGrab", + "PERSISTENT" : true + }, + { + "TARGET" : "lastOutput", + "PERSISTENT" : true + } + ], + "CREDIT" : "VIDVOX" +} +*/ + +void main() { + vec4 inputPixelColor = vec4(0.0); + + if (PASSINDEX == 0) { + inputPixelColor = (FRAMEINDEX <= 1) ? vec4(0.0) : IMG_PIXEL(writePos, vec2(0.0)); + float oldG = inputPixelColor.g; + inputPixelColor.g = inputPixelColor.g + flipRate; + inputPixelColor.b = 1.0; + inputPixelColor.a = 1.0; + if (inputPixelColor.g >= 1.0 + flipRate) { + //inputPixelColor.g = 0.0; + if (inputPixelColor.r >= holdTime) { + inputPixelColor.b = 0.0; + inputPixelColor.g = (holdTime > 0.0) ? 0.0 : mod(oldG,1.0); + } + else { + inputPixelColor.r = inputPixelColor.r + TIMEDELTA; + inputPixelColor.g = oldG; + } + } + else { + inputPixelColor.r = 0.0; + } + } + else if (PASSINDEX == 1) { + vec4 tmpColor = (FRAMEINDEX <= 1) ? vec4(0.0) : IMG_PIXEL(writePos, vec2(0.0)); + // update the image buffer + if (tmpColor.b == 0.0) { + inputPixelColor = IMG_THIS_PIXEL(inputImage); + } + // + else { + inputPixelColor = IMG_THIS_PIXEL(frameGrab); + } + } + else if (PASSINDEX == 2) { + if (FRAMEINDEX <= 1) { + inputPixelColor = IMG_THIS_PIXEL(frameGrab); + } + else { + vec4 tmpColor = (FRAMEINDEX <= 1) ? vec4(0.0) : IMG_PIXEL(writePos, vec2(0.0)); + vec2 loc = isf_FragNormCoord; + float grabTime = (tmpColor.g > 1.0) ? 1.0 : tmpColor.g; + float transTime = 0.0; + if (flipDirection == 0) { + transTime = loc.x; + loc.x = (stretchMode) ? 1.0 - (1.0 - loc.x) / (grabTime) : loc.x - (1.0 - grabTime); + } + else if (flipDirection == 1) { + transTime = 1.0 - loc.x; + loc.x = (stretchMode) ? loc.x / (grabTime) : loc.x + (1.0 - grabTime); + } + else if (flipDirection == 2) { + transTime = 1.0 - loc.y; + loc.y = (stretchMode) ? loc.y / (grabTime) : loc.y + (1.0 - grabTime); + } + else if (flipDirection == 3) { + transTime = loc.y; + loc.y = (stretchMode) ? 1.0 - (1.0 - loc.y) / (grabTime) : loc.y - (1.0 - grabTime); + } + + if (transTime <= 1.0 - grabTime) { + inputPixelColor = IMG_THIS_PIXEL(lastOutput); + } + else { + inputPixelColor = IMG_NORM_PIXEL(frameGrab, loc); + } + } + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Fly Eye.fs b/src/renderer/src/application/sample-modules/isf/Fly Eye.fs new file mode 100644 index 000000000..8a94086c2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Fly Eye.fs @@ -0,0 +1,79 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/flyeye.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.04, + "MAX": 1, + "MIN": 0, + "NAME": "size", + "TYPE": "float" + }, + { + "DEFAULT": 0.3, + "MAX": 1, + "MIN": 0, + "NAME": "colorSeparation", + "TYPE": "float" + }, + { + "DEFAULT": 50, + "MAX": 100, + "MIN": 0, + "NAME": "zoom", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + +vec4 transition(vec2 p) { + float inv = 1. - progress; + vec2 disp = size*vec2(cos(zoom*p.x), sin(zoom*p.y)); + vec4 texTo = getToColor(p + inv*disp); + vec4 texFrom = vec4( + getFromColor(p + progress*disp*(1.0 - colorSeparation)).r, + getFromColor(p + progress*disp).g, + getFromColor(p + progress*disp*(1.0 + colorSeparation)).b, + 1.0); + return texTo*progress + texFrom*inv; +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/FractilianParabolicCircleInversion.fs b/src/renderer/src/application/sample-modules/isf/FractilianParabolicCircleInversion.fs new file mode 100644 index 000000000..36693cc9e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/FractilianParabolicCircleInversion.fs @@ -0,0 +1,176 @@ +/*{ + "CREDIT": "by mojovideotech", + "DESCRIPTION": "", + "CATEGORIES": [ + "generator" + ], + "INPUTS": [ + { + "NAME" : "center", + "TYPE" : "point2D", + "DEFAULT" : [ 0.0, 0.0 ], + "MAX" : [ 1.0, 1.0 ], + "MIN" : [ -1.0, -1.0 ] + }, + { + "NAME" : "scale", + "TYPE" : "float", + "DEFAULT" : 2.0, + "MIN" : 0.5, + "MAX" : 3.0 + }, + { + "NAME" : "rate", + "TYPE" : "float", + "DEFAULT" : 0.15, + "MIN" : -0.5, + "MAX" : 0.5 + }, + { + "NAME" : "fine", + "TYPE" : "float", + "DEFAULT" : 0.35, + "MIN" : 0.01, + "MAX" : 0.5 + }, + { + "NAME" : "loops", + "TYPE" : "float", + "DEFAULT" : 32.0, + "MIN" : 12.0, + "MAX" : 100.0 + }, + { + "NAME" : "r1", + "TYPE" : "float", + "DEFAULT" : -0.25, + "MIN" : -0.5, + "MAX" : 0.5 + }, + { + "NAME" : "r2", + "TYPE" : "float", + "DEFAULT" : 0.275, + "MIN" : -0.5, + "MAX" : 0.5 + }, + { + "NAME" : "brightness", + "TYPE" : "float", + "DEFAULT" : 1.5, + "MIN" : 0.1, + "MAX" : 5.0 + }, + { + "NAME" : "bleed", + "TYPE" : "float", + "DEFAULT" : 0.75, + "MIN" : 0.01, + "MAX" : 0.99 + }, + { + "NAME" : "edge", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : 0.01, + "MAX" : 0.99 + }, + { + "NAME": "color", + "TYPE": "color", + "DEFAULT": [ 0.6, + 0.4, + 0.9, + 1.0 + ] + }, + { + "NAME" : "flipH", + "TYPE" : "bool", + "DEFAULT" : false + }, + { + "NAME" : "flipV", + "TYPE" : "bool", + "DEFAULT" : false + }, + { + "NAME" : "mirrorH", + "TYPE" : "bool", + "DEFAULT" : true + }, + { + "NAME" : "mirrorV", + "TYPE" : "bool", + "DEFAULT" : false + }, + { + "NAME" : "blend", + "TYPE" : "bool", + "DEFAULT" : false + }, + { + "NAME" : "invert", + "TYPE" : "bool", + "DEFAULT" : false + } + + + ] +}*/ + + + +//////////////////////////////////////////////////////////// +// FractilianParabolicCircleInversion by mojovideotech +// +// based on : +// Fractal Soup by @P_Malin +// shadertoy.com/\lsB3zR +// +// License: +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +//////////////////////////////////////////////////////////// + + +vec2 CircleInversion(vec2 vPos, vec2 vOrigin, float fRadius) { + vec2 vOP = vPos - vOrigin; + return vOrigin - vOP * fRadius * fRadius / dot(vOP, vOP); +} + +float Parabola( float x, float n ) { return pow( 4.0*x*(1.0-x), n ); } + +void main() +{ + vec2 pos = gl_FragCoord.xy/RENDERSIZE.xy; + if (mirrorV) { if (pos.y < 0.5) pos.y = 1.0-pos.y; } + if (mirrorH) { if (pos.x < 0.5) pos.x = 1.0-pos.x; } + pos.x *= RENDERSIZE.x / RENDERSIZE.y; + if (flipH) { pos.x = 1.0 - pos.x; } + if (flipV) { pos.y = 1.0 - pos.y; } + float T = TIME * rate + fine; + float TT = T * 0.05; + float drift = mix(pos.x+sin(TT),sin(pos.x)*cos(pos.x-TT),sin(pos.y-TT)); + vec2 spin = vec2( sin(T*r1), -sin(T*r2))+center; + float l = 0.0, b = 0.0, m = 10000.0; + vec2 p = pos.xy; + for(int i=0; i<100; i++) { + p.x = abs(p.x); + p = p * scale + spin; + p = CircleInversion(p, vec2(0.5, 0.5), 1.0); + l = length(p); + m = min(l, m); + m += Parabola(m, drift); + b += 1.0; + if (b>loops) break; +} + + vec3 col = color.rgb * l * l * brightness; + col = mix(col,pow(col,vec3(m)),1.0-edge); + col = mix(col,col*fract(m*col+m),bleed); + if (blend) { col = 1.0 - exp(-col); } + if (invert) { gl_FragColor = vec4(1.0-col,1.0); } + else + gl_FragColor = vec4(col,1.0); +} + diff --git a/src/renderer/src/application/sample-modules/isf/Freeze Frame.fs b/src/renderer/src/application/sample-modules/isf/Freeze Frame.fs new file mode 100644 index 000000000..6906df986 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Freeze Frame.fs @@ -0,0 +1,38 @@ +/*{ + "DESCRIPTION": "Holds the output on the captured freeze frame", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Utility" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "freeze", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET":"freezeBuffer", + "PERSISTENT": true + } + ] + +}*/ + +// Essentially the same as a feedback buffer where you can only set it to a mix of 1.0 + +void main() +{ + if (freeze) { + gl_FragColor = IMG_THIS_PIXEL(freezeBuffer); + } + else { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Frosted Glass.fs b/src/renderer/src/application/sample-modules/isf/Frosted Glass.fs new file mode 100644 index 000000000..6c15b87c4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Frosted Glass.fs @@ -0,0 +1,131 @@ +/*{ + "CATEGORIES": [ + "Stylize", + "Blur" + ], + "CREDIT": "geeks3d", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.01, + "LABEL": "Magnitude", + "MAX": 0.1, + "MIN": 0, + "NAME": "magnitude", + "TYPE": "float" + }, + { + "DEFAULT": 0.345, + "LABEL": "Seed", + "MAX": 1, + "MIN": 0, + "NAME": "seed", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +/* + + adapted from http://www.geeks3d.com/20101228/shader-library-frosted-glass-post-processing-shader-glsl/ + +*/ + + +const float vx_offset = 0.5; + +vec4 spline(float x, vec4 c1, vec4 c2, vec4 c3, vec4 c4, vec4 c5, vec4 c6, vec4 c7, vec4 c8, vec4 c9) +{ + float w1, w2, w3, w4, w5, w6, w7, w8, w9; + w1 = 0.0; + w2 = 0.0; + w3 = 0.0; + w4 = 0.0; + w5 = 0.0; + w6 = 0.0; + w7 = 0.0; + w8 = 0.0; + w9 = 0.0; + float tmp = x * 8.0; + if (tmp<=1.0) { + w1 = 1.0 - tmp; + w2 = tmp; + } + else if (tmp<=2.0) { + tmp = tmp - 1.0; + w2 = 1.0 - tmp; + w3 = tmp; + } + else if (tmp<=3.0) { + tmp = tmp - 2.0; + w3 = 1.0-tmp; + w4 = tmp; + } + else if (tmp<=4.0) { + tmp = tmp - 3.0; + w4 = 1.0-tmp; + w5 = tmp; + } + else if (tmp<=5.0) { + tmp = tmp - 4.0; + w5 = 1.0-tmp; + w6 = tmp; + } + else if (tmp<=6.0) { + tmp = tmp - 5.0; + w6 = 1.0-tmp; + w7 = tmp; + } + else if (tmp<=7.0) { + tmp = tmp - 6.0; + w7 = 1.0 - tmp; + w8 = tmp; + } + else { + //tmp = saturate(tmp - 7.0); + // http://www.ozone3d.net/blogs/lab/20080709/saturate-function-in-glsl/ + tmp = clamp(tmp - 7.0, 0.0, 1.0); + w8 = 1.0-tmp; + w9 = tmp; + } + return w1*c1 + w2*c2 + w3*c3 + w4*c4 + w5*c5 + w6*c6 + w7*c7 + w8*c8 + w9*c9; +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + vec2 uv = isf_FragNormCoord.xy; + vec4 tc = vec4(1.0, 0.0, 0.0, 0.0); + + float DeltaX = magnitude / 2.0; + float DeltaY = magnitude / 2.0; + vec2 ox = vec2(DeltaX,0.0); + vec2 oy = vec2(0.0,DeltaY); + vec2 PP = uv - oy; + vec4 C00 = IMG_NORM_PIXEL(inputImage,PP - ox); + vec4 C01 = IMG_NORM_PIXEL(inputImage,PP); + vec4 C02 = IMG_NORM_PIXEL(inputImage,PP + ox); + PP = uv; + vec4 C10 = IMG_NORM_PIXEL(inputImage,PP - ox); + vec4 C11 = IMG_NORM_PIXEL(inputImage,PP); + vec4 C12 = IMG_NORM_PIXEL(inputImage,PP + ox); + PP = uv + oy; + vec4 C20 = IMG_NORM_PIXEL(inputImage,PP - ox); + vec4 C21 = IMG_NORM_PIXEL(inputImage,PP); + vec4 C22 = IMG_NORM_PIXEL(inputImage,PP + ox); + + float n = rand(seed*uv); + n = mod(n, 0.111111)/0.111111; + vec4 result = spline(n,C00,C01,C02,C10,C11,C12,C20,C21,C22); + tc = result.rgba; + + gl_FragColor = tc; +} diff --git a/src/renderer/src/application/sample-modules/isf/Gamma Correction.fs b/src/renderer/src/application/sample-modules/isf/Gamma Correction.fs new file mode 100644 index 000000000..4014945de --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Gamma Correction.fs @@ -0,0 +1,33 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment", "Utility" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "gamma", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + } + ] +}*/ + + + +void main() { + // the input gamma range is 0.0-1.0 (normalized). the actual gamma range i want to use is 0.0 - 5.0. + // however, actual gamma 0.0-1.0 is just as interesting as actual gamma 1.0-5.0, so we scale the normalized input to match... + float realGamma = (gamma<=0.5) ? (gamma * 2.0) : (((gamma-0.5) * 2.0 * 4.0) + 1.0); + vec4 tmpColorA = IMG_THIS_PIXEL(inputImage); + vec4 tmpColorB; + tmpColorB.rgb = pow(tmpColorA.rgb, vec3(1.0/realGamma)); + tmpColorB.a = tmpColorA.a; + gl_FragColor = tmpColorB; +} diff --git a/src/renderer/src/application/sample-modules/isf/Ghosting.fs b/src/renderer/src/application/sample-modules/isf/Ghosting.fs new file mode 100644 index 000000000..d714ed85b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Ghosting.fs @@ -0,0 +1,130 @@ +/*{ + "CATEGORIES": [ + "Stylize", + "Feedback", + "Color Effect" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": -0.5, + "LABEL": "Bias", + "MAX": 0, + "MIN": -1, + "NAME": "uBias", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "LABEL": "Scale", + "MAX": 2, + "MIN": 0, + "NAME": "uScale", + "TYPE": "float" + }, + { + "DEFAULT": 5, + "LABEL": "Ghosts", + "MAX": 5, + "MIN": 0, + "NAME": "uGhosts", + "TYPE": "float" + }, + { + "DEFAULT": 0.0125, + "LABEL": "Ghost Dispersal", + "MAX": 0.1, + "MIN": 0, + "NAME": "uGhostDispersal", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Additive Mode", + "NAME": "uAdditive", + "TYPE": "bool" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "LABEL": "Direction", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "uDirection", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0.9, + 0.8, + 0.7, + 1 + ], + "LABEL": "Lens Color", + "NAME": "uLensColor", + "TYPE": "color" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "DESCRIPTION": "Downsample and threshold", + "HEIGHT": "floor($HEIGHT/1.0)", + "TARGET": "downsampleAndThresholdImage", + "WIDTH": "floor($WIDTH/1.0)" + }, + { + } + ] +} +*/ + + + + +void main() +{ + + if (PASSINDEX == 0) { + vec2 loc = isf_FragNormCoord; + gl_FragColor = max(vec4(0.0), IMG_NORM_PIXEL(inputImage,loc) + uBias) * uScale; + } + else if (PASSINDEX == 1) { + vec2 texcoord = isf_FragNormCoord; + vec2 texelSize = 1.0 / RENDERSIZE; + vec2 direction = vec2(1.0) - uDirection; + vec2 ghostVec = (direction - texcoord) * uGhostDispersal; + //vec2 direction = vec2(0.5,0.5); + vec4 result = vec4(0.0); + for (int i = 0; i < 5; ++i) { + if (float(i)>uGhosts) + break; + vec2 offset = fract(texcoord + ghostVec * float(i)); + + result += IMG_NORM_PIXEL(downsampleAndThresholdImage, offset) * uLensColor; + } + // apply the alpha + result.rgb = result.rgb * uLensColor.a; + if (uAdditive) { + result = result + IMG_NORM_PIXEL(inputImage, texcoord); + } + else { + result = result * IMG_NORM_PIXEL(inputImage, texcoord); + } + gl_FragColor = result; + } + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Glitch Displace.fs b/src/renderer/src/application/sample-modules/isf/Glitch Displace.fs new file mode 100644 index 000000000..1db064e2b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glitch Displace.fs @@ -0,0 +1,120 @@ +/* +{ + "CATEGORIES" : [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/GlitchDisplace.glsl", + "DESCRIPTION": "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "TYPE" : "image", + "NAME" : "endImage" + }, + { + "DEFAULT" : 0, + "MAX" : 1, + "NAME" : "progress", + "MIN" : 0, + "TYPE" : "float" + } + ] +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Matt DesLauriers +// License: MIT + +highp float random(vec2 co) +{ + highp float a = 12.9898; + highp float b = 78.233; + highp float c = 43758.5453; + highp float dt= dot(co.xy ,vec2(a,b)); + highp float sn= mod(dt,3.14); + return fract(sin(sn) * c); +} +float voronoi( in vec2 x ) { + vec2 p = floor( x ); + vec2 f = fract( x ); + float res = 8.0; + for( float j=-1.; j<=1.; j++ ) + for( float i=-1.; i<=1.; i++ ) { + vec2 b = vec2( i, j ); + vec2 r = b - f + random( p + b ); + float d = dot( r, r ); + res = min( res, d ); + } + return sqrt( res ); +} + +vec2 displace(vec4 tex, vec2 texCoord, float dotDepth, float textureDepth, float strength) { + float b = voronoi(.003 * texCoord + 2.0); + float g = voronoi(0.2 * texCoord); + float r = voronoi(texCoord - 1.0); + vec4 dt = tex * 1.0; + vec4 dis = dt * dotDepth + 1.0 - tex * textureDepth; + + dis.x = dis.x - 1.0 + textureDepth*dotDepth; + dis.y = dis.y - 1.0 + textureDepth*dotDepth; + dis.x *= strength; + dis.y *= strength; + vec2 res_uv = texCoord ; + res_uv.x = res_uv.x + dis.x - 0.0; + res_uv.y = res_uv.y + dis.y; + return res_uv; +} + +float ease1(float t) { + return t == 0.0 || t == 1.0 + ? t + : t < 0.5 + ? +0.5 * pow(2.0, (20.0 * t) - 10.0) + : -0.5 * pow(2.0, 10.0 - (t * 20.0)) + 1.0; +} +float ease2(float t) { + return t == 1.0 ? t : 1.0 - pow(2.0, -10.0 * t); +} + + + +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy; + vec4 color1 = getFromColor(p); + vec4 color2 = getToColor(p); + vec2 disp = displace(color1, p, 0.33, 0.7, 1.0-ease1(progress)); + vec2 disp2 = displace(color2, p, 0.33, 0.5, ease2(progress)); + vec4 dColor1 = getToColor(disp); + vec4 dColor2 = getFromColor(disp2); + float val = ease1(progress); + vec3 gray = vec3(dot(min(dColor2, dColor1).rgb, vec3(0.299, 0.587, 0.114))); + dColor2 = vec4(gray, 1.0); + dColor2 *= 2.0; + color1 = mix(color1, dColor2, smoothstep(0.0, 0.5, progress)); + color2 = mix(color2, dColor1, smoothstep(1.0, 0.5, progress)); + return mix(color1, color2, val); + //gl_FragColor = mix(gl_FragColor, dColor, smoothstep(0.0, 0.5, progress)); + + //gl_FragColor = mix(texture2D(from, p), texture2D(to, p), progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Glitch Memories.fs b/src/renderer/src/application/sample-modules/isf/Glitch Memories.fs new file mode 100644 index 000000000..39b851cbf --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glitch Memories.fs @@ -0,0 +1,60 @@ +/*{ + "CATEGORIES": [ + "Distortion", + "Glitch" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/GlitchMemories.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: Gunnar Roth +// based on work from natewave +// license: MIT +vec4 transition(vec2 p) { + vec2 block = floor(p.xy / vec2(16)); + vec2 uv_noise = block / vec2(64); + uv_noise += floor(vec2(progress) * vec2(1200.0, 3500.0)) / vec2(64); + vec2 dist = progress > 0.0 ? (fract(uv_noise) - 0.5) * 0.3 *(1.0 -progress) : vec2(0.0); + vec2 red = p + dist * 0.2; + vec2 green = p + dist * .3; + vec2 blue = p + dist * .5; + + return vec4(mix(getFromColor(red), getToColor(red), progress).r,mix(getFromColor(green), getToColor(green), progress).g,mix(getFromColor(blue), getToColor(blue), progress).b,1.0); +} + + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Glitch Shifter.fs b/src/renderer/src/application/sample-modules/isf/Glitch Shifter.fs new file mode 100644 index 000000000..f6ced9cbc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glitch Shifter.fs @@ -0,0 +1,155 @@ +/*{ + "CATEGORIES": [ + "Glitch" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.1, + "LABEL": "Size", + "MAX": 0.5, + "MIN": 0, + "NAME": "glitch_size", + "TYPE": "float" + }, + { + "DEFAULT": 0.2, + "LABEL": "Horizontal Amount", + "MAX": 1, + "MIN": 0, + "NAME": "glitch_horizontal", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Vertical Amount", + "MAX": 1, + "MIN": 0, + "NAME": "glitch_vertical", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Randomize Size", + "NAME": "randomize_size", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "LABEL": "Randomize Zoom", + "NAME": "randomize_zoom", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "LABEL": "Use Alt Image", + "NAME": "use_alt_image", + "TYPE": "bool" + }, + { + "NAME": "altImage", + "TYPE": "image" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "LABEL": "Offset", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "offset", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() +{ + vec2 xy; + vec4 returnMe; + bool shifted = false; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + // quantize the xy to the glitch_amount size + //xy = floor(xy / glitch_size) * glitch_size; + vec2 random; + + float local_glitch_size = glitch_size; + float random_offset = 0.0; + + if (randomize_size) { + random_offset = mod(rand(vec2(TIME,TIME)), 1.0); + local_glitch_size = random_offset * glitch_size; + } + + if (local_glitch_size > 0.0) { + random.x = rand(vec2(floor(random_offset + xy.y / local_glitch_size) * local_glitch_size, TIME)); + random.y = rand(vec2(floor(random_offset + xy.x / local_glitch_size) * local_glitch_size, TIME)); + } + else { + random.x = rand(vec2(xy.x, TIME)); + random.y = rand(vec2(xy.y, TIME)); + } + + if (randomize_zoom) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + float level = rand(vec2(random.x, random.y)) / 5.0 + 0.90; + xy = (xy - vec2(0.5))*(1.0/level) + vec2(0.5); + } + else if (random.x < glitch_horizontal) { + float level = (random.x) + 0.98; + xy = (xy - vec2(0.5))*(1.0/level) + vec2(0.5); + } + else if (random.y < glitch_vertical) { + float level = (random.y) + 0.98; + xy = (xy - vec2(0.5))*(1.0/level) + vec2(0.5); + } + } + + // if doing a horizontal glitch do a random shift + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + vec2 shift = (offset - 0.5); + shift = shift * rand(shift + random); + xy.x = mod(xy.x + random.x, 1.0); + xy.y = mod(xy.y + random.y, 1.0); + xy = xy + shift; + shifted = true; + } + else if (random.x < glitch_horizontal) { + vec2 shift = (offset - 0.5); + shift = shift * rand(shift + random); + xy = mod(xy + vec2(0.0, random.x) + shift, 1.0); + shifted = true; + } + else if (random.y < glitch_vertical) { + vec2 shift = (offset - 0.5); + shift = shift * rand(shift + random); + xy = mod(xy + vec2(random.y, 0.0) + shift, 1.0); + shifted = true; + } + + if ((shifted) && (use_alt_image) && (rand(vec2(TIME, xy.x)) < 0.5)) + returnMe = IMG_NORM_PIXEL(altImage, xy); + else + returnMe = IMG_NORM_PIXEL(inputImage, xy); + + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Gloom.fs b/src/renderer/src/application/sample-modules/isf/Gloom.fs new file mode 100644 index 000000000..7230667af --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Gloom.fs @@ -0,0 +1,131 @@ +/*{ + "CATEGORIES": [ + "Blur", + "Film" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 12, + "MAX": 12, + "MIN": 0, + "NAME": "blurAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "DESCRIPTION": "Pass 0", + "HEIGHT": "floor($HEIGHT/2.0)", + "TARGET": "halfSizeBaseRender", + "WIDTH": "floor($WIDTH/2.0)" + }, + { + "DESCRIPTION": "Pass 1", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterSizeBaseRender", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 2", + "HEIGHT": "floor($HEIGHT/8.0)", + "TARGET": "eighthSizeBaseRender", + "WIDTH": "floor($WIDTH/8.0)" + }, + { + "DESCRIPTION": "Pass 3", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 4", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 5", + "TARGET": "fullGaussA" + }, + { + "DESCRIPTION": "Pass 6", + "TARGET": "fullGaussB" + } + ] +} +*/ + + +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +in vec2 texOffsets[5]; +#endif + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first three passes are just copying the input image into the buffer at varying sizes + if (PASSINDEX==0) { + gl_FragColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + } + else if (PASSINDEX==1) { + gl_FragColor = IMG_NORM_PIXEL(halfSizeBaseRender, isf_FragNormCoord); + } + else if (PASSINDEX==2) { + gl_FragColor = IMG_NORM_PIXEL(quarterSizeBaseRender, isf_FragNormCoord); + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 3) { + vec4 sample0 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + } + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + } + // ...writes into the full-size + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + } + else if (PASSINDEX == 6) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + vec4 originalImg = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord); + if (blurLevel == 0) + blurredImg = mix(originalImg, blurredImg, (blurLevelModulus/6.0)); + + gl_FragColor = min(mix(originalImg,blurredImg*0.9,intensity), originalImg); + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Gloom.vs b/src/renderer/src/application/sample-modules/isf/Gloom.vs new file mode 100644 index 000000000..3c9a24ee7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Gloom.vs @@ -0,0 +1,70 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +out vec2 texOffsets[5]; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first three passes are just drawing the texture- do nothing + + // the next four passes do gaussion blurs on the various levels + if (PASSINDEX==3 || PASSINDEX==5) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 3 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==3) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 5 is sampling 1/4 and writing to full-size + else if (PASSINDEX==5) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==3) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==4 || PASSINDEX==6) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 4 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==4) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 6 is sampling 1/4 and writing to full-size + else if (PASSINDEX==6) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Glow-Fast.fs b/src/renderer/src/application/sample-modules/isf/Glow-Fast.fs new file mode 100644 index 000000000..bac632088 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glow-Fast.fs @@ -0,0 +1,154 @@ +/*{ + "CATEGORIES": [ + "Stylize" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + }, + { + "DEFAULT": 12, + "MAX": 12, + "MIN": 0, + "NAME": "blurAmount", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "DESCRIPTION": "Pass 0", + "HEIGHT": "floor($HEIGHT/2.0)", + "TARGET": "halfSizeBaseRender", + "WIDTH": "floor($WIDTH/2.0)" + }, + { + "DESCRIPTION": "Pass 1", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterSizeBaseRender", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 2", + "HEIGHT": "floor($HEIGHT/8.0)", + "TARGET": "eighthSizeBaseRender", + "WIDTH": "floor($WIDTH/8.0)" + }, + { + "DESCRIPTION": "Pass 3", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 4", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + "DESCRIPTION": "Pass 5", + "TARGET": "fullGaussA" + }, + { + "DESCRIPTION": "Pass 6", + "TARGET": "fullGaussB" + } + ] +} +*/ + + +// original blur implementation as v002.blur in QC by anton marini and tom butterworth, ported by zoidberg + +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +in vec2 texOffsets[5]; +#endif + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first three passes are just copying the input image into the buffer at varying sizes + if (PASSINDEX==0) { + gl_FragColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + } + else if (PASSINDEX==1) { + gl_FragColor = IMG_NORM_PIXEL(halfSizeBaseRender, isf_FragNormCoord); + } + else if (PASSINDEX==2) { + gl_FragColor = IMG_NORM_PIXEL(quarterSizeBaseRender, isf_FragNormCoord); + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 3) { + vec4 sample0 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + //gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + vec4 final = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + final = mix(sample0, final, intensity); + gl_FragColor = final; + } + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + //gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgb / (5.0), 1.0); + vec4 final = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + final = mix(sample0, final, intensity); + gl_FragColor = final; + } + // ...writes into the full-size + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + vec4 final = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + final = mix(sample0, final, intensity); + gl_FragColor = final; + } + else if (PASSINDEX == 6) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + vec4 final = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + vec4 original = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord); + final = mix(original, min((final*original),1.0), intensity); + // First do a gloom, + // then a bright / contrast tweak based on the amount + // then a bloom + vec4 tmpColorA = final; + float bright = intensity*0.222; + vec4 tmpColorB = tmpColorA + vec4(bright,bright,bright,bright); + // contrast + float contrast = 1.0 + intensity * (2.893); + tmpColorA.rgba = ((vec4(2.0) * (tmpColorB.rgba - vec4(0.5))) * vec4(contrast) / vec4(2.0)) + vec4(0.5); + final = min((tmpColorA + tmpColorA * original), 1.0); + gl_FragColor = final; + //if (blurLevel == 0) + // gl_FragColor = mix(IMG_NORM_PIXEL(inputImage,isf_FragNormCoord), blurredImg, (blurLevelModulus/6.1)); + //else + // gl_FragColor = blurredImg; + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Glow-Fast.vs b/src/renderer/src/application/sample-modules/isf/Glow-Fast.vs new file mode 100644 index 000000000..3c9a24ee7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glow-Fast.vs @@ -0,0 +1,70 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +out vec2 texOffsets[5]; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first three passes are just drawing the texture- do nothing + + // the next four passes do gaussion blurs on the various levels + if (PASSINDEX==3 || PASSINDEX==5) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 3 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==3) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 5 is sampling 1/4 and writing to full-size + else if (PASSINDEX==5) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==3) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==4 || PASSINDEX==6) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 4 is sampling 1/12, but writing to 1/4 + if (PASSINDEX==4) { + if (blurLevel==1) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>1) + blurRadius = 4.0; + } + // pass 6 is sampling 1/4 and writing to full-size + else if (PASSINDEX==6) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Glow.fs b/src/renderer/src/application/sample-modules/isf/Glow.fs new file mode 100644 index 000000000..272455bf5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glow.fs @@ -0,0 +1,158 @@ +/*{ + "CATEGORIES": [ + "Stylize" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "HEIGHT": "floor($HEIGHT*min((0.2),1.0))", + "TARGET": "smallA", + "WIDTH": "floor($WIDTH*min((0.2),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.2),1.0))", + "TARGET": "smallB", + "WIDTH": "floor($WIDTH*min((0.2),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.3),1.0))", + "TARGET": "smallC", + "WIDTH": "floor($WIDTH*min((0.3),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.3),1.0))", + "TARGET": "smallD", + "WIDTH": "floor($WIDTH*min((0.3),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.5),1.0))", + "TARGET": "smallE", + "WIDTH": "floor($WIDTH*min((0.5),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.5),1.0))", + "TARGET": "smallF", + "WIDTH": "floor($WIDTH*min((0.5),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.8),1.0))", + "TARGET": "smallG", + "WIDTH": "floor($WIDTH*min((0.8),1.0))" + }, + { + "HEIGHT": "floor($HEIGHT*min((0.8),1.0))", + "TARGET": "smallH", + "WIDTH": "floor($WIDTH*min((0.8),1.0))" + }, + { + "TARGET": "smallI" + }, + { + } + ] +} +*/ + + +// original blur implementation as v002.blur in QC by anton marini and tom butterworth, ported by zoidberg + +#if __VERSION__ <= 120 +varying vec2 texOffsets[3]; +#else +in vec2 texOffsets[3]; +#endif + + +void main() { + vec4 sample0; + vec4 sample1; + vec4 sample2; + if (PASSINDEX == 0) { + sample0 = IMG_NORM_PIXEL(inputImage,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(inputImage,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(inputImage,texOffsets[2]); + } + else if (PASSINDEX == 1) { + sample0 = IMG_NORM_PIXEL(smallA,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallA,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallA,texOffsets[2]); + } + else if (PASSINDEX == 2) { + sample0 = IMG_NORM_PIXEL(smallB,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallB,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallB,texOffsets[2]); + } + else if (PASSINDEX == 3) { + sample0 = IMG_NORM_PIXEL(smallC,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallC,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallC,texOffsets[2]); + } + else if (PASSINDEX == 4) { + sample0 = IMG_NORM_PIXEL(smallD,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallD,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallD,texOffsets[2]); + } + else if (PASSINDEX == 5) { + sample0 = IMG_NORM_PIXEL(smallE,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallE,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallE,texOffsets[2]); + } + else if (PASSINDEX == 6) { + sample0 = IMG_NORM_PIXEL(smallF,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallF,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallF,texOffsets[2]); + } + else if (PASSINDEX == 7) { + sample0 = IMG_NORM_PIXEL(smallG,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallG,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallG,texOffsets[2]); + } + else if (PASSINDEX == 8) { + sample0 = IMG_NORM_PIXEL(smallH,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallH,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallH,texOffsets[2]); + } + else if (PASSINDEX == 9) { + sample0 = IMG_NORM_PIXEL(smallI,texOffsets[0]); + sample1 = IMG_NORM_PIXEL(smallI,texOffsets[1]); + sample2 = IMG_NORM_PIXEL(smallI,texOffsets[2]); + } + else { + sample0 = vec4(1,0,0,1); + sample1 = vec4(1,0,0,1); + sample2 = vec4(1,0,0,1); + } + vec4 final = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + vec4 original = IMG_NORM_PIXEL(inputImage,texOffsets[0]); + if (PASSINDEX == 9) { + final = mix(original,min((final * original), 1.0), intensity); + // First do a gloom, + // then a bright / contrast tweak based on the amount + // then a bloom + vec4 tmpColorA = final; + float bright = intensity * 0.222; + vec4 tmpColorB = tmpColorA + vec4(bright, bright, bright, bright); + // contrast + float contrast = 1.0 + intensity * (2.893); + tmpColorA.rgba = ((vec4(2.0) * (tmpColorB.rgba - vec4(0.5))) * vec4(contrast) / vec4(2.0)) + vec4(0.5); + final = min((tmpColorA + tmpColorA * original), 1.0); + } + else { + final = mix(sample0, final, intensity); + } + gl_FragColor = final; +} diff --git a/src/renderer/src/application/sample-modules/isf/Glow.vs b/src/renderer/src/application/sample-modules/isf/Glow.vs new file mode 100644 index 000000000..cd97f0c14 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Glow.vs @@ -0,0 +1,33 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[3]; +#else +out vec2 texOffsets[3]; +#endif + +const float radius = 10.0; + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + if (PASSINDEX==0 || PASSINDEX==2 || PASSINDEX==4 || PASSINDEX==6 || PASSINDEX==8) { + float pixelWidth = 1.0/RENDERSIZE[0]*radius; + if (PASSINDEX >= 2) + pixelWidth *= .7; + else if (PASSINDEX >= 6) + pixelWidth *= 1.0; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = clamp(vec2(isf_FragNormCoord[0]-pixelWidth, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = clamp(vec2(isf_FragNormCoord[0]+pixelWidth, isf_FragNormCoord[1]),0.0,1.0); + } + else if (PASSINDEX==1 || PASSINDEX==3 || PASSINDEX==5 || PASSINDEX==7 || PASSINDEX==9) { + float pixelHeight = 1.0/RENDERSIZE[1]*radius; + if (PASSINDEX >= 3) + pixelHeight *= .7; + else if (PASSINDEX >= 6) + pixelHeight *= 1.0; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-pixelHeight),0.0,1.0); + texOffsets[2] = clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+pixelHeight),0.0,1.0); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Graph Paper.fs b/src/renderer/src/application/sample-modules/isf/Graph Paper.fs new file mode 100644 index 000000000..f728e76aa --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Graph Paper.fs @@ -0,0 +1,172 @@ +/* +{ + "CATEGORIES" : [ + "Pattern", "Color" + ], + "DESCRIPTION" : "Draws basic graph paper pattern", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "bgColor", + "TYPE" : "color", + "DEFAULT" : [ + 0.93999999761581421, + 0.93999999761581421, + 0.97000002861022949, + 1 + ] + }, + { + "NAME" : "lineColor", + "TYPE" : "color", + "DEFAULT" : [ + 0.63999998569488525, + 0.76999998092651367, + 0.95999997854232788, + 1 + ] + }, + { + "LABELS" : [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16" + ], + "NAME" : "majorDivisions", + "TYPE" : "long", + "DEFAULT" : 3, + "VALUES" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ] + }, + { + "LABELS" : [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + ], + "NAME" : "minorHDivisions", + "TYPE" : "long", + "DEFAULT" : 2, + "VALUES" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "LABELS" : [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + ], + "NAME" : "minorVDivisions", + "TYPE" : "long", + "DEFAULT" : 2, + "VALUES" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "NAME" : "majorDivisionLineWidth", + "TYPE" : "float", + "MAX" : 5, + "DEFAULT" : 3, + "MIN" : 1 + }, + { + "NAME" : "square", + "TYPE" : "bool", + "DEFAULT" : true + } + ], + "CREDIT" : "VIDVOX" +} +*/ + + +const float minorDivisionLineWidth = 1.0; + + +void main() { + vec4 inputPixelColor = bgColor; + vec2 loc = gl_FragCoord.xy; + vec2 divisionSize = (square) ? vec2(max(RENDERSIZE.x,RENDERSIZE.y)) : RENDERSIZE; + divisionSize = (divisionSize - majorDivisionLineWidth) / (1.0 + float(majorDivisions)); + vec2 modLoc = mod(loc,divisionSize); + if ((modLoc.x < majorDivisionLineWidth)||(modLoc.y < majorDivisionLineWidth)) { + inputPixelColor = lineColor; + } + if (minorHDivisions > 0) { + vec2 locDivisionSize = (divisionSize) / (1.0+float(minorHDivisions)); + modLoc = mod(loc,locDivisionSize); + if (modLoc.x < minorDivisionLineWidth) { + inputPixelColor = lineColor; + } + } + if (minorVDivisions > 0) { + vec2 locDivisionSize = (divisionSize) / (1.0+float(minorVDivisions)); + modLoc = mod(loc,locDivisionSize); + if (modLoc.y < minorDivisionLineWidth) { + inputPixelColor = lineColor; + } + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/GreatBallOfFire.fs b/src/renderer/src/application/sample-modules/isf/GreatBallOfFire.fs new file mode 100644 index 000000000..0d88e5fe9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/GreatBallOfFire.fs @@ -0,0 +1,240 @@ +/*{ + "CREDIT": "by mojovideotech", + "CATEGORIES": [ + "generator", + "fire" + ], + "INPUTS": [ + { + "NAME": "offset", + "TYPE": "point2D", + "MAX": [ + 1, + 1 + ], + "MIN": [ + -1, + -1 + ] + }, + { + "NAME": "rotation", + "TYPE": "float", + "DEFAULT": 0, + "MIN": -2, + "MAX": 2 + }, + { + "NAME": "size", + "TYPE": "float", + "DEFAULT": 2.5, + "MIN": 1, + "MAX": 4 + }, + { + "NAME": "depth", + "TYPE": "float", + "DEFAULT": 0.1, + "MIN": 0.03, + "MAX": 0.15 + }, + { + "NAME": "density", + "TYPE": "float", + "DEFAULT": 3.15, + "MIN": 0.1, + "MAX": 4 + }, + { + "NAME": "rateX", + "TYPE": "float", + "DEFAULT": 3, + "MIN": -9, + "MAX": 9 + }, + { + "NAME": "rateY", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": -9, + "MAX": 9 + }, + { + "NAME": "rateZ", + "TYPE": "float", + "DEFAULT": 1, + "MIN": -9, + "MAX": 9 + } + ], + "DESCRIPTION": "GreatBallOfFire" +}*/ + + +/////////////////////////////////////////// +// GreatBallOfFire by mojovideotech +// +// based on : +// +// glslsandbox.com\/e#30176.0 +// Fireball by @AlexWDunn +// +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. +/////////////////////////////////////////// + +#define saturate(oo) clamp(oo, 0.0, 1.0) +#define MarchSteps 4 +#define Radius 1.5 +#define NoiseSteps 4 +#define Color1 vec4(1.0, 1.0, 1.0, 1.0) +#define Color2 vec4(1.0, 0.8, 0.2, 1.0) +#define Color3 vec4(1.0, 0.03, 0.0, 1.0) +#define Color4 vec4(0.4, 0.02, 0.02, 1.0) + + +vec3 mod196(vec3 x) { return x - floor(x * (1.0 / 196.0)) * 196.0; } +vec4 mod196(vec4 x) { return x - floor(x * (1.0 / 196.0)) * 196.0; } +vec4 permute(vec4 x) { return mod196(((x*56.0)+1.0)*x); } +vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; } + +float snoise(vec3 v) +{ + const vec2 C = vec2(1.0/6.0, 1.0/3.0); + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + i = mod196(i); + vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + + float n_ = 0.142857142857; + vec3 ns = n_ * D.wyz - D.xzx; + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_); + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + + return 35.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3))); +} + +float Turbulence(vec3 position, float minFreq, float maxFreq, float qWidth) +{ + float value = 0.0; + float cutoff = clamp(0.5/qWidth, 0.0, maxFreq); + float fade; + float fOut = minFreq; + for(int i=NoiseSteps ; i>=0 ; i--) + { + if(fOut >= 0.5 * cutoff) break; + fOut *= 2.0; + value += abs(snoise(position * fOut))/fOut; + } + fade = clamp(2.0 * (cutoff-fOut)/cutoff, 0.0, 1.0); + value += fade * abs(snoise(position * fOut))/fOut; + + return 1.0-value; +} + +float SphereDist(vec3 position) +{ + return length(position) - Radius; +} + +vec4 Shade(float distance) +{ + float c1 = saturate(distance*5.0 + 0.5); + float c2 = saturate(distance*5.0); + float c3 = saturate(distance*3.4 - 0.5); + + vec4 a = mix(Color1,Color2, c1); + vec4 b = mix(a, Color3, c2); + return mix(b, Color4, c3); +} + +float RenderScene(vec3 position, out float distance) +{ + float noise = Turbulence(position * density + vec3(rateZ, rateX, rateY)*TIME, 0.1, 1.5, 0.03) * depth; + noise = saturate(abs(noise)); + distance = SphereDist(position) - noise; + + return noise; +} + +vec4 March(vec3 rayOrigin, vec3 rayStep) +{ + vec3 position = rayOrigin; + float distance; + float displacement; + for(int step = MarchSteps; step >=0 ; --step) + { + displacement = RenderScene(position, distance); + if(distance < 0.05) break; + position += rayStep * distance; + } + + return mix(Shade(displacement), vec4(0.0, 0.0, 0.0, 0.0), float(distance >= 0.5)); +} + +bool IntersectSphere(vec3 ro, vec3 rd, vec3 pos, float radius, out vec3 intersectPoint) +{ + vec3 relDistance = (ro - pos); + float b = dot(relDistance, rd); + float c = dot(relDistance, relDistance) - radius*radius; + float d = b*b - c; + intersectPoint = ro + rd*(-b - sqrt(d)); + + return d >= 0.0; +} + +void main(void) +{ + vec2 p = (gl_FragCoord.xy / RENDERSIZE.xy) * 2.0 - 1.0; + p += offset; + p.x *= RENDERSIZE.x/RENDERSIZE.y; + + float rotx = rotation* 4.0; + float roty = -rotation * 4.0; + float zoom = 16.0-(size*3.); + vec3 ro = zoom * normalize(vec3(cos(roty), cos(rotx), sin(roty))); + vec3 ww = normalize(vec3(0.0, 0.0, 0.0) - ro); + vec3 uu = normalize(cross( vec3(0.0, 1.0, 0.0), ww)); + vec3 vv = normalize(cross(ww, uu)); + vec3 rd = normalize(p.x*uu + p.y*vv + 1.5*ww); + vec4 col = vec4(0.0); + vec3 origin; + if(IntersectSphere(ro, rd, vec3(0.0), Radius + depth*7.0, origin)) + { + col = March(origin, rd); + } + + gl_FragColor = col; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Grid Flip.fs b/src/renderer/src/application/sample-modules/isf/Grid Flip.fs new file mode 100644 index 000000000..a68faf030 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Grid Flip.fs @@ -0,0 +1,158 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/GridFlip.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "pause", + "TYPE": "float" + }, + { + "DEFAULT": 0.05, + "MAX": 1, + "MIN": 0, + "NAME": "dividerWidth", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "randomness", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "bgcolor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 4, + 4 + ], + "MAX": [ + 10, + 10 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "size", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: TimDonselaar +// ported by gre from https://gist.github.com/TimDonselaar/9bcd1c4b5934ba60087bdb55c2ea92e5 + + +float rand (vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +float getDelta(vec2 p) { + vec2 rectanglePos = floor(vec2(size) * p); + vec2 rectangleSize = vec2(1.0 / vec2(size).x, 1.0 / vec2(size).y); + float top = rectangleSize.y * (rectanglePos.y + 1.0); + float bottom = rectangleSize.y * rectanglePos.y; + float left = rectangleSize.x * rectanglePos.x; + float right = rectangleSize.x * (rectanglePos.x + 1.0); + float minX = min(abs(p.x - left), abs(p.x - right)); + float minY = min(abs(p.y - top), abs(p.y - bottom)); + return min(minX, minY); +} + +float getDividerSize() { + vec2 rectangleSize = vec2(1.0 / vec2(size).x, 1.0 / vec2(size).y); + return min(rectangleSize.x, rectangleSize.y) * dividerWidth; +} + +vec4 transition(vec2 p) { + if(progress < pause) { + float currentProg = progress / pause; + float a = 1.0; + if(getDelta(p) < getDividerSize()) { + a = 1.0 - currentProg; + } + return mix(bgcolor, getFromColor(p), a); + } + else if(progress < 1.0 - pause){ + if(getDelta(p) < getDividerSize()) { + return bgcolor; + } else { + float currentProg = (progress - pause) / (1.0 - pause * 2.0); + vec2 q = p; + vec2 rectanglePos = floor(vec2(size) * q); + + float r = rand(rectanglePos) - randomness; + float cp = smoothstep(0.0, 1.0 - r, currentProg); + + float rectangleSize = 1.0 / vec2(size).x; + float delta = rectanglePos.x * rectangleSize; + float offset = rectangleSize / 2.0 + delta; + + p.x = (p.x - offset)/abs(cp - 0.5)*0.5 + offset; + vec4 a = getFromColor(p); + vec4 b = getToColor(p); + + float s = step(abs(vec2(size).x * (q.x - delta) - 0.5), abs(cp - 0.5)); + return mix(bgcolor, mix(b, a, step(cp, 0.5)), s); + } + } + else { + float currentProg = (progress - 1.0 + pause) / pause; + float a = 1.0; + if(getDelta(p) < getDividerSize()) { + a = currentProg; + } + return mix(bgcolor, getToColor(p), a); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Grid Warp.fs b/src/renderer/src/application/sample-modules/isf/Grid Warp.fs new file mode 100644 index 000000000..65d998b26 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Grid Warp.fs @@ -0,0 +1,232 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "DEFAULT": -0.5, + "MAX": 1, + "MIN": -1, + "NAME": "level", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "radius", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 0 + ], + "NAME": "bgColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.9436030983924866, + 0.5601619482040405, + 0, + 1 + ], + "NAME": "lineColor", + "TYPE": "color" + }, + { + "DEFAULT": 6, + "LABELS": [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16" + ], + "NAME": "majorDivisions", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ] + }, + { + "DEFAULT": 2, + "LABELS": [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + ], + "NAME": "minorHDivisions", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "DEFAULT": 1, + "LABELS": [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + ], + "NAME": "minorVDivisions", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "DEFAULT": 3, + "MAX": 5, + "MIN": 1, + "NAME": "majorDivisionLineWidth", + "TYPE": "float" + }, + { + "DEFAULT": true, + "NAME": "square", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +const float minorDivisionLineWidth = 1.0; +const float pi = 3.14159265359; + +#ifndef GL_ES +float distance (vec2 center, vec2 pt) +{ + float tmp = pow(center.x-pt.x,2.0)+pow(center.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +void main() { + vec2 uv = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 texSize = RENDERSIZE; + vec2 tc = uv * texSize; + vec2 modifiedCenter = center * RENDERSIZE; + float r = distance(modifiedCenter, tc); + float a = atan ((tc.y-modifiedCenter.y),(tc.x-modifiedCenter.x)); + float radius_sized = radius * length(RENDERSIZE); + vec4 inputPixelColor = bgColor; + + tc -= modifiedCenter; + + if (r < radius_sized) { + float percent = 1.0-(radius_sized - r) / radius_sized; + if (level>=0.0) { + percent = percent * percent; + tc.x = r*pow(percent,level) * cos(a); + tc.y = r*pow(percent,level) * sin(a); + } + else { + float adjustedLevel = level/2.0; + tc.x = r*pow(percent,adjustedLevel) * cos(a); + tc.y = r*pow(percent,adjustedLevel) * sin(a); + } + } + tc += modifiedCenter; + + vec2 loc = tc; + vec2 divisionSize = (square) ? vec2(max(RENDERSIZE.x,RENDERSIZE.y)) : RENDERSIZE; + divisionSize = (divisionSize - majorDivisionLineWidth) / (1.0 + float(majorDivisions)); + vec2 modLoc = mod(loc,divisionSize); + if ((modLoc.x < majorDivisionLineWidth)||(modLoc.y < majorDivisionLineWidth)) { + inputPixelColor = lineColor; + } + if (minorHDivisions > 0) { + vec2 locDivisionSize = (divisionSize) / (1.0+float(minorHDivisions)); + modLoc = mod(loc,locDivisionSize); + if (modLoc.x < minorDivisionLineWidth) { + inputPixelColor = lineColor; + } + } + if (minorVDivisions > 0) { + vec2 locDivisionSize = (divisionSize) / (1.0+float(minorVDivisions)); + modLoc = mod(loc,locDivisionSize); + if (modLoc.y < minorDivisionLineWidth) { + inputPixelColor = lineColor; + } + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/HSVtoRGB.fs b/src/renderer/src/application/sample-modules/isf/HSVtoRGB.fs new file mode 100644 index 000000000..c7c36519b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/HSVtoRGB.fs @@ -0,0 +1,46 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Utility" + ], + "CREDIT": "by zoidberg", + "DESCRIPTION": "swizzles RGBA to BGRA and vice versa", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ], + "ISFVSN": "2" +} +*/ + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + + +void main() +{ + vec4 inColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + gl_FragColor = vec4(hsv2rgb(inColor.rgb), inColor.a); +} diff --git a/src/renderer/src/application/sample-modules/isf/Hatch Blur.fs b/src/renderer/src/application/sample-modules/isf/Hatch Blur.fs new file mode 100644 index 000000000..edc7cc1c1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Hatch Blur.fs @@ -0,0 +1,95 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "width", + "LABEL": "Width", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "angle", + "LABEL": "Angle", + "TYPE": "float", + "MIN": -1.0, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "quality", + "LABEL": "Quality", + "VALUES": [ + 24, + 16, + 8, + 4 + ], + "LABELS": [ + "Low", + "Mid", + "High", + "Best" + ], + "DEFAULT": 16, + "TYPE": "long" + } + ] +}*/ + + +const float pi = 3.14159265359; + + +void main() { + vec2 loc = isf_FragNormCoord * RENDERSIZE; + + vec2 p1 = vec2(0.0); + vec2 p2 = vec2(1.0); + vec2 vector = vec2(cos(pi * angle),sin(pi * angle)); + + vec4 returnMe; + + if (width > 0.0) { + p1 = loc - width * RENDERSIZE * vector; + p2 = loc + width * RENDERSIZE * vector; + + // now we have the two points to smear between, + float i; + float count = clamp(width * max(RENDERSIZE.x,RENDERSIZE.y) / float(quality), 5.0, 75.0); + vec2 diff = p2 - p1; + for (float i = 0.0; i < 75.0; ++i) { + if (i > float(count)) + break; + float tmp = (i / (count - 1.0)); + returnMe = returnMe + IMG_PIXEL(inputImage, p1 + diff * tmp) / count; + } + + vector = vec2(cos(pi * (0.5+ angle)),sin(pi * (0.5+ angle))); + p1 = loc - width * RENDERSIZE * vector; + p2 = loc + width * RENDERSIZE * vector; + + // now we have the two points to smear between, + diff = p2 - p1; + for (float i = 0.0; i < 75.0; ++i) { + if (i > float(count)) + break; + float tmp = (i / (count - 1.0)); + returnMe = returnMe + IMG_PIXEL(inputImage, p1 + diff * tmp) / count; + } + returnMe = returnMe / 2.0; + } + else { + returnMe = IMG_THIS_PIXEL(inputImage); + } + gl_FragColor = returnMe; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Heart Transition.fs b/src/renderer/src/application/sample-modules/isf/Heart Transition.fs new file mode 100644 index 000000000..e6b252c99 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Heart Transition.fs @@ -0,0 +1,61 @@ +/*{ + "CATEGORIES": [ + "Wipe", + "Retro" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/heart.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + +float inHeart (vec2 p, vec2 center, float size) { + if (size==0.0) return 0.0; + vec2 o = (p-center)/(1.6*size); + float a = o.x*o.x+o.y*o.y-0.3; + return step(a*a*a, o.x*o.x*o.y*o.y*o.y); +} +vec4 transition (vec2 uv) { + return mix( + getFromColor(uv), + getToColor(uv), + inHeart(uv, vec2(0.5, 0.4), progress) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Heart.fs b/src/renderer/src/application/sample-modules/isf/Heart.fs new file mode 100644 index 000000000..3fbe4f1f6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Heart.fs @@ -0,0 +1,44 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry" + ], + "INPUTS": [ + { + "NAME": "size", + "LABEL": "Size", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.25 + }, + { + "NAME": "color", + "LABEL": "Fill Color", + "TYPE": "color", + "DEFAULT": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + } + ] +}*/ + + +// Adapted from https://glsl.io/transition/d71472a550601b96d69d + + +bool inHeart (vec2 p, vec2 center, float size) { + if (size == 0.0) return false; + vec2 o = (p-center)/(1.6*size); + return pow(o.x*o.x+o.y*o.y-0.3, 3.0) < o.x*o.x*pow(o.y, 3.0); +} + +void main() { + vec2 p = isf_FragNormCoord; + float m = inHeart(p, vec2(0.5, 0.4), size) ? 1.0 : 0.0; + gl_FragColor = mix(vec4(0.0), color, m); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/HexVortex.fs b/src/renderer/src/application/sample-modules/isf/HexVortex.fs new file mode 100644 index 000000000..cd9d90503 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/HexVortex.fs @@ -0,0 +1,148 @@ +/*{ + "CREDIT": "by mojovideotech", + "CATEGORIES": [ + "Automatically Converted" + ], + "DESCRIPTION": "", + "INPUTS": [ + { + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "DEFAULT": [ + 0.5, + 0.5 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "MAX": [ + 10, + 10 + ], + "MIN": [ + 0.5, + 0.5 + ], + "DEFAULT": [ + 2, + 2.25 + ], + "NAME": "shape", + "TYPE": "point2D" + }, + { + "NAME": "speed", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0001, + "MAX": 1 + }, + { + "NAME": "rotation", + "TYPE": "float", + "DEFAULT": 0.05, + "MIN": 0.005, + "MAX": 0.5 + }, + { + "NAME": "R", + "TYPE": "float", + "DEFAULT": 0.05, + "MIN": 0, + "MAX": 0.5 + }, + { + "NAME": "G", + "TYPE": "float", + "DEFAULT": 0.125, + "MIN": 0, + "MAX": 0.5 + }, + { + "NAME": "B", + "TYPE": "float", + "DEFAULT": 0.25, + "MIN": 0, + "MAX": 0.5 + } + ] +}*/ + + +/////////////////////////////////////////// +// HexVortex by mojovideotech +// +// based on: +// glslsandbox.com/\e#4671.0 +// +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +/////////////////////////////////////////// + +#ifdef GL_ES +precision mediump float; +#endif + + +#define pi 3.141592653589793 // pi +#define e 2.718281828459045 // eulers number +#define prphi 2.028876065463213 // pi root of phi +#define sqpi 1.772453850905516 // square root of pi +#define thpi 0.996272076220750 // tanh(pi) +#define lgpi 0.497149872694134 // log(pi) +#define rcpi 0.318309886183791 // reciprocal of pi , 1/pi + + +vec3 rotXY(vec3 p, vec2 rad) { + vec2 s = sin(rad); + vec2 c = cos(rad); + mat3 m = mat3(c.y, 0.0, -s.y, + -s.x * s.y, c.x, -s.x * c.y, + c.x * s.y, s.x, c.x * c.y); + return m * p; +} + +vec2 repeat(vec2 p, float n) { + vec2 np = p * n; + vec2 npfrct = fract(np); + vec2 npreal = np - npfrct; + np.x += fract(npreal.y * lgpi); + return fract(np) * prphi - 1.0; +} + +float hexDistance(vec2 ip) { + const float SQRT3 = sqpi; + vec2 TRIG30 = vec2 (sin(rcpi), cos(thpi)); + vec2 p = abs(ip * vec2(SQRT3 * shape.x, shape.y)); + float d = dot(p, vec2(-TRIG30.x, TRIG30.y)) - SQRT3 * 0.25; + return (d > 0.0)? min(d, (SQRT3 * 0.5 - p.x)) : min(-d, p.x); +} + +float smoothEdge(float edge, float margin, float x) { + return smoothstep(edge - margin, edge + margin, x); +} + +void main(void) { + float T = TIME * speed; + vec3 rgb; + vec2 nsc = (gl_FragCoord.xy - RENDERSIZE * 0.5) / RENDERSIZE.yy * e; + vec3 dir = normalize(vec3(nsc, -2.0)); + dir = rotXY(dir, vec2((center.yx - 0.5) * pi * 0.25)); + vec2 uv = vec2(atan(dir.y, dir.x) / (pi * 2.0) + 0.5, dir.z / length(dir.xy)); + vec2 pos = uv * vec2(1.0, 0.2) - vec2(T * rotation, T * 0.5); + vec2 p = repeat(pos, 16.0); + float d = hexDistance(p); + float dist = dir.z/length(dir.xy); + d/=-dist; + float fade = 1.0 / pow(1.0 / length(dir.xy) * 0.3, 1.0); + fade = clamp(fade, 0.0, 1.0); + rgb = mix(vec3(1.0)*fade, vec3(R,G,B), smoothEdge(0.03, 0.1, d)); + + gl_FragColor = vec4(rgb, 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Hexagonalize.fs b/src/renderer/src/application/sample-modules/isf/Hexagonalize.fs new file mode 100644 index 000000000..8a8d296c1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Hexagonalize.fs @@ -0,0 +1,131 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/hexagonalize.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 50, + "MAX": 100, + "MIN": 0, + "NAME": "steps", + "TYPE": "float" + }, + { + "DEFAULT": 20, + "MAX": 100, + "MIN": 0, + "NAME": "horizontalHexagons", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +float ratio = RENDERSIZE.x/RENDERSIZE.y; +// Author: Fernando Kuteken +// License: MIT +// Hexagonal math from: http://www.redblobgames.com/grids/hexagons/ + + +struct Hexagon { + float q; + float r; + float s; +}; + +Hexagon createHexagon(float q, float r){ + Hexagon hex; + hex.q = q; + hex.r = r; + hex.s = -q - r; + return hex; +} + +Hexagon roundHexagon(Hexagon hex){ + + float q = floor(hex.q + 0.5); + float r = floor(hex.r + 0.5); + float s = floor(hex.s + 0.5); + + float deltaQ = abs(q - hex.q); + float deltaR = abs(r - hex.r); + float deltaS = abs(s - hex.s); + + if (deltaQ > deltaR && deltaQ > deltaS) + q = -r - s; + else if (deltaR > deltaS) + r = -q - s; + else + s = -q - r; + + return createHexagon(q, r); +} + +Hexagon hexagonFromPoint(vec2 point, float size) { + + point.y /= ratio; + point = (point - 0.5) / size; + + float q = (sqrt(3.0) / 3.0) * point.x + (-1.0 / 3.0) * point.y; + float r = 0.0 * point.x + 2.0 / 3.0 * point.y; + + Hexagon hex = createHexagon(q, r); + return roundHexagon(hex); + +} + +vec2 pointFromHexagon(Hexagon hex, float size) { + + float x = (sqrt(3.0) * hex.q + (sqrt(3.0) / 2.0) * hex.r) * size + 0.5; + float y = (0.0 * hex.q + (3.0 / 2.0) * hex.r) * size + 0.5; + + return vec2(x, y * ratio); +} + +vec4 transition (vec2 uv) { + + float dist = 2.0 * min(progress, 1.0 - progress); + dist = steps > 0.0 ? ceil(dist * float(steps)) / float(steps) : dist; + + float size = (sqrt(3.0) / 3.0) * dist / horizontalHexagons; + + vec2 point = dist > 0.0 ? pointFromHexagon(hexagonFromPoint(uv, size), size) : uv; + + return mix(getFromColor(point), getToColor(point), progress); + +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} diff --git a/src/renderer/src/application/sample-modules/isf/Histogram Viewer.fs b/src/renderer/src/application/sample-modules/isf/Histogram Viewer.fs new file mode 100644 index 000000000..c158864ba --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Histogram Viewer.fs @@ -0,0 +1,35 @@ +/*{ + "CATEGORIES" : [ + "Histogram", "Utility" + ], + "DESCRIPTION": "Draws an RGB histogram from a provided histogram image", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "INPUTS": [ + { + "NAME": "histogramImage", + "TYPE": "image" + } + ] +}*/ + + +void main() { + vec4 outColor = vec4(0., 0., 0., 0.); + vec4 histoVals = IMG_NORM_PIXEL(histogramImage, vec2(isf_FragNormCoord.x, 0.5)); + if (histoVals.r >= isf_FragNormCoord.y) { + outColor.r = 1.0; + outColor.a = 1.0; + } + if (histoVals.g >= isf_FragNormCoord.y) { + outColor.g = 1.0; + outColor.a = 1.0; + } + if (histoVals.b >= isf_FragNormCoord.y) { + outColor.b = 1.0; + outColor.a = 1.0; + } + + gl_FragColor = outColor; + +} diff --git a/src/renderer/src/application/sample-modules/isf/HorizVertHold.fs b/src/renderer/src/application/sample-modules/isf/HorizVertHold.fs new file mode 100644 index 000000000..d35a121d7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/HorizVertHold.fs @@ -0,0 +1,65 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "hHold", + "LABEL": "Horiz. Hold", + "TYPE": "float", + "MIN": -0.45, + "MAX": 0.45, + "DEFAULT": 0.0 + }, + { + "NAME": "vHold", + "LABEL": "Vert. Hold", + "TYPE": "float", + "MIN": -0.45, + "MAX": 0.45, + "DEFAULT": 0.0 + }, + { + "NAME": "flashEvent", + "TYPE": "event" + } + ], + "PASSES": [ + { + "TARGET":"lastPosition", + "WIDTH": 1, + "HEIGHT": 1, + "FLOAT": true, + "PERSISTENT": true, + "DESCRIPTION": "this buffer stores the last frame's x/y offset in the first two components of its only pixel- note that it's requesting a FLOAT target buffer..." + }, + { + + } + ] + +}*/ + +void main() +{ + // if this is the first pass, i'm going to read the position from the "lastPosition" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastPosition,vec2(0.5)); + // i'm only using the X and Y components, which are the X and Y offset (normalized) for the frame + srcPixel.xy = (flashEvent) ? vec2(0.0) : (srcPixel.xy - vec2(hHold,vHold)); + gl_FragColor = mod(srcPixel,1.0); + } + // else this isn't the first pass- read the position value from the buffer which stores it + else { + vec4 lastPosVector = IMG_PIXEL(lastPosition,vec2(0.5)); + vec2 normPixelCoord = mod((isf_FragNormCoord.xy + lastPosVector.xy), 1.0); + gl_FragColor = IMG_NORM_PIXEL(inputImage,normPixelCoord); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Hue-Saturation.fs b/src/renderer/src/application/sample-modules/isf/Hue-Saturation.fs new file mode 100644 index 000000000..b658b2c39 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Hue-Saturation.fs @@ -0,0 +1,54 @@ +/* +{ + "DESCRIPTION" : "Hue\/Saturation adjustment", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "hue", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : -1, + "LABEL" : "Hue" + }, + { + "NAME" : "saturation", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "LABEL" : "Saturation", + "MIN" : -1 + } + ], + "CREDIT" : "2xAA" +} +*/ + +void main() { + vec4 color = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord.xy); + + /* hue adjustment */ + float angle = hue * 3.14159265; + float s = sin(angle), c = cos(angle); + vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0; + float len = length(color.rgb); + color.rgb = vec3( + dot(color.rgb, weights.xyz), + dot(color.rgb, weights.zxy), + dot(color.rgb, weights.yzx) + ); + + /* saturation adjustment */ + float average = (color.r + color.g + color.b) / 3.0; + if (saturation > 0.0) { + color.rgb += (average - color.rgb) * (1.0 - 1.0 / (1.001 - saturation)); + } else { + color.rgb += (average - color.rgb) * (-saturation); + } + + gl_FragColor = color; +} diff --git a/src/renderer/src/application/sample-modules/isf/Hyperspace.fs b/src/renderer/src/application/sample-modules/isf/Hyperspace.fs new file mode 100644 index 000000000..dd31f0df0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Hyperspace.fs @@ -0,0 +1,85 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "centerX", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 2, + "MIN": 0, + "NAME": "scrollAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "rightScrollOffset", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "IDENTITY": 1, + "MAX": 1, + "MIN": 0, + "NAME": "midHeight", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "NAME": "seamless", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec2 loc = isf_FragNormCoord.xy; + if (centerX == 0.0) { + + } + else if (centerX == 1.0) { + + } + else if (loc.x < centerX) { + loc.x = loc.x / centerX; + loc.y = mix(0.5 + (loc.y-0.5) / midHeight,loc.y,1.0-loc.x); + loc.x = loc.x+scrollAmount; + if (seamless) { + loc.x = ((loc.x <= 1.0)||(loc.x >= 2.0)) ? loc.x : 3.0 - loc.x; + } + loc.x = mod(loc.x,1.0); + } + else { + float rightScroll = mod(rightScrollOffset+scrollAmount,2.0); + loc.x = (1.0-loc.x) / (1.0-centerX); + loc.y = mix(0.5 + (loc.y-0.5) / midHeight,loc.y,1.0-loc.x); + loc.x = loc.x+rightScroll; + if (seamless) { + loc.x = ((loc.x <= 1.0)||(loc.x >= 2.0)) ? loc.x : 3.0 - loc.x; + } + loc.x = mod(loc.x,1.0); + } + + vec4 inputPixelColor = vec4(0.0); + + if ((loc.y >= 0.0)&&(loc.y <= 1.0)) + inputPixelColor = IMG_NORM_PIXEL(inputImage,loc); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Interlace Mirror.fs b/src/renderer/src/application/sample-modules/isf/Interlace Mirror.fs new file mode 100644 index 000000000..8a104f352 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Interlace Mirror.fs @@ -0,0 +1,45 @@ +/*{ + "CATEGORIES": [ + "Glitch", + "Retro" + ], + "CREDIT": "by Carter Rosenberg", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "LABEL": "Horizontal", + "NAME": "horizontal", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "LABEL": "Vertical", + "NAME": "vertical", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +void main() +{ + vec2 pixelCoord = isf_FragNormCoord * RENDERSIZE; + vec2 loc = pixelCoord; + if (vertical) { + if (mod(pixelCoord.x,2.0)>1.0) { + loc.y = RENDERSIZE.y - pixelCoord.y; + } + } + if (horizontal) { + if (mod(pixelCoord.y,2.0)>1.0) { + loc.x = RENDERSIZE.x - pixelCoord.x; + } + } + gl_FragColor = IMG_PIXEL(inputImage,loc); +} diff --git a/src/renderer/src/application/sample-modules/isf/Interlace.fs b/src/renderer/src/application/sample-modules/isf/Interlace.fs new file mode 100644 index 000000000..b7d8f41d1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Interlace.fs @@ -0,0 +1,57 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "by Carter Rosenberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Film" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "lineSize", + "LABEL": "Line Size", + "TYPE": "float", + "MIN": 1.0, + "MAX": 50.0, + "DEFAULT": 4.0 + } + ], + "PASSES": [ + { + "TARGET":"lastRow", + "WIDTH": "1", + "HEIGHT": "1", + "DESCRIPTION": "this buffer stores the last frame's odd / even state", + "PERSISTENT": true + }, + { + "TARGET":"lastFrame", + "PERSISTENT": true + } + ] + +}*/ + +void main() +{ + // if this is the first pass, i'm going to read the position from the "lastRow" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastRow,vec2(0.5)); + // i'm only using the X and Y components, which are the X and Y offset (normalized) for the frame + srcPixel.x = (srcPixel.x) > 0.5 ? 0.0 : 1.0; + gl_FragColor = srcPixel; + } + // else this isn't the first pass- read the position value from the buffer which stores it + else { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + vec2 pixelCoord = isf_FragNormCoord * RENDERSIZE; + + if (mod(floor(pixelCoord.y),2.0 * lineSize) < lineSize + lineSize * lastRow.x) + gl_FragColor = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord); + else + gl_FragColor = IMG_NORM_PIXEL(lastFrame,isf_FragNormCoord); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Inverted Page Curl.fs b/src/renderer/src/application/sample-modules/isf/Inverted Page Curl.fs new file mode 100644 index 000000000..2f96f32cc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Inverted Page Curl.fs @@ -0,0 +1,259 @@ +/*{ + "CATEGORIES": [ + "Wipe", + "Retro" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/InvertedPageCurl.glsl", + "DESCRIPTION": "Automatically converted from https://gl-transitions.com/", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: Hewlett-Packard +// license: BSD 3 Clause +// Adapted by Sergey Kosarevsky from: +// http://rectalogic.github.io/webvfx/examples_2transition-shader-pagecurl_8html-example.html + +/* +Copyright (c) 2010 Hewlett-Packard Development Company, L.P. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Hewlett-Packard nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +in vec2 texCoord; +*/ + +const float MIN_AMOUNT = -0.16; +const float MAX_AMOUNT = 1.5; +float amount = progress * (MAX_AMOUNT - MIN_AMOUNT) + MIN_AMOUNT; + +const float PI = 3.141592653589793; + +const float scale = 512.0; +const float sharpness = 3.0; + +float cylinderCenter = amount; +// 360 degrees * amount +float cylinderAngle = 2.0 * PI * amount; + +const float cylinderRadius = 1.0 / PI / 2.0; + +vec3 hitPoint(float hitAngle, float yc, vec3 point, mat3 rrotation) +{ + float hitPoint = hitAngle / (2.0 * PI); + point.y = hitPoint; + return rrotation * point; +} + +vec4 antiAlias(vec4 color1, vec4 color2, float distanc) +{ + distanc *= scale; + if (distanc < 0.0) return color2; + if (distanc > 2.0) return color1; + float dd = pow(1.0 - distanc / 2.0, sharpness); + return ((color2 - color1) * dd) + color1; +} + +float distanceToEdge(vec3 point) +{ + float dx = abs(point.x > 0.5 ? 1.0 - point.x : point.x); + float dy = abs(point.y > 0.5 ? 1.0 - point.y : point.y); + if (point.x < 0.0) dx = -point.x; + if (point.x > 1.0) dx = point.x - 1.0; + if (point.y < 0.0) dy = -point.y; + if (point.y > 1.0) dy = point.y - 1.0; + if ((point.x < 0.0 || point.x > 1.0) && (point.y < 0.0 || point.y > 1.0)) return sqrt(dx * dx + dy * dy); + return min(dx, dy); +} + +vec4 seeThrough(float yc, vec2 p, mat3 rotation, mat3 rrotation) +{ + float hitAngle = PI - (acos(yc / cylinderRadius) - cylinderAngle); + vec3 point = hitPoint(hitAngle, yc, rotation * vec3(p, 1.0), rrotation); + if (yc <= 0.0 && (point.x < 0.0 || point.y < 0.0 || point.x > 1.0 || point.y > 1.0)) + { + return getToColor(p); + } + + if (yc > 0.0) return getFromColor(p); + + vec4 color = getFromColor(point.xy); + vec4 tcolor = vec4(0.0); + + return antiAlias(color, tcolor, distanceToEdge(point)); +} + +vec4 seeThroughWithShadow(float yc, vec2 p, vec3 point, mat3 rotation, mat3 rrotation) +{ + float shadow = distanceToEdge(point) * 30.0; + shadow = (1.0 - shadow) / 3.0; + + if (shadow < 0.0) shadow = 0.0; else shadow *= amount; + + vec4 shadowColor = seeThrough(yc, p, rotation, rrotation); + shadowColor.r -= shadow; + shadowColor.g -= shadow; + shadowColor.b -= shadow; + + return shadowColor; +} + +vec4 backside(float yc, vec3 point) +{ + vec4 color = getFromColor(point.xy); + float gray = (color.r + color.b + color.g) / 15.0; + gray += (8.0 / 10.0) * (pow(1.0 - abs(yc / cylinderRadius), 2.0 / 10.0) / 2.0 + (5.0 / 10.0)); + color.rgb = vec3(gray); + return color; +} + +vec4 behindSurface(vec2 p, float yc, vec3 point, mat3 rrotation) +{ + float shado = (1.0 - ((-cylinderRadius - yc) / amount * 7.0)) / 6.0; + shado *= 1.0 - abs(point.x - 0.5); + + yc = (-cylinderRadius - cylinderRadius - yc); + + float hitAngle = (acos(yc / cylinderRadius) + cylinderAngle) - PI; + point = hitPoint(hitAngle, yc, point, rrotation); + + if (yc < 0.0 && point.x >= 0.0 && point.y >= 0.0 && point.x <= 1.0 && point.y <= 1.0 && (hitAngle < PI || amount > 0.5)) + { + shado = 1.0 - (sqrt(pow(point.x - 0.5, 2.0) + pow(point.y - 0.5, 2.0)) / (71.0 / 100.0)); + shado *= pow(-yc / cylinderRadius, 3.0); + shado *= 0.5; + } + else + { + shado = 0.0; + } + return vec4(getToColor(p).rgb - shado, 1.0); +} + +vec4 transition(vec2 p) { + + const float angle = 100.0 * PI / 180.0; + float c = cos(-angle); + float s = sin(-angle); + + mat3 rotation = mat3( c, s, 0, + -s, c, 0, + -0.801, 0.8900, 1 + ); + c = cos(angle); + s = sin(angle); + + mat3 rrotation = mat3( c, s, 0, + -s, c, 0, + 0.98500, 0.985, 1 + ); + + vec3 point = rotation * vec3(p, 1.0); + + float yc = point.y - cylinderCenter; + + if (yc < -cylinderRadius) + { + // Behind surface + return behindSurface(p,yc, point, rrotation); + } + + if (yc > cylinderRadius) + { + // Flat surface + return getFromColor(p); + } + + float hitAngle = (acos(yc / cylinderRadius) + cylinderAngle) - PI; + + float hitAngleMod = mod(hitAngle, 2.0 * PI); + if ((hitAngleMod > PI && amount < 0.5) || (hitAngleMod > PI/2.0 && amount < 0.0)) + { + return seeThrough(yc, p, rotation, rrotation); + } + + point = hitPoint(hitAngle, yc, point, rrotation); + + if (point.x < 0.0 || point.y < 0.0 || point.x > 1.0 || point.y > 1.0) + { + return seeThroughWithShadow(yc, p, point, rotation, rrotation); + } + + vec4 color = backside(yc, point); + + vec4 otherColor; + if (yc < 0.0) + { + float shado = 1.0 - (sqrt(pow(point.x - 0.5, 2.0) + pow(point.y - 0.5, 2.0)) / 0.71); + shado *= pow(-yc / cylinderRadius, 3.0); + shado *= 0.5; + otherColor = vec4(0.0, 0.0, 0.0, shado); + } + else + { + otherColor = getFromColor(p); + } + + color = antiAlias(color, otherColor, cylinderRadius - abs(yc)); + + vec4 cl = seeThroughWithShadow(yc, p, point, rotation, rrotation); + float dist = distanceToEdge(point); + + return antiAlias(color, cl, dist); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Kaleidoscope Tile.fs b/src/renderer/src/application/sample-modules/isf/Kaleidoscope Tile.fs new file mode 100644 index 000000000..193122cb4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Kaleidoscope Tile.fs @@ -0,0 +1,122 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Tile Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "size", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.5 + }, + { + "NAME": "sides", + "TYPE": "float", + "MIN": 1.0, + "MAX": 32.0, + "DEFAULT": 6.0 + }, + { + "NAME": "rotation", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "angle", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "slide1", + "TYPE": "point2D", + "DEFAULT": [ + 0.0, + 0.0 + ] + }, + { + "NAME": "slide2", + "TYPE": "point2D", + "DEFAULT": [ + 0.0, + 0.0 + ] + }, + { + "NAME": "shift", + "TYPE": "point2D", + "DEFAULT": [ + 0.0, + 0.0 + ] + } + ] +}*/ + + +const float tau = 6.28318530718; + + +vec2 pattern() { + float s = sin(tau * rotation); + float c = cos(tau * rotation); + vec2 tex = isf_FragNormCoord * RENDERSIZE; + float scale = 1.0 / max(size,0.001); + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale; + point = (point - shift) / RENDERSIZE; + // do this to repeat + point = mod(point,1.0); + if (point.x < 0.5) { + point.y = mod(point.y + slide1.y/RENDERSIZE.y, 1.0); + } + else { + point.y = mod(point.y + slide2.y/RENDERSIZE.y, 1.0); + } + if (point.y < 0.5) { + point.x = mod(point.x + slide1.x/RENDERSIZE.x, 1.0); + } + else { + point.x = mod(point.x + slide2.x/RENDERSIZE.x, 1.0); + } + // do this for relections + point = 1.0-abs(1.0-2.0*point); + + // Now let's do a squish based on angle + // convert to polar coordinates + vec2 center = vec2(0.5,0.5); + float r = distance(center, point); + float a = atan ((point.y-center.y),(point.x-center.x)); + + // now do the kaleidoscope + a = mod(a, tau/sides); + a = abs(a - tau/sides/2.); + + s = sin(a + tau * angle); + c = cos(a + tau * angle); + + float zoom = RENDERSIZE.x / RENDERSIZE.y; + + point.x = (r * c)/zoom + 0.5; + point.y = (r * s)/zoom + 0.5; + + return point; +} + + +void main() { + + vec2 pat = pattern(); + + gl_FragColor = IMG_NORM_PIXEL(inputImage,pat); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Kaleidoscope Transition.fs b/src/renderer/src/application/sample-modules/isf/Kaleidoscope Transition.fs new file mode 100644 index 000000000..dd654dbcd --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Kaleidoscope Transition.fs @@ -0,0 +1,84 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/kaleidoscope.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 1.5, + "MAX": 5, + "MIN": 0, + "NAME": "power", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "speed", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "angle", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: nwoeanhinnogaehr +// License: MIT + + +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy; + vec2 q = p; + float t = pow(progress, power)*speed; + p = p -0.5; + for (int i = 0; i < 7; i++) { + p = vec2(sin(t)*p.x + cos(t)*p.y, sin(t)*p.y - cos(t)*p.x); + t += angle; + p = abs(mod(p, 2.0) - 1.0); + } + abs(mod(p, 1.0)); + return mix( + mix(getFromColor(q), getToColor(q), progress), + mix(getFromColor(p), getToColor(p), progress), 1.0 - 2.0*abs(progress - 0.5)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Kaleidoscope.fs b/src/renderer/src/application/sample-modules/isf/Kaleidoscope.fs new file mode 100644 index 000000000..d4926e9f9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Kaleidoscope.fs @@ -0,0 +1,50 @@ +/* +{ + "CATEGORIES" : [ + "effect" + ], + "DESCRIPTION" : "Kaleidoscope", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "sides", + "TYPE" : "float", + "MAX" : 60, + "DEFAULT" : 5, + "MIN" : 2 + } + ], + "CREDIT" : "2xAA" +} +*/ + +void main() { + + vec2 uv = gl_FragCoord.xy/RENDERSIZE.xy; + + // normalize to the center + uv = uv - 0.5; + + // cartesian to polar coordinates + float r = length(uv); + float a = atan(uv.y, uv.x); + + // kaleidoscope + //float sides = 5.; + float tau = 2. * 3.1416; + a = mod(a, tau/sides); + a = abs(a - tau/sides/2.); + + // polar to cartesian coordinates + uv = r * vec2(cos(a), sin(a)); + + // recenter + uv = uv + 0.5; + + vec4 c = IMG_NORM_PIXEL(inputImage, uv); + gl_FragColor = c; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Key Frame Artifacts.fs b/src/renderer/src/application/sample-modules/isf/Key Frame Artifacts.fs new file mode 100644 index 000000000..53b409e8a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Key Frame Artifacts.fs @@ -0,0 +1,126 @@ +/* +{ + "CATEGORIES" : [ + "Glitch" + ], + "DESCRIPTION" : "Keeps an accumulation of difference since a key frame", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "updateKeyFrame", + "TYPE" : "bool", + "DEFAULT" : 0, + "LABEL" : "Update Key Frame" + }, + { + "NAME" : "adaptRate", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "LABEL" : "Adapt Rate", + "MIN" : 0 + }, + { + "NAME" : "numColors", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 1, + "LABEL" : "Color Quality", + "MIN" : 0 + }, + { + "NAME" : "buffQuality", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 1.0, + "LABEL" : "Buffer Quality", + "MIN" : 0 + } + ], + "PASSES" : [ + { + "TARGET" : "keyFrame", + "PERSISTENT" : true + }, + { + "TARGET" : "diffFrame", + "PERSISTENT" : true, + "WIDTH" : "max(8.0,floor($WIDTH*$buffQuality))", + "HEIGHT" : "max(8.0,floor($HEIGHT*$buffQuality))" + }, + { + "TARGET" : "lastFrame", + "PERSISTENT" : true + }, + { + + } + ], + "CREDIT" : "by zoidberg" +} +*/ + +vec3 cround (vec3 r) { + vec3 returnMe = r; + returnMe.r = (fract(returnMe.r) < 0.5) ? floor(returnMe.r) : ceil(returnMe.r); + returnMe.g = (fract(returnMe.g) < 0.5) ? floor(returnMe.g) : ceil(returnMe.g); + returnMe.b = (fract(returnMe.b) < 0.5) ? floor(returnMe.b) : ceil(returnMe.b); + return returnMe; +} + +void main() +{ + // on the first pass, set the key frame if needed + + if (PASSINDEX==0) { + + bool doUpdateKeyFrame = ((updateKeyFrame)||(FRAMEINDEX<5)) ? true : false; + vec4 result = (doUpdateKeyFrame) ? IMG_THIS_NORM_PIXEL(inputImage) : IMG_THIS_NORM_PIXEL(keyFrame); + if ((doUpdateKeyFrame)&&(numColors < 1.0)) { + float scaledColors = floor(pow(2.0,numColors * 8.0)); + scaledColors = (scaledColors < 2.0) ? 2.0 : scaledColors; + result.rgb = result.rgb * scaledColors; + result.rgb = cround(result.rgb); + result.rgb = result.rgb / (scaledColors); + } + gl_FragColor = result; + + } + // on the second pass, compare lastFrame to inputImage and add that amount to diffFrame + else if (PASSINDEX==1) { + bool doUpdateKeyFrame = ((updateKeyFrame)||(FRAMEINDEX<5)) ? true : false; + vec4 freshPixel = IMG_THIS_NORM_PIXEL(inputImage); + vec4 stalePixel = (doUpdateKeyFrame) ? IMG_THIS_NORM_PIXEL(keyFrame) : IMG_THIS_NORM_PIXEL(lastFrame); + vec4 diffPixel = (doUpdateKeyFrame) ? vec4(0.5) : IMG_THIS_NORM_PIXEL(diffFrame); + diffPixel.rgb = 2.0 * diffPixel.rgb - 1.0; + vec4 result = diffPixel + freshPixel - stalePixel; + result = (result + 1.0) / 2.0; + if (numColors < 1.0) { + float scaledColors = floor(pow(2.0,numColors * 8.0)); + scaledColors = (scaledColors < 2.0) ? 2.0 : scaledColors; + result.rgb = result.rgb * scaledColors; + result.rgb = cround(result.rgb); + result.rgb = result.rgb / (scaledColors); + } + + gl_FragColor = result; + + } + // add the new diffFrame to keyFrame to get the new resulting frame + else if (PASSINDEX==2) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + vec4 keyFramePixel = IMG_THIS_NORM_PIXEL(keyFrame); + vec4 diffPixel = IMG_THIS_NORM_PIXEL(diffFrame); + diffPixel.rgb = (2.0 * diffPixel.rgb - 1.0); + vec4 result = keyFramePixel + diffPixel; + vec4 freshPixel = IMG_THIS_NORM_PIXEL(inputImage); + result = mix(result,freshPixel,adaptRate); + gl_FragColor = result; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Layer Mask.fs b/src/renderer/src/application/sample-modules/isf/Layer Mask.fs new file mode 100644 index 000000000..16c0d4ac4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Layer Mask.fs @@ -0,0 +1,182 @@ +/*{ + "CATEGORIES": [ + "Masking", + "Color Effect", + "Utility" + ], + "CREDIT": "by zoidberg", + "DESCRIPTION": "Takes a mask image and applies it to the input image's alpha channel.", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "LABEL": "mask image", + "NAME": "maskImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "LABEL": "mask size mode", + "LABELS": [ + "Fit", + "Fill", + "Stretch", + "Copy" + ], + "NAME": "maskSizingMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3 + ] + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": -1, + "NAME": "bright", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 4, + "MIN": -4, + "NAME": "contrast", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "LABEL": "Alpha Mode", + "LABELS": [ + "Additive", + "Multiply", + "Replace" + ], + "NAME": "alphaMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + }, + { + "DEFAULT": 0, + "NAME": "applyAlpha", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +const vec4 lumcoeff = vec4(0.299, 0.587, 0.114, 0.0); + +// 'a' and 'b' are rects (x and y are the originx, z and w are the width and height) +// 'm' is the sizing mode as described above (fit/fill/stretch/copy) +vec4 RectThatFitsRectInRect(vec4 a, vec4 b, int m); + + + + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + + // get the rect of the mask image after it's been resized according to the passed sizing mode. this is in pixel coords relative to the rendering space! + //vec4 rectOfResizedMaskImage = RectThatFitsRectInRect(vec4(0.0, 0.0, _maskImage_imgRect.z, _maskImage_imgRect.w), vec4(0,0,RENDERSIZE.x,RENDERSIZE.y), maskSizingMode); + vec4 rectOfResizedMaskImage = RectThatFitsRectInRect(vec4(0.0, 0.0, IMG_SIZE(maskImage).x, IMG_SIZE(maskImage).y), vec4(0,0,RENDERSIZE.x,RENDERSIZE.y), maskSizingMode); + // i know the pixel coords of this frag in the render space- convert this to NORMALIZED texture coords for the resized mask image + vec2 normMaskSrcCoord; + normMaskSrcCoord.x = (gl_FragCoord.x-rectOfResizedMaskImage.x)/rectOfResizedMaskImage.z; + normMaskSrcCoord.y = (gl_FragCoord.y-rectOfResizedMaskImage.y)/rectOfResizedMaskImage.w; + + // get the color of the pixel from the mask image for these normalized coords + vec4 tmpColorA = IMG_NORM_PIXEL(maskImage, normMaskSrcCoord); + + // apply bright/contrast to this pixel value + vec4 tmpColorB = tmpColorA + vec4(bright, bright, bright, 0.0); + tmpColorA.rgb = ((vec3(2.0) * (tmpColorB.rgb - vec3(0.5))) * vec3(contrast) / vec3(2.0)) + vec3(0.5); + tmpColorA.a = ((2.0 * (tmpColorB.a - 0.5)) * abs(contrast) / 2.0) + 0.5; + + // get the luminance of this pixel value: this will be the new alpha for the source pixel + float luminance = dot(tmpColorA,lumcoeff); + + // if the alpha mode isn't replacing, add or multiply now + // (this makes it possible to stack multiple mask FX or preserve alpha from input) + if (alphaMode == 0) { + luminance = luminance + srcPixel.a; + } + else if (alphaMode == 1) { + luminance = luminance * srcPixel.a; + } + + if (applyAlpha) + gl_FragColor = vec4(luminance*srcPixel.r, luminance*srcPixel.g, luminance*srcPixel.b, 1.0); + else + gl_FragColor = vec4(srcPixel.r, srcPixel.g, srcPixel.b, luminance); +} + + +// rect that fits 'a' in 'b' using sizing mode 'm' +vec4 RectThatFitsRectInRect(vec4 a, vec4 b, int m) { + float bAspect = b.z/b.w; + float aAspect = a.z/a.w; + if (aAspect==bAspect) { + return b; + } + vec4 returnMe = vec4(0.0); + // fit + if (m==0) { + // if the rect i'm trying to fit stuff *into* is wider than the rect i'm resizing + if (bAspect > aAspect) { + returnMe.w = b.w; + returnMe.z = returnMe.w * aAspect; + } + // else if the rect i'm resizing is wider than the rect it's going into + else if (bAspect < aAspect) { + returnMe.z = b.z; + returnMe.w = returnMe.z / aAspect; + } + else { + returnMe.z = b.z; + returnMe.w = b.w; + } + returnMe.x = (b.z-returnMe.z)/2.0+b.x; + returnMe.y = (b.w-returnMe.w)/2.0+b.y; + } + // fill + else if (m==1) { + // if the rect i'm trying to fit stuff *into* is wider than the rect i'm resizing + if (bAspect > aAspect) { + returnMe.z = b.z; + returnMe.w = returnMe.z / aAspect; + } + // else if the rect i'm resizing is wider than the rect it's going into + else if (bAspect < aAspect) { + returnMe.w = b.w; + returnMe.z = returnMe.w * aAspect; + } + else { + returnMe.z = b.z; + returnMe.w = b.w; + } + returnMe.x = (b.z-returnMe.z)/2.0+b.x; + returnMe.y = (b.w-returnMe.w)/2.0+b.y; + } + // stretch + else if (m==2) { + returnMe = vec4(b.x, b.y, b.z, b.w); + } + // copy + else if (m==3) { + returnMe.z = float(int(a.z)); + returnMe.w = float(int(a.w)); + returnMe.x = float(int((b.z-returnMe.z)/2.0+b.x)); + returnMe.y = float(int((b.w-returnMe.w)/2.0+b.y)); + } + return returnMe; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Layer Position.fs b/src/renderer/src/application/sample-modules/isf/Layer Position.fs new file mode 100644 index 000000000..188f5eed2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Layer Position.fs @@ -0,0 +1,57 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment", "Utility" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "offset", + "TYPE": "point2D", + "DEFAULT": [ + 0.5, + 0.5 + ], + "MIN": [ + 0.0, + 0.0 + ], + "MAX": [ + 1.0, + 1.0 + ] + }, + { + "NAME": "repeatImage", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ] + +}*/ + +void main() { + vec4 outputColor = vec4(0.0); + vec2 newLoc = offset; + vec2 topSize = RENDERSIZE; + + newLoc = offset * RENDERSIZE; + newLoc.x = topSize.x - newLoc.x; + newLoc.y = topSize.y - newLoc.y; + newLoc = (gl_FragCoord.xy + 2.0*newLoc) - topSize; + + if (repeatImage) { + newLoc = mod(newLoc, RENDERSIZE); + } + + if ((newLoc.x >= 0.0)&&(newLoc.x < RENDERSIZE.x)&&(newLoc.y >= 0.0)&&(newLoc.y <= RENDERSIZE.y)) { + outputColor = IMG_PIXEL(inputImage, newLoc); + } + + gl_FragColor = outputColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Lens Flare.fs b/src/renderer/src/application/sample-modules/isf/Lens Flare.fs new file mode 100644 index 000000000..ffde4e630 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Lens Flare.fs @@ -0,0 +1,248 @@ +/*{ + "CATEGORIES": [ + "Film" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": -0.67, + "LABEL": "Bias", + "MAX": 0, + "MIN": -1, + "NAME": "uBias", + "TYPE": "float" + }, + { + "DEFAULT": 1.25, + "LABEL": "Scale", + "MAX": 10, + "MIN": 0, + "NAME": "uScale", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Source Gain", + "MAX": 1, + "MIN": 0, + "NAME": "uSource", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.75, + 0.875, + 1, + 0.75 + ], + "LABEL": "Chromatic Distortion", + "NAME": "uChromatic", + "TYPE": "color" + }, + { + "DEFAULT": 3, + "LABEL": "Ghosts", + "MAX": 5, + "MIN": 0, + "NAME": "uGhosts", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "LABEL": "Ghost Dispersal", + "MAX": 0.75, + "MIN": 0.125, + "NAME": "uGhostDispersal", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.67, + 0.5, + 0.75, + 1 + ], + "LABEL": "Lens Color", + "NAME": "uLensColor", + "TYPE": "color" + }, + { + "DEFAULT": 0.33, + "LABEL": "Halo Width", + "MAX": 1, + "MIN": 0, + "NAME": "uHaloWidth", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "LABEL": "Dirt", + "MAX": 1, + "MIN": 0, + "NAME": "uNoise", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "LABEL": "Center", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "uCenter", + "TYPE": "point2D" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "DESCRIPTION": "Downsample and threshold", + "HEIGHT": "floor($HEIGHT/2.0)", + "TARGET": "downsampleAndThresholdImage", + "WIDTH": "floor($WIDTH/2.0)" + }, + { + "DESCRIPTION": "Feature generation", + "HEIGHT": "floor($HEIGHT/2.0)", + "TARGET": "featureGenerationImage", + "WIDTH": "floor($WIDTH/2.0)" + }, + { + "DESCRIPTION": "Blur", + "HEIGHT": "floor($HEIGHT/4.0)", + "TARGET": "blurredImage", + "WIDTH": "floor($WIDTH/4.0)" + }, + { + } + ] +} +*/ + + +// as a guide, http://john-chapman-graphics.blogspot.co.uk/2013/02/pseudo-lens-flare.html +// 1 downsample and threshold +// 2 feature generation: ghosts, halos and chromatic distortion +// 3 blur +// 4 upscale / blend + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + + +const float seed = 0.87342; + + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + + +void main() +{ + + if (PASSINDEX == 0) { + vec2 loc = isf_FragNormCoord; + gl_FragColor = max(vec4(0.0), IMG_NORM_PIXEL(inputImage,loc) + uBias) * uScale; + } + else if (PASSINDEX == 1) { + // flip the coordinates on this pass + vec2 centerVec = 2.0*(uCenter - vec2(0.5)); + vec2 texcoord = vec2(1.0) - isf_FragNormCoord; + vec2 texelSize = 1.0 / RENDERSIZE; + vec2 ghostVec = (vec2(0.5) - texcoord + centerVec) * uGhostDispersal; + vec4 result = vec4(0.0); + for (int i = 0; i < 5; ++i) { + if (float(i)>uGhosts) + break; + vec2 offset = fract(texcoord + ghostVec * float(i)); + + //result += IMG_NORM_PIXEL(downsampleAndThresholdImage, offset); + + result.r += IMG_NORM_PIXEL(downsampleAndThresholdImage, offset * uChromatic.r).r * uChromatic.a; + result.g += IMG_NORM_PIXEL(downsampleAndThresholdImage, offset * uChromatic.g).g * uChromatic.a; + result.b += IMG_NORM_PIXEL(downsampleAndThresholdImage, offset * uChromatic.b).b * uChromatic.a; + + } + + //vec4 result = vec4(0.0); + for (int i = 0; i < 5; ++i) { + if (float(i)>uGhosts) + break; + vec2 offset = fract(texcoord + ghostVec * float(i)); + + float weight = length(vec2(0.5) - offset) / length(vec2(0.5)); + weight = pow(1.0 - weight, 10.0); + + result += IMG_NORM_PIXEL(downsampleAndThresholdImage, offset) * weight; + } + + vec2 haloVec = normalize(ghostVec) * uHaloWidth; + float weight = length(vec2(0.5) - fract(texcoord + haloVec)) / length(vec2(0.5)); + weight = pow(1.0 - weight, 5.0); + result += IMG_NORM_PIXEL(downsampleAndThresholdImage, texcoord + haloVec) * weight; + + result *= uLensColor; + + gl_FragColor = result; + } + else if (PASSINDEX == 2) { + vec4 color = IMG_THIS_NORM_PIXEL(featureGenerationImage); + vec4 colorL = IMG_NORM_PIXEL(featureGenerationImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(featureGenerationImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(featureGenerationImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(featureGenerationImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(featureGenerationImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(featureGenerationImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(featureGenerationImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(featureGenerationImage, rightb_coord); + + vec4 avg = (color + colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 9.0; + //avg = mix(color, (avg + depth*blur)/(1.0+depth), softness); + gl_FragColor = avg; + } + else { + vec2 loc = isf_FragNormCoord; + // SHOULD REPLACE THIS WITH A BETTER / ADJUSTABLE FILM GRAIN OR DIRT GENERATOR + vec4 noise = vec4(0.0); + noise = (1.0 - uNoise) + uNoise * vec4(rand(loc*vec2(uNoise*seed,uChromatic.x)),rand(loc*vec2(uNoise*seed,uChromatic.y)),rand(loc*vec2(uNoise*seed,uChromatic.z)),1.0); + + vec4 srcColor = IMG_NORM_PIXEL(inputImage,loc); + vec4 lensFlareColor = IMG_NORM_PIXEL(blurredImage,loc) * noise; + gl_FragColor = uSource * srcColor + lensFlareColor * lensFlareColor.a; + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Lens Flare.vs b/src/renderer/src/application/sample-modules/isf/Lens Flare.vs new file mode 100644 index 000000000..b0ba4b037 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Lens Flare.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Life.fs b/src/renderer/src/application/sample-modules/isf/Life.fs new file mode 100644 index 000000000..5a39cf2f7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Life.fs @@ -0,0 +1,154 @@ +/*{ + "DESCRIPTION": "Based on Conway Game of Life", + "CREDIT": "VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Noise" + ], + "INPUTS": [ + { + "NAME": "restartNow", + "TYPE": "event" + }, + { + "NAME": "startThresh", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME": "randomRegrowth", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 0.1 + }, + { + "NAME": "randomDeath", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 0.1 + } + ], + "PASSES": [ + { + "TARGET":"lastData", + "PERSISTENT": true + } + ] + +}*/ + + +/* + +Any live cell with fewer than two live neighbours dies, as if caused by under-population. +Any live cell with two or three live neighbours lives on to the next generation. +Any live cell with more than three live neighbours dies, as if by over-population. +Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. + +*/ + + + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + + + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 loc = gl_FragCoord.xy; + + if ((TIME < 0.1)||(restartNow)) { + // randomize the start conditions + float alive = rand(vec2(TIME+1.0,2.1*TIME+0.1)*loc); + if (alive > 1.0 - startThresh) { + inputPixelColor = vec4(1.0); + } + } + else { + vec4 color = IMG_PIXEL(lastData, loc); + vec4 colorL = IMG_PIXEL(lastData, left_coord); + vec4 colorR = IMG_PIXEL(lastData, right_coord); + vec4 colorA = IMG_PIXEL(lastData, above_coord); + vec4 colorB = IMG_PIXEL(lastData, below_coord); + + vec4 colorLA = IMG_PIXEL(lastData, lefta_coord); + vec4 colorRA = IMG_PIXEL(lastData, righta_coord); + vec4 colorLB = IMG_PIXEL(lastData, leftb_coord); + vec4 colorRB = IMG_PIXEL(lastData, rightb_coord); + + float neighborSum = gray(colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB); + float state = gray(color); + + // live cell + if (state > 0.0) { + if (neighborSum < 2.0) { + // under population + inputPixelColor = vec4(0.0); + } + else if (neighborSum < 4.0) { + // status quo + inputPixelColor = vec4(1.0); + + // spontaneous death? + float alive = rand(vec2(TIME+1.0,2.1*TIME+0.1)*loc); + if (alive > 1.0 - randomDeath) { + inputPixelColor = vec4(0.0); + } + } + else { + // over population + inputPixelColor = vec4(0.0); + } + } + // dead cell + else { + if ((neighborSum > 2.0)&&(neighborSum < 4.0)) { + // reproduction + inputPixelColor = vec4(1.0); + } + else if (neighborSum < 2.0) { + // spontaneous reproduction + float alive = rand(vec2(TIME+1.0,2.1*TIME+0.1)*loc); + if (alive > 1.0 - randomRegrowth) { + inputPixelColor = vec4(1.0); + } + } + } + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Life.vs b/src/renderer/src/application/sample-modules/isf/Life.vs new file mode 100644 index 000000000..1821fadd6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Life.vs @@ -0,0 +1,38 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]) * RENDERSIZE; + + left_coord = vec2(texc.xy + vec2(-1.0 , 0)); + right_coord = vec2(texc.xy + vec2(1.0 , 0)); + above_coord = vec2(texc.xy + vec2(0,1.0)); + below_coord = vec2(texc.xy + vec2(0,-1.0)); + + lefta_coord = vec2(texc.xy + vec2(-1.0 , 1.0)); + righta_coord = vec2(texc.xy + vec2(1.0 , 1.0)); + leftb_coord = vec2(texc.xy + vec2(-1.0 , -1.0)); + rightb_coord = vec2(texc.xy + vec2(1.0 , -1.0)); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Line Screen.fs b/src/renderer/src/application/sample-modules/isf/Line Screen.fs new file mode 100644 index 000000000..1292ae514 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Line Screen.fs @@ -0,0 +1,109 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Halftone Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "sharpness", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 1.0 + }, + { + "NAME": "offset", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "angle", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "scale", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 1.0 + }, + { + "NAME": "colorize", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "fill", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +const float tau = 6.28318530718; + +float pattern() { + float s = sin(tau * angle * 0.5); + float c = cos(tau * angle * 0.5); + vec2 tex = isf_FragNormCoord * RENDERSIZE; + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * max(scale,0.001); + float d = point.y; + + return ( fill + sin(d + offset * tau) ) * 4.0; +} + +void main() { + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = color + sharpness * (8.0*color - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + + float average = ( final.r + final.g + final.b ) / 3.0; + final = vec4( vec3( average * 10.0 - 5.0 + pattern() ), color.a ); + final = mix (color * final, final, 1.0-colorize); + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Line Screen.vs b/src/renderer/src/application/sample-modules/isf/Line Screen.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Line Screen.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Linear Blur.fs b/src/renderer/src/application/sample-modules/isf/Linear Blur.fs new file mode 100644 index 000000000..e77092756 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Linear Blur.fs @@ -0,0 +1,76 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/LinearBlur.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: gre +// license: MIT +const int passes = 6; + +vec4 transition(vec2 uv) { + vec4 c1 = vec4(0.0); + vec4 c2 = vec4(0.0); + + float disp = intensity*(0.5-distance(0.5, progress)); + for (int xi=0; xi spacing) { + w = 0.99*spacing; + } + return ( mod(d + shift*spacing + w * 0.5,spacing) ); +} + + +void main() { + // determine if we are on a line + // math goes something like, figure out distance to the closest line, then draw color2 if we're within range + // y = m*x + b + // m = (y1-y0)/(x1-x0) = tan(angle) + + vec4 out_color = color2; + float w = line_width; + if (w > spacing) { + w = 0.99*spacing; + } + float pat = pattern(); + if ((pat > 0.0)&&(pat <= w)) { + float percent = (1.0-abs(w-2.0*pat)/w); + percent = clamp(percent,0.0,1.0); + out_color = mix(color2,color1,percent); + } + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/LogTransWarpSpiral.fs b/src/renderer/src/application/sample-modules/isf/LogTransWarpSpiral.fs new file mode 100644 index 000000000..043f3924f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/LogTransWarpSpiral.fs @@ -0,0 +1,55 @@ +/*{ + "CREDIT": "by mojovideotech", + "CATEGORIES" : [ + "Distortion Effect", + "Geometry Adjustment", + "spiral", + "logarithmic", + "coordinatetransform" + ], + "DESCRIPTION" : "Transformation from screen-coordinates to logarithmic spiral with golden angle.", + "INPUTS" : [ + { + "NAME": "rate", + "TYPE": "float", + "DEFAULT": 0.25, + "MIN": -1.0, + "MAX": 1.0 + }, + { + "NAME" : "imageInput", + "TYPE" : "image", + "LABEL" : "imageInput" + }, + { + "NAME": "sinwarp", + "TYPE": "bool", + "DEFAULT": false + } + ] +} +*/ + +//////////////////////////////////////////////////////////// +// LogTransWarpSpiral by mojovideotech +// +// based on : +// shadertoy.com\/Msd3Dn +// Logarithmic Spiral Transform - 2015-12-02 by Jakob Thomsen +// +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +//////////////////////////////////////////////////////////// + + +#define twpi 6.2831853 // two pi, 2*pi +#define piphi 2.3999632 // pi*(3-sqrt(5)) + +void main() { + float T = TIME * rate; + vec2 p = (gl_FragCoord.xy+gl_FragCoord.xy-RENDERSIZE.xy)/RENDERSIZE.y; + p = vec2(0.0, T - log2(length(p.xy))) + atan(p.y, p.x) / twpi; + p.x = ceil(p.y) - p.x; + p.x *= piphi; + if (sinwarp) p.y -= pow(T/twpi,sin(T)); + gl_FragColor = IMG_NORM_PIXEL(imageInput,fract(p.yx+T)); +} diff --git a/src/renderer/src/application/sample-modules/isf/Long Exposure.fs b/src/renderer/src/application/sample-modules/isf/Long Exposure.fs new file mode 100644 index 000000000..dec72f3a7 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Long Exposure.fs @@ -0,0 +1,66 @@ +/* +{ + "CATEGORIES" : [ + "Color Effect", "Feedback", "Film" + ], + "DESCRIPTION" : "Bright objects burn in and optionally decay", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "absorptionRate", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "MIN" : -1, + "LABEL" : "Absorption Rate" + }, + { + "NAME" : "dischargeRate", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.25, + "MIN" : 0, + "LABEL" : "Discharge Rate" + } + ], + "PASSES" : [ + { + "TARGET" : "feedbackBuffer", + "PERSISTENT" : true, + "FLOAT" : true + }, + { + + } + ], + "CREDIT" : "VIDVOX" +} +*/ + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + vec4 stalePixel = IMG_PIXEL(feedbackBuffer,gl_FragCoord.xy); + vec4 resultPixel = vec4(0.0); + + // absorb and discharge + if (PASSINDEX==0) { + // start with the old pixel amount + resultPixel = stalePixel; + // discharge from previous pass + resultPixel *= (1.0 - dischargeRate); + // add this pass + resultPixel += (freshPixel * absorptionRate); + resultPixel = clamp(resultPixel,0.0,1.0); + } + // composite + else if (PASSINDEX==1) { + resultPixel = freshPixel + stalePixel; + } + + gl_FragColor = resultPixel; +} diff --git a/src/renderer/src/application/sample-modules/isf/Luma Transition.fs b/src/renderer/src/application/sample-modules/isf/Luma Transition.fs new file mode 100644 index 000000000..470bb1244 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Luma Transition.fs @@ -0,0 +1,60 @@ +/* +{ + "ISFVSN" : "2", + "CATEGORIES" : [ + "Dissolve" + ], + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "TYPE" : "float", + "MIN" : 0, + "DEFAULT" : 0, + "NAME" : "progress", + "MAX" : 1 + }, + { + "NAME" : "luma", + "TYPE" : "image" + } + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/luma.glsl", + "DESCRIPTION" : "Automatically converted from https://gl-transitions.com/" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +vec4 transition(vec2 uv) { + return mix( + getToColor(uv), + getFromColor(uv), + step(progress, IMG_NORM_PIXEL(luma, uv).r-0.001) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Luminance Melt.fs b/src/renderer/src/application/sample-modules/isf/Luminance Melt.fs new file mode 100644 index 000000000..86e84f8a8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Luminance Melt.fs @@ -0,0 +1,183 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/luminance_melt.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": true, + "NAME": "direction", + "TYPE": "bool" + }, + { + "DEFAULT": 0.8, + "MAX": 1, + "MIN": 0, + "NAME": "l_threshold", + "TYPE": "float" + }, + { + "NAME": "above", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: 0gust1 +// License: MIT +//My own first transition — based on crosshatch code (from pthrasher), using simplex noise formula (copied and pasted) +//-> cooler with high contrasted images (isolated dark subject on light background f.e.) +//TODO : try to rebase it on DoomTransition (from zeh)? +//optimizations : +//luminance (see http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color#answer-596241) +// Y = (R+R+B+G+G+G)/6 +//or Y = (R+R+R+B+G+G+G+G)>>3 + + +//direction of movement : 0 : up, 1, down +//luminance threshold +//does the movement takes effect above or below luminance threshold ? + + +//Random function borrowed from everywhere +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +// Simplex noise : +// Description : Array and textureless GLSL 2D simplex noise function. +// Author : Ian McEwan, Ashima Arts. +// Maintainer : ijm +// Lastmod : 20110822 (ijm) +// License : MIT +// 2011 Ashima Arts. All rights reserved. +// Distributed under the MIT License. See LICENSE file. +// https://github.com/ashima/webgl-noise +// + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float snoise(vec2 v) + { + const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 + 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) + -0.577350269189626, // -1.0 + 2.0 * C.x + 0.024390243902439); // 1.0 / 41.0 +// First corner + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + +// Other corners + vec2 i1; + //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 + //i1.y = 1.0 - i1.x; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + // x0 = x0 - 0.0 + 0.0 * C.xx ; + // x1 = x0 - i1 + 1.0 * C.xx ; + // x2 = x0 - 1.0 + 2.0 * C.xx ; + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + +// Permutations + i = mod289(i); // Avoid truncation effects in permutation + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + +// Gradients: 41 points uniformly over a line, mapped onto a diamond. +// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + +// Normalise gradients implicitly by scaling m +// Approximation of: m *= inversesqrt( a0*a0 + h*h ); + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + +// Compute final noise value at P + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +// Simplex noise -- end + +float luminance(vec4 color){ + //(0.299*R + 0.587*G + 0.114*B) + return color.r*0.299+color.g*0.587+color.b*0.114; +} + +vec2 center = vec2(1.0, direction); + +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy; + if (progress == 0.0) { + return getFromColor(p); + } else if (progress == 1.0) { + return getToColor(p); + } else { + float x = progress; + float dist = distance(center, p)- progress*exp(snoise(vec2(p.x, 0.0))); + float r = x - rand(vec2(p.x, 0.1)); + float m; + if(above){ + m = dist <= r && luminance(getFromColor(p))>l_threshold ? 1.0 : (progress*progress*progress); + } + else{ + m = dist <= r && luminance(getFromColor(p)) 1000000.) + break; + } + float r = length(z); + + return vec2(iter, r/abs(dr)); +} + + +//---------------------------------------------------------------------------------------- +float Map(vec3 pos) +{ + + if ( type == 0.0 ) + return DE(pos).y; + + vec4 p = vec4(pos,1); + vec4 p0 = p; // p.w is the distance estimate + + for (int i = 0; i < 11; i++) + { + p.xyz = clamp(p.xyz, -1.0, 1.0) * 2.0 - p.xyz; + + // sphere folding: + float r2 = dot(p.xyz, p.xyz); + + //if (r2 < minRad2) p /= minRad2; else if (r2 < 1.0) p /= r2; + p *= clamp(max(minRad2/r2, minRad2), 0.0, 1.0); + + // scale, translate + p = p*scale + p0; + } + + return ((length(p.xyz) - abs(SCALE) + 1.0) / p.w); +} + +vec3 hsv(in float h, in float s, in float v) { + return mix(vec3(1.0), clamp((abs(fract(h + vec3(3, 2, 1) / 3.0) * 6.0 - 3.0) - 1.0), 0.0 , 1.0), s) * v; +} + +mat3 RotationMatrix(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c); +} + +vec3 CameraPath( float t ) +{ + vec3 p = vec3(-.81 + 3. * sin(2.14*t),.05+2.5 * sin(.942*t+1.3),.05 + 3.5 * cos(3.594*t) ); + return p * e0; +} + +mat3 lookAt(vec3 eye, vec3 center, vec3 up) +{ + vec3 zaxis = normalize(center - eye); + vec3 xaxis = normalize(cross(up, zaxis)); + vec3 yaxis = cross(zaxis, xaxis); + + mat3 matrix; + //Column Major + matrix[0][0] = xaxis.x; + matrix[1][0] = yaxis.x; + matrix[2][0] = zaxis.x; + + matrix[0][1] = xaxis.y; + matrix[1][1] = yaxis.y; + matrix[2][1] = zaxis.y; + + matrix[0][2] = xaxis.z; + matrix[1][2] = yaxis.z; + matrix[2][2] = zaxis.z; + + return matrix; +} + +void main() { + + vec2 xy = gl_FragCoord.xy / RENDERSIZE - 0.5; + float aspect = RENDERSIZE.x / RENDERSIZE.y; + xy = vec2(aspect, 1.0) * xy * 2.; + + + float t = 0.; + vec3 p; + float t_cam = TIME/speed; + + + vec3 camPos = CameraPath(t_cam); + if ( speed == 0.0 ) + camPos = vec3(x,y,z) * e0; + + vec3 forward = CameraPath(t_cam + 0.1) - camPos; + + vec3 ray = normalize(vec3(xy, 1.0)); + //mat3 rot_cam = RotationMatrix(vec3(1., 1., 0.), t_cam); + mat3 rot_cam = lookAt(normalize(camPos), vec3(0.0,0.0,0.0), vec3(0.0,1.0, 0.0)); + + + ray = rot_cam * ray; + + // if(rotation < 4.) + // ray = RotationMatrix(vec3(0.,0.,1.), rotation * length(xy)) * ray; + // else + // ray = RotationMatrix(vec3(sin(TIME/speed/10. + 3.),sin(TIME/speed/20.),1.), sin(rotation * TIME/speed) ) * ray; + + float iter = 0.; + bool hit = false; + float last_t = 0.; + + for (int i = 0; i < MAX_ITER; i++) { + p = t * ray + camPos; + float d = Map(p); + float thr = exp ( t * dthresh)/ pow(10.0, 3.4) ; + if (d < thr){ + + hit = true; + break; + } + last_t = t; + t += d ;//max(thr, d); + iter++; + } + + + float d = 1.0 - iter / float(MAX_ITER); + + vec3 color = hsv(d/3. + dhue,1.,d); + + if(dhue == 0.0) + color = vec3(d); + + //color = hsv(t/4.,1.,1. - t/4.); + + gl_FragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Maximum Component.fs b/src/renderer/src/application/sample-modules/isf/Maximum Component.fs new file mode 100644 index 000000000..a5b34ef05 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Maximum Component.fs @@ -0,0 +1,19 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect", "Utility" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ] +}*/ + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + float maxComponent = max(srcPixel.r, max(srcPixel.g, srcPixel.b)); + gl_FragColor = vec4(maxComponent, maxComponent, maxComponent, srcPixel.a); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Median.fs b/src/renderer/src/application/sample-modules/isf/Median.fs new file mode 100644 index 000000000..e07fe1e8f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Median.fs @@ -0,0 +1,70 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "radius", + "LABEL": "Radius", + "TYPE": "float", + "DEFAULT": 4.0, + "MIN": 0.0, + "MAX": 8.0 + } + ] + +}*/ + + + +// adapted from +// 3x3 Median by Morgan McGuire and Kyle Whitson +// http://graphics.cs.williams.edu/papers/MedianShaderX6/median.pix + + + +#define s2(a, b) temp = a; a = min(a, b); b = max(temp, b); +#define mn3(a, b, c) s2(a, b); s2(a, c); +#define mx3(a, b, c) s2(b, c); s2(a, c); + +#define mnmx3(a, b, c) mx3(a, b, c); s2(a, b); // 3 exchanges +#define mnmx4(a, b, c, d) s2(a, b); s2(c, d); s2(a, c); s2(b, d); // 4 exchanges +#define mnmx5(a, b, c, d, e) s2(a, b); s2(c, d); mn3(a, c, e); mx3(b, d, e); // 6 exchanges +#define mnmx6(a, b, c, d, e, f) s2(a, d); s2(b, e); s2(c, f); mn3(a, b, c); mx3(d, e, f); // 7 exchanges + + + + +void main() { + vec4 v[9]; + + // Add the pixels which make up our window to the pixel array. + for(int dX = -1; dX <= 1; ++dX) { + for(int dY = -1; dY <= 1; ++dY) { + vec2 offset = vec2(float(dX), float(dY)); + + // If a pixel in the window is located at (x+dX, y+dY), put it at index (dX + R)(2R + 1) + (dY + R) of the + // pixel array. This will fill the pixel array, with the top left pixel of the window at pixel[0] and the + // bottom right pixel of the window at pixel[N-1]. + v[(dX + 1) * 3 + (dY + 1)] = IMG_PIXEL(inputImage, gl_FragCoord.xy + offset * radius); + //v[(dX + 1) * 3 + (dY + 1)] = IMG_PIXEL(inputImage, gl_FragCoord.xy); + } + } + + vec4 temp; + + // Starting with a subset of size 6, remove the min and max each time + mnmx6(v[0], v[1], v[2], v[3], v[4], v[5]); + mnmx5(v[1], v[2], v[3], v[4], v[6]); + mnmx4(v[2], v[3], v[4], v[7]); + mnmx3(v[3], v[4], v[8]); + + gl_FragColor = v[4]; +} diff --git a/src/renderer/src/application/sample-modules/isf/Meta Image.fs b/src/renderer/src/application/sample-modules/isf/Meta Image.fs new file mode 100644 index 000000000..7e6f2ecb3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Meta Image.fs @@ -0,0 +1,97 @@ +/*{ + "CREDIT": "by Toneburst", + "CATEGORIES": [ + "Toneburst", "Tile Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "cell_size", + "TYPE": "float", + "MIN": 0.001, + "MAX": 1.0, + "DEFAULT": 0.125 + }, + { + "NAME": "zoom_tile", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 1.0 + }, + { + "NAME": "mixAmount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "mode", + "VALUES": [ + 0, + 1 + ], + "LABELS": [ + "Multiply", + "Mix" + ], + "DEFAULT": 0, + "TYPE": "long" + } + ] +}*/ + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + // Position of current pixel + vec2 xy = gl_FragCoord.xy / RENDERSIZE.xy; + // Left and right of tile + float CellWidth = cell_size; + float CellHeight = cell_size; + float x1 = floor(xy.x / CellWidth)*CellWidth; + float x2 = clamp((ceil(xy.x / CellWidth)*CellWidth), 0.0, 1.0); + // Top and bottom of tile + float y1 = floor(xy.y / CellHeight)*CellHeight; + float y2 = clamp((ceil(xy.y / CellHeight)*CellHeight), 0.0, 1.0); + + // GET AVERAGE CELL COLOUR + // Average left and right pixels + vec4 avgX = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x2, y1)))) / 2.0; + // Average top and bottom pixels + vec4 avgY = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x1, y2)))) / 2.0; + // Centre pixel + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + vec4 avgClr = (avgX+avgY+avgC) / 3.0; + + // GET PIXELS FROM LITTLE IMAGE + // X-position in current cell + float cellPosX = (xy.x - x1) / CellWidth; + // Y-position in current cell + float cellPosY = (xy.y - y1) / CellHeight; + + vec2 loc = vec2(cellPosX, cellPosY); + vec2 modifiedCenter = vec2(0.5); + loc.x = (loc.x - modifiedCenter.x)*(1.0/zoom_tile) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/zoom_tile) + modifiedCenter.y; + + vec4 littlePix; + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + littlePix = vec4(0.0); + } + else { + littlePix = IMG_NORM_PIXEL(inputImage, loc); + } + + // MULTIPLY LITTLE IMAGE COLOUR WITH AVERAGE CELL COLOUR AND OUTPUT + if (mode == 0) { + gl_FragColor = vec4(littlePix * avgClr); + } + else { + gl_FragColor = vec4(mix(littlePix, avgClr, mixAmount)); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Micro Buffer RGB.fs b/src/renderer/src/application/sample-modules/isf/Micro Buffer RGB.fs new file mode 100644 index 000000000..ace9480c4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Micro Buffer RGB.fs @@ -0,0 +1,316 @@ +/* +{ + "CATEGORIES" : [ + "Glitch" + ], + "DESCRIPTION" : "Buffers 8 recent frames", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "inputRate", + "TYPE" : "float", + "MAX" : 20, + "DEFAULT" : 1, + "LABEL" : "Buffer Lag", + "MIN" : 0 + }, + { + "LABELS" : [ + "Color Picker", + "Image Input" + ], + "NAME" : "delayMode", + "TYPE" : "long", + "LABEL" : "Delay Mode", + "VALUES" : [ + 0, + 1 + ] + }, + { + "NAME" : "inputDelay", + "LABEL" : "Buffer", + "TYPE" : "color" + }, + { + "NAME" : "inputDelayImage", + "TYPE" : "image", + "LABEL" : "Delay Image" + } + ], + "PASSES" : [ + { + "WIDTH" : "1", + "DESCRIPTION" : "this buffer stores the last frame's odd \/ even state", + "HEIGHT" : "1", + "TARGET" : "lastRow", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer8", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer7", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer6", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer5", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer4", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer3", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer2", + "PERSISTENT" : true + }, + { + "TARGET" : "buffer1", + "PERSISTENT" : true + }, + { + + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + +void main() +{ + // first pass: read the "buffer7" into "buffer8" + // apply lag on each pass + // if this is the first pass, i'm going to read the position from the "lastRow" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastRow,vec2(0.5)); + // i'm only using the X and Y components, which are the X and Y offset (normalized) for the frame + if (inputRate == 0.0) { + srcPixel.x = 0.0; + srcPixel.y = 0.0; + } + else if (inputRate <= 1.0) { + srcPixel.x = (srcPixel.x) > 0.5 ? 0.0 : 1.0; + srcPixel.y = 0.0; + } + else { + srcPixel.x = srcPixel.x + 1.0 / inputRate + srcPixel.y; + if (srcPixel.x > 1.0) { + srcPixel.y = mod(srcPixel.x, 1.0); + srcPixel.x = 0.0; + } + } + gl_FragColor = srcPixel; + } + if (PASSINDEX == 1) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer8); + } + } + else if (PASSINDEX == 2) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + } + else if (PASSINDEX == 3) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + } + else if (PASSINDEX == 4) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + } + else if (PASSINDEX == 5) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + } + else if (PASSINDEX == 6) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + } + else if (PASSINDEX == 7) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + } + else if (PASSINDEX == 8) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + } + else if (PASSINDEX == 9) { + // Figure out which section I'm in and draw the appropriate buffer there + vec2 tex = isf_FragNormCoord; + vec4 color = vec4(0.0); + vec4 pixelBuffer = vec4(0.0); + if (delayMode == 0) { + pixelBuffer = inputDelay * 9.0; + } + else if (delayMode == 1) { + pixelBuffer = IMG_NORM_PIXEL(inputDelayImage, tex) * 9.0; + } + + if (pixelBuffer.r < 1.0) { + color.r = IMG_NORM_PIXEL(inputImage, tex).r; + } + else if (pixelBuffer.r < 2.0) { + color.r = IMG_NORM_PIXEL(buffer1, tex).r; + } + else if (pixelBuffer.r < 3.0) { + color.r = IMG_NORM_PIXEL(buffer2, tex).r; + } + else if (pixelBuffer.r < 4.0) { + color.r = IMG_NORM_PIXEL(buffer3, tex).r; + } + else if (pixelBuffer.r < 5.0) { + color.r = IMG_NORM_PIXEL(buffer4, tex).r; + } + else if (pixelBuffer.r < 6.0) { + color.r = IMG_NORM_PIXEL(buffer5, tex).r; + } + else if (pixelBuffer.r < 7.0) { + color.r = IMG_NORM_PIXEL(buffer6, tex).r; + } + else if (pixelBuffer.r < 8.0) { + color.r = IMG_NORM_PIXEL(buffer7, tex).r; + } + else { + color.r = IMG_NORM_PIXEL(buffer8, tex).r; + } + + if (pixelBuffer.g < 1.0) { + color.g = IMG_NORM_PIXEL(inputImage, tex).g; + } + else if (pixelBuffer.g < 2.0) { + color.g = IMG_NORM_PIXEL(buffer1, tex).g; + } + else if (pixelBuffer.g < 3.0) { + color.g = IMG_NORM_PIXEL(buffer2, tex).g; + } + else if (pixelBuffer.g < 4.0) { + color.g = IMG_NORM_PIXEL(buffer3, tex).g; + } + else if (pixelBuffer.g < 5.0) { + color.g = IMG_NORM_PIXEL(buffer4, tex).g; + } + else if (pixelBuffer.g < 6.0) { + color.g = IMG_NORM_PIXEL(buffer5, tex).g; + } + else if (pixelBuffer.g < 7.0) { + color.g = IMG_NORM_PIXEL(buffer6, tex).g; + } + else if (pixelBuffer.g < 8.0) { + color.g = IMG_NORM_PIXEL(buffer7, tex).g; + } + else { + color.g = IMG_NORM_PIXEL(buffer8, tex).g; + } + + if (pixelBuffer.b < 1.0) { + color.b = IMG_NORM_PIXEL(inputImage, tex).b; + } + else if (pixelBuffer.b < 2.0) { + color.b = IMG_NORM_PIXEL(buffer1, tex).b; + } + else if (pixelBuffer.b < 3.0) { + color.b = IMG_NORM_PIXEL(buffer2, tex).b; + } + else if (pixelBuffer.b < 4.0) { + color.b = IMG_NORM_PIXEL(buffer3, tex).b; + } + else if (pixelBuffer.b < 5.0) { + color.b = IMG_NORM_PIXEL(buffer4, tex).b; + } + else if (pixelBuffer.b < 6.0) { + color.b = IMG_NORM_PIXEL(buffer5, tex).b; + } + else if (pixelBuffer.b < 7.0) { + color.b = IMG_NORM_PIXEL(buffer6, tex).b; + } + else if (pixelBuffer.b < 8.0) { + color.b = IMG_NORM_PIXEL(buffer7, tex).b; + } + else { + color.b = IMG_NORM_PIXEL(buffer8, tex).b; + } + + if (pixelBuffer.a < 1.0) { + color.a = IMG_NORM_PIXEL(inputImage, tex).a; + } + else if (pixelBuffer.a < 2.0) { + color.a = IMG_NORM_PIXEL(buffer1, tex).a; + } + else if (pixelBuffer.a < 3.0) { + color.a = IMG_NORM_PIXEL(buffer2, tex).a; + } + else if (pixelBuffer.a < 4.0) { + color.a = IMG_NORM_PIXEL(buffer3, tex).a; + } + else if (pixelBuffer.a < 5.0) { + color.a = IMG_NORM_PIXEL(buffer4, tex).a; + } + else if (pixelBuffer.a < 6.0) { + color.a = IMG_NORM_PIXEL(buffer5, tex).a; + } + else if (pixelBuffer.a < 7.0) { + color.a = IMG_NORM_PIXEL(buffer6, tex).a; + } + else if (pixelBuffer.a < 8.0) { + color.a = IMG_NORM_PIXEL(buffer7, tex).a; + } + else { + color.a = IMG_NORM_PIXEL(buffer8, tex).a; + } + + gl_FragColor = color; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Micro Buffer.fs b/src/renderer/src/application/sample-modules/isf/Micro Buffer.fs new file mode 100644 index 000000000..3f9f21f9b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Micro Buffer.fs @@ -0,0 +1,312 @@ +/*{ + "DESCRIPTION": "Buffers 8 recent frames", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "inputDelay", + "LABEL": "Buffer", + "TYPE": "float", + "MIN": 0.0, + "MAX": 9.0, + "DEFAULT": 0.0 + }, + { + "NAME": "inputDelay2", + "LABEL": "Buffer 2", + "TYPE": "float", + "MIN": 0.0, + "MAX": 9.0, + "DEFAULT": 0.0 + }, + { + "NAME": "inputDelay3", + "LABEL": "Buffer 3", + "TYPE": "float", + "MIN": 0.0, + "MAX": 9.0, + "DEFAULT": 0.0 + }, + { + "NAME": "inputRate", + "LABEL": "Buffer Lag", + "TYPE": "float", + "MIN": 0.0, + "MAX": 20.0, + "DEFAULT": 1.0 + }, + { + "NAME": "mode", + "VALUES": [ + 0, + 1, + 2 + ], + "LABELS": [ + "Single", + "Double", + "Triple" + ], + "DEFAULT": 0, + "TYPE": "long" + } + ], + "PASSES": [ + { + "TARGET":"lastRow", + "WIDTH": "1", + "HEIGHT": "1", + "PERSISTENT": true, + "DESCRIPTION": "this buffer stores the last frame's odd / even state" + }, + { + "TARGET":"buffer8", + "PERSISTENT": true + }, + { + "TARGET":"buffer7", + "PERSISTENT": true + }, + { + "TARGET":"buffer6", + "PERSISTENT": true + }, + { + "TARGET":"buffer5", + "PERSISTENT": true + }, + { + "TARGET":"buffer4", + "PERSISTENT": true + }, + { + "TARGET":"buffer3", + "PERSISTENT": true + }, + { + "TARGET":"buffer2", + "PERSISTENT": true + }, + { + "TARGET":"buffer1", + "PERSISTENT": true + }, + { + + } + ] + +}*/ + +void main() +{ + // first pass: read the "buffer7" into "buffer8" + // apply lag on each pass + // if this is the first pass, i'm going to read the position from the "lastRow" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastRow,vec2(0.5)); + // i'm only using the X and Y components, which are the X and Y offset (normalized) for the frame + if (inputRate == 0.0) { + srcPixel.x = 0.0; + srcPixel.y = 0.0; + } + else if (inputRate <= 1.0) { + srcPixel.x = (srcPixel.x) > 0.5 ? 0.0 : 1.0; + srcPixel.y = 0.0; + } + else { + srcPixel.x = srcPixel.x + 1.0 / inputRate + srcPixel.y; + if (srcPixel.x > 1.0) { + srcPixel.y = mod(srcPixel.x, 1.0); + srcPixel.x = 0.0; + } + } + gl_FragColor = srcPixel; + } + if (PASSINDEX == 1) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer8); + } + } + else if (PASSINDEX == 2) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + } + else if (PASSINDEX == 3) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + } + else if (PASSINDEX == 4) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + } + else if (PASSINDEX == 5) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + } + else if (PASSINDEX == 6) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + } + else if (PASSINDEX == 7) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + } + else if (PASSINDEX == 8) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + } + else if (PASSINDEX == 9) { + // Figure out which section I'm in and draw the appropriate buffer there + vec2 tex = isf_FragNormCoord; + vec4 returnMe = vec4(0.0); + vec4 color = vec4(0.0); + float pixelBuffer = inputDelay; + + // If the glitch factor is turned on I might randomly go to another delay point + + if (pixelBuffer < 1.0) { + color = IMG_NORM_PIXEL(inputImage, tex); + } + else if (pixelBuffer < 2.0) { + color = IMG_NORM_PIXEL(buffer1, tex); + } + else if (pixelBuffer < 3.0) { + color = IMG_NORM_PIXEL(buffer2, tex); + } + else if (pixelBuffer < 4.0) { + color = IMG_NORM_PIXEL(buffer3, tex); + } + else if (pixelBuffer < 5.0) { + color = IMG_NORM_PIXEL(buffer4, tex); + } + else if (pixelBuffer < 6.0) { + color = IMG_NORM_PIXEL(buffer5, tex); + } + else if (pixelBuffer < 7.0) { + color = IMG_NORM_PIXEL(buffer6, tex); + } + else if (pixelBuffer < 8.0) { + color = IMG_NORM_PIXEL(buffer7, tex); + } + else { + color = IMG_NORM_PIXEL(buffer8, tex); + } + + returnMe = color; + + if ((mode == 1)||(mode == 2)) { + pixelBuffer = inputDelay2; + if (pixelBuffer < 1.0) { + color = IMG_NORM_PIXEL(inputImage, tex); + } + else if (pixelBuffer < 2.0) { + color = IMG_NORM_PIXEL(buffer1, tex); + } + else if (pixelBuffer < 3.0) { + color = IMG_NORM_PIXEL(buffer2, tex); + } + else if (pixelBuffer < 4.0) { + color = IMG_NORM_PIXEL(buffer3, tex); + } + else if (pixelBuffer < 5.0) { + color = IMG_NORM_PIXEL(buffer4, tex); + } + else if (pixelBuffer < 6.0) { + color = IMG_NORM_PIXEL(buffer5, tex); + } + else if (pixelBuffer < 7.0) { + color = IMG_NORM_PIXEL(buffer6, tex); + } + else if (pixelBuffer < 8.0) { + color = IMG_NORM_PIXEL(buffer7, tex); + } + else { + color = IMG_NORM_PIXEL(buffer8, tex); + } + returnMe = (returnMe + color); + } + + if (mode == 2) { + pixelBuffer = inputDelay3; + if (pixelBuffer < 1.0) { + color = IMG_NORM_PIXEL(inputImage, tex); + } + else if (pixelBuffer < 2.0) { + color = IMG_NORM_PIXEL(buffer1, tex); + } + else if (pixelBuffer < 3.0) { + color = IMG_NORM_PIXEL(buffer2, tex); + } + else if (pixelBuffer < 4.0) { + color = IMG_NORM_PIXEL(buffer3, tex); + } + else if (pixelBuffer < 5.0) { + color = IMG_NORM_PIXEL(buffer4, tex); + } + else if (pixelBuffer < 6.0) { + color = IMG_NORM_PIXEL(buffer5, tex); + } + else if (pixelBuffer < 7.0) { + color = IMG_NORM_PIXEL(buffer6, tex); + } + else if (pixelBuffer < 8.0) { + color = IMG_NORM_PIXEL(buffer7, tex); + } + else { + color = IMG_NORM_PIXEL(buffer8, tex); + } + returnMe = (returnMe + color); + } + + returnMe = returnMe / (1.0+float(mode)); + + gl_FragColor = returnMe; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Minimum Component.fs b/src/renderer/src/application/sample-modules/isf/Minimum Component.fs new file mode 100644 index 000000000..c27fdfead --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Minimum Component.fs @@ -0,0 +1,21 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Utility" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + float minComponent = min(srcPixel.r, min(srcPixel.g, srcPixel.b)); + gl_FragColor = vec4(minComponent, minComponent, minComponent, srcPixel.a); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Mirror Edge.fs b/src/renderer/src/application/sample-modules/isf/Mirror Edge.fs new file mode 100644 index 000000000..3bc336db8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Mirror Edge.fs @@ -0,0 +1,46 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Tile Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "angle", + "LABEL": "Angle", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "shift", + "LABEL": "Shift", + "TYPE": "point2D", + "DEFAULT": [ + 0.0, + 0.5 + ] + } + ] +}*/ + + +varying vec2 translated_coord; + + +void main() { + vec2 loc = translated_coord; + vec2 modifiedCenter = shift / RENDERSIZE; + + loc = mod(loc + modifiedCenter, 1.0); + + // mirror the image so it's repeated 4 times at different reflections + loc = 2.0 * abs(loc - 0.5); + + gl_FragColor = IMG_NORM_PIXEL(inputImage, loc); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Mirror Edge.vs b/src/renderer/src/application/sample-modules/isf/Mirror Edge.vs new file mode 100644 index 000000000..be47c0bb2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Mirror Edge.vs @@ -0,0 +1,18 @@ +varying vec2 translated_coord; + +const float pi = 3.14159265359; + +void main() { + isf_vertShaderInit(); + + vec2 loc = RENDERSIZE * vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + + float r = distance(RENDERSIZE/2.0, loc); + float a = atan ((loc.y-RENDERSIZE.y/2.0),(loc.x-RENDERSIZE.x/2.0)); + + loc.x = r * cos(a + 2.0 * pi * angle) + 0.5; + loc.y = r * sin(a + 2.0 * pi * angle) + 0.5; + + translated_coord = loc / RENDERSIZE + vec2(0.5); + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Mirror.fs b/src/renderer/src/application/sample-modules/isf/Mirror.fs new file mode 100644 index 000000000..cb83d2fce --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Mirror.fs @@ -0,0 +1,41 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "horizontal", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "vertical", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ] +}*/ + +void main() { + // isf_FragNormCoord[0] and isf_FragNormCoord[1] are my normalized x/y coordinates + // if we're not doing a flip in either direction we can just pass thru + vec2 normSrcCoord; + + normSrcCoord.x = isf_FragNormCoord[0]; + normSrcCoord.y = isf_FragNormCoord[1]; + + if ((normSrcCoord.x > 0.5)&&(horizontal)) { + normSrcCoord.x = (1.0-normSrcCoord.x); + } + if ((normSrcCoord.y > 0.5)&&(vertical)) { + normSrcCoord.y = (1.0-normSrcCoord.y); + } + + gl_FragColor = IMG_NORM_PIXEL(inputImage, normSrcCoord); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Morph.fs b/src/renderer/src/application/sample-modules/isf/Morph.fs new file mode 100644 index 000000000..3f15f9874 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Morph.fs @@ -0,0 +1,66 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/morph.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "strength", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: paniq +// License: MIT + +vec4 transition(vec2 p) { + vec4 ca = getFromColor(p); + vec4 cb = getToColor(p); + + vec2 oa = (((ca.rg+ca.b)*0.5)*2.0-1.0); + vec2 ob = (((cb.rg+cb.b)*0.5)*2.0-1.0); + vec2 oc = mix(oa,ob,0.5)*strength; + + float w0 = progress; + float w1 = 1.0-w0; + return mix(getFromColor(p+oc*w0), getToColor(p-oc*w1), progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Mosaic.fs b/src/renderer/src/application/sample-modules/isf/Mosaic.fs new file mode 100644 index 000000000..344952f24 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Mosaic.fs @@ -0,0 +1,98 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/Mosaic.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": -1, + "MAX": 10, + "MIN": -10, + "NAME": "endy", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": -10, + "NAME": "endx", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: Xaychru +// ported by gre from https://gist.github.com/Xaychru/130bb7b7affedbda9df5 + +#define PI 3.14159265358979323 +#define POW2(X) X*X +#define POW3(X) X*X*X + +float Rand(vec2 v) { + return fract(sin(dot(v.xy ,vec2(12.9898,78.233))) * 43758.5453); +} +vec2 Rotate(vec2 v, float a) { + mat2 rm = mat2(cos(a), -sin(a), + sin(a), cos(a)); + return rm*v; +} +float CosInterpolation(float x) { + return -cos(x*PI)/2.+.5; +} +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy - .5; + vec2 rp = p; + float rpr = (progress*2.-1.); + float z = -(rpr*rpr*2.) + 3.; + float az = abs(z); + rp *= az; + rp += mix(vec2(.5, .5), vec2(float(endx) + .5, float(endy) + .5), POW2(CosInterpolation(progress))); + vec2 mrp = mod(rp, 1.); + vec2 crp = rp; + bool onEnd = int(floor(crp.x))==int(endx)&&int(floor(crp.y))==int(endy); + if(!onEnd) { + float ang = float(int(Rand(floor(crp))*4.))*.5*PI; + mrp = vec2(.5) + Rotate(mrp-vec2(.5), ang); + } + if(onEnd || Rand(floor(crp))>.5) { + return getToColor(mrp); + } else { + return getFromColor(mrp); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} diff --git a/src/renderer/src/application/sample-modules/isf/Multi Gradient.fs b/src/renderer/src/application/sample-modules/isf/Multi Gradient.fs new file mode 100644 index 000000000..ee837da1f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Multi Gradient.fs @@ -0,0 +1,339 @@ +/* +{ + "CATEGORIES" : [ + "Color" + ], + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "lookupImage", + "TYPE" : "image" + }, + { + "NAME" : "frequency1", + "TYPE" : "float", + "MAX" : 16, + "DEFAULT" : 1.0, + "MIN" : 0.5 + }, + { + "NAME" : "phase1", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "amplitude1", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 1, + "MIN" : -2 + }, + { + "NAME" : "offset1", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : -1 + }, + { + "NAME" : "angle1", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "VALUES" : [ + 0, + 1, + 2, + 3, + 4 + ], + "NAME" : "curve1", + "TYPE" : "long", + "DEFAULT" : 0, + "LABELS" : [ + "Ramp", + "Triangle", + "Sine", + "Exponential", + "Look Up Table" + ] + }, + { + "NAME" : "mixLevel1", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 1, + "MIN" : -1 + }, + { + "NAME" : "startColor1", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 0, + 0 + ] + }, + { + "NAME" : "endColor1", + "TYPE" : "color", + "DEFAULT" : [ + 1, + 0, + 0, + 1 + ] + }, + { + "NAME" : "frequency2", + "TYPE" : "float", + "MAX" : 16, + "DEFAULT" : 1.0, + "MIN" : 0.5 + }, + { + "NAME" : "phase2", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "amplitude2", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 1, + "MIN" : -2 + }, + { + "NAME" : "offset2", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : -1 + }, + { + "NAME" : "angle2", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.75, + "MIN" : 0 + }, + { + "VALUES" : [ + 0, + 1, + 2, + 3, + 4 + ], + "NAME" : "curve2", + "TYPE" : "long", + "DEFAULT" : 0, + "LABELS" : [ + "Ramp", + "Triangle", + "Sine", + "Exponential", + "Look Up Table" + ] + }, + { + "NAME" : "mixLevel2", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 1, + "MIN" : -1 + }, + { + "NAME" : "startColor2", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 0, + 0 + ] + }, + { + "NAME" : "endColor2", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 1, + 0, + 1 + ] + }, + { + "NAME" : "frequency3", + "TYPE" : "float", + "MAX" : 16, + "DEFAULT" : 2.0, + "MIN" : 0.5 + }, + { + "NAME" : "phase3", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "amplitude3", + "TYPE" : "float", + "MAX" : 2, + "DEFAULT" : 1, + "MIN" : -2 + }, + { + "NAME" : "offset3", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : -1 + }, + { + "NAME" : "angle3", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "VALUES" : [ + 0, + 1, + 2, + 3, + 4 + ], + "NAME" : "curve3", + "TYPE" : "long", + "DEFAULT" : 0, + "LABELS" : [ + "Ramp", + "Triangle", + "Sine", + "Exponential", + "Look Up Table" + ] + }, + { + "NAME" : "mixLevel3", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 1, + "MIN" : -1 + }, + { + "NAME" : "startColor3", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 0, + 0 + ] + }, + { + "NAME" : "endColor3", + "TYPE" : "color", + "DEFAULT" : [ + 0, + 0, + 1, + 1 + ] + } + ], + "CREDIT" : "by Carter Rosenberg" +} +*/ + + + +const float pi = 3.14159265359; +const float e = 2.71828182846; + + +float doMath(int curve, float freq, float phase, float val) { + float returnMe = phase + freq * val; + + if (curve == 0) { + returnMe = mod(returnMe,1.0); + } + else if (curve == 1) { + returnMe = mod(2.0 * returnMe,2.0); + returnMe = (returnMe < 1.0) ? returnMe : 1.0 - (returnMe - floor(returnMe)); + } + else if (curve == 2) { + returnMe = sin(returnMe * pi * 2.0 - pi / 2.0) * 0.5 + 0.5; + } + else if (curve == 3) { + returnMe = mod(2.0 * returnMe, 2.0); + returnMe = (returnMe < 1.0) ? returnMe : 1.0 - (returnMe - floor(returnMe)); + returnMe = pow(returnMe, 2.0); + } + else if (curve == 4) { + vec2 loc = mod(returnMe+isf_FragNormCoord,1.0); + vec4 tmp = IMG_NORM_PIXEL(lookupImage,loc); + returnMe = (tmp.r+tmp.g+tmp.b)*tmp.a/3.0; + } + return returnMe; +} + +// note that this works on normalized points, but respects aspect ratio +vec2 rotatePoint(vec2 pt, float angle) { + vec2 returnMe = pt * RENDERSIZE;; + + float r = distance(RENDERSIZE/2.0, returnMe); + float a = atan ((returnMe.y-RENDERSIZE.y/2.0),(returnMe.x-RENDERSIZE.x/2.0)); + + returnMe.x = r * cos(a + 2.0 * pi * angle - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * angle - pi) + 0.5; + + returnMe = returnMe / RENDERSIZE + vec2(0.5); + + return returnMe; +} + + +void main() { + vec4 returnMe = vec4(0.0); + vec4 blendColor = vec4(0.0); + float mixAmount = 0.0; + vec2 loc = isf_FragNormCoord; + + loc = rotatePoint(isf_FragNormCoord,angle1); + mixAmount = doMath(curve1,frequency1,phase1,1.0-loc.x); + mixAmount = (amplitude1 >= 0.0) ? mixAmount * amplitude1 : (1.0 - mixAmount) * abs(amplitude1); + mixAmount += offset1; + blendColor = mix(startColor1,endColor1,mixAmount); + returnMe.rgb += blendColor.rgb * mixLevel1; + returnMe.a += abs(blendColor.a); + + loc = rotatePoint(isf_FragNormCoord,angle2); + mixAmount = doMath(curve2,frequency2,phase2,1.0-loc.x); + mixAmount = (amplitude2 >= 0.0) ? mixAmount * amplitude2 : (1.0 - mixAmount) * abs(amplitude2); + mixAmount += offset2; + blendColor = mix(startColor2,endColor2,mixAmount); + returnMe.rgb += blendColor.rgb * mixLevel2; + returnMe.a += abs(blendColor.a); + + loc = rotatePoint(isf_FragNormCoord,angle3); + mixAmount = doMath(curve3,frequency3,phase3,loc.x); + mixAmount = (amplitude3 >= 0.0) ? mixAmount * amplitude3 : (1.0 - mixAmount) * abs(amplitude3); + mixAmount += offset3; + blendColor = mix(startColor3,endColor3,mixAmount); + returnMe.rgb += blendColor.rgb * mixLevel3; + returnMe.a += abs(blendColor.a); + + gl_FragColor = returnMe; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Multi Hue Shift.fs b/src/renderer/src/application/sample-modules/isf/Multi Hue Shift.fs new file mode 100644 index 000000000..e536ce92b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Multi Hue Shift.fs @@ -0,0 +1,89 @@ +/*{ + "DESCRIPTION": "Performs hue shifts of different amounts depending on brightness levels", + "CREDIT": "VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "shiftLow", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME": "shiftMid", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME": "shiftHigh", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 1.0 + } + ] + +}*/ + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + +void main() { + vec4 inputPixelColor = IMG_THIS_PIXEL(inputImage); + + // don't bother doing anything if we're not shifting anything + if ((shiftLow > 0.0)||(shiftMid > 0.0)||(shiftHigh > 0.0)) { + // what is the brightness? + float val = (inputPixelColor.r + inputPixelColor.g + inputPixelColor.b) / 3.0; + + // how much do we shift by based on that brightness? + if (val < 0.25) { + val = shiftLow; + } + else if (val < 0.5) { + val = mix(shiftLow, shiftMid, (val-0.25) * 4.0); + } + else if (val < 0.75) { + val = mix(shiftMid, shiftHigh, (val-0.5) * 4.0); + } + else { + val = shiftHigh; + } + + inputPixelColor.rgb = rgb2hsv(inputPixelColor.rgb); + inputPixelColor.r = fract(inputPixelColor.r + val); + inputPixelColor.rgb = hsv2rgb(inputPixelColor.rgb); + + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.fs b/src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.fs new file mode 100644 index 000000000..74e467dc2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.fs @@ -0,0 +1,183 @@ +/*{ + "CREDIT": "original implementation as v002.blur in QC by anton marini and tom butterworth, ported by zoidberg", + "ISFVSN": "2", + "DESCRIPTION": "Performs a deep blur", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "blurAmount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 24.0, + "DEFAULT": 24.0 + } + ], + "PASSES": [ + { + "TARGET": "halfSizeBaseRender", + "WIDTH": "floor($WIDTH/3.0)", + "HEIGHT": "floor($HEIGHT/3.0)", + "DESCRIPTION": "Pass 0" + }, + { + "TARGET": "quarterSizeBaseRender", + "WIDTH": "floor($WIDTH/6.0)", + "HEIGHT": "floor($HEIGHT/6.0)", + "DESCRIPTION": "Pass 1" + }, + { + "TARGET": "eighthSizeBaseRender", + "WIDTH": "floor($WIDTH/12.0)", + "HEIGHT": "floor($HEIGHT/12.0)", + "DESCRIPTION": "Pass 2" + }, + { + "TARGET": "eighthGaussA", + "WIDTH": "floor($WIDTH/12.0)", + "HEIGHT": "floor($HEIGHT/12.0)", + "DESCRIPTION": "Pass 3" + }, + { + "TARGET": "eighthGaussB", + "WIDTH": "floor($WIDTH/12.0)", + "HEIGHT": "floor($HEIGHT/12.0)", + "DESCRIPTION": "Pass 4" + }, + { + "TARGET": "quarterGaussA", + "WIDTH": "floor($WIDTH/6.0)", + "HEIGHT": "floor($HEIGHT/6.0)", + "DESCRIPTION": "Pass 5" + }, + { + "TARGET": "quarterGaussB", + "WIDTH": "floor($WIDTH/6.0)", + "HEIGHT": "floor($HEIGHT/6.0)", + "DESCRIPTION": "Pass 6" + }, + { + "TARGET": "halfGaussA", + "WIDTH": "floor($WIDTH/3.0)", + "HEIGHT": "floor($HEIGHT/3.0)", + "DESCRIPTION": "Pass 7" + }, + { + "TARGET": "halfGaussB", + "WIDTH": "floor($WIDTH/3.0)", + "HEIGHT": "floor($HEIGHT/3.0)", + "DESCRIPTION": "Pass 8" + }, + { + "TARGET": "fullGaussA", + "DESCRIPTION": "Pass 9" + }, + { + "TARGET": "fullGaussB", + "DESCRIPTION": "Pass 10" + } + ] +}*/ + + +/* + eighth + quarter 0 1 2 3 4 5 "blurRadius" (different resolutions have different blur radiuses based on the "blurAmount" and its derived "blurLevel") + half 0 1 2 3 4 5 "blurRadius" + normal 0 1 2 3 4 5 "blurRadius" + 0 1 2 3 4 5 5 "blurRadius" + 0 6 12 18 24 "blurAmount" (attrib) + 0 1 2 3 "blurLevel" (local var) +*/ + +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +in vec2 texOffsets[5]; +#endif + + +void main() { + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + // first three passes are just copying the input image into the buffer at varying sizes + if (PASSINDEX==0) { + gl_FragColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + } + else if (PASSINDEX==1) { + gl_FragColor = IMG_NORM_PIXEL(halfSizeBaseRender, isf_FragNormCoord); + } + else if (PASSINDEX==2) { + gl_FragColor = IMG_NORM_PIXEL(quarterSizeBaseRender, isf_FragNormCoord); + } + // start reading from the previous stage- each two passes completes a gaussian blur, then + // we increase the resolution & blur (the lower-res blurred image from the previous pass) again... + else if (PASSINDEX == 3) { + vec4 sample0 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthSizeBaseRender,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + else if (PASSINDEX == 4) { + vec4 sample0 = IMG_NORM_PIXEL(eighthGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthGaussA,texOffsets[2]); + vec4 sample3 = IMG_NORM_PIXEL(eighthGaussA,texOffsets[3]); + vec4 sample4 = IMG_NORM_PIXEL(eighthGaussA,texOffsets[4]); + //gl_FragColor = vec4((sample0 + sample1 + sample2).rgb / (3.0), 1.0); + gl_FragColor = vec4((sample0 + sample1 + sample2 + sample3 + sample4).rgba / (5.0)); + } + // ...writes into the quarter-size + else if (PASSINDEX == 5) { + vec4 sample0 = IMG_NORM_PIXEL(eighthGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(eighthGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(eighthGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + else if (PASSINDEX == 6) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussA,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + // ...writes into the half-size + else if (PASSINDEX == 7) { + vec4 sample0 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(quarterGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + else if (PASSINDEX == 8) { + vec4 sample0 = IMG_NORM_PIXEL(halfGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(halfGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(halfGaussA,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + // ...writes into the full-size + else if (PASSINDEX == 9) { + vec4 sample0 = IMG_NORM_PIXEL(halfGaussB,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(halfGaussB,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(halfGaussB,texOffsets[2]); + gl_FragColor = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + } + else if (PASSINDEX == 10) { + // this is the last pass- calculate the blurred image as i have in previous passes, then mix it in with the full-size input image using the blur amount so i get a smooth transition into the blur at low blur levels + vec4 sample0 = IMG_NORM_PIXEL(fullGaussA,texOffsets[0]); + vec4 sample1 = IMG_NORM_PIXEL(fullGaussA,texOffsets[1]); + vec4 sample2 = IMG_NORM_PIXEL(fullGaussA,texOffsets[2]); + vec4 blurredImg = vec4((sample0 + sample1 + sample2).rgba / (3.0)); + if (blurLevel == 0) + gl_FragColor = mix(IMG_NORM_PIXEL(inputImage,isf_FragNormCoord), blurredImg, (blurLevelModulus/6.0)); + else + gl_FragColor = blurredImg; + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.vs b/src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.vs new file mode 100644 index 000000000..ef7ba5114 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Multi Pass Gaussian Blur.vs @@ -0,0 +1,98 @@ +#if __VERSION__ <= 120 +varying vec2 texOffsets[5]; +#else +out vec2 texOffsets[5]; +#endif + +void main(void) { + // load the main shader stuff + isf_vertShaderInit(); + + + int blurLevel = int(floor(blurAmount/6.0)); + float blurLevelModulus = mod(blurAmount, 6.0); + float blurRadius = 0.0; + float blurRadiusInPixels = 0.0; + // first three passes are just drawing the texture- do nothing + + // the next six passes do gaussion blurs on the various levels + if (PASSINDEX==3 || PASSINDEX==5 || PASSINDEX==7 || PASSINDEX==9) { + float pixelWidth = 1.0/RENDERSIZE.x; + // pass 3 is eighth-size (blurLevel 3) + if (PASSINDEX==3) { + if (blurLevel==3) + blurRadius = blurLevelModulus/2.0; + else if (blurLevel>3) + blurRadius = 3.0; + } + // pass 5 is quarter-size (blurLevel 2) + else if (PASSINDEX==5) { + if (blurLevel==2) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>2) + blurRadius = 4.0; + } + // pass 7 is half-size (blurLevel 1) + else if (PASSINDEX==7) { + if (blurLevel==1) + blurRadius = blurLevelModulus; + else if (blurLevel>1) + blurRadius = 6.0; + } + // pass 9 is normal-size (blurLevel 0) + else if (PASSINDEX==9) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelWidth * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+blurRadiusInPixels, isf_FragNormCoord[1]),0.0,1.0); + if (PASSINDEX==3) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]-(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0]+(2.0*blurRadiusInPixels), isf_FragNormCoord[1]),0.0,1.0); + } + } + else if (PASSINDEX==4 || PASSINDEX==6 || PASSINDEX==8 || PASSINDEX==10) { + float pixelHeight = 1.0/RENDERSIZE.y; + // pass 4 is eighth-size (blurLevel 3) + if (PASSINDEX==4) { + if (blurLevel==3) + blurRadius = blurLevelModulus/2.0; + else if (blurLevel>3) + blurRadius = 3.0; + } + // pass 6 is quarter-size (blurLevel 2) + else if (PASSINDEX==6) { + if (blurLevel==2) + blurRadius = blurLevelModulus/1.5; + else if (blurLevel>2) + blurRadius = 4.0; + } + // pass 8 is half-size (blurLevel 1) + else if (PASSINDEX==8) { + if (blurLevel==1) + blurRadius = blurLevelModulus; + else if (blurLevel>1) + blurRadius = 6.0; + } + // pass 10 is normal-size (blurLevel 0) + else if (PASSINDEX==10) { + if (blurLevel==0) + blurRadius = blurLevelModulus; + else if (blurLevel>0) + blurRadius = 6.0; + } + blurRadiusInPixels = pixelHeight * blurRadius; + texOffsets[0] = isf_FragNormCoord; + texOffsets[1] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-blurRadiusInPixels),0.0,1.0); + texOffsets[2] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+blurRadiusInPixels),0.0,1.0); + if (PASSINDEX==4) { + texOffsets[3] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]-(2.0*blurRadiusInPixels)),0.0,1.0); + texOffsets[4] = (blurRadius==0.0) ? isf_FragNormCoord : clamp(vec2(isf_FragNormCoord[0], isf_FragNormCoord[1]+(2.0*blurRadiusInPixels)),0.0,1.0); + } + } + +} diff --git a/src/renderer/src/application/sample-modules/isf/Multi-Pixellate.fs b/src/renderer/src/application/sample-modules/isf/Multi-Pixellate.fs new file mode 100644 index 000000000..c9a39dcc9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Multi-Pixellate.fs @@ -0,0 +1,139 @@ +/*{ + "CATEGORIES": [ + "Stylize" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.125, + "MAX": 0.5, + "MIN": 0.001, + "NAME": "cell_size", + "TYPE": "float" + }, + { + "DEFAULT": 0.05000000074505806, + "MAX": 0.5, + "MIN": 0.001, + "NAME": "min_cell_size", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "rSeed", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Square", + "Rectangle" + ], + "NAME": "shape", + "TYPE": "long", + "VALUES": [ + 0, + 1 + ] + }, + { + "DEFAULT": 2, + "LABELS": [ + "Off", + "2", + "3", + "5" + ], + "NAME": "round_to_divisions", + "TYPE": "long", + "VALUES": [ + 0, + 2, + 3, + 5 + ] + } + ], + "ISFVSN": "2" +} +*/ + +#ifndef GL_ES +float distance (vec2 center, vec2 pt) +{ + float tmp = pow(center.x-pt.x,2.0)+pow(center.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + // At 0.0 just do a pass-thru + if (cell_size == 0.0) { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // Position of current pixel + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + + // Left and right of tile + float max_cell_size = cell_size + min_cell_size; + float CellWidth = max_cell_size; + float CellHeight = max_cell_size; + if (shape==0) { + CellHeight = max_cell_size * RENDERSIZE.x / RENDERSIZE.y; + } + + float x1 = floor(xy.x / CellWidth)*CellWidth; + // Top and bottom of tile + float y1 = floor(xy.y / CellHeight)*CellHeight; + + float newCellSize = max_cell_size; + + if (round_to_divisions > 0) { + float maxCount = (min_cell_size > 0.0) ? max_cell_size / min_cell_size : 10.0; + float val = floor(1.0+(maxCount-1.0)*rand(0.1+rSeed+vec2(x1,y1))); + newCellSize = newCellSize / pow(float(round_to_divisions),val); + } + else { + newCellSize = min_cell_size + (cell_size - min_cell_size) * rand(0.1+rSeed+vec2(x1,y1)); + } + + CellWidth = newCellSize; + CellHeight = newCellSize; + if (shape==0) { + CellHeight = newCellSize * RENDERSIZE.x / RENDERSIZE.y; + } + + x1 = floor(xy.x / CellWidth)*CellWidth; + float x2 = clamp((ceil(xy.x / CellWidth)*CellWidth), 0.0, 1.0); + // Top and bottom of tile + y1 = floor(xy.y / CellHeight)*CellHeight; + float y2 = clamp((ceil(xy.y / CellHeight)*CellHeight), 0.0, 1.0); + + // GET AVERAGE CELL COLOUR + // Average left and right pixels + vec4 avgX = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x2, y1)))) / 2.0; + // Average top and bottom pixels + vec4 avgY = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x1, y2)))) / 2.0; + // Centre pixel + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + vec4 avgClr = (avgX+avgY+avgC) / 3.0; + + gl_FragColor = vec4(avgClr); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/MultiFrame 2x2.fs b/src/renderer/src/application/sample-modules/isf/MultiFrame 2x2.fs new file mode 100644 index 000000000..6ebc2564e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/MultiFrame 2x2.fs @@ -0,0 +1,145 @@ +/*{ + "DESCRIPTION": "buffers the last 3 frames and draws a 2x2 grid of the 4 current frames available", + "ISFVSN": "2", + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Tile Effect", "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "lag", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "hueShift", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET":"buffer3", + "PERSISTENT": true, + "WIDTH": "$WIDTH/2.0", + "HEIGHT": "$HEIGHT/2.0" + }, + { + "TARGET":"buffer2", + "PERSISTENT": true, + "WIDTH": "$WIDTH/2.0", + "HEIGHT": "$HEIGHT/2.0" + }, + { + "TARGET":"buffer1", + "PERSISTENT": true, + "WIDTH": "$WIDTH/2.0", + "HEIGHT": "$HEIGHT/2.0" + }, + { + + } + ] + +}*/ + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + +void main() +{ + // first pass: read the "buffer2" into "buffer3" + // apply lag on each pass + if (PASSINDEX == 0) { + if ((lag == false)||(mod(floor(TIME*60.0+2.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + } + // first pass: read the "buffer1" into "buffer2" + else if (PASSINDEX == 1) { + if ((lag == false)||(mod(floor(TIME*60.0+2.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + } + // third pass: read from "inputImage" into "buffer1" + else if (PASSINDEX == 2) { + if ((lag == false)||(mod(floor(TIME*60.0+2.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + } + else if (PASSINDEX == 3) { + // Figure out which section I'm in and draw the appropriate buffer there + vec2 tex = isf_FragNormCoord; + vec4 color = vec4(0.0); + // TL – buffer1 + if ((tex.x < 0.5) && (tex.y > 0.5)) { + tex.x = tex.x * 2.0; + tex.y = (tex.y - 0.5) * 2.0; + color = IMG_NORM_PIXEL(inputImage, tex); + } + // TR – buffer2 + else if ((tex.x > 0.5) && (tex.y > 0.5)) { + tex.x = (tex.x - 0.5) * 2.0; + tex.y = (tex.y - 0.5) * 2.0; + + color = IMG_NORM_PIXEL(buffer1, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // BR – buffer2 + else if ((tex.x < 0.5) && (tex.y < 0.5)) { + tex = tex * 2.0; + + color = IMG_NORM_PIXEL(buffer2, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 2.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + else { + tex.x = (tex.x - 0.5) * 2.0; + tex.y = tex.y * 2.0; + + color = IMG_NORM_PIXEL(buffer3, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 3.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // BL - buffer3 + gl_FragColor = color; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/MultiFrame 3x3.fs b/src/renderer/src/application/sample-modules/isf/MultiFrame 3x3.fs new file mode 100644 index 000000000..51212c1ef --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/MultiFrame 3x3.fs @@ -0,0 +1,268 @@ +/*{ + "DESCRIPTION": "buffers the last 3 frames and draws a 2x2 grid of the 4 current frames available", + "ISFVSN": "2", + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Tile Effect", "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "lag", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "hueShift", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET":"buffer8", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer7", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer6", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer5", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer4", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer3", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer2", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + "TARGET":"buffer1", + "PERSISTENT": true, + "WIDTH": "$WIDTH/3.0", + "HEIGHT": "$HEIGHT/3.0" + }, + { + + } + ] + +}*/ + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + +void main() +{ + // first pass: read the "buffer7" into "buffer8" + // apply lag on each pass + if (PASSINDEX == 0) { + if ((lag == false)||(mod(floor(TIME*60.0+5.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer8); + } + } + else if (PASSINDEX == 1) { + if ((lag == false)||(mod(floor(TIME*60.0+3.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + } + else if (PASSINDEX == 2) { + if ((lag == false)||(mod(floor(TIME*60.0+2.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + } + else if (PASSINDEX == 3) { + if ((lag == false)||(mod(floor(TIME*60.0+5.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + } + else if (PASSINDEX == 4) { + if ((lag == false)||(mod(floor(TIME*60.0+1.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + } + else if (PASSINDEX == 5) { + if ((lag == false)||(mod(floor(TIME*60.0+3.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + } + else if (PASSINDEX == 6) { + if ((lag == false)||(mod(floor(TIME*60.0+5.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + } + else if (PASSINDEX == 7) { + if ((lag == false)||(mod(floor(TIME*60.0+3.0),6.0)<=1.0)) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + } + else if (PASSINDEX == 8) { + // Figure out which section I'm in and draw the appropriate buffer there + vec2 tex = isf_FragNormCoord; + vec4 color = vec4(0.0); + // TL – input + if (tex.y > 0.667) { + if (tex.x < 0.333) { + tex.x = tex.x * 3.0; + tex.y = (tex.y - 0.667) * 3.0; + color = IMG_NORM_PIXEL(inputImage, tex); + } + // TM – buffer1 + else if ((tex.x > 0.333) && (tex.x < 0.667)) { + tex.x = (tex.x - 0.333) * 3.0; + tex.y = (tex.y - 0.667) * 3.0; + + color = IMG_NORM_PIXEL(buffer1, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 1.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // TL – buffer2 + else { + tex.x = (tex.x - 0.667) * 3.0; + tex.y = (tex.y - 0.667) * 3.0; + + color = IMG_NORM_PIXEL(buffer2, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 2.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + } + // BL - buffer3 + else if (tex.y > 0.333) { + if (tex.x < 0.333) { + tex.x = tex.x * 3.0; + tex.y = (tex.y - 0.333) * 3.0; + color = IMG_NORM_PIXEL(buffer3, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 3.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // TM – buffer1 + else if ((tex.x > 0.333) && (tex.x < 0.667)) { + tex.x = (tex.x - 0.333) * 3.0; + tex.y = (tex.y - 0.333) * 3.0; + + color = IMG_NORM_PIXEL(buffer4, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 4.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // TL – buffer2 + else { + tex.x = (tex.x - 0.667) * 3.0; + tex.y = (tex.y - 0.333) * 3.0; + + color = IMG_NORM_PIXEL(buffer5, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 5.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + } + else { + if (tex.x < 0.333) { + tex.x = tex.x * 3.0; + tex.y = tex.y * 3.0; + color = IMG_NORM_PIXEL(buffer6, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 6.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // TM – buffer1 + else if ((tex.x > 0.333) && (tex.x < 0.667)) { + tex.x = (tex.x - 0.333) * 3.0; + tex.y = tex.y * 3.0; + + color = IMG_NORM_PIXEL(buffer7, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 7.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + // TL – buffer2 + else { + tex.x = (tex.x - 0.667) * 3.0; + tex.y = tex.y * 3.0; + + color = IMG_NORM_PIXEL(buffer8, tex); + color.rgb = rgb2hsv(color.rgb); + color.r = mod(color.r + 8.0 * hueShift, 1.0); + color.rgb = hsv2rgb(color.rgb); + } + } + + gl_FragColor = color; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Multiply Blend.fs b/src/renderer/src/application/sample-modules/isf/Multiply Blend.fs new file mode 100644 index 000000000..181ba0396 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Multiply Blend.fs @@ -0,0 +1,62 @@ +/* +{ + "ISFVSN" : "2", + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/multiply_blend.glsl", + "DESCRIPTION": "", + "CATEGORIES" : [ + "Dissolve" + ], + "INPUTS" : [ + { + "NAME" : "startImage", + "TYPE" : "image" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0, + "TYPE" : "float", + "NAME" : "progress" + } + ] +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Fernando Kuteken +// License: MIT + +vec4 blend(vec4 a, vec4 b) { + return a * b; +} + +vec4 transition (vec2 uv) { + + vec4 blended = blend(getFromColor(uv), getToColor(uv)); + + if (progress < 0.5) + return mix(getFromColor(uv), blended, 2.0 * progress); + else + return mix(blended, getToColor(uv), 2.0 * progress - 1.0); +} + + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Neon.fs b/src/renderer/src/application/sample-modules/isf/Neon.fs new file mode 100644 index 000000000..2adfaad78 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Neon.fs @@ -0,0 +1,125 @@ +// adapted from https://github.com/neilmendoza/ofxPostProcessing/blob/master/src/GodRaysPass.cpp + + +/*{ + "DESCRIPTION": "", + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0, + "MAX": 10, + "DEFAULT": 5 + }, + { + "NAME": "gain", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 1 + }, + { + "NAME": "neonColor", + "TYPE": "color", + "DEFAULT": [ + 1, + 0.4, + 0.64, + 1 + ] + } + ] +}*/ + + +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + + +void main(void) +{ + + // edges // rays // color + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + float gx = (0.0); + float gy = (0.0); + gx = (-1.0 * gray(colorLA)) + (-1.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (1.0 * gray(colorR)) + (1.0 * gray(colorRB)); + gy = (1.0 * gray(colorLA)) + (1.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-1.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + + + + float bright = pow(gx*gx + gy*gy,0.5); + vec4 final = color * bright; + + // if the brightness is below the threshold draw black + if (bright < 0.01) { + final = vec4(0.0); + } + else { + final = final * intensity; + } + + gl_FragColor = final; + + vec4 origColor = final; + vec4 raysColor = color; + int NUM_SAMPLES = 30; + + float exposure = 0.1/float(NUM_SAMPLES); + float decay = 1.0 ; + float density = 0.5; + float weight = 6.0; + float illuminationDecay = 1.0; + vec2 normSrcCoord; + + normSrcCoord.x = isf_FragNormCoord[0]; + normSrcCoord.y = isf_FragNormCoord[1]; + + vec2 deltaTextCoord = vec2(normSrcCoord.st - 0.5); + vec2 textCoo = normSrcCoord; + deltaTextCoord *= 1.0 / float(NUM_SAMPLES) * density; + + for(float i=0.0; i < 30.0 ; i++) { + textCoo -= deltaTextCoord; + vec4 tsample = IMG_NORM_PIXEL(inputImage, textCoo); + tsample *= illuminationDecay * weight; + raysColor += tsample; + illuminationDecay *= decay; + } + raysColor *= exposure * gain; + float p = 0.3 *raysColor.g + 0.59*raysColor.r + 0.11*raysColor.b; + + gl_FragColor = gray(origColor + p) * neonColor; + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Neon.vs b/src/renderer/src/application/sample-modules/isf/Neon.vs new file mode 100644 index 000000000..baa287577 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Neon.vs @@ -0,0 +1,28 @@ +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + d = 1.0/RENDERSIZE; + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Night Vision.fs b/src/renderer/src/application/sample-modules/isf/Night Vision.fs new file mode 100644 index 000000000..164103f49 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Night Vision.fs @@ -0,0 +1,99 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "luminanceThreshold", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.2 + }, + { + "NAME": "colorAmplification", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 4.0 + }, + { + "NAME": "noiseLevel", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.1 + }, + { + "NAME": "visionColor", + "TYPE": "color", + "DEFAULT": [ + 0.1, + 0.95, + 0.2, + 1.0 + ] + }, + { + "NAME": "visionMovement", + "TYPE": "bool", + "DEFAULT": 0.0 + }, + { + "NAME": "visionMovementSpeed", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 50.0 + + } + ], + "IMPORTED": { + "noiseTex": { + "PATH": "noise2d.png" + } + } +}*/ + + + +void main () { + vec2 normSrcCoord; + + normSrcCoord.x = isf_FragNormCoord[0]; + normSrcCoord.y = isf_FragNormCoord[1]; + + vec4 finalColor; + + // Set effectCoverage to 1.0 for normal use. + + vec2 uv; + + if (visionMovement) { + uv.x = 0.4*sin(TIME*visionMovementSpeed); + uv.y = 0.4*cos(TIME*visionMovementSpeed); + + uv = mod(normSrcCoord.xy * 3.5 + uv, 1.0); + } else { + uv = normSrcCoord.xy; + } + + vec3 n = IMG_NORM_PIXEL(noiseTex, uv).rgb; + vec3 c = IMG_THIS_PIXEL(inputImage).rgb + n.rgb * noiseLevel; + + const vec4 lumacoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + float lum = dot(vec4(c,1.), lumacoeff); + if (lum < luminanceThreshold) { + c *= colorAmplification; + } + finalColor.rgb = (c + (n*0.2)) * visionColor.rgb; + + gl_FragColor.rgb = finalColor.rgb; + gl_FragColor.a = 1.0; +} diff --git a/src/renderer/src/application/sample-modules/isf/Noise Adapt.fs b/src/renderer/src/application/sample-modules/isf/Noise Adapt.fs new file mode 100644 index 000000000..b4319c567 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Noise Adapt.fs @@ -0,0 +1,98 @@ +/*{ + "CATEGORIES": [ + "Noise" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Pixels that change become noise until they match the input again", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "adaptRate", + "TYPE": "float" + }, + { + "DEFAULT": 0.05, + "MAX": 1, + "MIN": 0, + "NAME": "threshold", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "NAME": "useRGBA", + "TYPE": "bool" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "PERSISTENT": true, + "TARGET": "adaptiveBuffer1" + }, + { + } + ] +} +*/ + + +float minThresh = 3.0/255.0; + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} +float steppedRand(vec2 co){ + return floor(256.0*rand(co))/255.0; +} +vec4 randColor(vec2 co){ + vec4 c = vec4(0.0); + c.r = steppedRand(co+0.234); + c.g = steppedRand(co+0.193); + c.b = steppedRand(co+0.625); + c.a = 1.0; + return c; +} + +float luma(vec4 c) { + return c.a*(c.r+c.g+c.b)/3.0; +} + + +void main() +{ + + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + vec4 stalePixel = IMG_PIXEL(adaptiveBuffer1,gl_FragCoord.xy); + if (useRGBA == false) { + float freshLuma = luma(freshPixel); + float staleLuma = luma(stalePixel); + float thresh = (threshold < minThresh) ? minThresh : threshold; + if (abs(freshLuma-staleLuma)>=thresh) { + stalePixel = randColor(gl_FragCoord.xy+TIME); + } + } + else { + float thresh = (threshold < minThresh) ? minThresh : threshold; + if (abs(freshPixel.r-stalePixel.r)>=thresh) { + stalePixel.r = steppedRand(gl_FragCoord.xy+TIME+1.234); + } + if (abs(freshPixel.g-stalePixel.g)>=thresh) { + stalePixel.g = steppedRand(gl_FragCoord.xy+TIME+2.193); + } + if (abs(freshPixel.b-stalePixel.b)>=thresh) { + stalePixel.b = steppedRand(gl_FragCoord.xy+TIME+3.625); + } + if (abs(freshPixel.a-stalePixel.a)>=thresh) { + stalePixel.a = steppedRand(gl_FragCoord.xy+TIME+4.479); + } + } + gl_FragColor = mix(stalePixel,freshPixel,adaptRate); + +} diff --git a/src/renderer/src/application/sample-modules/isf/Noise Displace.fs b/src/renderer/src/application/sample-modules/isf/Noise Displace.fs new file mode 100644 index 000000000..f5351a813 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Noise Displace.fs @@ -0,0 +1,77 @@ +/* +{ + "CATEGORIES" : [ + "Distortion Effect", "Noise" + ], + "DESCRIPTION" : "Displaces image randomly", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "displaceX", + "TYPE" : "float", + "MAX" : 0.1, + "DEFAULT" : 0.0, + "MIN" : 0.0, + "LABEL" : "Displace X" + }, + { + "NAME" : "displaceY", + "TYPE" : "float", + "MAX" : 0.1, + "DEFAULT" : 0.0, + "MIN" : 0.0, + "LABEL" : "Displace Y" + }, + { + "NAME" : "detailX", + "TYPE" : "float", + "MAX" : 1.0, + "DEFAULT" : 0.1, + "MIN" : 0.0, + "LABEL" : "Detail X" + }, + { + "NAME" : "detailY", + "TYPE" : "float", + "MAX" : 1.0, + "DEFAULT" : 0.1, + "MIN" : 0.0, + "LABEL" : "Detail Y" + }, + { + "NAME" : "updateTime", + "TYPE" : "float", + "MAX" : 0.1, + "DEFAULT" : 0.01, + "MIN" : 0.0, + "LABEL" : "Frequency" + } + ], + "CREDIT" : "VIDVOX" +} +*/ + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + + vec4 inputPixelColor; + vec2 uv = isf_FragNormCoord.xy; + float wobbleTime = (updateTime == 0.0) ? TIME : floor(TIME/updateTime); + vec2 waveLoc = fract((uv)*vec2(detailX, detailY)); + float val1 = rand(vec2(waveLoc.x, wobbleTime)); + float val2 = rand(vec2(waveLoc.y, wobbleTime+1.0)); + vec2 wave = vec2(val1, val2)-0.5; + wave *= vec2(displaceY, displaceX); + + inputPixelColor = IMG_NORM_PIXEL(inputImage, uv + wave.yx); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Noise Pixellate.fs b/src/renderer/src/application/sample-modules/isf/Noise Pixellate.fs new file mode 100644 index 000000000..6ad0e68d5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Noise Pixellate.fs @@ -0,0 +1,120 @@ + +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize", "Noise" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "cell_size", + "TYPE": "float", + "MIN": 0.001, + "MAX": 0.5, + "DEFAULT": 0.025 + }, + { + "NAME": "sigGain", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "mode", + "VALUES": [ + 0, + 1 + ], + "LABELS": [ + "Multiply", + "Threshold" + ], + "DEFAULT": 1, + "TYPE": "long" + }, + { + "NAME": "shape", + "VALUES": [ + 0, + 1 + ], + "LABELS": [ + "Square", + "Rectangle" + ], + "DEFAULT": 0, + "TYPE": "long" + } + ] +}*/ + +#ifndef GL_ES +float distance (vec2 center, vec2 pt) +{ + float tmp = pow(center.x-pt.x,2.0)+pow(center.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + // At 0.0 just do a pass-thru + if (cell_size == 0.0) { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // Position of current pixel + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + + // Left and right of tile + float CellWidth = cell_size; + float CellHeight = cell_size; + if (shape==0) { + CellHeight = cell_size * RENDERSIZE.x / RENDERSIZE.y; + } + + float x1 = floor(xy.x / CellWidth)*CellWidth; + float x2 = clamp((ceil(xy.x / CellWidth)*CellWidth), 0.0, 1.0); + // Top and bottom of tile + float y1 = floor(xy.y / CellHeight)*CellHeight; + float y2 = clamp((ceil(xy.y / CellHeight)*CellHeight), 0.0, 1.0); + + // GET AVERAGE CELL COLOUR + // Average left and right pixels + vec4 original = IMG_THIS_PIXEL(inputImage); + vec4 avgX = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x2, y1)))) / 2.0; + // Average top and bottom pixels + vec4 avgY = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x1, y2)))) / 2.0; + // Centre pixel + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + vec4 avgClr = (avgX+avgY+avgC) / 3.0; + avgClr.a = original.a; + + float thresh = (avgClr.r + avgClr.g + avgClr.b) * avgClr.a / 3.0; + + if (mode == 0) { + float rVal = (1.0 + sigGain) * rand(xy + thresh); + rVal = (rVal > 1.0) ? 1.0 : rVal; + avgClr.rgb *= rVal; + } + else if (mode == 1) { + float rVal = rand(xy + thresh) / (1.0 + sigGain); + rVal = (rVal > 1.0) ? 1.0 : rVal; + avgClr = (thresh > rVal) ? avgClr : vec4(0.0,0.0,0.0,original.a); + } + gl_FragColor = avgClr; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Noise.fs b/src/renderer/src/application/sample-modules/isf/Noise.fs new file mode 100644 index 000000000..3a1ffa7bc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Noise.fs @@ -0,0 +1,172 @@ + +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Noise" + ], + "INPUTS": [ + { + "NAME": "seed", + "LABEL": "Random Seed", + "TYPE": "float", + "MIN": 0.01, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "cell_size", + "LABEL": "Cell Size", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.5, + "DEFAULT": 0.125 + }, + { + "NAME": "threshold", + "LABEL": "Threshold", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "use_time", + "LABEL": "Animated", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "color_mode", + "LABEL": "Color Mode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3 + ], + "LABELS": [ + "B&W", + "Alpha", + "RGB", + "RGBA" + ], + "DEFAULT": 2 + } + ] +}*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + float tmpSeed = seed; + if (use_time) { + tmpSeed = max(mod(tmpSeed * TIME,1.0),0.01); + } + + // if the size is 0.0 do this for every pixel + if (cell_size == 0.0) { + vec4 outColor = vec4(0.0); + float translated = RENDERSIZE.x * isf_FragNormCoord[0] + isf_FragNormCoord[1]; + float val = rand(vec2(translated, tmpSeed)); + if (val >= threshold) { + // b&w + if (color_mode == 0) { + outColor = vec4(1.0); + } + // grayscale, use the alpha + else if (color_mode == 1) { + outColor = vec4(1.0, 1.0, 1.0, val); + } + // RGB + else if (color_mode == 2) { + float rRand = rand(vec2(translated + 0.1542, tmpSeed)); + float gRand = rand(vec2(translated + 0.0835, tmpSeed)); + float bRand = rand(vec2(translated + 0.2547, tmpSeed)); + outColor = vec4(rRand, gRand, bRand, 1.0); + } + // RGBA + else if (color_mode == 3) { + float rRand = rand(vec2(translated + 0.1542, tmpSeed)); + float gRand = rand(vec2(translated + 0.0835, tmpSeed)); + float bRand = rand(vec2(translated + 0.2547, tmpSeed)); + outColor = vec4(rRand, gRand, bRand, val); + } + } + gl_FragColor = outColor; + } + else { + // Position of current pixel + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + + // Left and right of tile + float CellWidth = cell_size; + float CellHeight = cell_size; + + + // divide 1 by the cell width and cell height to determine the count + float rows = floor(1.0/CellHeight); + float cols = floor(1.0/CellWidth); + float count = floor(rows * cols); + + // figure out the ID # of the region + float region = cols*floor(xy.x / CellWidth) + floor(xy.y / CellHeight); + + // use this to draw the gradient of the regions as gray colors.. + //gl_FragColor = vec4(vec3(region/count),1.0); + + // now translate this region to another random region using our seed and region + float translated = clamp(rand(vec2(region/count, tmpSeed)),0.0,1.0); + //translated = region/count; + //gl_FragColor = vec4(vec3(translated),1.0); + + // quantize the translated! + translated = floor(count * translated); + //gl_FragColor = vec4(vec3(translated),1.0); + // now convert the translated region back to an xy location + // get the relative position within the original block and then add on the translated amount + xy.x = (xy.x - floor(xy.x / CellWidth)*CellWidth) + CellWidth * floor(translated / rows); + //xy.x = (xy.x - floor(xy.x / CellWidth)*CellWidth); + xy.y = xy.y - floor(xy.y / CellHeight)*CellHeight + CellHeight * floor(mod(translated , cols)); + + float val = rand(vec2(translated, tmpSeed)); + + vec4 outColor = vec4(0.0); + + if (val >= threshold) { + // b&w + if (color_mode == 0) { + outColor = vec4(1.0); + } + // grayscale, use the alpha + else if (color_mode == 1) { + outColor = vec4(1.0, 1.0, 1.0, val); + } + // RGB + else if (color_mode == 2) { + float rRand = rand(vec2(translated + 0.1542, tmpSeed)); + float gRand = rand(vec2(translated + 0.0835, tmpSeed)); + float bRand = rand(vec2(translated + 0.2547, tmpSeed)); + outColor = vec4(rRand, gRand, bRand, 1.0); + } + // RGBA + else if (color_mode == 3) { + float rRand = rand(vec2(translated + 0.1542, tmpSeed)); + float gRand = rand(vec2(translated + 0.0835, tmpSeed)); + float bRand = rand(vec2(translated + 0.2547, tmpSeed)); + outColor = vec4(rRand, gRand, bRand, val); + } + } + + gl_FragColor = outColor; + + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Optical Flow Distort.fs b/src/renderer/src/application/sample-modules/isf/Optical Flow Distort.fs new file mode 100644 index 000000000..28b8327cd --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Optical Flow Distort.fs @@ -0,0 +1,182 @@ +/*{ + "DESCRIPTION": "Uses an optical flow mask to create a distortion", + "CREDIT": "by VIDVOX, based on original implementation by Andrew Benson and v002", + "CATEGORIES": [ + "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "amt", + "LABEL": "Distortion Amount", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.5 + }, + { + "NAME": "maskHold", + "LABEL": "Flow Persistence", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.98 + }, + { + "NAME": "inputScale", + "LABEL": "Scale", + "TYPE": "float", + "MIN": 0, + "MAX": 10, + "DEFAULT": 2 + }, + { + "NAME": "inputOffset", + "LABEL": "Offset", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.1 + }, + { + "NAME": "inputLambda", + "LABEL": "Noise Removal", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 1 + } + ], + "PASSES": [ + { + "TARGET": "maskBuffer", + "persistent": true + }, + { + "TARGET": "delayBuffer", + "persistent": true + }, + {} + ] +}*/ + + +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + + +// based on v002 Optical Flow which is itself a port of Andrew Bensons HS Flow implementation on the GPU. +// https://github.com/v002/v002-Optical-Flow + + +const vec4 coeffs = vec4(0.2126, 0.7152, 0.0722, 1.0); + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + // on the first pass generate the mask using the previous delayBuffer and inputImage + // on the 2nd pass update the delayBuffer to hold inputImage + // on the 3rd pass output the new mask + if (PASSINDEX == 0) { + // convert to grayscale + vec4 a = IMG_THIS_PIXEL(inputImage) * coeffs; + float brightness = gray(a); + a = vec4(brightness); + vec4 b = IMG_THIS_PIXEL(delayBuffer) * coeffs; + brightness = gray(b); + b = vec4(brightness); + + vec2 x1 = vec2(inputOffset * RENDERSIZE.x, 0.0); + vec2 y1 = vec2(0.0,inputOffset * RENDERSIZE.y); + vec2 texcoord0 = isf_FragNormCoord.xy * RENDERSIZE; + vec2 texcoord1 = isf_FragNormCoord.xy * RENDERSIZE; + + //get the difference + vec4 curdif = b-a; + + //calculate the gradient + vec4 gradx = IMG_PIXEL(delayBuffer, texcoord1+x1)-IMG_PIXEL(delayBuffer, texcoord1-x1); + gradx += IMG_PIXEL(inputImage, texcoord0+x1)-IMG_PIXEL(inputImage, texcoord0-x1); + + vec4 grady = IMG_PIXEL(delayBuffer, texcoord1+y1)-IMG_PIXEL(delayBuffer, texcoord1-y1); + grady += IMG_PIXEL(inputImage, texcoord0+y1)-IMG_PIXEL(inputImage, texcoord0-y1); + + vec4 gradmag = sqrt((gradx*gradx)+(grady*grady)+vec4(inputLambda)); + + vec4 vx = curdif*(gradx/gradmag); + float vxd = gray(vx);//assumes greyscale + //format output for flowrepos, out(-x,+x,-y,+y) + vec2 xout = vec2(max(vxd,0.),abs(min(vxd,0.)))*inputScale; + + vec4 vy = curdif*(grady/gradmag); + float vyd = gray(vy);//assumes greyscale + //format output for flowrepos, out(-x,+x,-y,+y) + vec2 yout = vec2(max(vyd,0.),abs(min(vyd,0.)))*inputScale; + + vec4 mask = clamp(vec4(xout.xy,yout.xy), 0.0, 1.0); + + vec4 color = IMG_THIS_NORM_PIXEL(maskBuffer); + vec4 colorL = IMG_NORM_PIXEL(maskBuffer, left_coord); + vec4 colorR = IMG_NORM_PIXEL(maskBuffer, right_coord); + vec4 colorA = IMG_NORM_PIXEL(maskBuffer, above_coord); + vec4 colorB = IMG_NORM_PIXEL(maskBuffer, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(maskBuffer, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(maskBuffer, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(maskBuffer, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(maskBuffer, rightb_coord); + + // blur the feedback buffer + vec4 blurVector = (color + colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 9.0; + gl_FragColor = mask + maskHold * blurVector; + } + else if (PASSINDEX == 1) { + // here we just buffer the current frame for next time + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // NOW DO SOMETHING WITH THE MASK - BLUR THE IMAGE AND THE MASK IMAGE + + // blur the mask image + vec2 texcoord0 = isf_FragNormCoord.xy; + + vec4 color = IMG_THIS_NORM_PIXEL(maskBuffer); + vec4 colorL = IMG_NORM_PIXEL(maskBuffer, left_coord); + vec4 colorR = IMG_NORM_PIXEL(maskBuffer, right_coord); + vec4 colorA = IMG_NORM_PIXEL(maskBuffer, above_coord); + vec4 colorB = IMG_NORM_PIXEL(maskBuffer, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(maskBuffer, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(maskBuffer, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(maskBuffer, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(maskBuffer, rightb_coord); + + vec4 blurVector = (color + colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 9.0; + //vec4 blurVector = IMG_THIS_PIXEL(maskBuffer); + + vec2 blurAmount = vec2(blurVector.y-blurVector.x, blurVector.w-blurVector.z); + vec2 tmp = texcoord0 + blurAmount * amt; + tmp.x = clamp(tmp.x,0.0,1.0); + tmp.y = clamp(tmp.y,0.0,1.0); + vec4 sample0 = IMG_NORM_PIXEL(inputImage, tmp); + tmp = (1.02 + texcoord0) + blurAmount * amt * amt; + tmp.x = clamp(tmp.x,0.0,1.0); + tmp.y = clamp(tmp.y,0.0,1.0); + vec4 sample1 = IMG_NORM_PIXEL(inputImage, tmp); + gl_FragColor = (sample0 * 3.0 + sample1) / 4.0; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Optical Flow Distort.vs b/src/renderer/src/application/sample-modules/isf/Optical Flow Distort.vs new file mode 100644 index 000000000..84fb87d4f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Optical Flow Distort.vs @@ -0,0 +1,27 @@ +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 4.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Optical Flow Generator.fs b/src/renderer/src/application/sample-modules/isf/Optical Flow Generator.fs new file mode 100644 index 000000000..e6401b193 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Optical Flow Generator.fs @@ -0,0 +1,125 @@ +/*{ + "CATEGORIES": [ + "Masking", + "Utility" + ], + "CREDIT": "by VIDVOX / v002 / Andrew Benson", + "DESCRIPTION": "Creates a raw optical flow mask from the input image", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 10, + "LABEL": "Scale", + "MAX": 50, + "MIN": 0, + "NAME": "inputScale", + "TYPE": "float" + }, + { + "DEFAULT": 0.01, + "LABEL": "Offset", + "MAX": 0.5, + "MIN": 0, + "NAME": "inputOffset", + "TYPE": "float" + }, + { + "DEFAULT": 0.2, + "LABEL": "Noise Removal", + "MAX": 1, + "MIN": 0, + "NAME": "inputLambda", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "maskOpacity", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "TARGET": "maskBuffer" + }, + { + "PERSISTENT": true, + "TARGET": "delayBuffer" + }, + { + } + ] +} +*/ + + +// based on v002 Optical Flow which is itself a port of Andrew Bensons HS Flow implementation on the GPU. +// https://github.com/v002/v002-Optical-Flow + +const vec4 coeffs = vec4(0.2126, 0.7152, 0.0722, 1.0); + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + // on the first pass generate the mask using the previous delayBuffer and inputImage + // on the 2nd pass update the delayBuffer to hold inputImage + // on the 3rd pass output the new mask + if (PASSINDEX == 0) { + // convert to grayscale + vec4 a = IMG_THIS_PIXEL(inputImage) * coeffs; + float brightness = gray(a); + a = vec4(brightness); + vec4 b = IMG_THIS_PIXEL(delayBuffer) * coeffs; + brightness = gray(b); + b = vec4(brightness); + + vec2 x1 = vec2(inputOffset * RENDERSIZE.x, 0.0); + vec2 y1 = vec2(0.0,inputOffset * RENDERSIZE.y / 2.0); + vec2 texcoord0 = isf_FragNormCoord.xy * RENDERSIZE; + vec2 texcoord1 = isf_FragNormCoord.xy * RENDERSIZE; + + //get the difference + vec4 curdif = b-a; + + //calculate the gradient + vec4 gradx = IMG_PIXEL(delayBuffer, texcoord1+x1)-IMG_PIXEL(delayBuffer, texcoord1-x1); + gradx += IMG_PIXEL(inputImage, texcoord0+x1)-IMG_PIXEL(inputImage, texcoord0-x1); + + vec4 grady = IMG_PIXEL(delayBuffer, texcoord1+y1)-IMG_PIXEL(delayBuffer, texcoord1-y1); + grady += IMG_PIXEL(inputImage, texcoord0+y1)-IMG_PIXEL(inputImage, texcoord0-y1); + + vec4 gradmag = sqrt((gradx*gradx)+(grady*grady)+vec4(inputLambda)); + + vec4 vx = curdif*(gradx/gradmag); + float vxd = gray(vx);//assumes greyscale + //format output for flowrepos, out(-x,+x,-y,+y) + vec2 xout = vec2(max(vxd,0.),abs(min(vxd,0.)))*inputScale; + + vec4 vy = curdif*(grady/gradmag); + float vyd = gray(vy);//assumes greyscale + //format output for flowrepos, out(-x,+x,-y,+y) + vec2 yout = vec2(max(vyd,0.),abs(min(vyd,0.)))*inputScale; + + vec4 mask = clamp(vec4(xout.xy,yout.xy), 0.0, 1.0); + gl_FragColor = mask; + } + else if (PASSINDEX == 1) { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // NOW DO SOMETHING WITH THE MASK + vec4 mask = IMG_THIS_NORM_PIXEL(maskBuffer); + //mask.a = 1.0; + mask.a = mix(mask.a, 1.0, maskOpacity); + gl_FragColor = mask; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Perlin Transition.fs b/src/renderer/src/application/sample-modules/isf/Perlin Transition.fs new file mode 100644 index 000000000..ad2810c70 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Perlin Transition.fs @@ -0,0 +1,126 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/perlin.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 12.9898, + "MAX": 100, + "MIN": 0, + "NAME": "seed", + "TYPE": "float" + }, + { + "DEFAULT": 0.01, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + }, + { + "DEFAULT": 4, + "MAX": 10, + "MIN": 0, + "NAME": "scale", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Rich Harris +// License: MIT + +#ifdef GL_ES +precision mediump float; +#endif + + + +// http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +float random(vec2 co) +{ + highp float a = seed; + highp float b = 78.233; + highp float c = 43758.5453; + highp float dt= dot(co.xy ,vec2(a,b)); + highp float sn= mod(dt,3.14); + return fract(sin(sn) * c); +} + +// 2D Noise based on Morgan McGuire @morgan3d +// https://www.shadertoy.com/view/4dS3Wd +float noise (in vec2 st) { + vec2 i = floor(st); + vec2 f = fract(st); + + // Four corners in 2D of a tile + float a = random(i); + float b = random(i + vec2(1.0, 0.0)); + float c = random(i + vec2(0.0, 1.0)); + float d = random(i + vec2(1.0, 1.0)); + + // Smooth Interpolation + + // Cubic Hermine Curve. Same as SmoothStep() + vec2 u = f*f*(3.0-2.0*f); + // u = smoothstep(0.,1.,f); + + // Mix 4 coorners porcentages + return mix(a, b, u.x) + + (c - a)* u.y * (1.0 - u.x) + + (d - b) * u.x * u.y; +} + +vec4 transition (vec2 uv) { + vec4 from = getFromColor(uv); + vec4 to = getToColor(uv); + float n = noise(uv * scale); + + float p = mix(-smoothness, 1.0 + smoothness, progress); + float lower = p - smoothness; + float higher = p + smoothness; + + float q = smoothstep(lower, higher, n); + + return mix( + from, + to, + 1.0 - q + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Pinch.fs b/src/renderer/src/application/sample-modules/isf/Pinch.fs new file mode 100644 index 000000000..1221f7de6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Pinch.fs @@ -0,0 +1,55 @@ +/* +{ + "CATEGORIES" : [ + "effect" + ], + "DESCRIPTION" : "Pinch", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "DEFAULT": [0.5,0.5], + "MIN": [0,0], + "MAX": [1,1] + }, + { + "NAME" : "amount", + "TYPE" : "float", + "MAX" : 4.0, + "DEFAULT" : 2.0, + "MIN" : 0 + } + ], + "CREDIT" : "2xAA" +} +*/ + +precision mediump float; + +void main() { + + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy; + + // normalize to the center + uv = uv - center; + + // cartesian to polar coordinates + float r = length(uv); + float a = atan(uv.y, uv.x); + + // distort + r = sqrt(r/amount); // pinch + + // polar to cartesian coordinates + uv = r * vec2(cos(a), sin(a)); + + uv = uv + center; + + vec4 c = IMG_NORM_PIXEL(inputImage, uv); + gl_FragColor = c; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Pinwheel.fs b/src/renderer/src/application/sample-modules/isf/Pinwheel.fs new file mode 100644 index 000000000..62ea97e89 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Pinwheel.fs @@ -0,0 +1,66 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/pinwheel.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": 0, + "NAME": "speed", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Mr Speaker +// License: MIT + + +vec4 transition(vec2 uv) { + + vec2 p = uv.xy / vec2(1.0).xy; + + float circPos = atan(p.y - 0.5, p.x - 0.5) + progress * speed; + float modPos = mod(circPos, 3.1415 / 4.); + float signed = sign(progress - modPos); + + return mix(getToColor(p), getFromColor(p), step(signed, 0.5)); + +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Pixel Shifter.fs b/src/renderer/src/application/sample-modules/isf/Pixel Shifter.fs new file mode 100644 index 000000000..141d129da --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Pixel Shifter.fs @@ -0,0 +1,114 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Shifts pixels up and down", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "LABEL": "Horizontal Phase", + "MAX": 1, + "MIN": 0, + "NAME": "hPhase", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Horizontal Frequency", + "MAX": 16, + "MIN": -16, + "NAME": "hFrequency", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Horizontal Random", + "MAX": 1, + "MIN": 0, + "NAME": "hRandom", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Vertical Phase", + "MAX": 1, + "MIN": 0, + "NAME": "vPhase", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Vertical Frequency", + "MAX": 16, + "MIN": -16, + "NAME": "vFrequency", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Vertical Random", + "MAX": 1, + "MIN": 0, + "NAME": "vRandom", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Sinusoidal", + "NAME": "doSin", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "LABEL": "Mirror", + "NAME": "mirror", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + + +float PI_CONST = 3.14159265359; + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +void main() +{ + // start with the pixel + vec2 loc = isf_FragNormCoord; + float modVal = 1.0; + + if (mirror) + modVal = 2.0; + + // shift the loc.x by the frequency * loc.y + phase + if (doSin) + loc.x = mod(hRandom * rand(vec2(TIME * 0.127, loc.x)) + loc.x + sign(hFrequency) * 0.5 * (1.0+cos(2.0 * PI_CONST * (hPhase + hFrequency * loc.y))), modVal); + else + loc.x = mod(hRandom * rand(vec2(TIME * 0.129, loc.x)) + loc.x + hFrequency * loc.y + hPhase, modVal); + + // shift the loc.y by the frequency * loc.x + phase + if (doSin) + loc.y = mod(vRandom * rand(vec2(TIME * 0.273, loc.y)) + loc.y + sign(vFrequency) * 0.5 * (1.0+cos(2.0 * PI_CONST * (vPhase + vFrequency * loc.x))), modVal); + else + loc.y = mod(vRandom * rand(vec2(TIME * 0.341, loc.y)) +loc.y + vFrequency * loc.x + vPhase, modVal); + + if (loc.x > 1.0) + loc.x = 2.0 - loc.x; + + if (loc.y > 1.0) + loc.y = 2.0 - loc.y; + + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); +} diff --git a/src/renderer/src/application/sample-modules/isf/Pixelize.fs b/src/renderer/src/application/sample-modules/isf/Pixelize.fs new file mode 100644 index 000000000..d0cfe34fb --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Pixelize.fs @@ -0,0 +1,80 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/pixelize.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 50, + "MAX": 100, + "MIN": 0, + "NAME": "steps", + "TYPE": "float" + }, + { + "DEFAULT": [ + 20, + 20 + ], + "MAX": [ + 100, + 100 + ], + "MIN": [ + 1, + 1 + ], + "NAME": "squaresMin", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT +// forked from https://gist.github.com/benraziel/c528607361d90a072e98 + + +float d = min(progress, 1.0 - progress); +float dist = steps>0.0 ? ceil(d * float(steps)) / float(steps) : d; +vec2 squareSize = 2.0 * dist / vec2(squaresMin); + +vec4 transition(vec2 uv) { + vec2 p = dist>0.0 ? (floor(uv / squareSize) + 0.5) * squareSize : uv; + return mix(getFromColor(p), getToColor(p), progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} diff --git a/src/renderer/src/application/sample-modules/isf/Pixellate.fs b/src/renderer/src/application/sample-modules/isf/Pixellate.fs new file mode 100644 index 000000000..039aed6d5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Pixellate.fs @@ -0,0 +1,82 @@ + +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "cell_size", + "TYPE": "float", + "MIN": 0.001, + "MAX": 0.5, + "DEFAULT": 0.125 + }, + { + "NAME": "shape", + "VALUES": [ + 0, + 1 + ], + "LABELS": [ + "Square", + "Rectangle" + ], + "DEFAULT": 0, + "TYPE": "long" + } + ] +}*/ + +#ifndef GL_ES +float distance (vec2 center, vec2 pt) +{ + float tmp = pow(center.x-pt.x,2.0)+pow(center.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + // At 0.0 just do a pass-thru + if (cell_size == 0.0) { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // Position of current pixel + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + + // Left and right of tile + float CellWidth = cell_size; + float CellHeight = cell_size; + if (shape==0) { + CellHeight = cell_size * RENDERSIZE.x / RENDERSIZE.y; + } + + float x1 = floor(xy.x / CellWidth)*CellWidth; + float x2 = clamp((ceil(xy.x / CellWidth)*CellWidth), 0.0, 1.0); + // Top and bottom of tile + float y1 = floor(xy.y / CellHeight)*CellHeight; + float y2 = clamp((ceil(xy.y / CellHeight)*CellHeight), 0.0, 1.0); + + // GET AVERAGE CELL COLOUR + // Average left and right pixels + vec4 avgX = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x2, y1)))) / 2.0; + // Average top and bottom pixels + vec4 avgY = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x1, y2)))) / 2.0; + // Centre pixel + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + vec4 avgClr = (avgX+avgY+avgC) / 3.0; + + gl_FragColor = vec4(avgClr); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Polar Function.fs b/src/renderer/src/application/sample-modules/isf/Polar Function.fs new file mode 100644 index 000000000..d54ed99db --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Polar Function.fs @@ -0,0 +1,70 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/polar_function.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 5, + "MAX": 10, + "MIN": 0, + "NAME": "segments", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Fernando Kuteken +// License: MIT + +#define PI 3.14159265359 + + +vec4 transition (vec2 uv) { + + float angle = atan(uv.y - 0.5, uv.x - 0.5) - 0.5 * PI; + float normalized = (angle + 1.5 * PI) * (2.0 * PI); + + float radius = (cos(float(segments) * angle) + 4.0) / 4.0; + float difference = length(uv - vec2(0.5, 0.5)); + + if (difference > radius * progress) + return getFromColor(uv); + else + return getToColor(uv); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Polka Dots Curtain.fs b/src/renderer/src/application/sample-modules/isf/Polka Dots Curtain.fs new file mode 100644 index 000000000..ebb443cbc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Polka Dots Curtain.fs @@ -0,0 +1,75 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/PolkaDotsCurtain.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 20, + "MAX": 100, + "MIN": 0, + "NAME": "dots", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: bobylito +// license: MIT +const float SQRT_2 = 1.414213562373; + +vec4 transition(vec2 uv) { + bool nextImage = distance(fract(uv * dots), vec2(0.5, 0.5)) < ( progress / distance(uv, center)); + return nextImage ? getToColor(uv) : getFromColor(uv); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Poly Star.fs b/src/renderer/src/application/sample-modules/isf/Poly Star.fs new file mode 100644 index 000000000..2dea544cf --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Poly Star.fs @@ -0,0 +1,95 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "", + "DESCRIPTION": "", + "INPUTS": [ + { + "DEFAULT": 5, + "LABEL": "Point Count", + "MAX": 20, + "MIN": 3, + "NAME": "pointCount", + "TYPE": "float" + }, + { + "DEFAULT": 5, + "LABEL": "Buldge Amount", + "MAX": 10, + "MIN": 0, + "NAME": "buldge", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "LABEL": "Radius Inside", + "MAX": 1, + "MIN": 0, + "NAME": "pointRadiusInside", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "LABEL": "Radius Outside", + "MAX": 1, + "MIN": 0, + "NAME": "pointRadiusOutside", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Rotation", + "MAX": 1, + "MIN": 0, + "NAME": "pointRotation", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "LABEL": "Star Color", + "NAME": "starColor", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +const float pi = 3.14159265359; + +void main() { + vec4 inputPixelColor = vec4(0.0); + float angleInc = 1.0 / pointCount; + + vec2 loc = RENDERSIZE * isf_FragNormCoord; + // 'r' is the radius- the distance in pixels from 'loc' to the center of the rendering space + float r = distance(RENDERSIZE/2.0, loc); + r /= max(RENDERSIZE.x,RENDERSIZE.y); + // 'a' is the angle of the line segment from the center to loc is rotated + float a = atan ((loc.y-RENDERSIZE.y/2.0),(loc.x-RENDERSIZE.x/2.0)); + a += pi; + a /= 2.0*pi; + a += pointRotation; + // 'at' is the angle time + float at = mod(a,angleInc) / angleInc; + // 'rd' is the radius at this angle for the shape + float starness = 10.0 - buldge; + float rd = pointRadiusOutside * pow(at,starness) + pointRadiusInside; + inputPixelColor = (r < rd) ? starColor : vec4(0.0); + at = 1.0 - at; + rd = pointRadiusOutside * pow(at,starness) + pointRadiusInside; + inputPixelColor = (r < rd) ? starColor : inputPixelColor; + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Posterize.fs b/src/renderer/src/application/sample-modules/isf/Posterize.fs new file mode 100644 index 000000000..26061595c --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Posterize.fs @@ -0,0 +1,46 @@ +/*{ + "CATEGORIES": [ + "Retro", + "Stylize", + "Color Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Posterizes an image", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1.25, + "LABEL": "Gamma", + "MAX": 2, + "MIN": 0.5, + "NAME": "gamma", + "TYPE": "float" + }, + { + "DEFAULT": 6, + "LABEL": "Quality", + "MAX": 32, + "MIN": 3, + "NAME": "numColors", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + + vec4 c = IMG_THIS_PIXEL(inputImage); + + c.rgb = pow(c.rgb, vec3(gamma, gamma, gamma)); + c.rgb = c.rgb * numColors; + c.rgb = floor(c.rgb); + c.rgb = c.rgb / numColors; + c.rgb = pow(c.rgb, vec3(1.0/gamma)); + + gl_FragColor = c; +} diff --git a/src/renderer/src/application/sample-modules/isf/Power Warp.fs b/src/renderer/src/application/sample-modules/isf/Power Warp.fs new file mode 100644 index 000000000..d00b2740f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Power Warp.fs @@ -0,0 +1,109 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "", + "DESCRIPTION": "Power curves distortions with shifting", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 4, + "MIN": 0.25, + "NAME": "power_x", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 4, + "MIN": 0.25, + "NAME": "power_y", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "shift_x", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "shift_y", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABELS": [ + "Style 1", + "Style 2" + ], + "NAME": "mode_x", + "TYPE": "long", + "VALUES": [ + 0, + 1 + ] + }, + { + "DEFAULT": 1, + "LABELS": [ + "Style 1", + "Style 2" + ], + "NAME": "mode_y", + "TYPE": "long", + "VALUES": [ + 0, + 1 + ] + } + ], + "ISFVSN": "2" +} +*/ + + +const float pi = 3.14159265359; + + + +void main() { + vec4 inputPixelColor; + vec2 pos = isf_FragNormCoord.xy; + + if (mode_x == 0) { + pos.x = pow(pos.x, power_x); + } + else { + if (pos.x > 0.5) + pos.x = 0.5 + pow(2.0*(pos.x - 0.5), power_x) / 2.0; + else { + pos.x = pow(1.0 - 2.0*pos.x, power_x) / 2.0; + pos.x = 0.5 - pos.x; + } + } + pos.x = mod(pos.x + shift_x, 1.0); + + if (mode_y == 0) { + pos.y = pow(pos.y, power_y); + } + else { + if (pos.y > 0.5) + pos.y = 0.5 + pow(2.0*(pos.y - 0.5), power_y) / 2.0; + else { + pos.y = pow(1.0 - 2.0*pos.y, power_y) / 2.0; + pos.y = 0.5 - pos.y; + } + } + pos.y = mod(pos.y + shift_y, 1.0); + + inputPixelColor = IMG_NORM_PIXEL(inputImage, pos); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Quad Mask.fs b/src/renderer/src/application/sample-modules/isf/Quad Mask.fs new file mode 100644 index 000000000..f61a94fd8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Quad Mask.fs @@ -0,0 +1,150 @@ +/*{ + "CATEGORIES": [ + "Masking", + "Geometry Adjustment" + ], + "CREDIT": "", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "LABEL": "Bottom left", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt1", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 0 + ], + "LABEL": "Top left", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt2", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 1 + ], + "LABEL": "Top right", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt3", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0, + 1 + ], + "LABEL": "Bottom right", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt4", + "TYPE": "point2D" + }, + { + "DEFAULT": 0, + "LABEL": "Invert Mask", + "NAME": "invertMask", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "LABEL": "Apply Mask", + "LABELS": [ + "Apply Mask", + "Set Alpha", + "Show Mask" + ], + "NAME": "maskApplyMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + } + ], + "ISFVSN": "2" +} +*/ + + +float sign(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3) +{ + bool b1, b2, b3; + + b1 = sign(pt, v1, v2) < 0.0; + b2 = sign(pt, v2, v3) < 0.0; + b3 = sign(pt, v3, v1) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + + +void main() { + vec4 inputPixelColor = IMG_THIS_PIXEL(inputImage); + vec2 loc = isf_FragNormCoord; + bool drawPixel = false; + + if ((PointInTriangle(loc,pt1,pt2,pt3))||(PointInTriangle(loc,pt1,pt4,pt3))) { + drawPixel = true; + } + + if (invertMask) + drawPixel = !drawPixel; + + if (maskApplyMode == 0) { + inputPixelColor = (drawPixel) ? inputPixelColor : vec4(0.0); + } + // in this mode only the alpha is changed; the rgb remains intact and may still be visible if another filter adjusts the alpha again + else if (maskApplyMode == 1) { + inputPixelColor.a = (drawPixel) ? inputPixelColor.a : 0.0; + } + else if (maskApplyMode == 2) { + inputPixelColor = (drawPixel) ? vec4(1.0) : vec4(0.0); + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Quad Tile.fs b/src/renderer/src/application/sample-modules/isf/Quad Tile.fs new file mode 100644 index 000000000..655c68b77 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Quad Tile.fs @@ -0,0 +1,136 @@ +/*{ + "CATEGORIES": [ + "Tile Effect" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 2, + "MIN": 0, + "NAME": "size", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "rotation", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "slide1", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "slide2", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "shift", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + +const float tau = 6.28318530718; + + +vec2 pattern() { + float s = sin(tau * rotation); + float c = cos(tau * rotation); + vec2 tex = isf_FragNormCoord * RENDERSIZE; + float scale = 1.0 / max(size,0.001); + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale; + point = (point - shift*RENDERSIZE) / RENDERSIZE; + // do this to repeat + point = mod(point,1.0); + if (point.x < 0.5) { + point.y = mod(point.y + slide1.y, 1.0); + } + else { + point.y = mod(point.y + slide2.y, 1.0); + } + if (point.y < 0.5) { + point.x = mod(point.x + slide1.x, 1.0); + } + else { + point.x = mod(point.x + slide2.x, 1.0); + } + // do this for relections + point = 1.0-abs(1.0-2.0*point); + + // Now let's do a squish based on angle + // convert to polar coordinates + vec2 center = vec2(0.5,0.5); + float r = distance(center, point); + float a = atan ((point.y-center.y),(point.x-center.x)); + + s = sin(a + tau * angle); + c = cos(a + tau * angle); + + float zoom = RENDERSIZE.x / RENDERSIZE.y; + + point.x = (r * c)/zoom + 0.5; + point.y = (r * s)/zoom + 0.5; + + return point; +} + + +void main() { + + vec2 pat = pattern(); + + gl_FragColor = IMG_NORM_PIXEL(inputImage,pat); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/RE RGB Gradient Generator.fs b/src/renderer/src/application/sample-modules/isf/RE RGB Gradient Generator.fs new file mode 100644 index 000000000..53d2ab2ae --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RE RGB Gradient Generator.fs @@ -0,0 +1,344 @@ +/*{ + "CATEGORIES": [ + "Color", + "Utility" + ], + "CREDIT": "by Carter Rosenberg", + "INPUTS": [ + { + "NAME": "lookupImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 16, + "MIN": 0.5, + "NAME": "frequency1", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "phase1", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": -2, + "NAME": "amplitude1", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": -1, + "NAME": "offset1", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle1", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Ramp", + "Triangle", + "Sine", + "Exponential", + "Look Up Table" + ], + "NAME": "curve1", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": -1, + "NAME": "mixLevel1", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "startColor1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 1, + 0, + 0, + 1 + ], + "NAME": "endColor1", + "TYPE": "color" + }, + { + "DEFAULT": 1, + "MAX": 16, + "MIN": 0.5, + "NAME": "frequency2", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "phase2", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": -2, + "NAME": "amplitude2", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": -1, + "NAME": "offset2", + "TYPE": "float" + }, + { + "DEFAULT": 0.75, + "MAX": 1, + "MIN": 0, + "NAME": "angle2", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Ramp", + "Triangle", + "Sine", + "Exponential", + "Look Up Table" + ], + "NAME": "curve2", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": -1, + "NAME": "mixLevel2", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "startColor2", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 1, + 0, + 1 + ], + "NAME": "endColor2", + "TYPE": "color" + }, + { + "DEFAULT": 2, + "MAX": 16, + "MIN": 0.5, + "NAME": "frequency3", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "phase3", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": -2, + "NAME": "amplitude3", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": -1, + "NAME": "offset3", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle3", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Ramp", + "Triangle", + "Sine", + "Exponential", + "Look Up Table" + ], + "NAME": "curve3", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": -1, + "NAME": "mixLevel3", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "startColor3", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 1, + 1 + ], + "NAME": "endColor3", + "TYPE": "color" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "FLOAT": true + } + ] +} +*/ + + + +const float pi = 3.14159265359; +const float e = 2.71828182846; + + +float doMath(int curve, float freq, float phase, float val, vec2 pt) { + float returnMe = phase + freq * val; + + if (curve == 0) { + returnMe = mod(returnMe,1.0); + } + else if (curve == 1) { + returnMe = mod(2.0 * returnMe,2.0); + returnMe = (returnMe < 1.0) ? returnMe : 1.0 - (returnMe - floor(returnMe)); + } + else if (curve == 2) { + returnMe = sin(returnMe * pi * 2.0 - pi / 2.0) * 0.5 + 0.5; + } + else if (curve == 3) { + returnMe = mod(2.0 * returnMe, 2.0); + returnMe = (returnMe < 1.0) ? returnMe : 1.0 - (returnMe - floor(returnMe)); + returnMe = pow(returnMe, 2.0); + } + else if (curve == 4) { + vec2 loc = mod(pt * freq + phase,1.0); + vec4 tmp = IMG_NORM_PIXEL(lookupImage,loc); + returnMe = (tmp.r+tmp.g+tmp.b)*tmp.a/3.0; + } + return returnMe; +} + +// note that this works on normalized points, but respects aspect ratio +vec2 rotatePoint(vec2 pt, float angle) { + vec2 returnMe = pt * RENDERSIZE;; + + float r = distance(RENDERSIZE/2.0, returnMe); + float a = atan ((returnMe.y-RENDERSIZE.y/2.0),(returnMe.x-RENDERSIZE.x/2.0)); + + returnMe.x = r * cos(a + 2.0 * pi * angle - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * angle - pi) + 0.5; + + returnMe = returnMe / RENDERSIZE + vec2(0.5); + + return returnMe; +} + + +void main() { + vec4 returnMe = vec4(0.0); + vec4 blendColor = vec4(0.0); + float mixAmount = 0.0; + vec2 loc = isf_FragNormCoord; + + loc = rotatePoint(isf_FragNormCoord,angle1); + mixAmount = doMath(curve1,frequency1,phase1,1.0-loc.x,loc); + mixAmount = (amplitude1 >= 0.0) ? mixAmount * amplitude1 : (1.0 - mixAmount) * abs(amplitude1); + mixAmount += offset1; + blendColor = mix(startColor1,endColor1,mixAmount); + returnMe.rgb += blendColor.rgb * mixLevel1; + returnMe.a += abs(blendColor.a); + + loc = rotatePoint(isf_FragNormCoord,angle2); + mixAmount = doMath(curve2,frequency2,phase2,1.0-loc.x,loc); + mixAmount = (amplitude2 >= 0.0) ? mixAmount * amplitude2 : (1.0 - mixAmount) * abs(amplitude2); + mixAmount += offset2; + blendColor = mix(startColor2,endColor2,mixAmount); + returnMe.rgb += blendColor.rgb * mixLevel2; + returnMe.a += abs(blendColor.a); + + loc = rotatePoint(isf_FragNormCoord,angle3); + mixAmount = doMath(curve3,frequency3,phase3,1.0-loc.x,loc); + mixAmount = (amplitude3 >= 0.0) ? mixAmount * amplitude3 : (1.0 - mixAmount) * abs(amplitude3); + mixAmount += offset3; + blendColor = mix(startColor3,endColor3,mixAmount); + returnMe.rgb += blendColor.rgb * mixLevel3; + returnMe.a += abs(blendColor.a); + + gl_FragColor = returnMe; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/RGB EQ.fs b/src/renderer/src/application/sample-modules/isf/RGB EQ.fs new file mode 100644 index 000000000..451af11f8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGB EQ.fs @@ -0,0 +1,61 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "red", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 1.0 + }, + { + "NAME": "green", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 1.0 + }, + { + "NAME": "blue", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 1.0 + }, + { + "NAME": "gain", + "TYPE": "float", + "MIN": -1.0, + "MAX": 1.0, + "DEFAULT": 0.0 + } + ] +}*/ + + + + +void main() { + vec4 pixel = IMG_THIS_PIXEL(inputImage); + float brightness = (pixel.r * red + pixel.g * green + pixel.b * blue) / 3.0; + + pixel.r = pixel.r * red; + pixel.g = pixel.g * green; + pixel.b = pixel.b * blue; + + if (gain >= 0.0) { + pixel.a = (brightness >= gain) ? pixel.a : 0.0; + } + else { + pixel.a = (brightness <= 1.0-abs(gain)) ? pixel.a : 0.0; + } + gl_FragColor = pixel; +} diff --git a/src/renderer/src/application/sample-modules/isf/RGB Halftone-lookaround.fs b/src/renderer/src/application/sample-modules/isf/RGB Halftone-lookaround.fs new file mode 100644 index 000000000..84bdc853b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGB Halftone-lookaround.fs @@ -0,0 +1,106 @@ +/*{ + "CREDIT": "by zoidberg", + "CATEGORIES": [ + "Halftone Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "gridSize", + "TYPE": "float", + "MIN": 1, + "MAX": 256, + "DEFAULT": 45 + }, + { + "NAME": "smoothing", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.15 + } + ] +}*/ + +vec4 gridRot = vec4(15.0, 45.0, 75.0, 0.0); +//vec4 gridRot = vec4(15.0, 75.0, 0.0, 45.0); +//vec4 gridRot = vec4(0.0, 0.0, 0.0, 0.0); + + + +// during calculation we find the closest dot to a frag, determine its size, and then determine the size of the four dots above/below/right/left of it. this array of offsets move "one left", "one up", "one right", and "one down"... +vec2 originOffsets[4]; + + + +void main() { + // a halftone is an overlapping series of grids of dots + // each grid of dots is rotated by a different amount + // the size of the dots determines the colors. the shape of the dot should never change (always be a dot with regular edges) + originOffsets[0] = vec2(-1.0, 0.0); + originOffsets[1] = vec2(0.0, 1.0); + originOffsets[2] = vec2(1.0, 0.0); + originOffsets[3] = vec2(0.0, -1.0); + + vec3 rgbAmounts = vec3(0.0); + + // for each of the channels (i) of RGB... + for (float i=0.0; i<3.0; ++i) { + // figure out the rotation of the grid in radians + float rotRad = radians(gridRot[int(i)]); + // the grids are rotated counter-clockwise- to find the nearest dot, take the fragment pixel loc, + // rotate it clockwise, and split by the grid to find the center of the dot. then rotate this + // coord counter-clockwise to yield the location of the center of the dot in pixel coords local to the render space + mat2 ccTrans = mat2(vec2(cos(rotRad), sin(rotRad)), vec2(-1.0*sin(rotRad), cos(rotRad))); + mat2 cTrans = mat2(vec2(cos(rotRad), -1.0*sin(rotRad)), vec2(sin(rotRad), cos(rotRad))); + + // find the location of the frag in the grid (prior to rotating it) + vec2 gridFragLoc = cTrans * gl_FragCoord.xy; + // find the center of the dot closest to the frag- there's no "round" in GLSL 1.2, so do a "floor" to find the dot to the bottom-left of the frag, then figure out if the frag would be in the top and right halves of that square to find the closest dot to the frag + vec2 gridOriginLoc = vec2(floor(gridFragLoc.x/gridSize), floor(gridFragLoc.y/gridSize)); + + vec2 tmpGridCoords = gridFragLoc/vec2(gridSize); + bool fragAtTopOfGrid = ((tmpGridCoords.y-floor(tmpGridCoords.y)) > (gridSize/2.0)) ? true : false; + bool fragAtRightOfGrid = ((tmpGridCoords.x-floor(tmpGridCoords.x)) > (gridSize/2.0)) ? true : false; + if (fragAtTopOfGrid) + gridOriginLoc.y = gridOriginLoc.y + 1.0; + if (fragAtRightOfGrid) + gridOriginLoc.x = gridOriginLoc.x + 1.0; + // ...at this point, "gridOriginLoc" contains the grid coords of the nearest dot to the fragment being rendered + // convert the location of the center of the dot from grid coords to pixel coords + vec2 gridDotLoc = vec2(gridOriginLoc.x*gridSize, gridOriginLoc.y*gridSize) + vec2(gridSize/2.0); + // rotate the pixel coords of the center of the dot so they become relative to the rendering space + vec2 renderDotLoc = ccTrans * gridDotLoc; + // get the color of the pixel of the input image under this dot (the color will ultimately determine the size of the dot) + vec4 renderDotImageColorRGB = IMG_PIXEL(inputImage, renderDotLoc); + + + // the amount of this channel is taken from the same channel of the color of the pixel of the input image under this halftone dot + float imageChannelAmount = renderDotImageColorRGB[int(i)]; + // the size of the dot is determined by the value of the channel + float dotRadius = imageChannelAmount * (gridSize*1.50/2.0); + float fragDistanceToDotCenter = distance(gl_FragCoord.xy, renderDotLoc); + if (fragDistanceToDotCenter < dotRadius) { + rgbAmounts[int(i)] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToDotCenter); + } + + // calcluate the size of the dots abov/below/to the left/right to see if they're overlapping + for (float j=0.0; j<4.0; ++j) { + gridDotLoc = vec2((gridOriginLoc.x+originOffsets[int(j)].x)*gridSize, (gridOriginLoc.y+originOffsets[int(j)].y)*gridSize) + vec2(gridSize/2.0); + renderDotLoc = ccTrans * gridDotLoc; + renderDotImageColorRGB = IMG_PIXEL(inputImage, renderDotLoc); + + imageChannelAmount = renderDotImageColorRGB[int(i)]; + dotRadius = imageChannelAmount * (gridSize*1.50/2.0); + fragDistanceToDotCenter = distance(gl_FragCoord.xy, renderDotLoc); + if (fragDistanceToDotCenter < dotRadius) { + rgbAmounts[int(i)] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToDotCenter); + } + } + } + + gl_FragColor = vec4(rgbAmounts[0], rgbAmounts[1], rgbAmounts[2], 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/RGB Halftone.fs b/src/renderer/src/application/sample-modules/isf/RGB Halftone.fs new file mode 100644 index 000000000..2bacf4bb1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGB Halftone.fs @@ -0,0 +1,67 @@ +/*{ + "CATEGORIES": [ + "Halftone Effect", + "Retro" + ], + "CREDIT": "by zoidberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 45, + "MAX": 256, + "MIN": 1, + "NAME": "gridSize", + "TYPE": "float" + }, + { + "DEFAULT": 0.15, + "MAX": 1, + "MIN": 0, + "NAME": "smoothing", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +vec3 gridRot = vec3(15.0, 45.0, 75.0); + +void main() { + // a halftone is an overlapping series of grids of dots + // each grid of dots is rotated by a different amount + // the size of the dots determines the colors. the shape of the dot should never change (always be a dot with regular edges) + + vec4 rgbaAmounts = vec4(0.0); + + // for each of the channels (i) of RGB... + for (int i=0; i<3; ++i) { + // figure out the rotation of the grid in radians + float rotRad = radians(gridRot[i]); + // the grids are rotated counter-clockwise- to find the nearest dot, take the fragment pixel loc, + // rotate it clockwise, and split by the grid to find the center of the dot. then rotate this + // coord counter-clockwise to yield the location of the center of the dot in pixel coords local to the render space + mat2 ccTrans = mat2(vec2(cos(rotRad), sin(rotRad)), vec2(-1.0*sin(rotRad), cos(rotRad))); + mat2 cTrans = mat2(vec2(cos(rotRad), -1.0*sin(rotRad)), vec2(sin(rotRad), cos(rotRad))); + + // render loc -> grid loc -> grid dot loc -> grid dot loc in render coords -> pixel color under grid dot loc + vec2 gridFragLoc = cTrans * gl_FragCoord.xy; + vec2 gridDotLoc = vec2(floor(gridFragLoc.x/gridSize)*gridSize, floor(gridFragLoc.y/gridSize)*gridSize); + gridDotLoc = gridDotLoc + vec2(gridSize/2.0); + vec2 renderDotLoc = ccTrans * gridDotLoc; + vec4 renderDotImageColorRGB = IMG_PIXEL(inputImage, renderDotLoc); + + float channelAmount = renderDotImageColorRGB[i]; + float dotRadius = channelAmount * (gridSize/2.0); + float fragDistanceToGridCenter = distance(gl_FragCoord.xy, renderDotLoc); + // the amount of the channel depends on the distance to the center of the grid, the size of the dot, and smoothing + float smoothDist = smoothing * (gridSize/6.0); + rgbaAmounts[i] += smoothstep(dotRadius, dotRadius-(dotRadius*smoothing), fragDistanceToGridCenter); + } + + rgbaAmounts.a = 1.0; + gl_FragColor = rgbaAmounts; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/RGB Invert.fs b/src/renderer/src/application/sample-modules/isf/RGB Invert.fs new file mode 100644 index 000000000..b3144c0ce --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGB Invert.fs @@ -0,0 +1,48 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Utility" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "NAME": "r", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "NAME": "g", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "NAME": "b", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "a", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + if (r) + srcPixel.r = 1.0-srcPixel.r; + if (g) + srcPixel.g = 1.0-srcPixel.g; + if (b) + srcPixel.b = 1.0-srcPixel.b; + if (a) + srcPixel.a = 1.0-srcPixel.a; + gl_FragColor = srcPixel; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/RGB Strobe.fs b/src/renderer/src/application/sample-modules/isf/RGB Strobe.fs new file mode 100644 index 000000000..aa54c22f8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGB Strobe.fs @@ -0,0 +1,108 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "r", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "g", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "b", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "a", + "TYPE": "bool", + "DEFAULT": 0.0 + }, + { + "NAME": "strobeRates", + "LABEL": "Strobe Rates", + "TYPE": "color", + "DEFAULT": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + } + ], + "PASSES": [ + { + "TARGET":"lastState", + "WIDTH": "1", + "HEIGHT": "1", + "PERSISTENT": true, + "DESCRIPTION": "Stores the current strobe state of each of the color channels." + }, + { + + } + ] + +}*/ + + + +void main() +{ + // if this is the first pass, i'm going to read the position from the "lastPosition" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastState,vec2(0.5)); + // i'm only using the X, which is the last render time we reset + if (strobeRates.r == 0.0) { + srcPixel.r = (srcPixel.r == 0.0) ? 1.0 : 0.0; + } + else { + srcPixel.r = (mod(TIME, strobeRates.r) <= strobeRates.r / 2.0) ? 1.0 : 0.0; + } + if (strobeRates.g == 0.0) { + srcPixel.g = (srcPixel.g == 0.0) ? 1.0 : 0.0; + } + else { + srcPixel.g = (mod(TIME, strobeRates.g) <= strobeRates.g / 2.0) ? 1.0 : 0.0; + } + if (strobeRates.b == 0.0) { + srcPixel.b = (srcPixel.b == 0.0) ? 1.0 : 0.0; + } + else { + srcPixel.b = (mod(TIME, strobeRates.b) <= strobeRates.b / 2.0) ? 1.0 : 0.0; + } + if (strobeRates.a == 0.0) { + srcPixel.a = (srcPixel.a == 0.0) ? 1.0 : 0.0; + } + else { + srcPixel.a = (mod(TIME, strobeRates.a) <= strobeRates.a / 2.0) ? 1.0 : 0.0; + } + gl_FragColor = srcPixel; + } + // else this isn't the first pass- read the position value from the buffer which stores it + else { + vec4 lastStateVector = IMG_PIXEL(lastState,vec2(0.5)); + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + float red = (r == true) ? 1.0 : 0.0; + float green = (g == true) ? 1.0 : 0.0; + float blue = (b == true) ? 1.0 : 0.0; + float alpha = (a == true) ? 1.0 : 0.0; + srcPixel.r = (lastStateVector.r == 0.0) ? srcPixel.r : abs(red-srcPixel.r); + srcPixel.g = (lastStateVector.g == 0.0) ? srcPixel.g : abs(green-srcPixel.g); + srcPixel.b = (lastStateVector.b == 0.0) ? srcPixel.b : abs(blue-srcPixel.b); + srcPixel.a = (lastStateVector.a == 0.0) ? srcPixel.a : abs(alpha-srcPixel.a); + gl_FragColor = srcPixel; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/RGB Trails 3.0.fs b/src/renderer/src/application/sample-modules/isf/RGB Trails 3.0.fs new file mode 100644 index 000000000..ad297514d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGB Trails 3.0.fs @@ -0,0 +1,53 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "DESCRIPTION": "a persistent buffer is used to maintain an image which is constantly updated. Similar to VVMotionBlur, but each channel has its own weight", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "rWeight", + "TYPE": "float" + }, + { + "NAME": "gWeight", + "TYPE": "float" + }, + { + "NAME": "bWeight", + "TYPE": "float" + }, + { + "NAME": "aWeight", + "TYPE": "float", + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET":"accum", + "PERSISTENT": true, + "FLOAT": true + }, + { + + } + ] + +}*/ + +void main() +{ + if (PASSINDEX==0) { + vec4 freshPixel = IMG_THIS_PIXEL(inputImage); + vec4 stalePixel = IMG_THIS_PIXEL(accum); + gl_FragColor = vec4(mix(freshPixel.r,stalePixel.r,rWeight), mix(freshPixel.g,stalePixel.g,gWeight), mix(freshPixel.b,stalePixel.b,bWeight), mix(freshPixel.a,stalePixel.a,aWeight)); + } + else + gl_FragColor = IMG_THIS_PIXEL(accum); +} diff --git a/src/renderer/src/application/sample-modules/isf/RGBA Swap.fs b/src/renderer/src/application/sample-modules/isf/RGBA Swap.fs new file mode 100644 index 000000000..ae81705be --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGBA Swap.fs @@ -0,0 +1,166 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "LABEL": "Red", + "NAME": "redInput", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ], + "LABELS": [ + "R", + "G", + "B", + "A", + "Average" + ], + "DEFAULT": 0 + }, + { + "LABEL": "Green", + "NAME": "greenInput", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ], + "LABELS": [ + "R", + "G", + "B", + "A", + "Average" + ], + "DEFAULT": 1 + }, + { + "LABEL": "Blue", + "NAME": "blueInput", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ], + "LABELS": [ + "R", + "G", + "B", + "A", + "Average" + ], + "DEFAULT": 2 + }, + { + "LABEL": "Alpha", + "NAME": "alphaInput", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ], + "LABELS": [ + "R", + "G", + "B", + "A", + "Average" + ], + "DEFAULT": 3 + } + ] +}*/ + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + vec4 outputPixel = srcPixel; + float avgVal = (srcPixel.r + srcPixel.g + srcPixel.b) * srcPixel.a / 3.0; + + if (redInput == 0) { + outputPixel.r = srcPixel.r; + } + else if (redInput == 1) { + outputPixel.r = srcPixel.g; + } + else if (redInput == 2) { + outputPixel.r = srcPixel.b; + } + else if (redInput == 3) { + outputPixel.r = srcPixel.a; + } + else if (redInput == 4) { + outputPixel.r = avgVal; + } + + if (greenInput == 0) { + outputPixel.g = srcPixel.r; + } + else if (greenInput == 1) { + outputPixel.g = srcPixel.g; + } + else if (greenInput == 2) { + outputPixel.g = srcPixel.b; + } + else if (greenInput == 3) { + outputPixel.g = srcPixel.a; + } + else if (greenInput == 4) { + outputPixel.g = avgVal; + } + + if (blueInput == 0) { + outputPixel.b = srcPixel.r; + } + else if (blueInput == 1) { + outputPixel.b = srcPixel.g; + } + else if (blueInput == 2) { + outputPixel.b = srcPixel.b; + } + else if (blueInput == 3) { + outputPixel.b = srcPixel.a; + } + else if (blueInput == 4) { + outputPixel.b = avgVal; + } + + + if (alphaInput == 0) { + outputPixel.a = srcPixel.r; + } + else if (alphaInput == 1) { + outputPixel.a = srcPixel.g; + } + else if (alphaInput == 2) { + outputPixel.a = srcPixel.b; + } + else if (alphaInput == 3) { + outputPixel.a = srcPixel.a; + } + else if (alphaInput == 4) { + outputPixel.a = avgVal; + } + + gl_FragColor = outputPixel; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/RGBtoHSV.fs b/src/renderer/src/application/sample-modules/isf/RGBtoHSV.fs new file mode 100644 index 000000000..90240d8c3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/RGBtoHSV.fs @@ -0,0 +1,46 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Utility" + ], + "CREDIT": "by zoidberg", + "DESCRIPTION": "swizzles RGBA to BGRA and vice versa", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ], + "ISFVSN": "2" +} +*/ + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + + +void main() +{ + vec4 inColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + gl_FragColor = vec4(rgb2hsv(inColor.rgb), inColor.a); +} diff --git a/src/renderer/src/application/sample-modules/isf/Radial Gradient.fs b/src/renderer/src/application/sample-modules/isf/Radial Gradient.fs new file mode 100644 index 000000000..438cf0e87 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Radial Gradient.fs @@ -0,0 +1,76 @@ +/*{ + "CATEGORIES": [ + "Color" + ], + "CREDIT": "by Carter Rosenberg", + "INPUTS": [ + { + "DEFAULT": 0.1, + "NAME": "radius1", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "NAME": "radius2", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + 0.75, + 0, + 1 + ], + "NAME": "startColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0.25, + 0.75, + 1 + ], + "NAME": "endColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "location", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +void main() { + vec2 tmpPt = location; + float mixOffset = distance(tmpPt * RENDERSIZE.x / RENDERSIZE.y, isf_FragNormCoord * RENDERSIZE.x / RENDERSIZE.y); + float tmpRadius = radius1 + radius2; + if (mixOffset <= radius1) { + gl_FragColor = startColor; + } + else if (mixOffset > tmpRadius) { + gl_FragColor = endColor; + } + else if (radius1 == tmpRadius) { + gl_FragColor = endColor; + } + else { + gl_FragColor = mix(startColor,endColor,(mixOffset-radius1)/(tmpRadius-radius1)); + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Radial Replicate.fs b/src/renderer/src/application/sample-modules/isf/Radial Replicate.fs new file mode 100644 index 000000000..a6cce2f00 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Radial Replicate.fs @@ -0,0 +1,104 @@ +/*{ + "CATEGORIES": [ + "Kaleidoscope" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Repllcates a radial slice of an image", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "LABEL": "Post Rotate Angle", + "MAX": 360, + "MIN": 0, + "NAME": "postRotateAngle", + "TYPE": "float" + }, + { + "DEFAULT": 12, + "LABEL": "Number Of Divisions", + "MAX": 360, + "MIN": 1, + "NAME": "numberOfDivisions", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Pre Rotate Angle", + "MAX": 180, + "MIN": -180, + "NAME": "preRotateAngle", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "IDENTITY": 0, + "LABEL": "Radius Start", + "MAX": 1, + "MIN": 0, + "NAME": "centerRadiusStart", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "IDENTITY": 0, + "LABEL": "Radius End", + "MAX": 2, + "MIN": 0, + "NAME": "centerRadiusEnd", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +const float pi = 3.14159265359; + + + + +void main() { + + vec4 inputPixelColor = vec4(0.0); + vec2 loc = _inputImage_imgRect.zw * vec2(isf_FragNormCoord.x,isf_FragNormCoord.y); + // 'r' is the radius- the distance in pixels from 'loc' to the center of the rendering space + //float r = distance(IMG_SIZE(inputImage)/2.0, loc); + float r = distance(_inputImage_imgRect.zw/2.0, loc); + // 'a' is the angle of the line segment from the center to loc is rotated + //float a = atan ((loc.y-IMG_SIZE(inputImage).y/2.0),(loc.x-IMG_SIZE(inputImage).x/2.0)); + float a = atan ((loc.y-_inputImage_imgRect.w/2.0),(loc.x-_inputImage_imgRect.z/2.0)); + float modAngle = 2.0 * pi / numberOfDivisions; + float scaledCenterRadiusStart = centerRadiusStart * max(RENDERSIZE.x,RENDERSIZE.y); + float scaledCenterRadiusEnd = centerRadiusEnd * max(RENDERSIZE.x,RENDERSIZE.y); + + if (scaledCenterRadiusStart > scaledCenterRadiusEnd) { + scaledCenterRadiusStart = scaledCenterRadiusEnd; + scaledCenterRadiusEnd = centerRadiusStart * max(RENDERSIZE.x,RENDERSIZE.y); + } + + if ((centerRadiusEnd != centerRadiusStart)&&(r >= scaledCenterRadiusStart)&&(r <= scaledCenterRadiusEnd)) { + r = (r - scaledCenterRadiusStart) / (centerRadiusEnd - centerRadiusStart); + + a = mod(a + pi * postRotateAngle/360.0,modAngle); + + // now modify 'a', and convert the modified polar coords (radius/angle) back to cartesian coords (x/y pixels) + loc.x = r * cos(a + 2.0 * pi * (preRotateAngle) / 360.0); + loc.y = r * sin(a + 2.0 * pi * (preRotateAngle) / 360.0); + + loc = loc / _inputImage_imgRect.zw + vec2(0.5); + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + inputPixelColor = vec4(0.0); + } + else { + inputPixelColor = IMG_NORM_PIXEL(inputImage,loc); + } + } + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Radial.fs b/src/renderer/src/application/sample-modules/isf/Radial.fs new file mode 100644 index 000000000..f3618fd38 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Radial.fs @@ -0,0 +1,66 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/Radial.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: Xaychru +// ported by gre from https://gist.github.com/Xaychru/ce1d48f0ce00bb379750 + + +const float PI = 3.141592653589; + +vec4 transition(vec2 p) { + vec2 rp = p*2.-1.; + return mix( + getToColor(p), + getFromColor(p), + smoothstep(0., smoothness, atan(rp.y,rp.x) - (progress-.5) * PI * 2.5) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Random Checkerboard.fs b/src/renderer/src/application/sample-modules/isf/Random Checkerboard.fs new file mode 100644 index 000000000..e42f6b327 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Checkerboard.fs @@ -0,0 +1,121 @@ +/*{ + "CATEGORIES": [ + "Pattern", "Color" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Creates a checkerboard pattern with randomized colors", + "INPUTS": [ + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "width", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "offset", + "TYPE": "point2D" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "hue", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "saturation", + "TYPE": "float" + }, + { + "DEFAULT": 0.95, + "MAX": 1, + "MIN": 0, + "NAME": "brightness", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "NAME": "randHue", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "randSaturation", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "randBright", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "randAlpha", + "TYPE": "bool" + }, + { + "DEFAULT": 0.71, + "MAX": 1, + "MIN": 0, + "NAME": "rSeed", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 rand4(vec4 co) { + vec4 returnMe = vec4(0.0); + returnMe.r = rand(co.rg); + returnMe.g = rand(co.gb); + returnMe.b = rand(co.ba); + returnMe.a = rand(co.rb); + return returnMe; +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + + vec4 out_color = vec4(0.0); + vec2 coord = isf_FragNormCoord; + vec4 hsv = vec4(hue, saturation, brightness, 1.0); + float minW = max(width,1.0/RENDERSIZE.y); + vec2 indexes = floor((coord+offset) / minW); + float index = indexes.x + indexes.y/minW; + + hsv.r = (randHue) ? mod(hsv.r + rand(vec2(index, rSeed + 0.34219)),1.0) : hsv.r; + hsv.g = (randSaturation) ? mod(hsv.g + rand(vec2(index, rSeed + 0.57731)),1.0) : hsv.g; + hsv.b = (randBright) ? mod(hsv.b + rand(vec2(index, rSeed + 0.79436)),1.0) : hsv.b; + hsv.a = (randAlpha) ? rand(vec2(hsv.a + index, rSeed + 1.37665)) : hsv.a; + + out_color.rgb = hsv2rgb(hsv.rgb); + out_color.a = hsv.a; + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Random Freeze.fs b/src/renderer/src/application/sample-modules/isf/Random Freeze.fs new file mode 100644 index 000000000..eb49b0699 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Freeze.fs @@ -0,0 +1,97 @@ +/* +{ + "CATEGORIES" : [ + "Glitch" + ], + "DESCRIPTION" : "Causes only part of an image to update", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "maxUpdateSize", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "LABEL" : "Size", + "MIN" : 0 + }, + { + "NAME" : "maxBlendAmount", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "resetImage", + "TYPE" : "event", + "LABEL" : "Reset Image" + } + ], + "PASSES" : [ + { + "TARGET" : "lastState", + "PERSISTENT" : true, + "DESCRIPTION" : "" + } + ], + "CREDIT" : "VIDVOX" +} +*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 rand4(vec4 co) { + vec4 returnMe = vec4(0.0); + returnMe.r = rand(co.rg); + returnMe.g = rand(co.gb); + returnMe.b = rand(co.ba); + returnMe.a = rand(co.rb); + return returnMe; +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +const float pi = 3.14159265359; + + +bool pointInRect(vec2 pt, vec4 r) +{ + bool returnMe = false; + if ((pt.x >= r.x)&&(pt.y >= r.y)&&(pt.x <= r.x + r.z)&&(pt.y <= r.y + r.w)) + returnMe = true; + return returnMe; +} + +void main() { + vec2 loc = isf_FragNormCoord.xy; + bool doReset = ((resetImage)||(FRAMEINDEX==0)); + vec4 returnMe = (doReset) ? IMG_THIS_PIXEL(inputImage) : IMG_THIS_PIXEL(lastState); + vec4 seeds1 = TIME * vec4(0.2123,0.34517,0.53428,0.7431); + vec4 randCoords = rand4(seeds1); + randCoords.zw *= maxUpdateSize; + if (randCoords.x + randCoords.z > 1.0) + randCoords.z = 1.0 - randCoords.x; + if (randCoords.y + randCoords.w > 1.0) + randCoords.w = 1.0 - randCoords.y; + + bool isInShape = pointInRect(loc,randCoords); + + if (isInShape) { + float mixAmount = maxBlendAmount * rand(vec2(TIME,0.32234)); + vec4 newColor = IMG_THIS_PIXEL(inputImage); + newColor.a = 1.0; + returnMe = mix(newColor,returnMe,mixAmount); + } + + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Random Lines.fs b/src/renderer/src/application/sample-modules/isf/Random Lines.fs new file mode 100644 index 000000000..fab9f5d1b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Lines.fs @@ -0,0 +1,300 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "DEFAULT": 15, + "LABEL": "Line Count", + "MAX": 60, + "MIN": 1, + "NAME": "lineCount", + "TYPE": "float" + }, + { + "DEFAULT": 0.025, + "LABEL": "Max Line Width", + "MAX": 0.25, + "MIN": 0, + "NAME": "lineWidth", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "LABEL": "Random Seed", + "MAX": 1, + "MIN": 0.01, + "NAME": "randomSeed", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Wobble Amount", + "MAX": 0.1, + "MIN": 0, + "NAME": "wobbleAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0.2, + "LABEL": "Hue Range", + "MAX": 1, + "MIN": 0, + "NAME": "hueRange", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Saturation", + "MAX": 1, + "MIN": 0, + "NAME": "colorSaturation", + "TYPE": "float" + }, + { + "DEFAULT": true, + "LABEL": "Randomize Brightness", + "NAME": "randomizeBrightness", + "TYPE": "bool" + }, + { + "DEFAULT": false, + "LABEL": "Randomize Width", + "NAME": "randomizeWidth", + "TYPE": "bool" + }, + { + "DEFAULT": false, + "LABEL": "Randomize Points", + "NAME": "randomizeAllPoints", + "TYPE": "bool" + }, + { + "DEFAULT": false, + "LABEL": "Randomize Alpha", + "NAME": "randomizeAlpha", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +bool is_point_above_line(vec2 pt, float slope, float intercept) { + bool returnMe = false; + float y = slope * pt.x + intercept; + if (y < pt.y) + returnMe = true; + return returnMe; +} + +// returns two values – distrance from the line and the percentage of the way on the line +float distance_from_point_to_line(vec2 pt, vec2 l1, vec2 l2){ + float returnMe = 0.0; + float a = (l2.y - l1.y); + float b = (l2.x - l1.x); + float c = 0.0; + + // if b is zero, this is a vertical line! + // in which case distance is based on x distance alone + if (b == 0.0) { + float minY = min(l1.y, l2.y); + float maxY = max(l1.y, l2.y); + + // if we're between the two points distrance is straight up x diff + if ((pt.y > minY) && (pt.y < maxY)) { + returnMe = abs(pt.x-l1.x); + } + else { + //returnMe = min(distance(pt, l1), distance(pt, l2)); + returnMe = -1.0; + } + } + // if a is zero, this is a horizontal line + else if (a == 0.0) { + float minX = min(l1.x, l2.x); + float maxX = max(l1.x, l2.x); + + // if we're between the two points distrance is straight up y diff + if ((pt.x > minX) && (pt.x < maxX)) { + returnMe = abs(pt.y - l1.y); + } + else { + //returnMe = min(distance(pt, l1), distance(pt, l2)); + returnMe = -1.0; + } + } + // if b isn't 0, solve for c now that we know a, b, and either l1 or l2 + else { + // here's the tricky bit- + // if pt is beyond l1 and l2, we should switch to distance from those points + // in order to determine this we need to use the perpendicular lines to the segment l1|l2 that pass through l1 & l2 + // the slope of the perp line will be -1.0 / slope of original line + float m = a / b; + float perpm = -b / a; + vec2 left_line_pt = l1; + vec2 right_line_pt = l2; + if (l1.x > l2.x) { + left_line_pt = l2; + right_line_pt = l1; + } + + float perp_intercept1 = left_line_pt.y - perpm * left_line_pt.x; + float perp_intercept2 = right_line_pt.y - perpm * right_line_pt.x; + + if (m > 0.0) { + /* + if (is_point_above_line(pt, perpm, perp_intercept1)==false) { + //returnMe = distance(pt, left_line_pt); + returnMe = -1.0; + } + else if (is_point_above_line(pt, perpm, perp_intercept2)==true) { + //returnMe = distance(pt, right_line_pt); + returnMe = -1.0; + } + else { + returnMe = abs(a * pt.x - b * pt.y + l2.x * l1.y - l2.y * l1.x) / sqrt(a*a + b*b); + } + */ + returnMe = abs(a * pt.x - b * pt.y + l2.x * l1.y - l2.y * l1.x) / sqrt(a*a + b*b); + } + else { + /* + if (is_point_above_line(pt, perpm, perp_intercept1)==true) { + //returnMe = distance(pt, left_line_pt); + returnMe = -1.0; + } + else if (is_point_above_line(pt, perpm, perp_intercept2)==false) { + //returnMe = distance(pt, right_line_pt); + returnMe = -1.0; + } + else { + returnMe = abs(a * pt.x - b * pt.y + l2.x * l1.y - l2.y * l1.x) / sqrt(a*a + b*b); + } + */ + returnMe = abs(a * pt.x - b * pt.y + l2.x * l1.y - l2.y * l1.x) / sqrt(a*a + b*b); + } + } + + return returnMe; +} + +void main() { + float maxWidth = lineWidth; + float minWidth = 1.0 / min(RENDERSIZE.x, RENDERSIZE.y); + if (maxWidth < minWidth) + maxWidth = minWidth; + + vec4 result = vec4(0.0); + vec2 thisPoint = isf_FragNormCoord; + vec3 colorHSL; + vec2 pt1, pt2; + float baseHue = rand(vec2(floor(lineCount), 1.0)); + + colorHSL.x = baseHue; + colorHSL.y = colorSaturation; + colorHSL.z = 1.0; + if (randomizeBrightness) { + colorHSL.z = rand(vec2(floor(lineCount)+randomSeed * 3.72, randomSeed + lineCount * 0.649)); + } + + vec2 wobbleVector = vec2(0.0); + + pt1 = vec2(rand(vec2(floor(lineCount)+randomSeed*1.123,randomSeed*1.321)),rand(vec2(randomSeed*2.123,randomSeed*3.325))); + pt2 = vec2(rand(vec2(floor(lineCount)+randomSeed*0.317,randomSeed*2.591)),rand(vec2(randomSeed*1.833,randomSeed*4.916))); + + if (wobbleAmount > 0.0) { + wobbleVector = wobbleAmount * vec2(rand(vec2(TIME*1.123,TIME*3.239)),rand(vec2(TIME*3.321,TIME*2.131))) - vec2(wobbleAmount / 2.0); + pt1 = pt1 + wobbleVector; + + wobbleVector = wobbleAmount * vec2(rand(vec2(TIME*6.423,TIME*1.833)),rand(vec2(TIME*2.436,TIME*7.532))) - vec2(wobbleAmount / 2.0); + pt2 = pt2 + wobbleVector; + } + + float randomWidth = maxWidth; + + if (randomizeWidth) { + randomWidth = clamp(maxWidth * rand(vec2(1.0 + randomSeed * 4.672, randomSeed * lineCount * 2.523)), minWidth, maxWidth); + } + + if (distance_from_point_to_line(thisPoint, pt1, pt2) < randomWidth) { + float newAlpha = 1.0; + + if (randomizeAlpha) { + newAlpha = 0.25 + 0.5 * rand(vec2(1.0 + floor(lineCount)+randomSeed * 1.938, randomSeed * lineCount * 1.541)); + } + + result.rgb = hsv2rgb(colorHSL); + result.a = result.a + newAlpha; + } + + for (float i = 0.0; i < 60.0; ++i) { + if (result.a > 0.75) + break; + if (i >= lineCount - 1.0) + break; + if (randomizeAllPoints) { + pt1 = vec2(rand(vec2(i+randomSeed*1.123,floor(lineCount)+randomSeed*1.321)),rand(vec2((1.0+i)*floor(lineCount)+randomSeed*2.123,i+randomSeed*1.325))); + + if (wobbleAmount > 0.0) { + wobbleVector = wobbleAmount * vec2(rand(vec2(i*floor(lineCount)+TIME*3.123,i*floor(lineCount)+TIME*3.239)),rand(vec2(i*floor(lineCount)+TIME*3.321,i*floor(lineCount)+TIME*2.131))) - vec2(wobbleAmount / 2.0); + pt1 = pt1 + wobbleVector; + } + } + else { + pt1 = pt2; + } + pt2 = vec2(rand(vec2(i*floor(lineCount)+randomSeed*3.573,i+randomSeed*6.273)),rand(vec2(i+randomSeed*9.253,i+randomSeed*7.782))); + + if (wobbleAmount > 0.0) { + wobbleVector = wobbleAmount * vec2(rand(vec2(i*floor(lineCount)+TIME*3.573,i+randomSeed*6.273)),rand(vec2(i+TIME*9.253,i+TIME*7.782))) - vec2(wobbleAmount / 2.0); + pt2 = pt2 + wobbleVector; + } + + if (randomizeWidth) { + randomWidth = clamp(maxWidth * rand(vec2(i + randomSeed * 4.672, 1.673 + i * randomSeed * 2.523)), minWidth, maxWidth); + } + + if (distance_from_point_to_line(thisPoint, pt1, pt2) < randomWidth) { + //result = vec4(1.0); + float newAlpha = 1.0; + + if (randomizeAlpha) { + newAlpha = 0.25 + 0.25 * rand(vec2(i + floor(lineCount)+randomSeed * 1.938, randomSeed * lineCount * 1.541)); + } + + colorHSL.x = mod(baseHue + hueRange * rand(vec2(floor(lineCount)+randomSeed, i)), 1.0); + if (randomizeBrightness) { + colorHSL.z = 0.15 + 0.85 * rand(vec2(i + floor(lineCount)+randomSeed * 2.78, randomSeed + lineCount * 0.249)); + } + result.rgb = result.rgb + hsv2rgb(colorHSL) * newAlpha; + result.a = result.a + newAlpha; + } + } + + gl_FragColor = result; +} diff --git a/src/renderer/src/application/sample-modules/isf/Random Shape Blast.fs b/src/renderer/src/application/sample-modules/isf/Random Shape Blast.fs new file mode 100644 index 000000000..e29a9d0a4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Shape Blast.fs @@ -0,0 +1,224 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "DEFAULT": 0.5, + "LABEL": "Saturation", + "MAX": 1, + "MIN": 0, + "NAME": "saturation", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Brightness", + "MAX": 1, + "MIN": 0, + "NAME": "brightness", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Mix Amount", + "MAX": 1, + "MIN": 0, + "NAME": "mixAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Mask Shape Mode", + "LABELS": [ + "Random", + "Rectangle", + "Triangle", + "Circle", + "Diamond" + ], + "NAME": "maskShapeMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "DEFAULT": 0, + "LABEL": "Anchor To Bottom", + "NAME": "anchorToBottom", + "TYPE": "bool" + }, + { + "LABEL": "Reset", + "NAME": "resetImage", + "TYPE": "event" + } + ], + "ISFVSN": "2", + "KEYWORDS": [ + "Abstract", + "Geometric" + ], + "PASSES": [ + { + "DESCRIPTION": "Holds the last render state for drawing over", + "PERSISTENT": true, + "TARGET": "lastState" + } + ] +} +*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 rand4(vec4 co) { + vec4 returnMe = vec4(0.0); + returnMe.r = rand(co.rg); + returnMe.g = rand(co.gb); + returnMe.b = rand(co.ba); + returnMe.a = rand(co.rb); + return returnMe; +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +const float pi = 3.14159265359; + + +vec2 rotatePoint(vec2 pt, float angle, vec2 center) +{ + vec2 returnMe; + float s = sin(angle * pi); + float c = cos(angle * pi); + + returnMe = pt; + + // translate point back to origin: + returnMe.x -= center.x; + returnMe.y -= center.y; + + // rotate point + float xnew = returnMe.x * c - returnMe.y * s; + float ynew = returnMe.x * s + returnMe.y * c; + + // translate point back: + returnMe.x = xnew + center.x; + returnMe.y = ynew + center.y; + return returnMe; +} + +float sign(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool RotatedPointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3, vec2 center) +{ + bool b1, b2, b3; + + vec2 v1r = v1; + vec2 v2r = v2; + vec2 v3r = v3; + + b1 = sign(pt, v1r, v2r) < 0.0; + b2 = sign(pt, v2r, v3r) < 0.0; + b3 = sign(pt, v3r, v1r) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + + +float isPointInShape(vec2 pt, int shape, vec4 shapeCoordinates) { + float returnMe = 0.0; + + // rectangle + if (shape == 0) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + // soft edge if needed + if ((pt.x > shapeCoordinates.x) && (pt.x < shapeCoordinates.x)) { + returnMe = clamp(((pt.x - shapeCoordinates.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + else if ((pt.x > shapeCoordinates.x + shapeCoordinates.z) && (pt.x < shapeCoordinates.x + shapeCoordinates.z)) { + returnMe = clamp(((shapeCoordinates.x + shapeCoordinates.z - pt.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + } + else if (RotatedPointInTriangle(pt, shapeCoordinates.xy + shapeCoordinates.zw, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + // soft edge if needed + if ((pt.x > shapeCoordinates.x) && (pt.x < shapeCoordinates.x)) { + returnMe = clamp(((pt.x - shapeCoordinates.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + else if ((pt.x > shapeCoordinates.x + shapeCoordinates.z) && (pt.x < shapeCoordinates.x + shapeCoordinates.z)) { + returnMe = clamp(((shapeCoordinates.x + shapeCoordinates.z - pt.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + } + } + // triangle + else if (shape == 1) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy, shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + } + // oval + else if (shape == 2) { + returnMe = distance(pt, vec2(shapeCoordinates.xy + shapeCoordinates.zw / 2.0)); + if (returnMe < min(shapeCoordinates.z,shapeCoordinates.w) / 2.0) { + returnMe = 1.0; + } + else { + returnMe = 0.0; + } + } + // diamond + else if (shape == 3) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w / 2.0), shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, shapeCoordinates.w / 2.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + else if (RotatedPointInTriangle(pt, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w / 2.0), shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, 0.0), shapeCoordinates.xy + vec2(shapeCoordinates.z, shapeCoordinates.w / 2.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + } + + return returnMe; +} + + +void main() { + vec2 loc = isf_FragNormCoord.xy; + vec4 returnMe = (resetImage) ? vec4(0.0) : IMG_NORM_PIXEL(lastState,loc); + vec4 seeds1 = TIME * vec4(0.2123,0.34517,0.53428,0.7431); + vec4 randCoords = rand4(seeds1); + if (anchorToBottom == true) { + randCoords.y = 0.0; + } + int shapeMode = (maskShapeMode != 0) ? maskShapeMode - 1 : int(floor(3.99 * rand(vec2(TIME+0.213,0.43*TIME+0.831)))); + float isInShape = isPointInShape(loc,shapeMode,randCoords); + + if (isInShape > 0.0) { + float randHue = rand(vec2(TIME,0.32234)); + vec4 newColor = vec4(0.0); + newColor.rgb = hsv2rgb(vec3(randHue,saturation,brightness)); + newColor.a = 1.0; + returnMe = mix(returnMe,newColor,mixAmount); + } + + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Random Shape.fs b/src/renderer/src/application/sample-modules/isf/Random Shape.fs new file mode 100644 index 000000000..be9acb9d0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Shape.fs @@ -0,0 +1,280 @@ +/*{ + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Generator" + ], + "INPUTS": [ + { + "NAME": "pointCount", + "LABEL": "Point Count", + "TYPE": "float", + "MIN": 3, + "MAX": 90, + "DEFAULT": 15 + }, + { + "NAME": "randomSeed", + "LABEL": "Random Seed", + "TYPE": "float", + "MIN": 0.01, + "MAX": 1, + "DEFAULT": 0.125 + }, + { + "NAME": "wobbleAmount", + "LABEL": "Wobble Amount", + "TYPE": "float", + "MIN": 0, + "MAX": 0.25, + "DEFAULT": 0 + }, + { + "NAME": "zoomStart", + "LABEL": "Zoom Start", + "TYPE": "float", + "MIN": 0.001, + "MAX": 4, + "DEFAULT": 0.75 + }, + { + "NAME": "zoomEnd", + "LABEL": "Zoom End", + "TYPE": "float", + "MIN": 0.001, + "MAX": 4, + "DEFAULT": 1 + }, + { + "NAME": "rotationStart", + "LABEL": "Winding Start", + "TYPE": "float", + "MIN": -4, + "MAX": 4, + "DEFAULT": 0 + }, + { + "NAME": "rotationEnd", + "LABEL": "Winding End", + "TYPE": "float", + "MIN": -4, + "MAX": 4, + "DEFAULT": 0 + }, + { + "NAME": "colorSaturation", + "LABEL": "Saturation", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 1 + }, + { + "NAME": "hueBase", + "LABEL": "Hue Base", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.2 + }, + { + "NAME": "hueRange", + "LABEL": "Hue Range", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0.2 + }, + { + "NAME": "offsetEnd", + "TYPE": "point2D", + "DEFAULT": [ + 0.5, + 0.5 + ] + }, + { + "NAME": "randomizeBrightness", + "LABEL": "Randomize Brightness", + "TYPE": "bool", + "DEFAULT": true + }, + { + "NAME": "randomizeAlpha", + "LABEL": "Randomize Alpha", + "TYPE": "bool", + "DEFAULT": false + }, + { + "NAME": "randomizeAllPoints", + "LABEL": "Randomize Points", + "TYPE": "bool", + "DEFAULT": false + } + ] +}*/ + + + +const float pi = 3.14159265359; + + + +vec2 rotatePoint(vec2 pt, float angle, vec2 center) +{ + vec2 returnMe; + float s = sin(angle * pi); + float c = cos(angle * pi); + + returnMe = pt; + + // translate point back to origin: + returnMe.x -= center.x; + returnMe.y -= center.y; + + // rotate point + float xnew = returnMe.x * c - returnMe.y * s; + float ynew = returnMe.x * s + returnMe.y * c; + + // translate point back: + returnMe.x = xnew + center.x; + returnMe.y = ynew + center.y; + return returnMe; +} + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +float sign(vec2 p1, vec2 p2, vec2 p3) { + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3) { + bool b1, b2, b3; + + b1 = sign(pt, v1, v2) < 0.0; + b2 = sign(pt, v2, v3) < 0.0; + b3 = sign(pt, v3, v1) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + + +void main() { + vec4 result = vec4(0.0); + vec2 thisPoint = gl_FragCoord.xy; + vec3 colorHSL; + vec2 pt1, pt2, pt3; + vec2 offsetIncrement = (4.0 * (offsetEnd / RENDERSIZE - vec2(0.5)) / (pointCount - 2.0)); + float rotationIncrement = (rotationEnd - rotationStart) / pointCount; + float zoomIncrement = (zoomEnd - zoomStart) / pointCount; + + thisPoint = rotatePoint(thisPoint, rotationStart, RENDERSIZE/2.0); + thisPoint = thisPoint / RENDERSIZE; + thisPoint = (thisPoint - vec2(0.5)) / zoomStart + vec2(0.5); + + colorHSL.x = hueBase; + colorHSL.y = colorSaturation; + colorHSL.z = 1.0; + if (randomizeBrightness) { + colorHSL.z = rand(vec2(floor(pointCount)+randomSeed * 3.72, randomSeed + pointCount * 0.649)); + } + + vec2 wobbleVector = vec2(0.0); + + pt1 = vec2(rand(vec2(floor(pointCount)+randomSeed*1.123,randomSeed*1.321)),rand(vec2(randomSeed*2.123,randomSeed*3.325))); + pt2 = vec2(rand(vec2(floor(pointCount)+randomSeed*5.317,randomSeed*2.591)),rand(vec2(randomSeed*1.833,randomSeed*4.916))); + pt3 = vec2(rand(vec2(floor(pointCount)+randomSeed*3.573,randomSeed*6.273)),rand(vec2(randomSeed*9.253,randomSeed*7.782))); + + if (wobbleAmount > 0.0) { + wobbleVector = wobbleAmount * vec2(rand(vec2(TIME*1.123,TIME*3.239)),rand(vec2(TIME*3.321,TIME*2.131))) - vec2(wobbleAmount / 2.0); + pt1 = pt1 + wobbleVector; + + wobbleVector = wobbleAmount * vec2(rand(vec2(TIME*6.423,TIME*1.833)),rand(vec2(TIME*2.436,TIME*7.532))) - vec2(wobbleAmount / 2.0); + pt2 = pt2 + wobbleVector; + + wobbleVector = wobbleAmount * vec2(rand(vec2(TIME*3.951,TIME*3.538)),rand(vec2(TIME*8.513,TIME*6.335))) - vec2(wobbleAmount / 2.0); + pt3 = pt3 + wobbleVector; + } + + if (PointInTriangle(thisPoint,pt1,pt2,pt3)) { + float newAlpha = 1.0; + + if (randomizeAlpha) { + newAlpha = 0.5 + 0.5 * rand(vec2(1.0 + floor(pointCount)+randomSeed * 1.938, randomSeed * pointCount * 1.541)); + } + + result.rgb = hsv2rgb(colorHSL); + result.a = result.a + newAlpha; + } + + for (float i = 0.0; i < 90.0; ++i) { + if (result.a > 0.75) + break; + if (i > pointCount - 3.0) + break; + if (randomizeAllPoints) { + pt1 = vec2(rand(vec2(i+randomSeed*1.123,i*floor(pointCount)+randomSeed*1.321)),rand(vec2(i*floor(pointCount)+randomSeed*2.123,i+randomSeed*1.325))); + pt2 = vec2(rand(vec2(i*floor(pointCount)+randomSeed*5.317,randomSeed*2.591)),rand(vec2(i+randomSeed*1.833,i*floor(pointCount)+randomSeed*4.916))); + + if (wobbleAmount > 0.0) { + wobbleVector = wobbleAmount * vec2(rand(vec2(i*floor(pointCount)+TIME*3.123,i*floor(pointCount)+TIME*3.239)),rand(vec2(i*floor(pointCount)+TIME*3.321,i*floor(pointCount)+TIME*2.131))) - vec2(wobbleAmount / 2.0); + pt1 = pt1 + wobbleVector; + + wobbleVector = wobbleAmount * vec2(rand(vec2(i*floor(pointCount)+TIME*6.423,i*floor(pointCount)+TIME*1.833)),rand(vec2(i*floor(pointCount)+TIME*2.436,i*floor(pointCount)+TIME*7.532))) - vec2(wobbleAmount / 2.0); + pt2 = pt2 + wobbleVector; + } + } + else { + pt1 = pt2; + pt2 = pt3; + } + pt3 = vec2(rand(vec2(i*floor(pointCount)+randomSeed*3.573,i+randomSeed*6.273)),rand(vec2(i+randomSeed*9.253,i+randomSeed*7.782))); + pt3 = (pt3 - vec2(0.5)) * (zoomStart + zoomIncrement * i) + vec2(0.5); + pt3 = rotatePoint(pt3, rotationStart + rotationIncrement * i, vec2(0.5)); + pt3 = pt3 + offsetIncrement * i; + + if (wobbleAmount > 0.0) { + wobbleVector = wobbleAmount * vec2(rand(vec2(i*floor(pointCount)+TIME*3.573,i+randomSeed*6.273)),rand(vec2(i+TIME*9.253,i+TIME*7.782))) - vec2(wobbleAmount / 2.0); + pt3 = pt3 + wobbleVector; + } + + if (PointInTriangle(thisPoint,pt1,pt2,pt3)) { + //result = vec4(1.0); + float newAlpha = 1.0; + + if (randomizeAlpha) { + newAlpha = 0.1 + 0.25 * rand(vec2(i + floor(pointCount)+randomSeed * 1.938, randomSeed * pointCount * 1.541)); + } + + colorHSL.x = mod(hueBase + hueRange * rand(vec2(floor(pointCount)+randomSeed, i)), 1.0); + if (randomizeBrightness) { + colorHSL.z = 0.25 + 0.85 * rand(vec2(i + floor(pointCount)+randomSeed * 2.78, randomSeed + pointCount * 0.249)); + } + result.rgb = result.rgb + hsv2rgb(colorHSL) * newAlpha; + result.a = result.a + newAlpha; + } + } + + gl_FragColor = result; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Random Squares Mask.fs b/src/renderer/src/application/sample-modules/isf/Random Squares Mask.fs new file mode 100644 index 000000000..49c4c527d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Squares Mask.fs @@ -0,0 +1,100 @@ +/* +{ + "CATEGORIES" : [ + "Masking" + ], + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "width", + "TYPE" : "float", + "DEFAULT" : 0.125 + }, + { + "NAME" : "offset", + "TYPE" : "point2D", + "DEFAULT" : [ + 0, + 0 + ], + "MIN": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ] + }, + { + "NAME" : "alpha1", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "alpha2", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 1, + "MIN" : 0 + }, + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "seed1", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.241, + "MIN" : 0 + }, + { + "NAME" : "randomThreshold", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "MIN" : 0 + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + + +// glsl doesn't include random functions +// this is a pseudo-random function +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +void main() { + // determine if we are on an even or odd line + // math goes like.. + // mod(((coord+offset) / width),2) + + + vec4 out_color = IMG_THIS_NORM_PIXEL(inputImage); + float alphaAdjust = alpha2; + vec2 coord = isf_FragNormCoord * RENDERSIZE; + vec2 shift = offset * RENDERSIZE; + float size = width * RENDERSIZE.x; + vec2 gridIndex = vec2(0.0); + + if (size == 0.0) { + alphaAdjust = alpha1; + } + else { + gridIndex = floor((shift + coord) / size); + float value = rand(seed1*gridIndex); + if (value < randomThreshold) + alphaAdjust = alpha1; + } + + out_color.a *= alphaAdjust; + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Random Squares.fs b/src/renderer/src/application/sample-modules/isf/Random Squares.fs new file mode 100644 index 000000000..1b962e34e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Squares.fs @@ -0,0 +1,80 @@ +/*{ + "CATEGORIES": [ + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/randomsquares.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + }, + { + "DEFAULT": [ + 10, + 10 + ], + "MAX": [ + 100, + 100 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "size", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +float rand (vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 transition(vec2 p) { + float r = rand(floor(vec2(size) * p)); + float m = smoothstep(0.0, -smoothness, r - (progress * (1.0 + smoothness))); + return mix(getFromColor(p), getToColor(p), m); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Random Stripes.fs b/src/renderer/src/application/sample-modules/isf/Random Stripes.fs new file mode 100644 index 000000000..f30de2b22 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Random Stripes.fs @@ -0,0 +1,116 @@ +/*{ + "CATEGORIES": [ + "Pattern", "Color" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Creates a stripe pattern with randomized colors", + "INPUTS": [ + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "width", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "offset", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "hue", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "saturation", + "TYPE": "float" + }, + { + "DEFAULT": 0.95, + "MAX": 1, + "MIN": 0, + "NAME": "brightness", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "NAME": "vertical", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "NAME": "randHue", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "randSaturation", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "randBright", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "randAlpha", + "TYPE": "bool" + }, + { + "DEFAULT": 0.71, + "MAX": 1, + "MIN": 0, + "NAME": "rSeed", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 rand4(vec4 co) { + vec4 returnMe = vec4(0.0); + returnMe.r = rand(co.rg); + returnMe.g = rand(co.gb); + returnMe.b = rand(co.ba); + returnMe.a = rand(co.rb); + return returnMe; +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + + vec4 out_color = vec4(0.0); + float coord = (vertical) ? isf_FragNormCoord.x : isf_FragNormCoord.y; + vec4 hsv = vec4(hue, saturation, brightness, 1.0); + float minW = (vertical) ? max(width,1.0/RENDERSIZE.x) : max(width,1.0/RENDERSIZE.y); + float index = floor((coord+offset) / minW); + + hsv.r = (randHue) ? mod(hsv.r + rand(vec2(index, rSeed + 0.34219)),1.0) : hsv.r; + hsv.g = (randSaturation) ? mod(hsv.g + rand(vec2(index, rSeed + 0.57731)),1.0) : hsv.g; + hsv.b = (randBright) ? mod(hsv.b + rand(vec2(index, rSeed + 0.79436)),1.0) : hsv.b; + hsv.a = (randAlpha) ? rand(vec2(hsv.a + index, rSeed + 1.37665)) : hsv.a; + + out_color.rgb = hsv2rgb(hsv.rgb); + out_color.a = hsv.a; + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Replicate Random.fs b/src/renderer/src/application/sample-modules/isf/Replicate Random.fs new file mode 100644 index 000000000..691deae6f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Replicate Random.fs @@ -0,0 +1,97 @@ +/*{ + "CATEGORIES": [ + "Tile Effect" + ], + "CREDIT": "", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "randomSeed", + "TYPE": "float" + }, + { + "DEFAULT": 5, + "MAX": 15, + "MIN": 1, + "NAME": "repetitions", + "TYPE": "float" + }, + { + "NAME": "randomizeOpacity", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +vec2 paddedZoomedPosition(vec2 loc, float zl, vec2 c, float p) { + vec2 returnMe = loc; + float zoomMult = (1.0/zl); + vec2 modifiedCenter = 2.0*(1.0+p)*c/RENDERSIZE-(1.0+p); + float modifiedPadding = p; + + returnMe.x = (returnMe.x)*zoomMult + p/2.0 - modifiedCenter.x; + returnMe.y = (returnMe.y)*zoomMult + p/2.0 - modifiedCenter.y; + returnMe.x = mod(returnMe.x,1.0+modifiedPadding) - p/2.0; + returnMe.y = mod(returnMe.y,1.0+modifiedPadding) - p/2.0; + + return returnMe; +} + +vec2 randomPaddedZoomedPositionWithSeed(vec2 loc, vec2 seed) { + float minZoomLevel = (4.0/RENDERSIZE.x); + vec2 zSeed = seed * vec2(0.128,9.21) + vec2(1.42,2.17); + vec2 cSeed1 = seed * vec2(0.436,0.931) + vec2(2.76,3.779); + vec2 cSeed2 = seed * vec2(2.831,2.173) + vec2(1.73,6.256); + float modZoom = 0.9*rand(zSeed); + vec2 modCenter = RENDERSIZE*vec2(rand(cSeed1),rand(cSeed2)); + float modPad = 0.5*rand(seed); + modZoom = (modZoom < minZoomLevel) ? minZoomLevel : modZoom; + + vec2 returnMe = paddedZoomedPosition(loc,modZoom,modCenter,modPad); + + return returnMe; +} + + + + +void main() { + vec4 inputPixelColor = vec4(0.0); + + int depth = int(repetitions); + vec2 loc = isf_FragNormCoord; + + for (int i = 0;i < 15;++i) { + if (i >= depth) + break; + vec2 tmpSeed = vec2((1.12+float(i))*randomSeed+1.37,(1.92+float(i))*randomSeed+1.37); + float modOpacity = (randomizeOpacity) ? 0.25+0.75*rand(tmpSeed) : 1.0; + loc = randomPaddedZoomedPositionWithSeed(isf_FragNormCoord,tmpSeed); + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + //inputPixelColor = vec4(0.0); + } + else { + vec4 tmpColor = IMG_NORM_PIXEL(inputImage,loc); + inputPixelColor.rgb = inputPixelColor.rgb + tmpColor.rgb * tmpColor.a * modOpacity; + inputPixelColor.a += (tmpColor.a * modOpacity); + if (inputPixelColor.a > 0.99) { + break; + } + } + + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Replicate.fs b/src/renderer/src/application/sample-modules/isf/Replicate.fs new file mode 100644 index 000000000..7108f70db --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Replicate.fs @@ -0,0 +1,127 @@ +/*{ + "CATEGORIES": [ + "Tile Effect" + ], + "CREDIT": "", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "IDENTITY": 1, + "MAX": 2, + "MIN": 0, + "NAME": "startSize", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "startOpacity", + "TYPE": "float" + }, + { + "NAME": "startCenter", + "TYPE": "point2D" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "startPadding", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 2, + "MIN": 0, + "NAME": "endSize", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "endOpacity", + "TYPE": "float" + }, + { + "NAME": "endCenter", + "TYPE": "point2D" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "endPadding", + "TYPE": "float" + }, + { + "DEFAULT": 5, + "MAX": 15, + "MIN": 1, + "NAME": "repetitions", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +vec2 paddedZoomedPosition(vec2 loc, float zl, vec2 c, float p) { + vec2 returnMe = loc; + float zoomMult = (1.0/zl); + vec2 modifiedCenter = 2.0*(1.0+p)*c/RENDERSIZE-(1.0+p); + float modifiedPadding = p; + + returnMe.x = (returnMe.x)*zoomMult + p/2.0 - modifiedCenter.x; + returnMe.y = (returnMe.y)*zoomMult + p/2.0 - modifiedCenter.y; + returnMe.x = mod(returnMe.x,1.0+modifiedPadding) - p/2.0; + returnMe.y = mod(returnMe.y,1.0+modifiedPadding) - p/2.0; + + return returnMe; +} + + +void main() { + vec4 inputPixelColor = vec4(0.0); + + int depth = int(repetitions); + vec2 loc = isf_FragNormCoord; + float minZoomLevel = (1.0/RENDERSIZE.x); + float startZoomLevel = (startSize < minZoomLevel) ? minZoomLevel : startSize; + float endZoomLevel = (endSize < minZoomLevel) ? minZoomLevel : endSize; + float zoomIncrement = (depth < 2) ? 0.0 : (endZoomLevel - startZoomLevel)/float(depth-1); + vec2 centerIncrement = (depth < 2) ? vec2(0.0) : (endCenter - startCenter)/float(depth-1); + float paddingIncrement = (depth < 2) ? 0.0 : (endPadding - startPadding)/float(depth-1); + float opacityIncrement = (depth < 2) ? 0.0 : (endOpacity - startOpacity)/float(depth-1); + + for (int i = 0;i < 15;++i) { + if (i >= depth) + break; + float modZoom = startZoomLevel + zoomIncrement * float(i); + vec2 modCenter = startCenter + centerIncrement * float(i); + float modPad = startPadding + paddingIncrement * float(i); + float modOpacity = startOpacity + opacityIncrement * float(i); + modOpacity = clamp(modOpacity,0.0,1.0); + loc = paddedZoomedPosition(isf_FragNormCoord,modZoom,modCenter,modPad); + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + //inputPixelColor = vec4(0.0); + } + else { + vec4 tmpColor = IMG_NORM_PIXEL(inputImage,loc); + inputPixelColor.rgb = inputPixelColor.rgb + tmpColor.rgb * tmpColor.a * modOpacity; + inputPixelColor.a += (tmpColor.a * modOpacity); + if (inputPixelColor.a > 0.99) { + break; + } + } + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Resize Glitch.fs b/src/renderer/src/application/sample-modules/isf/Resize Glitch.fs new file mode 100644 index 000000000..bec06a5c2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Resize Glitch.fs @@ -0,0 +1,115 @@ +/*{ + "CATEGORIES": [ + "Glitch", + "Geometry Adjustment" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "randomFrequency", + "TYPE": "float" + }, + { + "NAME": "glitchNow", + "TYPE": "event" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": 0.01, + "NAME": "levelX", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": 0.01, + "NAME": "levelY", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "DEFAULT": true, + "NAME": "randomizeWidth", + "TYPE": "bool" + }, + { + "DEFAULT": true, + "NAME": "randomizeHeight", + "TYPE": "bool" + }, + { + "DEFAULT": true, + "NAME": "randomizeCenter", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + +float random (vec2 st) { + return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123); +} + +void main() { + vec2 loc; + vec2 modifiedCenter; + + loc = isf_FragNormCoord; + modifiedCenter = (randomizeCenter) ? vec2(random(vec2(TIME*1.24,0.234)),random(vec2(TIME*2.93,1.234))) : center; + + float newWidth = 1.0; + float newHeight = 1.0; + + bool doGlitch = false; + + if (glitchNow) { + doGlitch = true; + } + else if (randomFrequency == 1.0) { + doGlitch = true; + } + else { + float val = random(vec2(TIME,0.2321)); + if (val <= randomFrequency) { + doGlitch = true; + } + } + + if (doGlitch == true) { + newWidth = (randomizeWidth == false) ? levelX : levelX * random(vec2(TIME+0.315,FRAMEINDEX+32)); + newHeight = (randomizeHeight == false) ? levelY : levelY * random(vec2(TIME+0.942,FRAMEINDEX+43)); + } + + loc.x = (loc.x - modifiedCenter.x)*(1.0/newWidth) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/newHeight) + modifiedCenter.y; + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Ripple Transition.fs b/src/renderer/src/application/sample-modules/isf/Ripple Transition.fs new file mode 100644 index 000000000..a08d39a4d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Ripple Transition.fs @@ -0,0 +1,72 @@ +/*{ + "CATEGORIES": [ + "Distortion", + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/ripple.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 100, + "MAX": 100, + "MIN": 0, + "NAME": "amplitude", + "TYPE": "float" + }, + { + "DEFAULT": 50, + "MAX": 100, + "MIN": 0, + "NAME": "speed", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + +vec4 transition (vec2 uv) { + vec2 dir = uv - vec2(.5); + float dist = length(dir); + vec2 offset = dir * (sin(progress * dist * amplitude - progress * speed) + .5) / 30.; + return mix( + getFromColor(uv + offset), + getToColor(uv), + smoothstep(0.2, 1.0, progress) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Ripples.fs b/src/renderer/src/application/sample-modules/isf/Ripples.fs new file mode 100644 index 000000000..fcd2a03c2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Ripples.fs @@ -0,0 +1,118 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "by carter rosenberg", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 32, + "MIN": 0.1, + "NAME": "level", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "offset", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "x_smear", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0.01, + "NAME": "y_smear", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Single", + "Double" + ], + "NAME": "mode", + "TYPE": "long", + "VALUES": [ + 0, + 1 + ] + } + ], + "ISFVSN": "2" +} +*/ + +const float pi = 3.14159265359; + +#ifndef GL_ES +float distance (vec2 inCenter, vec2 pt) +{ + float tmp = pow(inCenter.x-pt.x,2.0)+pow(inCenter.y-pt.y,2.0); + return pow(tmp,0.5); +} +#endif + +void main() { + vec2 uv = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 texSize = RENDERSIZE; + vec2 tc = uv * texSize; + vec2 modifiedCenter = center * RENDERSIZE; + float r = distance(modifiedCenter, tc); + float a = atan ((tc.y-modifiedCenter.y),(tc.x-modifiedCenter.x)); + float radius = 1.0; + float radius_sized = radius * length(RENDERSIZE); + + tc -= modifiedCenter; + + if (r < radius_sized) { + float percent = 1.0-(radius_sized - r) / radius_sized; + float adjustedOffset = offset * 2.0 * pi; + if (mode == 0) { + tc.x = r*(1.0+sin(percent * level * 2.0 * pi + adjustedOffset))/2.0 * cos(a); + tc.y = r*(1.0+sin(percent * level * 2.0 * pi + adjustedOffset))/2.0 * sin(a); + } + else if (mode == 1) { + tc.x = r*(1.0+sin(percent * level * 2.0 * pi * cos(adjustedOffset + percent * percent * level * 2.0 * pi)))/2.0 * cos(a); + tc.y = r*(1.0+sin(percent * level * 2.0 * pi * cos(adjustedOffset + percent * percent * level * 2.0 * pi)))/2.0 * sin(a); + } + tc.x = mix(uv.x, tc.x, max(1.0-x_smear,0.001)); + tc.y = mix(uv.y, tc.y, max(1.0-y_smear,0.001)); + } + tc += modifiedCenter; + vec2 loc = tc / texSize; + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage, loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Rotate Scale Fade.fs b/src/renderer/src/application/sample-modules/isf/Rotate Scale Fade.fs new file mode 100644 index 000000000..bf85cbc0a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Rotate Scale Fade.fs @@ -0,0 +1,112 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/rotate_scale_fade.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 8, + "MAX": 10, + "MIN": 0, + "NAME": "scale", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.09803921568627451, + 0.09803921568627451, + 0.09803921568627451, + 1 + ], + "NAME": "backColor", + "TYPE": "color" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "rotations", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Fernando Kuteken +// License: MIT + +#define PI 3.14159265359 + + +vec4 transition (vec2 uv) { + + vec2 difference = uv - center; + vec2 dir = normalize(difference); + float dist = length(difference); + + float angle = 2.0 * PI * rotations * progress; + + float c = cos(angle); + float s = sin(angle); + + float currentScale = mix(scale, 1.0, 2.0 * abs(progress - 0.5)); + + vec2 rotatedDir = vec2(dir.x * c - dir.y * s, dir.x * s + dir.y * c); + vec2 rotatedUv = center + rotatedDir * dist / currentScale; + + if (rotatedUv.x < 0.0 || rotatedUv.x > 1.0 || + rotatedUv.y < 0.0 || rotatedUv.y > 1.0) + return backColor; + + return mix(getFromColor(rotatedUv), getToColor(rotatedUv), progress); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Rotate.fs b/src/renderer/src/application/sample-modules/isf/Rotate.fs new file mode 100644 index 000000000..3e7b6f900 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Rotate.fs @@ -0,0 +1,39 @@ +/*{ + "CATEGORIES": [ + "Geometry Adjustment", + "Utility" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +#if __VERSION__ <= 120 +varying vec2 translated_coord; +#else +in vec2 translated_coord; +#endif + +void main() { + vec2 loc = translated_coord; + // if out of range draw black + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Rotate.vs b/src/renderer/src/application/sample-modules/isf/Rotate.vs new file mode 100644 index 000000000..6ec668393 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Rotate.vs @@ -0,0 +1,21 @@ +#if __VERSION__ <= 120 +varying vec2 translated_coord; +#else +out vec2 translated_coord; +#endif + +const float pi = 3.14159265359; + +void main() { + isf_vertShaderInit(); + + vec2 loc = RENDERSIZE * vec2(vv_FragNormCoord[0],vv_FragNormCoord[1]); + float r = distance(RENDERSIZE/2.0, loc); + float a = atan ((loc.y-RENDERSIZE.y/2.0),(loc.x-RENDERSIZE.x/2.0)); + + loc.x = r * cos(a + 2.0 * pi * angle) + 0.5; + loc.y = r * sin(a + 2.0 * pi * angle) + 0.5; + + translated_coord = loc / RENDERSIZE + vec2(0.5); + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Saturation Bleed.fs b/src/renderer/src/application/sample-modules/isf/Saturation Bleed.fs new file mode 100644 index 000000000..0ef8bf070 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Saturation Bleed.fs @@ -0,0 +1,143 @@ +/*{ + "CATEGORIES": [ + "Color Effect" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Applies a blur to the saturation levels of pixels.", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "bleedLevel", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 10, + "MIN": 1, + "NAME": "depth", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 4, + "MIN": 0, + "NAME": "gainLevel", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "HEIGHT": "max(floor($HEIGHT*0.02),1.0)", + "TARGET": "smaller", + "WIDTH": "max(floor($WIDTH*0.02),1.0)" + }, + { + "HEIGHT": "max(floor($HEIGHT*0.25),1.0)", + "TARGET": "small", + "WIDTH": "max(floor($WIDTH*0.25),1.0)" + }, + { + } + ] +} +*/ + + +// A simple three pass blur – first reduce the size, then do a weighted blur, then do the same thing +// but we are only going to blur the saturation + + +// Inspired by Oversaturation glitches +// https://bavc.github.io/avaa/artifacts/oversaturation.html + + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + + + +vec4 rgb2hsv(vec4 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x,c.a); +} + +vec4 hsv2rgb(vec4 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return vec4(c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y),c.a); +} + + + + +void main() +{ + + vec4 color = rgb2hsv(IMG_THIS_NORM_PIXEL(inputImage)); + vec4 colorL = rgb2hsv(IMG_NORM_PIXEL(inputImage, left_coord)); + vec4 colorR = rgb2hsv(IMG_NORM_PIXEL(inputImage, right_coord)); + vec4 colorA = rgb2hsv(IMG_NORM_PIXEL(inputImage, above_coord)); + vec4 colorB = rgb2hsv(IMG_NORM_PIXEL(inputImage, below_coord)); + + vec4 colorLA = rgb2hsv(IMG_NORM_PIXEL(inputImage, lefta_coord)); + vec4 colorRA = rgb2hsv(IMG_NORM_PIXEL(inputImage, righta_coord)); + vec4 colorLB = rgb2hsv(IMG_NORM_PIXEL(inputImage, leftb_coord)); + vec4 colorRB = rgb2hsv(IMG_NORM_PIXEL(inputImage, rightb_coord)); + + vec4 avg = gainLevel * (colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 8.0; + + + if (PASSINDEX == 0) { + avg = mix(color, (avg + depth)/(1.0+depth), bleedLevel); + color.g = avg.g; + } + else if (PASSINDEX == 1) { + vec4 blur = rgb2hsv(IMG_THIS_NORM_PIXEL(smaller)); + avg = mix(color, (avg + depth*blur)/(1.0+depth), bleedLevel); + color.g = avg.g; + } + else if (PASSINDEX == 2) { + vec4 blur = rgb2hsv(IMG_THIS_NORM_PIXEL(small)); + avg = mix(color, (avg + depth*blur)/(1.0+depth), bleedLevel); + color.g = avg.g; + } + if (color.g < 0.0) + color.g = 0.0; + else if (color.g > 1.0) + color.g = 1.0; + color = hsv2rgb(color); + gl_FragColor = color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Saturation Bleed.vs b/src/renderer/src/application/sample-modules/isf/Saturation Bleed.vs new file mode 100644 index 000000000..b0ba4b037 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Saturation Bleed.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sepia Tone.fs b/src/renderer/src/application/sample-modules/isf/Sepia Tone.fs new file mode 100644 index 000000000..58c9b52a3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sepia Tone.fs @@ -0,0 +1,44 @@ +/*{ + "CATEGORIES": [ + "Color Effect", + "Retro" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.8, + "MAX": 1.2, + "MIN": 0.8, + "NAME": "contrast", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +// Adapted from https://www.omniref.com/ruby/gems/essytas/0.0.1/files/lib/glsl/sepia.frag + +void main() { + + vec4 rawColor = IMG_THIS_PIXEL(inputImage); + vec4 color = rawColor; + vec4 sepia1 = vec4( 0.2, 0.05, 0.0, 1.0 ); + vec4 sepia2 = vec4( 1.0, 0.9, 0.5, 1.0 ); + float sepiaMix = dot(vec3(0.3, 0.59, 0.11), color.rgb); + color = mix(color, vec4(sepiaMix), 0.5); + vec4 sepia = mix(sepia1, sepia2, sepiaMix); + sepia = vec4( min( vec3( 1.0 ), sepia.rgb ), color.a ); + + float bright = 0.05; + sepia = sepia + vec4(bright, bright, bright, 0.0); + sepia.rgb = ((vec3(2.0) * (sepia.rgb - vec3(0.5))) * vec3(contrast) / vec3(2.0)) + vec3(0.5); + sepia.a = rawColor.a; + gl_FragColor = sepia; + +} diff --git a/src/renderer/src/application/sample-modules/isf/Set Alpha.fs b/src/renderer/src/application/sample-modules/isf/Set Alpha.fs new file mode 100644 index 000000000..71f94f818 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Set Alpha.fs @@ -0,0 +1,32 @@ +/*{ + "CATEGORIES": [ + "Color Adjustment", + "Utility" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Sets the alpha channel of the image", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "LABEL": "New Alpha", + "MAX": 1, + "MIN": 0, + "NAME": "newAlpha", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 inputPixelColor = IMG_THIS_NORM_PIXEL(inputImage); + + inputPixelColor.a = newAlpha; + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Shake.fs b/src/renderer/src/application/sample-modules/isf/Shake.fs new file mode 100644 index 000000000..8e9019cb8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Shake.fs @@ -0,0 +1,54 @@ +/*{ + "CATEGORIES": [ + "Noise", + "Geometry Adjustment" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 2, + "MIN": 0, + "NAME": "magnitude", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "intensity", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +const float pi = 3.14159265359; + + +float rand(vec2 co) { + return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453); +} + +void main(void) { + float offset = 0.1 * magnitude; + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy; + float rotation = intensity * 2.0 * pi * rand(vec2(magnitude, TIME)); + float yOffset = offset * sin(TIME * 1.0 * cos(TIME * intensity) + rotation) * rand(vec2(magnitude, TIME)); + float xOffset = offset * cos(TIME * 1.0 * cos(TIME * intensity) + rotation) * rand(vec2(1.0-magnitude, TIME));; + + float zoom = 1.0 + offset; + + uv = (uv - 0.5) / zoom + 0.5; + + uv.y += yOffset; + uv.x += xOffset; + + gl_FragColor = IMG_NORM_PIXEL(inputImage, uv); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Shape Mask.fs b/src/renderer/src/application/sample-modules/isf/Shape Mask.fs new file mode 100644 index 000000000..d7046d110 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Shape Mask.fs @@ -0,0 +1,304 @@ +/*{ + "CATEGORIES": [ + "Masking" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "LABEL": "Mask Shape Mode", + "LABELS": [ + "Rectangle", + "Triangle", + "Circle", + "Diamond" + ], + "NAME": "maskShapeMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3 + ] + }, + { + "DEFAULT": 0.5, + "LABEL": "Shape Width", + "MAX": 2, + "MIN": 0, + "NAME": "shapeWidth", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "LABEL": "Shape Height", + "MAX": 2, + "MIN": 0, + "NAME": "shapeHeight", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "DEFAULT": false, + "LABEL": "Invert Mask", + "NAME": "invertMask", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "LABEL": "Horizontal Repeat", + "LABELS": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + "NAME": "horizontalRepeat", + "TYPE": "long", + "VALUES": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "DEFAULT": 1, + "LABEL": "Vertical Repeat", + "LABELS": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + "NAME": "verticalRepeat", + "TYPE": "long", + "VALUES": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "DEFAULT": 0, + "LABEL": "Apply Mask", + "LABELS": [ + "Apply Mask", + "Set Alpha", + "Show Mask" + ], + "NAME": "maskApplyMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + } + ], + "ISFVSN": "2" +} +*/ + + + +const float pi = 3.14159265359; + + +vec2 rotatePoint(vec2 pt, float angle, vec2 center) +{ + vec2 returnMe; + float s = sin(angle * pi); + float c = cos(angle * pi); + + returnMe = pt; + + // translate point back to origin: + returnMe.x -= center.x; + returnMe.y -= center.y; + + // rotate point + float xnew = returnMe.x * c - returnMe.y * s; + float ynew = returnMe.x * s + returnMe.y * c; + + // translate point back: + returnMe.x = xnew + center.x; + returnMe.y = ynew + center.y; + return returnMe; +} + +float sign(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3) +{ + bool b1, b2, b3; + + b1 = sign(pt, v1, v2) < 0.0; + b2 = sign(pt, v2, v3) < 0.0; + b3 = sign(pt, v3, v1) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + +bool RotatedPointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3, vec2 center) +{ + bool b1, b2, b3; + + vec2 v1r = v1; + vec2 v2r = v2; + vec2 v3r = v3; + + b1 = sign(pt, v1r, v2r) < 0.0; + b2 = sign(pt, v2r, v3r) < 0.0; + b3 = sign(pt, v3r, v1r) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + + +float isPointInShape(vec2 pt, int shape, vec4 shapeCoordinates) { + float returnMe = 0.0; + + // rectangle + if (shape == 0) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + // soft edge if needed + if ((pt.x > shapeCoordinates.x) && (pt.x < shapeCoordinates.x)) { + returnMe = clamp(((pt.x - shapeCoordinates.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + else if ((pt.x > shapeCoordinates.x + shapeCoordinates.z) && (pt.x < shapeCoordinates.x + shapeCoordinates.z)) { + returnMe = clamp(((shapeCoordinates.x + shapeCoordinates.z - pt.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + } + else if (RotatedPointInTriangle(pt, shapeCoordinates.xy + shapeCoordinates.zw, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + // soft edge if needed + if ((pt.x > shapeCoordinates.x) && (pt.x < shapeCoordinates.x)) { + returnMe = clamp(((pt.x - shapeCoordinates.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + else if ((pt.x > shapeCoordinates.x + shapeCoordinates.z) && (pt.x < shapeCoordinates.x + shapeCoordinates.z)) { + returnMe = clamp(((shapeCoordinates.x + shapeCoordinates.z - pt.x) / RENDERSIZE.x), 0.0, 1.0); + returnMe = pow(returnMe, 0.5); + } + } + } + // triangle + else if (shape == 1) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy, shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, 0.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + } + // oval + else if (shape == 2) { + returnMe = distance(pt, vec2(shapeCoordinates.xy + shapeCoordinates.zw / 2.0)); + if (returnMe < min(shapeCoordinates.z,shapeCoordinates.w) / 2.0) { + returnMe = 1.0; + } + else { + returnMe = 0.0; + } + } + // diamond + else if (shape == 3) { + if (RotatedPointInTriangle(pt, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w / 2.0), shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, shapeCoordinates.w), shapeCoordinates.xy + vec2(shapeCoordinates.z, shapeCoordinates.w / 2.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + else if (RotatedPointInTriangle(pt, shapeCoordinates.xy + vec2(0.0, shapeCoordinates.w / 2.0), shapeCoordinates.xy + vec2(shapeCoordinates.z / 2.0, 0.0), shapeCoordinates.xy + vec2(shapeCoordinates.z, shapeCoordinates.w / 2.0), shapeCoordinates.xy + shapeCoordinates.zw / 2.0)) { + returnMe = 1.0; + } + } + + return returnMe; +} + + + +void main() { + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + vec2 centerPt = RENDERSIZE * center; + vec2 tmpVec = RENDERSIZE * vec2(shapeWidth,shapeHeight) / 2.0; + vec4 patternRect = vec4(vec2(centerPt - tmpVec),tmpVec * 2.0); + vec2 thisPoint = RENDERSIZE * isf_FragNormCoord; + + if ((thisPoint.x >= patternRect.x) && (thisPoint.x <= patternRect.x + abs(patternRect.z))) { + patternRect.z = patternRect.z / float(horizontalRepeat); + patternRect.x = patternRect.x + abs(patternRect.z) * floor((thisPoint.x - patternRect.x) / abs(patternRect.z)); + } + else { + patternRect.z = patternRect.z / float(horizontalRepeat); + } + + if ((thisPoint.y >= patternRect.y) && (thisPoint.y <= patternRect.y + abs(patternRect.w))) { + patternRect.w = patternRect.w / float(verticalRepeat); + patternRect.y = patternRect.y + abs(patternRect.w) * floor((thisPoint.y - patternRect.y) / abs(patternRect.w)); + } + else { + patternRect.w = patternRect.w / float(verticalRepeat); + } + + float luminance = isPointInShape(thisPoint.xy, maskShapeMode, patternRect); + + if (invertMask) + luminance = 1.0 - luminance; + + if (maskApplyMode == 0) { + srcPixel = srcPixel * luminance; + } + else if (maskApplyMode == 1) { + srcPixel.a = srcPixel.a * luminance; + } + else if (maskApplyMode == 2) { + srcPixel.rgb = vec3(luminance); + } + + gl_FragColor = srcPixel; +} + diff --git a/src/renderer/src/application/sample-modules/isf/Shape Morph Feedback Mask.fs b/src/renderer/src/application/sample-modules/isf/Shape Morph Feedback Mask.fs new file mode 100644 index 000000000..6a43c5999 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Shape Morph Feedback Mask.fs @@ -0,0 +1,321 @@ +/*{ + "CATEGORIES": [ + "Feedback" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "maskRadius", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 16, + "MIN": 0, + "NAME": "feedbackRate", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "mixPoint", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Circle", + "Triangle", + "Rect", + "Pentagram", + "Hexagon", + "Star1", + "Star2", + "Heart", + "Rays" + ], + "NAME": "shape1", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "DEFAULT": 1, + "LABELS": [ + "Circle", + "Triangle", + "Rect", + "Pentagram", + "Hexagon", + "Star1", + "Star2", + "Heart", + "Rays" + ], + "NAME": "shape2", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "DEFAULT": 0, + "MAX": 2, + "MIN": 0, + "NAME": "shapeWobble", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 0.25, + "MIN": -0.25, + "NAME": "twirlAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "fadeRate", + "TYPE": "float" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "centerFeedback", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "feedbackCenter", + "TYPE": "point2D" + }, + { + "DEFAULT": 2, + "LABELS": [ + "Mask", + "CenteredMask", + "Scaled", + "Wrap", + "MirrorWrap", + "InvertedMask" + ], + "NAME": "styleMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + { + "NAME": "clearBuffer", + "TYPE": "event" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "PERSISTENT": true, + "TARGET": "feedbackBuffer" + } + ] +} +*/ + +const float pi = 3.1415926535897932384626433832795; +const float tau = 6.2831853071795864769252867665590; + +// borrowed from pixel spirit deck! +// https://github.com/patriciogonzalezvivo/PixelSpiritDeck/tree/master/lib + +float triSDF(vec2 st) { + st = (st*2.-1.)*2.; + return max(abs(st.x) * 0.866025 + st.y * 0.5, -st.y * 0.5); +} +float circleSDF(vec2 st) { + return length(st-.5)*2.; +} +float polySDF(vec2 st, int V) { + st = st*2.-1.; + float a = atan(st.x,st.y)+pi; + float r = length(st); + float v = tau/float(V); + return cos(floor(.5+a/v)*v-a)*r; +} +float pentSDF(vec2 st) { + vec2 pt = st; + pt.y /= 0.89217; + return polySDF(pt, 5); +} +float hexSDF(vec2 st) { + st = abs(st*2.-1.); + return max(abs(st.y), st.x * 0.866025 + st.y * 0.5); +} +float flowerSDF(vec2 st, int N) { + st = st*2.-1.; + float r = length(st)*2.; + float a = atan(st.y,st.x); + float v = float(N)*.5; + return 1.-(abs(cos(a*v))*.5+.5)/r; +} +float heartSDF(vec2 st) { + st -= vec2(.5,.8); + float r = length(st)*5.5; + st = normalize(st); + return r - + ((st.y*pow(abs(st.x),0.67))/ + (st.y+1.5)-(2.)*st.y+1.26); +} +float starSDF(vec2 st, int V, float s) { + st = st*4.-2.; + float a = atan(st.y, st.x)/tau; + float seg = a * float(V); + a = ((floor(seg) + 0.5)/float(V) + + mix(s,-s,step(.5,fract(seg)))) + * tau; + return abs(dot(vec2(cos(a),sin(a)), + st)); +} +float raysSDF(vec2 st, int N) { + st -= .5; + return fract(atan(st.y,st.x)/tau*float(N)); +} + +float shapeForType(vec2 st, int shape) { + if (shape == 0) + return circleSDF(st); + else if (shape == 1) + return triSDF(st); + else if (shape == 2) + return polySDF(st,4); + else if (shape == 3) + return pentSDF(st); + else if (shape == 4) + return hexSDF(st); + else if (shape == 5) + return starSDF(st,5,0.07); + else if (shape == 6) + return starSDF(st,12,0.12); + else if (shape == 7) + return heartSDF(st); + else if (shape == 8) + return raysSDF(st,6); +} + + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 loc = isf_FragNormCoord.xy; + loc -= (feedbackCenter - vec2(0.5)); + loc = mix(vec2((loc.x*RENDERSIZE.x/RENDERSIZE.y)-(RENDERSIZE.x*.5-RENDERSIZE.y*.5)/RENDERSIZE.y,loc.y), + vec2(loc.x,loc.y*(RENDERSIZE.y/RENDERSIZE.x)-(RENDERSIZE.y*.5-RENDERSIZE.x*.5)/RENDERSIZE.x), + step(RENDERSIZE.x,RENDERSIZE.y)); + + float val1 = shapeForType(loc,shape1); + float val2 = shapeForType(loc,shape2); + vec2 locCenter = feedbackCenter * RENDERSIZE; + loc = gl_FragCoord.xy; + float val = mix(val1,val2,mixPoint); + float a1 = (atan(locCenter.y-loc.y,locCenter.x-loc.x) + pi) / (tau); + + val += (shapeWobble == 0.0) ? 0.0 : shapeWobble * ((sin(TIME+10.0*tau*(a1)))/13.0 + (sin(-TIME*2.1+17.0*tau*(a1)))/17.0 + (sin(19.0*tau*(a1)))/19.0); + + float scaledRadius = maskRadius * min(RENDERSIZE.x,RENDERSIZE.y); + float dist = val * min(RENDERSIZE.x,RENDERSIZE.y); + bool invertMask = (styleMode == 5); + float feedbackLevel = 1.0; + + // if within the shape, just do the shape + if (((dist>scaledRadius)&&(invertMask))||((dist 0.0)&&(clearBuffer == false)) { + inputPixelColor = mix(inputPixelColor,IMG_THIS_PIXEL(feedbackBuffer),centerFeedback); + } + */ + //inputPixelColor = vec4(loc.x,loc.y,0.0,1.0); + } + if ((clearBuffer == false)&&(feedbackLevel > 0.0)) { + //float r = distance(RENDERSIZE/2.0,loc); + float a = atan((loc.y-locCenter.y),(loc.x-locCenter.x)); + //a = tau * (floor(a * 5.0 / tau) / 5.0); + float shiftAmount = -1.0 * feedbackRate; + vec2 shift = shiftAmount * vec2(cos(a + twirlAmount * tau), sin(a + twirlAmount * tau)); + loc = (invertMask) ? loc - shift : loc + shift; + inputPixelColor = mix(inputPixelColor,IMG_PIXEL(feedbackBuffer,loc),feedbackLevel); + inputPixelColor.a -= fadeRate / 50.0; + //inputPixelColor = vec4((a+pi)/(2.0*pi),2.0*r/RENDERSIZE.x,0.0,1.0); + //inputPixelColor = vec4(shift.x*2.0-1.0,shift.y*2.0-1.0,0.0,1.0); + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Shape Morph Wrap.fs b/src/renderer/src/application/sample-modules/isf/Shape Morph Wrap.fs new file mode 100644 index 000000000..80e406088 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Shape Morph Wrap.fs @@ -0,0 +1,358 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Wraps an image into a shape that is created by morphing two primitive shapes together", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "mixPoint", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Circle", + "Triangle", + "Rect", + "Pentagram", + "Hexagon", + "Star1", + "Star2", + "Heart", + "Rays" + ], + "NAME": "shape1", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "DEFAULT": 1, + "LABELS": [ + "Circle", + "Triangle", + "Rect", + "Pentagram", + "Hexagon", + "Star1", + "Star2", + "Heart", + "Rays" + ], + "NAME": "shape2", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "DEFAULT": 0, + "MAX": 2, + "MIN": 0, + "NAME": "shapeWobble", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "preRotateAngle", + "TYPE": "float" + }, + { + "DEFAULT": 0.75, + "MAX": 1, + "MIN": 0, + "NAME": "angleShift", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "None", + "Extend", + "Repeat", + "Reflect" + ], + "NAME": "repeatStyle", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3 + ] + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "repeatDecay", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "resultSize", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": 0, + "NAME": "resultWidth", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "resultAngle", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "resultCenter", + "TYPE": "point2D" + }, + { + "DEFAULT": 1, + "NAME": "mirrorX", + "TYPE": "bool" + }, + { + "DEFAULT": 0, + "NAME": "mirrorY", + "TYPE": "bool" + } + ], + "ISFVSN": "2", + "VSN": "1" +} +*/ + + +const float pi = 3.1415926535897932384626433832795; +const float tau = 6.2831853071795864769252867665590; + +vec2 rotatePoint(vec2 pt, float rot) { + vec2 returnMe = pt * RENDERSIZE; + + float r = distance(RENDERSIZE/2.0, returnMe); + float a = atan ((returnMe.y-RENDERSIZE.y/2.0),(returnMe.x-RENDERSIZE.x/2.0)); + + returnMe.x = r * cos(a + 2.0 * pi * rot - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * rot - pi) + 0.5; + + returnMe = returnMe / RENDERSIZE + vec2(0.5); + + return returnMe; +} + +vec2 rotatePointNorm(vec2 pt, float rot) { + vec2 returnMe = pt; + + float r = distance(vec2(0.50), returnMe); + float a = atan((returnMe.y-0.5),(returnMe.x-0.5)); + + returnMe.x = r * cos(a + 2.0 * pi * rot - pi) + 0.5; + returnMe.y = r * sin(a + 2.0 * pi * rot - pi) + 0.5; + + returnMe = returnMe; + + return returnMe; +} +vec2 rotatePointNormPt(vec2 pt, float rot, vec2 rpt) { + vec2 returnMe = pt; + + float r = distance(vec2(0.50), returnMe); + float a = atan((returnMe.y-rpt.y),(returnMe.x-rpt.x)); + + returnMe.x = r * cos(a + 2.0 * pi * rot - pi) + rpt.x; + returnMe.y = r * sin(a + 2.0 * pi * rot - pi) + rpt.y; + + returnMe = returnMe; + + return returnMe; +} + +// borrowed from pixel spirit deck! +// https://github.com/patriciogonzalezvivo/PixelSpiritDeck/tree/master/lib + +float triSDF(vec2 st) { + st = (st*2.-1.)*2.; + return max(abs(st.x) * 0.866025 + st.y * 0.5, -st.y * 0.5); +} +float circleSDF(vec2 st) { + return length(st-.5)*2.; +} +float polySDF(vec2 st, int V) { + st = st*2.-1.; + float a = atan(st.x,st.y)+pi; + float r = length(st); + float v = tau/float(V); + return cos(floor(.5+a/v)*v-a)*r; +} +float pentSDF(vec2 st) { + vec2 pt = st; + pt.y /= 0.89217; + return polySDF(pt, 5); +} +float hexSDF(vec2 st) { + st = abs(st*2.-1.); + return max(abs(st.y), st.x * 0.866025 + st.y * 0.5); +} +float flowerSDF(vec2 st, int N) { + st = st*2.-1.; + float r = length(st)*2.; + float a = atan(st.y,st.x); + float v = float(N)*.5; + return 1.-(abs(cos(a*v))*.5+.5)/r; +} +float heartSDF(vec2 st) { + st -= vec2(.5,.8); + float r = length(st)*5.5; + st = normalize(st); + return r - + ((st.y*pow(abs(st.x),0.67))/ + (st.y+1.5)-(2.)*st.y+1.26); +} +float starSDF(vec2 st, int V, float s) { + st = st*4.-2.; + float a = atan(st.y, st.x)/tau; + float seg = a * float(V); + a = ((floor(seg) + 0.5)/float(V) + + mix(s,-s,step(.5,fract(seg)))) + * tau; + return abs(dot(vec2(cos(a),sin(a)), + st)); +} +float raysSDF(vec2 st, int N) { + st -= .5; + return fract(atan(st.y,st.x)/tau*float(N)); +} + +float shapeForType(vec2 st, int shape) { + if (shape == 0) + return circleSDF(st); + else if (shape == 1) + return triSDF(st); + else if (shape == 2) + return polySDF(st,4); + else if (shape == 3) + return pentSDF(st); + else if (shape == 4) + return hexSDF(st); + else if (shape == 5) + return starSDF(st,5,0.07); + else if (shape == 6) + return starSDF(st,12,0.12); + else if (shape == 7) + return heartSDF(st); + else if (shape == 8) + return raysSDF(st,6); +} + +void main() { + vec4 returnMe = vec4(0.0); + vec2 st = gl_FragCoord.xy/RENDERSIZE; + + st += (0.5 - resultCenter); + st = rotatePoint(st,resultAngle); + + // size + st -= 0.5; + //st += (0.5 - resultCenter); + st /= max(0.000001,resultSize); + st.x /= resultWidth; + st += 0.5; + + st = mix(vec2((st.x*RENDERSIZE.x/RENDERSIZE.y)-(RENDERSIZE.x*.5-RENDERSIZE.y*.5)/RENDERSIZE.y,st.y), + vec2(st.x,st.y*(RENDERSIZE.y/RENDERSIZE.x)-(RENDERSIZE.y*.5-RENDERSIZE.x*.5)/RENDERSIZE.x), + step(RENDERSIZE.x,RENDERSIZE.y)); + float val1 = shapeForType(st,shape1); + float val2 = shapeForType(st,shape2); + //val1 = min(max(val1,0.0),1.0); + //val2 = min(max(val2,0.0),1.0); + + float val = mix(val1,val2,mixPoint); + vec2 cnt = vec2(0.5,0.5); + float a = (atan(cnt.y-st.y,cnt.x-st.x) + pi) / (tau); + + val += (shapeWobble == 0.0) ? 0.0 : shapeWobble * ((sin(TIME+10.0*tau*(a)))/13.0 + (sin(-TIME*2.1+17.0*tau*(a)))/17.0 + (sin(19.0*tau*(a)))/19.0); + + float r = val; + if (repeatStyle == 1) + r = max(0.0,min(r,1.0)); + else if (repeatStyle == 2) + r = mod(r,1.0); + else if (repeatStyle == 3) { + r = mod(r,2.0); + r = (r > 1.0) ? 2.0 - r : r; + } + + if (r <= 1.0) { + + a = mod(a + angleShift, 1.0); + vec2 pt = vec2(a,r); + //returnMe = vec4(r,a,0.0,1.0); + + if (mirrorX) { + pt.x = (pt.x < 0.5) ? pt.x * 2.0 : 2.0 - pt.x * 2.0; + } + if (mirrorY) { + pt.y = (pt.y < 0.5) ? pt.y * 2.0 : 2.0 - pt.y * 2.0; + } + + pt = rotatePointNorm(pt,preRotateAngle); + returnMe = IMG_NORM_PIXEL(inputImage,pt); + + if (repeatStyle > 0) { + if (repeatStyle == 1) + returnMe.a *= (val > 1.0) ? 1.0 - repeatDecay * (val) : 1.0; + else { + float repCount = 1.0 / (1.0+resultSize); + returnMe.a *= 1.0 - repeatDecay * floor(val) / repCount; + } + } + } + + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Sharpen Luminance.fs b/src/renderer/src/application/sample-modules/isf/Sharpen Luminance.fs new file mode 100644 index 000000000..94cf08535 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sharpen Luminance.fs @@ -0,0 +1,66 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Sharpen" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 1.0 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + float colorL = gray(IMG_NORM_PIXEL(inputImage, left_coord)); + float colorR = gray(IMG_NORM_PIXEL(inputImage, right_coord)); + float colorA = gray(IMG_NORM_PIXEL(inputImage, above_coord)); + float colorB = gray(IMG_NORM_PIXEL(inputImage, below_coord)); + + float colorLA = gray(IMG_NORM_PIXEL(inputImage, lefta_coord)); + float colorRA = gray(IMG_NORM_PIXEL(inputImage, righta_coord)); + float colorLB = gray(IMG_NORM_PIXEL(inputImage, leftb_coord)); + float colorRB = gray(IMG_NORM_PIXEL(inputImage, rightb_coord)); + + vec4 final = color + color * intensity * (8.0*gray(color) - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sharpen Luminance.vs b/src/renderer/src/application/sample-modules/isf/Sharpen Luminance.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sharpen Luminance.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sharpen RGB.fs b/src/renderer/src/application/sample-modules/isf/Sharpen RGB.fs new file mode 100644 index 000000000..7c269cd19 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sharpen RGB.fs @@ -0,0 +1,83 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Sharpen" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensityR", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 1.0 + }, + { + "NAME": "intensityG", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 1.0 + }, + { + "NAME": "intensityB", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 1.0 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = color; + final.r = color.r + intensityR * (8.0*color.r - colorL.r - colorR.r - colorA.r - colorB.r - colorLA.r - colorRA.r - colorLB.r - colorRB.r); + final.g = color.g + intensityG * (8.0*color.g - colorL.g - colorR.g - colorA.g - colorB.g - colorLA.g - colorRA.g - colorLB.g - colorRB.g); + final.b = color.b + intensityB * (8.0*color.b - colorL.b - colorR.b - colorA.b - colorB.b - colorLA.b - colorRA.b - colorLB.b - colorRB.b); + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sharpen RGB.vs b/src/renderer/src/application/sample-modules/isf/Sharpen RGB.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sharpen RGB.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Shockwave Pulse.fs b/src/renderer/src/application/sample-modules/isf/Shockwave Pulse.fs new file mode 100644 index 000000000..434bde012 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Shockwave Pulse.fs @@ -0,0 +1,105 @@ +/* +{ + "CATEGORIES" : [ + "Distortion Effect" + ], + "DESCRIPTION" : "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "pulse", + "TYPE" : "event" + }, + { + "NAME" : "rate", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 1, + "LABEL" : "rate", + "MIN" : 0 + }, + { + "NAME" : "magnitude", + "TYPE" : "float", + "MAX" : 0.20000000000000001, + "DEFAULT" : 0.080000000000000002, + "LABEL" : "magnitude", + "MIN" : 0 + }, + { + "NAME" : "distortion", + "TYPE" : "float", + "MAX" : 20, + "DEFAULT" : 10, + "LABEL" : "distortion", + "MIN" : 0 + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + } + ], + "PASSES" : [ + { + "PERSISTENT" : true, + "WIDTH" : "1", + "DESCRIPTION" : "this buffer stores the last frame's time offset in the first component of its only pixel- note that it's requesting a FLOAT target buffer...", + "HEIGHT" : "1", + "TARGET" : "lastTime", + "FLOAT" : true + }, + { + + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + + + +void main() +{ + // if this is the first pass, i'm going to read the position from the "lastPosition" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastTime,vec2(0.5)); + // i'm only using the X, which is the last render time we reset + srcPixel.r = (pulse && FRAMEINDEX>10) ? 0.0 : clamp(srcPixel.r + rate * 0.01,0.0,1.0); + gl_FragColor = srcPixel; + } + // else this isn't the first pass- read the position value from the buffer which stores it + else { + vec2 uv = isf_FragNormCoord.xy; + vec2 texCoord = uv; + vec2 mod_center = center; + float distance = distance(uv, mod_center); + vec4 lastPosVector = IMG_PIXEL(lastTime,vec2(0.5)); + float adustedTime = lastPosVector.r * (1.0+length(distance)); + + if ( (distance <= (adustedTime + magnitude)) && (distance >= (adustedTime - magnitude)) ) { + float diff = (distance - adustedTime); + float powDiff = 1.0 - pow(abs(diff*distortion), + 0.8); + float diffTime = diff * powDiff; + vec2 diffUV = normalize(uv - mod_center); + texCoord = uv + (diffUV * diffTime); + } + gl_FragColor = IMG_NORM_PIXEL(inputImage, texCoord); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Shockwave.fs b/src/renderer/src/application/sample-modules/isf/Shockwave.fs new file mode 100644 index 000000000..928295e73 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Shockwave.fs @@ -0,0 +1,82 @@ +/*{ + "DESCRIPTION": "", + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "positionVal", + "TYPE": "float", + "MIN": 0, + "MAX": 1, + "DEFAULT": 0 + }, + { + "NAME": "distortion", + "TYPE": "float", + "MIN": 1, + "MAX": 20, + "DEFAULT": 10 + }, + { + "NAME": "magnitude", + "TYPE": "float", + "MIN": 0, + "MAX": 0.2, + "DEFAULT": 0.08 + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.5 + ], + "MIN" : [ + 0, + 0 + ] + }, + { + "NAME": "background", + "TYPE": "bool", + "DEFAULT": 1 + } + ] +}*/ + + + +void main() +{ + // do this junk so that the ripple starts from nothing + vec2 uv = isf_FragNormCoord.xy; + vec2 texCoord = uv; + vec2 mod_center = center; + vec4 color = vec4(0.0); + float dist = distance(uv, mod_center); + float adjustedTime = (positionVal * RENDERSIZE.x/RENDERSIZE.y - magnitude)/(1.0 - magnitude); + + if ( (dist <= (adjustedTime + magnitude)) && (dist >= (adjustedTime - magnitude)) ) { + float diff = (dist - adjustedTime); + float powDiff = 1.0 - pow(abs(diff*distortion), + 0.8); + float diffTime = diff * powDiff; + vec2 diffUV = normalize(uv - mod_center); + texCoord = uv + (diffUV * diffTime); + color = IMG_NORM_PIXEL(inputImage, texCoord); + } + else if (background) { + color = IMG_NORM_PIXEL(inputImage, texCoord); + } + gl_FragColor = color; +} diff --git a/src/renderer/src/application/sample-modules/isf/Show Alpha.fs b/src/renderer/src/application/sample-modules/isf/Show Alpha.fs new file mode 100644 index 000000000..55af35287 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Show Alpha.fs @@ -0,0 +1,22 @@ +/*{ + "CATEGORIES": [ + "Utility" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Maps the alpha to grayscale", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 inputPixelColor = IMG_THIS_PIXEL(inputImage); + inputPixelColor.rgb = vec3(inputPixelColor.a); + inputPixelColor.a = 1.0; + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Side Scroller And Flip.fs b/src/renderer/src/application/sample-modules/isf/Side Scroller And Flip.fs new file mode 100644 index 000000000..6a24a47ad --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Side Scroller And Flip.fs @@ -0,0 +1,58 @@ +/*{ + "CREDIT": "BrianChasalow", + "ISFVSN": "2", + "CATEGORIES": [ + "Geometry Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "slide", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.0 + }, + { + "NAME": "shift", + "TYPE": "float", + "MIN": 0.0, + "MAX": 2.0, + "DEFAULT": 0.0 + }, + { + "NAME": "mirrorHorizontal", + "TYPE": "bool", + "MIN": false, + "MAX": true, + "DEFAULT": true + }, + { + "NAME": "mirrorVertical", + "TYPE": "bool", + "MIN": false, + "MAX": true, + "DEFAULT": true + } + + ] + }*/ + +void main(void) +{ + vec2 retard = isf_FragNormCoord; + retard.x += slide; + retard.y += shift; + vec2 moddedRetard = mod(retard,1.0); + + if(mirrorHorizontal && retard.x >= 1.0 && retard.x <= 2.0) + moddedRetard = vec2(1.0-moddedRetard.x, moddedRetard.y); + if(mirrorVertical && retard.y >= 1.0 && retard.y <= 2.0) + moddedRetard = vec2(moddedRetard.x, 1.0-moddedRetard.y); + + vec4 pixel = IMG_NORM_PIXEL(inputImage, moddedRetard); + gl_FragColor = pixel; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Simple Zoom Transition.fs b/src/renderer/src/application/sample-modules/isf/Simple Zoom Transition.fs new file mode 100644 index 000000000..17a80e989 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Simple Zoom Transition.fs @@ -0,0 +1,68 @@ +/*{ + "CATEGORIES": [ + "Distortion", + "Dissolve" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/SimpleZoom.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.8, + "MAX": 1, + "MIN": 0, + "NAME": "zoom_quickness", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: 0gust1 +// License: MIT + +float nQuick = clamp(zoom_quickness,0.2,1.0); + +vec2 zoom(vec2 uv, float amount) { + return 0.5 + ((uv - 0.5) * (1.0-amount)); +} + +vec4 transition (vec2 uv) { + return mix( + getFromColor(zoom(uv, smoothstep(0.0, nQuick, progress))), + getToColor(uv), + smoothstep(nQuick-0.2, 1.0, progress) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sine Warp Gradient.fs b/src/renderer/src/application/sample-modules/isf/Sine Warp Gradient.fs new file mode 100644 index 000000000..b52bbdf2a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sine Warp Gradient.fs @@ -0,0 +1,119 @@ +/*{ + "CATEGORIES": [ + "Color" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "size", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "rotation", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "shift", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 0.5, + 0, + 1 + ], + "NAME": "xcolor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0.5, + 1, + 1 + ], + "NAME": "ycolor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 0 + ], + "NAME": "background", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + + +// Basically just uses the same gradient as the Sine Warp Tile but uses the x/y values as the mix amounts for our colors + + +const float tau = 6.28318530718; + + +vec2 pattern() { + float s = sin(tau * rotation * 0.5); + float c = cos(tau * rotation * 0.5); + vec2 tex = isf_FragNormCoord; + float scale = 1.0 / max(size,0.001); + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale; + point = point - scale * shift; + // do the sine distort + point = 0.5 + 0.5 * vec2( sin(scale * point.x), sin(scale * point.y)); + + // now do a rotation + vec2 center = vec2(0.5,0.5); + float r = distance(center, point); + float a = atan ((point.y-center.y),(point.x-center.x)); + + s = sin(a + tau * angle); + c = cos(a + tau * angle); + + float zoom = max(abs(s),abs(c))*RENDERSIZE.x / RENDERSIZE.y; + + point.x = (r * c)/zoom + 0.5; + point.y = (r * s)/zoom + 0.5; + + return point; +} + + +void main() { + + vec2 pat = pattern(); + + gl_FragColor = background + pat.x * xcolor + pat.y * ycolor; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sine Warp Tile.fs b/src/renderer/src/application/sample-modules/isf/Sine Warp Tile.fs new file mode 100644 index 000000000..53ec86a19 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sine Warp Tile.fs @@ -0,0 +1,80 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Tile Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "size", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.5, + "DEFAULT": 0.5 + }, + { + "NAME": "rotation", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "angle", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "shift", + "TYPE": "point2D", + "DEFAULT": [ + 0.5, + 0.5 + ] + } + ] +}*/ + + +const float tau = 6.28318530718; + + +vec2 pattern() { + float s = sin(tau * rotation * 0.5); + float c = cos(tau * rotation * 0.5); + vec2 tex = isf_FragNormCoord; + float scale = 1.0 / max(size,0.001); + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale; + point = point - scale * shift / RENDERSIZE; + // do the sine distort + point = 0.5 + 0.5 * vec2( sin(scale * point.x), sin(scale * point.y)); + + // now do a rotation + vec2 center = vec2(0.5,0.5); + float r = distance(center, point); + float a = atan ((point.y-center.y),(point.x-center.x)); + + s = sin(a + tau * angle); + c = cos(a + tau * angle); + + float zoom = max(abs(s),abs(c))*RENDERSIZE.x / RENDERSIZE.y; + + point.x = (r * c)/zoom + 0.5; + point.y = (r * s)/zoom + 0.5; + + return point; +} + + +void main() { + + vec2 pat = pattern(); + + gl_FragColor = IMG_NORM_PIXEL(inputImage,pat); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sketch.fs b/src/renderer/src/application/sample-modules/isf/Sketch.fs new file mode 100644 index 000000000..a9a354b6d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sketch.fs @@ -0,0 +1,72 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 1.0 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + // blur, do edges, then use the edge as a mask on the blurred + + float gx = (-1.0 * gray(colorLA)) + (-2.0 * gray(colorL)) + (-1.0 * gray(colorLB)) + (1.0 * gray(colorRA)) + (2.0 * gray(colorR)) + (1.0 * gray(colorRB)); + float gy = (1.0 * gray(colorLA)) + (2.0 * gray(colorA)) + (1.0 * gray(colorRA)) + (-1.0 * gray(colorRB)) + (-2.0 * gray(colorB)) + (-1.0 * gray(colorLB)); + + float edge = clamp(pow(gx*gx + gy*gy,0.5) * intensity,0.0,1.0); + vec4 blurred = (color + colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 9.0; + //gl_FragColor = vec4(edge,edge,edge,1.0); + gl_FragColor = blurred * (1.0-edge); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sketch.vs b/src/renderer/src/application/sample-modules/isf/Sketch.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sketch.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Slice.fs b/src/renderer/src/application/sample-modules/isf/Slice.fs new file mode 100644 index 000000000..7494eca70 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Slice.fs @@ -0,0 +1,54 @@ +/* +{ + "CATEGORIES" : [ + "effect" + ], + "DESCRIPTION" : "Slice", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "slices", + "TYPE" : "float", + "MAX" : 200, + "DEFAULT" : 10, + "MIN" : 1 + }, + { + "NAME" : "offset", + "TYPE" : "float", + "MAX" : 0.5, + "DEFAULT" : 0.03, + "MIN" : 0 + }, + { + "NAME" : "timeMultiplier", + "TYPE" : "float", + "MAX" : 3.0, + "DEFAULT" : 1.0, + "MIN" : 0.0 + } + ], + "CREDIT" : "https://getmosh.io/" +} +*/ + +precision mediump float; + +varying vec2 vUv; + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + vec2 p = gl_FragCoord.xy / RENDERSIZE.xy; + float yInt = floor(p.y * slices) / slices; + float rnd = rand(vec2(yInt, yInt)); + p.x += sin((TIME * timeMultiplier) * rnd / 5.0) * offset - offset / 2.0; + p.x = fract(p.x); + gl_FragColor = texture2D(inputImage, p); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sliding Strips.fs b/src/renderer/src/application/sample-modules/isf/Sliding Strips.fs new file mode 100644 index 000000000..56d2cd6e6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sliding Strips.fs @@ -0,0 +1,76 @@ +/* +{ + "CATEGORIES" : [ + "Geometry Adjustment" + ], + "DESCRIPTION" : "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "xShiftAmount", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "yShiftAmount", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "xTileSize", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.25, + "MIN" : 0 + }, + { + "NAME" : "yTileSize", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.25, + "MIN" : 0 + } + ], + "CREDIT" : "" +} +*/ + +void main() { + vec2 loc = isf_FragNormCoord.xy; + vec2 coords = vec2(0.0); + coords.x = (xTileSize == 0.0) ? gl_FragCoord.x : floor(loc.x / yTileSize); + coords.y = (yTileSize == 0.0) ? gl_FragCoord.y : floor(loc.y / xTileSize); + + vec2 maxCoords = vec2(0.0); + maxCoords.x = (xTileSize == 0.0) ? RENDERSIZE.x : (1.0 / yTileSize); + maxCoords.y = (yTileSize == 0.0) ? RENDERSIZE.y : (1.0 / xTileSize); + vec2 shiftAmount = vec2(xShiftAmount, yShiftAmount); + + shiftAmount.x = shiftAmount.x + shiftAmount.x * coords.y / maxCoords.y; + + if (shiftAmount.x < 0.0) + shiftAmount.x = 0.0; + else if (shiftAmount.x > 1.0) + shiftAmount.x = 1.0; + + loc.x = mod(loc.x + shiftAmount.x, 1.0); + + shiftAmount.y = shiftAmount.y + shiftAmount.y * coords.x / maxCoords.x; + + if (shiftAmount.y < 0.0) + shiftAmount.y = 0.0; + else if (shiftAmount.y > 1.0) + shiftAmount.y = 1.0; + + loc.y = mod(loc.y + shiftAmount.y, 1.0); + + gl_FragColor = IMG_NORM_PIXEL(inputImage, loc); +} diff --git a/src/renderer/src/application/sample-modules/isf/Slit Scan Mask.fs b/src/renderer/src/application/sample-modules/isf/Slit Scan Mask.fs new file mode 100644 index 000000000..b2b1f55a4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Slit Scan Mask.fs @@ -0,0 +1,91 @@ +/*{ + "CATEGORIES": [ + "Masking" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Pixels update only if within range of the specified lines to create a slit scan style", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 1.5, + "MIN": 0, + "NAME": "spacing", + "TYPE": "float" + }, + { + "DEFAULT": 0.33, + "MAX": 1, + "MIN": 0, + "NAME": "line_width", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": -1, + "NAME": "angle", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "shift", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "edgeSharpness", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +const float pi = 3.14159265359; + + +float pattern() { + float s = sin(angle * pi); + float c = cos(angle * pi); + vec2 tex = isf_FragNormCoord * RENDERSIZE; + float spaced = length(RENDERSIZE) * spacing; + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * max(1.0/spaced,0.001); + float d = point.y; + float w = line_width; + if (w > spacing) { + w = 0.99*spacing; + } + return ( mod(d + shift*spacing + w * 0.5,spacing) ); +} + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + // If we're on the line, update, otherwise use the stale pixel + //vec4 result = IMG_PIXEL(bufferVariableNameA,gl_FragCoord.xy); + vec4 result = vec4(0.0); + float pat = pattern(); + float w = line_width; + if (w > spacing) { + w = 0.99*spacing; + } + + if ((pat > 0.0)&&(pat <= w)) { + float percent = (1.0-abs(w-2.0*pat)/w); + percent = clamp(percent,0.0,1.0); + percent = mix(percent, pow(percent,1.0/5.0), edgeSharpness); + result = mix(result, freshPixel, percent); + //result = vec4(percent,percent,percent,1.0); + //result = clamp(result+edgeSharpness, 0.0, 1.0); + } + gl_FragColor = result; +} diff --git a/src/renderer/src/application/sample-modules/isf/Slit Scan.fs b/src/renderer/src/application/sample-modules/isf/Slit Scan.fs new file mode 100644 index 000000000..b1d40f922 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Slit Scan.fs @@ -0,0 +1,87 @@ +/*{ + "CATEGORIES": [ + "Glitch" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Pixels update only if within range of the specified lines to create a slit scan style", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 1.5, + "MIN": 0, + "NAME": "spacing", + "TYPE": "float" + }, + { + "DEFAULT": 0.33, + "MAX": 1, + "MIN": 0, + "NAME": "line_width", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": -1, + "NAME": "angle", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "shift", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "PERSISTENT": true, + "TARGET": "bufferVariableNameA" + } + ] +} +*/ + + +const float pi = 3.14159265359; + + +float pattern() { + float s = sin(angle * pi); + float c = cos(angle * pi); + vec2 tex = isf_FragNormCoord * RENDERSIZE; + float spaced = length(RENDERSIZE) * spacing; + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * max(1.0/spaced,0.001); + float d = point.y; + float w = line_width; + if (w > spacing) { + w = 0.99*spacing; + } + return ( mod(d + shift*spacing + w * 0.5,spacing) ); +} + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + // If we're on the line, update, otherwise use the stale pixel + vec4 result = IMG_PIXEL(bufferVariableNameA,gl_FragCoord.xy); + float pat = pattern(); + float w = line_width; + if (w > spacing) { + w = 0.99*spacing; + } + + if ((pat > 0.0)&&(pat <= w)) { + float percent = (1.0-abs(w-2.0*pat)/w); + percent = clamp(percent,0.0,1.0); + result = mix(result, freshPixel, percent); + //result = vec4(percent,percent,percent,1.0); + } + gl_FragColor = result; +} diff --git a/src/renderer/src/application/sample-modules/isf/Smoke Screen.fs b/src/renderer/src/application/sample-modules/isf/Smoke Screen.fs new file mode 100644 index 000000000..78d91dbc4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Smoke Screen.fs @@ -0,0 +1,105 @@ +/* +{ + "CATEGORIES" : [ + "Stylize" + ], + "DESCRIPTION" : "A smoke screen overlay effect", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "smokeColor", + "TYPE" : "color", + "DEFAULT" : [ + 0.75, + 0.75, + 0.75, + 0.75 + ] + }, + { + "NAME" : "smokeIntensity", + "TYPE" : "float", + "MAX" : 10, + "DEFAULT" : 1, + "MIN" : 0 + }, + { + "NAME" : "smokeDirection", + "TYPE" : "point2D", + "MAX" : [ + 1, + 1 + ], + "DEFAULT" : [ + 0.5, + 0.75 + ], + "MIN" : [ + 0, + 0 + ] + } + ], + "CREDIT" : "jackdavenport" +} +*/ + + + +// Converted from https://www.shadertoy.com/view/4t2SRz by jackdavenport + + + +float random(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +float noise(vec2 p) { + + return random(vec2(p.x + p.y * 10000.0,p.y + p.x * 10000.0)); + +} + +vec2 sw(vec2 p) { return vec2(floor(p.x), floor(p.y)); } +vec2 se(vec2 p) { return vec2(ceil(p.x), floor(p.y)); } +vec2 nw(vec2 p) { return vec2(floor(p.x), ceil(p.y)); } +vec2 ne(vec2 p) { return vec2(ceil(p.x), ceil(p.y)); } + +float smoothNoise(vec2 p) { + + vec2 interp = smoothstep(0., 1., fract(p)); + float s = mix(noise(sw(p)), noise(se(p)), interp.x); + float n = mix(noise(nw(p)), noise(ne(p)), interp.x); + return mix(s, n, interp.y); + +} + +float fractalNoise(vec2 p) { + + float n = 0.; + n += smoothNoise(p); + n += smoothNoise(p * 2.) / 2.; + n += smoothNoise(p * 4.) / 4.; + n += smoothNoise(p * 8.) / 8.; + n += smoothNoise(p * 16.) / 16.; + n /= 1. + 1./2. + 1./4. + 1./8. + 1./16.; + return n; + +} + +void main() { + vec2 uv = isf_FragNormCoord; + vec2 sd = (smokeDirection - vec2(0.5)); + vec2 nuv = vec2(uv.x - sd.x * TIME / 3.0, uv.y - sd.y * TIME / 3.0); + + float x = fractalNoise(nuv * 6.0); + vec4 inputPixel = IMG_NORM_PIXEL(inputImage,uv); + vec4 final = mix(vec4(x * smokeColor.rgb,max(x,inputPixel.a)), inputPixel, pow(abs(uv.y),pow(smokeColor.a*smokeIntensity*x,2.0))); + + gl_FragColor = final; +} + diff --git a/src/renderer/src/application/sample-modules/isf/Smudged Lens.fs b/src/renderer/src/application/sample-modules/isf/Smudged Lens.fs new file mode 100644 index 000000000..f96ab3654 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Smudged Lens.fs @@ -0,0 +1,217 @@ +/* +{ + "CATEGORIES" : [ + "Film", + "Blur" + ], + "DESCRIPTION" : "Blurs parts of an image as if the lens is smudged", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "scale", + "TYPE" : "float", + "MAX" : 100, + "DEFAULT" : 6, + "MIN" : 0, + "LABEL" : "Dirt Size" + }, + { + "NAME" : "brightness", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 4, + "MIN" : 0, + "LABEL" : "Dirt Thickness" + }, + { + "NAME" : "brightnessCurve", + "TYPE" : "float", + "MAX" : 4, + "DEFAULT" : 4, + "LABEL" : "Dirt Shape", + "MIN" : 1 + }, + { + "NAME" : "radius", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "LABEL" : "Dirt Spread", + "MIN" : 0 + }, + { + "NAME" : "noiseSeed", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.49379485845565796, + "MIN" : 0, + "LABEL" : "Dirt Seed" + }, + { + "NAME" : "blurLevel", + "TYPE" : "float", + "MAX" : 0.5, + "DEFAULT" : 0.5, + "MIN" : 0 + }, + { + "NAME" : "blurDepth", + "TYPE" : "float", + "MAX" : 10, + "DEFAULT" : 3.75, + "MIN" : 1 + } + ], + "ISFVSN" : "2", + "PASSES" : [ + { + "TARGET" : "smaller", + "WIDTH" : "max(floor($WIDTH*0.02),1.0)", + "HEIGHT" : "max(floor($HEIGHT*0.02),1.0)" + }, + { + "TARGET" : "small", + "WIDTH" : "max(floor($WIDTH*0.25),1.0)", + "HEIGHT" : "max(floor($HEIGHT*0.25),1.0)" + }, + { + + } + ], + "CREDIT" : "Inigo Quilez ported by @colin_movecraft and VIDVOX" +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + + +// Value Noise (http://en.wikipedia.org/wiki/Value_noise), not to be confused with Perlin's +// Noise, is probably the simplest way to generate noise (a random smooth signal with +// mostly all its energy in the low frequencies) suitable for procedural texturing/shading, +// modeling and animation. +// +// It produces lowe quality noise than Gradient Noise (https://www.shadertoy.com/view/XdXGW8) +// but it is slightly faster to compute. When used in a fractal construction, the blockyness +// of Value Noise gets qcuikly hidden, making it a very popular alternative to Gradient Noise. +// +// The princpiple is to create a virtual grid/latice all over the plane, and assign one +// random value to every vertex in the grid. When querying/requesting a noise value at +// an arbitrary point in the plane, the grid cell in which the query is performed is +// determined (line 30), the four vertices of the grid are determined and their random +// value fetched (lines 35 to 38) and then bilinearly interpolated (lines 35 to 38 again) +// with a smooth interpolant (line 31 and 33). + + +// Value Noise 2D, Derivatives: https://www.shadertoy.com/view/4dXBRH +// Gradient Noise 2D, Derivatives: https://www.shadertoy.com/view/XdXBRH +// Value Noise 3D, Derivatives: https://www.shadertoy.com/view/XsXfRH +// Gradient Noise 3D, Derivatives: https://www.shadertoy.com/view/4dffRH +// Value Noise 2D : https://www.shadertoy.com/view/lsf3WH +// Value Noise 3D : https://www.shadertoy.com/view/4sfGzS +// Gradient Noise 2D : https://www.shadertoy.com/view/XdXGW8 +// Gradient Noise 3D : https://www.shadertoy.com/view/Xsl3Dl +// Simplex Noise 2D : https://www.shadertoy.com/view/Msf3WH + + +float hash(vec2 p) // replace this by something better +{ + p = 50.0*fract(noiseSeed + p*0.3183099 + vec2(0.71,0.113)); + return -1.0+2.0*fract( p.x*p.y*(p.x+p.y) ); +} + +float noise( in vec2 p ) +{ + vec2 i = floor( p ); + vec2 f = fract( p ); + + vec2 u = f*f*(3.0-2.0*f); + + return mix( mix( hash( i + vec2(0.0,0.0) ), + hash( i + vec2(1.0,0.0) ), u.x), + mix( hash( i + vec2(0.0,1.0) ), + hash( i + vec2(1.0,1.0) ), u.x), u.y); +} + +float map(float n, float i1, float i2, float o1, float o2){ + return o1 + (o2-o1) * (n-i1)/(i2-i1); + +} + +void main(){ + vec2 p = gl_FragCoord.xy / RENDERSIZE; + + vec2 uv = p*vec2(RENDERSIZE.x/RENDERSIZE.y,1.0); + float f = 0.0; + + //fbm - fractal noise (4 octaves) + { + uv *= scale; + mat2 m = mat2( 1.6, 1.2, -1.2, 1.6 ); + f = 0.5000*noise( uv ); uv = m*uv; + f += 0.2500*noise( uv ); uv = m*uv; + f += 0.1250*noise( uv ); uv = m*uv; + f += 0.0625*noise( uv ); uv = m*uv; + } + + f = 1.0-pow(f,(5.0-brightnessCurve))*brightness; + + float d = distance(isf_FragNormCoord,vec2(0.5)); + if (d > radius) { + f = f + (d-radius); + } + + f = (f > 1.0) ? 1.0 : f; + + vec4 color = IMG_THIS_NORM_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 avg = (color + colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 9.0; + + if (PASSINDEX == 1) { + vec4 blur = IMG_THIS_NORM_PIXEL(smaller); + avg = mix(color, (avg + blurDepth*blur)/(1.0+blurDepth), (1.0-f)*blurLevel); + } + else if (PASSINDEX == 2) { + vec4 blur = IMG_THIS_NORM_PIXEL(small); + avg = mix(color, (avg + blurDepth*blur)/(1.0+blurDepth), (1.0-f)*blurLevel); + } + + gl_FragColor = avg; +} + + + +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/renderer/src/application/sample-modules/isf/Smudged Lens.vs b/src/renderer/src/application/sample-modules/isf/Smudged Lens.vs new file mode 100644 index 000000000..b0ba4b037 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Smudged Lens.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Soft Blur.fs b/src/renderer/src/application/sample-modules/isf/Soft Blur.fs new file mode 100644 index 000000000..fe6762946 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Soft Blur.fs @@ -0,0 +1,96 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "softness", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.9 + }, + { + "NAME": "depth", + "TYPE": "float", + "MIN": 1.0, + "MAX": 10.0, + "DEFAULT": 10.0 + } + ], + "PASSES": [ + { + "TARGET": "smaller", + "WIDTH": "max(floor($WIDTH*0.02),1.0)", + "HEIGHT": "max(floor($HEIGHT*0.02),1.0)" + }, + { + "TARGET": "small", + "WIDTH": "max(floor($WIDTH*0.25),1.0)", + "HEIGHT": "max(floor($HEIGHT*0.25),1.0)" + }, + { + + } + ] +}*/ + + +// A simple three pass blur – first reduce the size, then do a weighted blur, then do the same thing + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + + + +void main() +{ + + vec4 color = IMG_THIS_NORM_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 avg = (color + colorL + colorR + colorA + colorB + colorLA + colorRA + colorLB + colorRB) / 9.0; + + if (PASSINDEX == 1) { + vec4 blur = IMG_THIS_NORM_PIXEL(smaller); + avg = mix(color, (avg + depth*blur)/(1.0+depth), softness); + } + else if (PASSINDEX == 2) { + vec4 blur = IMG_THIS_NORM_PIXEL(small); + avg = mix(color, (avg + depth*blur)/(1.0+depth), softness); + } + gl_FragColor = avg; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Soft Blur.vs b/src/renderer/src/application/sample-modules/isf/Soft Blur.vs new file mode 100644 index 000000000..b0ba4b037 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Soft Blur.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Soft Flip.fs b/src/renderer/src/application/sample-modules/isf/Soft Flip.fs new file mode 100644 index 000000000..d70231a70 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Soft Flip.fs @@ -0,0 +1,109 @@ +/*{ + "CATEGORIES": [ + "Geometry Adjustment" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": -0.25, + "MAX": 1, + "MIN": -1, + "NAME": "angle", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "centerPt", + "TYPE": "point2D" + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "lineWidth", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "NAME": "flipH", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "NAME": "flipV", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + + + +const float pi = 3.14159265359; + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +// returns the distance from pt0 to the line defined by pt1 and pt2 +float distancePtToLine(vec2 pt1, vec2 pt2, vec2 pt0) { + return ((pt2.y-pt1.y)*pt0.x-(pt2.x-pt1.x)*pt0.y+pt2.x*pt1.y-pt2.y*pt1.x)/(sqrt(pow(pt2.y-pt1.y,2.0)+pow(pt2.x-pt1.x,2.0))); +} + +void main() { + vec2 loc = isf_FragNormCoord; + vec4 returnMe = vec4(vec3(0.0),1.0); + vec2 p1 = centerPt; + vec2 p2 = p1 + vec2(cos(angle*pi),sin(angle*pi)); + float val = distancePtToLine(p1,p2,loc); + vec2 flipLoc = loc; + flipLoc.x = (flipH) ? 1.0 - flipLoc.x : flipLoc.x; + flipLoc.y = (flipV) ? 1.0 - flipLoc.y : flipLoc.y; + + if (abs(val) < lineWidth) { + vec4 pix1 = IMG_NORM_PIXEL(inputImage,loc); + vec4 pix2 = IMG_NORM_PIXEL(inputImage,flipLoc); + returnMe = mix(pix1,pix2,((-val+lineWidth)) / (2.0*lineWidth)); + //returnMe.r = 2.0 * ((val+lineWidth)) / (2.0*lineWidth); + //returnMe.g = 2.0 - 2.0 * ((val+lineWidth)) / (2.0*lineWidth); + } + else if (val > 0.0) { + returnMe = IMG_NORM_PIXEL(inputImage,loc); + } + else { + returnMe = IMG_NORM_PIXEL(inputImage,flipLoc); + } + gl_FragColor = returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Solarize.fs b/src/renderer/src/application/sample-modules/isf/Solarize.fs new file mode 100644 index 000000000..f0b800214 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Solarize.fs @@ -0,0 +1,91 @@ +/*{ + "DESCRIPTION": "Solarizes an image", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "centerBrightness", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "powerCurve", + "TYPE": "float", + "MIN": 0.0, + "MAX": 4.0, + "DEFAULT": 1.0 + }, + { + "NAME": "colorize", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "inverse", + "TYPE": "bool", + "DEFAULT": 1.0 + } + ] +}*/ + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + + +void main() +{ + vec4 inColor = IMG_NORM_PIXEL(inputImage, isf_FragNormCoord); + vec4 hslColor; + vec4 outColor; + + // convert to HSV + hslColor.rgb = rgb2hsv(inColor.rgb); + outColor.rgb = hslColor.rgb; + outColor.a = inColor.a; + + // drop the saturation + //outColor.g = 0.0; + + // adjust the brightness curve + outColor.b = (outColor.b < centerBrightness) ? (1.0 - outColor.b / centerBrightness) : (outColor.b - centerBrightness) / centerBrightness; + outColor.b = pow(outColor.b, powerCurve); + outColor.b = (inverse) ? 1.0 - outColor.b : outColor.b; + + outColor.g = (inverse) ? outColor.g * (1.0-hslColor.b) * colorize : outColor.g * hslColor.b * colorize; + + // convert back to rgb + outColor.rgb = hsv2rgb(outColor.rgb); + + gl_FragColor = outColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Solid Color.fs b/src/renderer/src/application/sample-modules/isf/Solid Color.fs new file mode 100644 index 000000000..0cb8819ca --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Solid Color.fs @@ -0,0 +1,27 @@ +/*{ + "CATEGORIES": [ + "Color", + "Utility" + ], + "CREDIT": "by Carter Rosenberg", + "DESCRIPTION": "demonstrates the use of color-type image inputs", + "INPUTS": [ + { + "DEFAULT": [ + 1, + 0, + 0, + 1 + ], + "NAME": "Color", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + +void main() +{ + gl_FragColor = Color; +} diff --git a/src/renderer/src/application/sample-modules/isf/Sorting Smear.fs b/src/renderer/src/application/sample-modules/isf/Sorting Smear.fs new file mode 100644 index 000000000..7e37ea5f5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sorting Smear.fs @@ -0,0 +1,119 @@ +/*{ + "CATEGORIES": [ + "Glitch", + "Feedback" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "LABEL": "Flush Buffer", + "NAME": "resetInput", + "TYPE": "event" + }, + { + "DEFAULT": 0.25, + "LABEL": "Adapt Rate", + "MAX": 1, + "MIN": 0, + "NAME": "adaptLevel", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Sort Rate", + "MAX": 1, + "MIN": 0, + "NAME": "sortRate", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "H Sort", + "NAME": "horizontalSort", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "LABEL": "V Sort", + "NAME": "verticalSort", + "TYPE": "bool" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "PERSISTENT": true, + "TARGET": "lastRender" + } + ] +} +*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; +#endif + + + +void main() +{ + vec4 result = vec4(0.0); + vec4 color = IMG_THIS_NORM_PIXEL(inputImage); + //float localAdaptRate = 1.0 - pow(adaptLevel,2.0); + float localAdaptRate = adaptLevel; + // if the frame index is 0, or the reset event was triggered, use the original color + if ((FRAMEINDEX <= 1)||(resetInput)) { + result = color; + } + else { + vec4 oldColor = IMG_THIS_NORM_PIXEL(lastRender); + color = mix(color, oldColor, localAdaptRate); + result = color; + float b0 = (color.r + color.b + color.g) / 3.0; + + if (verticalSort) { + vec4 colorA = IMG_NORM_PIXEL(lastRender, above_coord); + vec4 colorB = IMG_NORM_PIXEL(lastRender, below_coord); + float bA = (colorA.r + colorA.b + colorA.g) / 3.0; + float bB = (colorB.r + colorB.b + colorB.g) / 3.0; + float localSortRate = sortRate; + // if this pixel is brighter to the one on the left, we are swapping with it + if (b0 < bA) { + result = mix(result, colorA, localSortRate); + } + // if this pixel is not as bright as the one to the right, we are swapping with it + if (b0 > bB) { + result = mix(result, colorB, localSortRate); + } + } + if (horizontalSort) { + vec4 colorL = IMG_NORM_PIXEL(lastRender, left_coord); + vec4 colorR = IMG_NORM_PIXEL(lastRender, right_coord); + float bL = (colorL.r + colorL.b + colorL.g) / 3.0; + float bR = (colorR.r + colorR.b + colorR.g) / 3.0; + float localSortRate = (verticalSort) ? 0.5 : 1.0; + localSortRate = localSortRate * sortRate; + // if this pixel is brighter to the one on the left, we are swapping with it + if (b0 > bL) { + result = mix(result, colorL, localSortRate); + } + // if this pixel is not as bright as the one to the right, we are swapping with it + if (b0 < bR) { + result = mix(result, colorR, localSortRate); + } + } + } + gl_FragColor = result; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sorting Smear.vs b/src/renderer/src/application/sample-modules/isf/Sorting Smear.vs new file mode 100644 index 000000000..4f5982db3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sorting Smear.vs @@ -0,0 +1,24 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Sphere Map.fs b/src/renderer/src/application/sample-modules/isf/Sphere Map.fs new file mode 100644 index 000000000..f0daa863c --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Sphere Map.fs @@ -0,0 +1,76 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Maps video onto a sphere", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "LABEL": "Image Scale", + "MAX": 1, + "MIN": 0.125, + "NAME": "imageScale", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Radius Scale", + "MAX": 1.999, + "MIN": 0, + "NAME": "radiusScale", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "LABEL": "Rotate", + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pointInput", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +const float pi = 3.14159265359; + + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 rotate = pointInput; + vec2 p = 2.0 * isf_FragNormCoord.xy - 1.0; + float aspect = RENDERSIZE.x / RENDERSIZE.y; + p.x = p.x * aspect; + + float r = sqrt(dot(p,p)) * (2.0-radiusScale); + if (r < 1.0) { + vec2 uv; + float f = imageScale * (1.0-sqrt(1.0-r))/(r); + uv.x = mod(p.x*f + rotate.x,1.0); + uv.y = mod(p.y*f + rotate.y,1.0); + inputPixelColor = IMG_NORM_PIXEL(inputImage, uv); + } + + + // both of these are also the same + //inputPixelColor = IMG_NORM_PIXEL(inputImage, loc); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Spiral.fs b/src/renderer/src/application/sample-modules/isf/Spiral.fs new file mode 100644 index 000000000..f5ff9733d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Spiral.fs @@ -0,0 +1,107 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "rotation", + "TYPE": "float" + }, + { + "DEFAULT": 2, + "MAX": 50, + "MIN": 0.1, + "NAME": "count", + "TYPE": "float" + }, + { + "DEFAULT": 0.125, + "MAX": 0.25, + "MIN": 0, + "NAME": "width", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "softness", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 0 + ], + "NAME": "color1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "NAME": "color2", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + +const float pi = 3.14159265359; + + +void main() { + // determine if we are on an even or odd line + // math goes like.. + // r = a + b*theta + // Changing the parameter 'a' will turn the spiral, while 'b' controls the distance between successive turnings. + + + vec4 out_color = color1; + + // convert to polar coordinates + vec2 loc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + loc.y = (loc.y - 0.5) * RENDERSIZE.y / RENDERSIZE.x + 0.5; + float r = 2.0 * count * distance(vec2(0.5,0.5), loc) + width; + float theta = atan ((loc.y-0.5),(loc.x-0.5)); + + loc.y = r * sin(theta + 2.0 * pi * rotation) + 0.5; + + if (loc.y < 0.5) { + theta = theta + 2.0 * pi; + theta = mod(theta + rotation * 2.0 * pi, 2.0 * pi); + theta = (theta + 2.0 * pi * floor(r - width)); + } + else { + theta = mod(theta + rotation * 2.0 * pi, 2.0 * pi); + theta = (theta + 2.0 * pi * floor(r + width)); + } + + if (width == 0.0) { + out_color = color1; + } + else { + float dist = abs(r - theta/(2.0*pi)); + if (dist < width) { + if (dist > width * (1.0-softness)) { + out_color = mix(color2, color1, (dist - width * (1.0-softness))/(width - width * (1.0-softness))); + } + else { + out_color = color2; + } + } + } + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Squares Wire.fs b/src/renderer/src/application/sample-modules/isf/Squares Wire.fs new file mode 100644 index 000000000..2132e3564 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Squares Wire.fs @@ -0,0 +1,100 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/squareswire.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 1.6, + "MAX": 10, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + -0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + -1, + -1 + ], + "NAME": "direction", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 10, + 10 + ], + "MAX": [ + 100, + 100 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "squares", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +const vec2 center = vec2(0.5, 0.5); +vec4 transition (vec2 p) { + vec2 v = normalize(direction); + v /= abs(v.x)+abs(v.y); + float d = v.x * center.x + v.y * center.y; + float offset = smoothness; + float pr = smoothstep(-offset, 0.0, v.x * p.x + v.y * p.y - (d-0.5+progress*(1.+offset))); + vec2 squarep = fract(p*vec2(squares)); + vec2 squaremin = vec2(pr/2.0); + vec2 squaremax = vec2(1.0 - pr/2.0); + float a = (1.0 - step(progress, 0.0)) * step(squaremin.x, squarep.x) * step(squaremin.y, squarep.y) * step(squarep.x, squaremax.x) * step(squarep.y, squaremax.y); + return mix(getFromColor(p), getToColor(p), a); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Squeeze.fs b/src/renderer/src/application/sample-modules/isf/Squeeze.fs new file mode 100644 index 000000000..adeb39dc3 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Squeeze.fs @@ -0,0 +1,69 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/squeeze.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.04, + "MAX": 1, + "MIN": 0, + "NAME": "colorSeparation", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +vec4 transition (vec2 uv) { + float y = 0.5 + (uv.y-0.5) / (1.0-progress); + if (y < 0.0 || y > 1.0) { + return getToColor(uv); + } + else { + vec2 fp = vec2(uv.x, y); + vec2 off = progress * vec2(0.0, colorSeparation); + vec4 c = getFromColor(fp); + vec4 cn = getFromColor(fp - off); + vec4 cp = getFromColor(fp + off); + return vec4(cn.r, c.g, cp.b, c.a); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Star.fs b/src/renderer/src/application/sample-modules/isf/Star.fs new file mode 100644 index 000000000..489f8a11e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Star.fs @@ -0,0 +1,109 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "DEFAULT": 0.25, + "LABEL": "Size", + "MAX": 1, + "MIN": 0, + "NAME": "size", + "TYPE": "float" + }, + { + "DEFAULT": 0.01, + "LABEL": "Stroke Width", + "MAX": 0.1, + "MIN": 0, + "NAME": "bordersize", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + 0.6, + 0.75, + 1 + ], + "LABEL": "Fill Color", + "NAME": "color1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "LABEL": "Stroke Color", + "NAME": "color2", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + +// Adapted from https://glsl.io/transition/d1f891c5585fc40b55ea + + +vec2 circlePoint( float ang ) +{ + ang += 6.28318 * 0.15; + return vec2( cos(ang), sin(ang) ); +} + +float cross2d( vec2 a, vec2 b ) +{ + return ( a.x * b.y - a.y * b.x ); +} + +// quickly knocked together with some math from http://www.pixeleuphoria.com/node/30 +float star( vec2 p, float size ) +{ + if( size <= 0.0 ) + { + return 0.0; + } + p /= size; + + vec2 p0 = circlePoint( 0.0 ); + vec2 p1 = circlePoint( 6.28318 * 1.0 / 5.0 ); + vec2 p2 = circlePoint( 6.28318 * 2.0 / 5.0 ); + vec2 p3 = circlePoint( 6.28318 * 3.0 / 5.0 ); + vec2 p4 = circlePoint( 6.28318 * 4.0 / 5.0 ); + + // are we on this side of the line + float s0 = ( cross2d( p1 - p0, p - p0 ) ); + float s1 = ( cross2d( p2 - p1, p - p1 ) ); + float s2 = ( cross2d( p3 - p2, p - p2 ) ); + float s3 = ( cross2d( p4 - p3, p - p3 ) ); + float s4 = ( cross2d( p0 - p4, p - p4 ) ); + + // some trial and error math to get the star shape. I'm sure there's some elegance I'm missing. + float s5 = min( min( min( s0, s1 ), min( s2, s3 ) ), s4 ); + float s = max( 1.0 - sign( s0 * s1 * s2 * s3 * s4 ) + sign(s5), 0.0 ); + s = sign( 2.6 - length(p) ) * s; + + return max( s, 0.0 ); +} + +void main() +{ + vec2 p = isf_FragNormCoord; + vec2 o = p * 2.0 - 1.0; + + float t = size * 1.4; + + float c1 = star( o, t ); + float c2 = star( o, t - bordersize ); + + float border = max( c1 - c2, 0.0 ); + + gl_FragColor = mix(mix(vec4(0.0), color1, c1), color2, border); +} + diff --git a/src/renderer/src/application/sample-modules/isf/Stereo Viewer.fs b/src/renderer/src/application/sample-modules/isf/Stereo Viewer.fs new file mode 100644 index 000000000..78c3965cc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Stereo Viewer.fs @@ -0,0 +1,269 @@ +/*{ + "CATEGORIES": [ + "Wipe", + "Retro" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/StereoViewer.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.88, + "MAX": 1, + "MIN": 0, + "NAME": "zoom", + "TYPE": "float" + }, + { + "DEFAULT": 0.22, + "MAX": 1, + "MIN": 0, + "NAME": "corner_radius", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + +float ratio = RENDERSIZE.x/RENDERSIZE.y; + + + +// Tunable parameters +// How much to zoom (out) for the effect ~ 0.5 - 1.0 +// Corner radius as a fraction of the image height + +// author: Ted Schundler +// license: BSD 2 Clause +// Free for use and modification by anyone with credit + +// Copyright (c) 2016, Theodore K Schundler +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/////////////////////////////////////////////////////////////////////////////// +// Stereo Viewer Toy Transition // +// // +// Inspired by ViewMaster / Image3D image viewer devices. // +// This effect is similar to what you see when you press the device's lever. // +// There is a quick zoom in / out to make the transition 'valid' for GLSL.io // +/////////////////////////////////////////////////////////////////////////////// + +const vec4 black = vec4(0.0, 0.0, 0.0, 1.0); +const vec2 c00 = vec2(0.0, 0.0); // the four corner points +const vec2 c01 = vec2(0.0, 1.0); +const vec2 c11 = vec2(1.0, 1.0); +const vec2 c10 = vec2(1.0, 0.0); + +// Check if a point is within a given corner +bool in_corner(vec2 p, vec2 corner, vec2 radius) { + // determine the direction we want to be filled + vec2 axis = (c11 - corner) - corner; + + // warp the point so we are always testing the bottom left point with the + // circle centered on the origin + p = p - (corner + axis * radius); + p *= axis / radius; + return (p.x > 0.0 && p.y > -1.0) || (p.y > 0.0 && p.x > -1.0) || dot(p, p) < 1.0; +} + +// Check all four corners +// return a float for v2 for anti-aliasing? +bool test_rounded_mask(vec2 p, vec2 corner_size) { + return + in_corner(p, c00, corner_size) && + in_corner(p, c01, corner_size) && + in_corner(p, c10, corner_size) && + in_corner(p, c11, corner_size); +} + +// Screen blend mode - https://en.wikipedia.org/wiki/Blend_modes +// This more closely approximates what you see than linear blending +vec4 screen(vec4 a, vec4 b) { + return 1.0 - (1.0 - a) * (1.0 -b); +} + +// Given RGBA, find a value that when screened with itself +// will yield the original value. +vec4 unscreen(vec4 c) { + return 1.0 - sqrt(1.0 - c); +} + +// Grab a pixel, only if it isn't masked out by the rounded corners +vec4 sample_with_corners_from(vec2 p, vec2 corner_size) { + p = (p - 0.5) / zoom + 0.5; + if (!test_rounded_mask(p, corner_size)) { + return black; + } + return unscreen(getFromColor(p)); +} + +vec4 sample_with_corners_to(vec2 p, vec2 corner_size) { + p = (p - 0.5) / zoom + 0.5; + if (!test_rounded_mask(p, corner_size)) { + return black; + } + return unscreen(getToColor(p)); +} + +// special sampling used when zooming - extra zoom parameter and don't unscreen +vec4 simple_sample_with_corners_from(vec2 p, vec2 corner_size, float zoom_amt) { + p = (p - 0.5) / (1.0 - zoom_amt + zoom * zoom_amt) + 0.5; + if (!test_rounded_mask(p, corner_size)) { + return black; + } + return getFromColor(p); +} + +vec4 simple_sample_with_corners_to(vec2 p, vec2 corner_size, float zoom_amt) { + p = (p - 0.5) / (1.0 - zoom_amt + zoom * zoom_amt) + 0.5; + if (!test_rounded_mask(p, corner_size)) { + return black; + } + return getToColor(p); +} + +// Basic 2D affine transform matrix helpers +// These really shouldn't be used in a fragment shader - I should work out the +// the math for a translate & rotate function as a pair of dot products instead + +mat3 rotate2d(float angle, float ratio) { + float s = sin(angle); + float c = cos(angle); + return mat3( + c, s ,0.0, + -s, c, 0.0, + 0.0, 0.0, 1.0); +} + +mat3 translate2d(float x, float y) { + return mat3( + 1.0, 0.0, 0, + 0.0, 1.0, 0, + -x, -y, 1.0); +} + +mat3 scale2d(float x, float y) { + return mat3( + x, 0.0, 0, + 0.0, y, 0, + 0, 0, 1.0); +} + +// Split an image and rotate one up and one down along off screen pivot points +vec4 get_cross_rotated(vec3 p3, float angle, vec2 corner_size, float ratio) { + angle = angle * angle; // easing + angle /= 2.4; // works out to be a good number of radians + + mat3 center_and_scale = translate2d(-0.5, -0.5) * scale2d(1.0, ratio); + mat3 unscale_and_uncenter = scale2d(1.0, 1.0/ratio) * translate2d(0.5,0.5); + mat3 slide_left = translate2d(-2.0,0.0); + mat3 slide_right = translate2d(2.0,0.0); + mat3 rotate = rotate2d(angle, ratio); + + mat3 op_a = center_and_scale * slide_right * rotate * slide_left * unscale_and_uncenter; + mat3 op_b = center_and_scale * slide_left * rotate * slide_right * unscale_and_uncenter; + + vec4 a = sample_with_corners_from((op_a * p3).xy, corner_size); + vec4 b = sample_with_corners_from((op_b * p3).xy, corner_size); + + return screen(a, b); +} + +// Image stays put, but this time move two masks +vec4 get_cross_masked(vec3 p3, float angle, vec2 corner_size, float ratio) { + angle = 1.0 - angle; + angle = angle * angle; // easing + angle /= 2.4; + + vec4 img; + + mat3 center_and_scale = translate2d(-0.5, -0.5) * scale2d(1.0, ratio); + mat3 unscale_and_uncenter = scale2d(1.0 / zoom, 1.0 / (zoom * ratio)) * translate2d(0.5,0.5); + mat3 slide_left = translate2d(-2.0,0.0); + mat3 slide_right = translate2d(2.0,0.0); + mat3 rotate = rotate2d(angle, ratio); + + mat3 op_a = center_and_scale * slide_right * rotate * slide_left * unscale_and_uncenter; + mat3 op_b = center_and_scale * slide_left * rotate * slide_right * unscale_and_uncenter; + + bool mask_a = test_rounded_mask((op_a * p3).xy, corner_size); + bool mask_b = test_rounded_mask((op_b * p3).xy, corner_size); + + if (mask_a || mask_b) { + img = sample_with_corners_to(p3.xy, corner_size); + return screen(mask_a ? img : black, mask_b ? img : black); + } else { + return black; + } +} + +vec4 transition(vec2 uv) { + float a; + vec2 p=uv.xy/vec2(1.0).xy; + vec3 p3 = vec3(p.xy, 1.0); // for 2D matrix transforms + + // corner is warped to represent to size after mapping to 1.0, 1.0 + vec2 corner_size = vec2(corner_radius / ratio, corner_radius); + + if (progress <= 0.0) { + // 0.0: start with the base frame always + return getFromColor(p); + } else if (progress < 0.1) { + // 0.0-0.1: zoom out and add rounded corners + a = progress / 0.1; + return simple_sample_with_corners_from(p, corner_size * a, a); + } else if (progress < 0.48) { + // 0.1-0.48: Split original image apart + a = (progress - 0.1)/0.38; + return get_cross_rotated(p3, a, corner_size, ratio); + } else if (progress < 0.9) { + // 0.48-0.52: black + // 0.52 - 0.9: unmask new image + return get_cross_masked(p3, (progress - 0.52)/0.38, corner_size, ratio); + } else if (progress < 1.0) { + // zoom out and add rounded corners + a = (1.0 - progress) / 0.1; + return simple_sample_with_corners_to(p, corner_size * a, a); + } else { + // 1.0 end with base frame + return getToColor(p); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Stripes.fs b/src/renderer/src/application/sample-modules/isf/Stripes.fs new file mode 100644 index 000000000..aa56cc489 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Stripes.fs @@ -0,0 +1,76 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "VIDVOX", + "INPUTS": [ + { + "DEFAULT": 0.25, + "NAME": "width", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "NAME": "offset", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "NAME": "vertical", + "TYPE": "bool" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "NAME": "color1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "color2", + "TYPE": "color" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "splitPos", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +void main() { + // determine if we are on an even or odd line + // math goes like.. + // mod(((coord+offset) / width),2) + + + vec4 out_color = color2; + float coord = isf_FragNormCoord[0]; + + if (vertical) { + coord = isf_FragNormCoord[1]; + } + if (width == 0.0) { + out_color = color1; + } + else if(mod(((coord+offset) / width),2.0) < 2.0 * splitPos) { + out_color = color1; + } + + gl_FragColor = out_color; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Strobe.fs b/src/renderer/src/application/sample-modules/isf/Strobe.fs new file mode 100644 index 000000000..b0a154129 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Strobe.fs @@ -0,0 +1,91 @@ +/* +{ + "CATEGORIES" : [ + "Color Effect" + ], + "DESCRIPTION" : "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "strobeRate", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "LABEL" : "Strobe Rate", + "MIN" : 0 + }, + { + "LABELS" : [ + "Invert", + "Color" + ], + "NAME" : "strobeMode", + "TYPE" : "long", + "LABEL" : "Strobe Mode", + "VALUES" : [ + 0, + 1 + ] + }, + { + "NAME" : "strobeColor", + "TYPE" : "color", + "DEFAULT" : [ + 1, + 1, + 1, + 1 + ], + "LABEL" : "Strobe Color" + } + ], + "PASSES" : [ + { + "WIDTH" : "1", + "DESCRIPTION" : "this buffer stores the last frame's time offset in the first component of its only pixel- note that it's requesting a FLOAT target buffer...", + "HEIGHT" : "1", + "TARGET" : "lastState", + "PERSISTENT" : true + }, + { + + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + + + +void main() +{ + // if this is the first pass, i'm going to read the position from the "lastPosition" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastState,vec2(0.5)); + // i'm only using the X, which is the last render time we reset + if (strobeRate == 0.0) { + srcPixel.r = (srcPixel.r == 0.0) ? 1.0 : 0.0; + } + else { + srcPixel.r = (mod(TIME, strobeRate) <= strobeRate / 2.0) ? 1.0 : 0.0; + } + gl_FragColor = srcPixel; + } + // else this isn't the first pass- read the position value from the buffer which stores it + else { + vec4 lastStateVector = IMG_PIXEL(lastState,vec2(0.5)); + vec4 srcPixel = IMG_THIS_PIXEL(inputImage); + // invert or flash a color? + if (strobeMode == 0) { + srcPixel = (lastStateVector.r == 0.0) ? srcPixel : vec4(1.0-srcPixel.r, 1.0-srcPixel.g, 1.0-srcPixel.b, srcPixel.a); + } + else if (strobeMode == 1) { + srcPixel = (lastStateVector.r == 0.0) ? srcPixel : strobeColor; + } + gl_FragColor = srcPixel; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Swap Transition.fs b/src/renderer/src/application/sample-modules/isf/Swap Transition.fs new file mode 100644 index 000000000..137566a68 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Swap Transition.fs @@ -0,0 +1,122 @@ +/*{ + "CATEGORIES": [ + "Wipe", + "Retro" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/swap.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.2, + "MAX": 1, + "MIN": 0, + "NAME": "perspective", + "TYPE": "float" + }, + { + "DEFAULT": 3, + "MAX": 10, + "MIN": 0, + "NAME": "depth", + "TYPE": "float" + }, + { + "DEFAULT": 0.4, + "MAX": 1, + "MIN": 0, + "NAME": "reflection", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT +// General parameters + +const vec4 black = vec4(0.0, 0.0, 0.0, 1.0); +const vec2 boundMin = vec2(0.0, 0.0); +const vec2 boundMax = vec2(1.0, 1.0); + +bool inBounds (vec2 p) { + return all(lessThan(boundMin, p)) && all(lessThan(p, boundMax)); +} + +vec2 project (vec2 p) { + return p * vec2(1.0, -1.2) + vec2(0.0, -0.02); +} + +vec4 bgColor (vec2 p, vec2 pfr, vec2 pto) { + vec4 c = black; + pfr = project(pfr); + if (inBounds(pfr)) { + c += mix(black, getFromColor(pfr), reflection * mix(1.0, 0.0, pfr.y)); + } + pto = project(pto); + if (inBounds(pto)) { + c += mix(black, getToColor(pto), reflection * mix(1.0, 0.0, pto.y)); + } + return c; +} + +vec4 transition(vec2 p) { + vec2 pfr, pto = vec2(-1.); + + float size = mix(1.0, depth, progress); + float persp = perspective * progress; + pfr = (p + vec2(-0.0, -0.5)) * vec2(size/(1.0-perspective*progress), size/(1.0-size*persp*p.x)) + vec2(0.0, 0.5); + + size = mix(1.0, depth, 1.-progress); + persp = perspective * (1.-progress); + pto = (p + vec2(-1.0, -0.5)) * vec2(size/(1.0-perspective*(1.0-progress)), size/(1.0-size*persp*(0.5-p.x))) + vec2(1.0, 0.5); + + if (progress < 0.5) { + if (inBounds(pfr)) { + return getFromColor(pfr); + } + if (inBounds(pto)) { + return getToColor(pto); + } + } + if (inBounds(pto)) { + return getToColor(pto); + } + if (inBounds(pfr)) { + return getFromColor(pfr); + } + return bgColor(p, pfr, pto); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Swirl.fs b/src/renderer/src/application/sample-modules/isf/Swirl.fs new file mode 100644 index 000000000..925884e63 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Swirl.fs @@ -0,0 +1,75 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/Swirl.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: Sergey Kosarevsky +// ( http://www.linderdaum.com ) +// ported by gre from https://gist.github.com/corporateshark/cacfedb8cca0f5ce3f7c + +vec4 transition(vec2 UV) +{ + float Radius = 1.0; + + float T = progress; + + UV -= vec2( 0.5, 0.5 ); + + float Dist = length(UV); + + if ( Dist < Radius ) + { + float Percent = (Radius - Dist) / Radius; + float A = ( T <= 0.5 ) ? mix( 0.0, 1.0, T/0.5 ) : mix( 1.0, 0.0, (T-0.5)/0.5 ); + float Theta = Percent * Percent * A * 8.0 * 3.14159; + float S = sin( Theta ); + float C = cos( Theta ); + UV = vec2( dot(UV, vec2(C, -S)), dot(UV, vec2(S, C)) ); + } + UV += vec2( 0.5, 0.5 ); + + vec4 C0 = getFromColor(UV); + vec4 C1 = getToColor(UV); + + return mix( C0, C1, T ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/TV Static.fs b/src/renderer/src/application/sample-modules/isf/TV Static.fs new file mode 100644 index 000000000..d1257b3da --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/TV Static.fs @@ -0,0 +1,75 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/TVStatic.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.05, + "MAX": 1, + "MIN": 0, + "NAME": "offset", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: Brandon Anzaldi +// license: MIT + +// Pseudo-random noise function +// http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +highp float noise(vec2 co) +{ + highp float a = 12.9898; + highp float b = 78.233; + highp float c = 43758.5453; + highp float dt= dot(co.xy * progress, vec2(a, b)); + highp float sn= mod(dt,3.14); + return fract(sin(sn) * c); +} + +vec4 transition(vec2 p) { + if (progress < offset) { + return getFromColor(p); + } else if (progress > (1.0 - offset)) { + return getToColor(p); + } else { + return vec4(vec3(noise(p)), 1.0); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Time Glitch RGB.fs b/src/renderer/src/application/sample-modules/isf/Time Glitch RGB.fs new file mode 100644 index 000000000..cbb1be1eb --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Time Glitch RGB.fs @@ -0,0 +1,461 @@ +/*{ + "DESCRIPTION": "Buffers 8 recent frames", + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "inputDelay", + "LABEL": "Buffer", + "TYPE": "color", + "DEFAULT": [ + 0.25, + 0.5, + 0.75, + 0.5 + ] + }, + { + "NAME": "inputRate", + "LABEL": "Buffer Lag", + "TYPE": "float", + "MIN": 1.0, + "MAX": 20.0, + "DEFAULT": 4.0 + }, + { + "NAME": "glitch_size", + "LABEL": "Size", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.5, + "DEFAULT": 0.1 + }, + { + "NAME": "glitch_horizontal", + "LABEL": "Horizontal Amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.2 + }, + { + "NAME": "glitch_vertical", + "LABEL": "Vertical Amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "randomize_size", + "LABEL": "Randomize Size", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "randomize_position", + "LABEL": "Randomize Position", + "TYPE": "bool", + "DEFAULT": 0.0 + }, + { + "NAME": "randomize_zoom", + "LABEL": "Randomize Zoom", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET":"lastRow", + "WIDTH": "1", + "HEIGHT": "1", + "PERSISTENT": true, + "DESCRIPTION": "this buffer stores the last frame's odd / even state" + }, + { + "TARGET":"buffer8", + "PERSISTENT": true + }, + { + "TARGET":"buffer7", + "PERSISTENT": true + }, + { + "TARGET":"buffer6", + "PERSISTENT": true + }, + { + "TARGET":"buffer5", + "PERSISTENT": true + }, + { + "TARGET":"buffer4", + "PERSISTENT": true + }, + { + "TARGET":"buffer3", + "PERSISTENT": true + }, + { + "TARGET":"buffer2", + "PERSISTENT": true + }, + { + "TARGET":"buffer1", + "PERSISTENT": true + }, + { + + } + ] + +}*/ + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +void main() +{ + // first pass: read the "buffer7" into "buffer8" + // apply lag on each pass + // if this is the first pass, i'm going to read the position from the "lastRow" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastRow,vec2(0.5)); + // i'm only using the X and Y components, which are the X and Y offset (normalized) for the frame + if (inputRate == 0.0) { + srcPixel.x = 0.0; + srcPixel.y = 0.0; + } + else if (inputRate <= 1.0) { + srcPixel.x = (srcPixel.x) > 0.5 ? 0.0 : 1.0; + srcPixel.y = 0.0; + } + else { + srcPixel.x = srcPixel.x + 1.0 / inputRate + srcPixel.y; + if (srcPixel.x > 1.0) { + srcPixel.y = mod(srcPixel.x, 1.0); + srcPixel.x = 0.0; + } + } + gl_FragColor = srcPixel; + } + if (PASSINDEX == 1) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer8); + } + } + else if (PASSINDEX == 2) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + } + else if (PASSINDEX == 3) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + } + else if (PASSINDEX == 4) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + } + else if (PASSINDEX == 5) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + } + else if (PASSINDEX == 6) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + } + else if (PASSINDEX == 7) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + } + else if (PASSINDEX == 8) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + } + else if (PASSINDEX == 9) { + // Figure out which section I'm in and draw the appropriate buffer there + vec2 tex = isf_FragNormCoord; + vec4 color = vec4(0.0); + // figure out the "input delay shift" for this pixel... + float randomDelayShift = 0.0; + + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + // quantize the xy to the glitch_amount size + //xy = floor(xy / glitch_size) * glitch_size; + vec2 random; + + float local_glitch_size = glitch_size; + float random_offset = 0.0; + + if (randomize_size) { + random_offset = mod(rand(vec2(TIME,TIME)), 1.0); + local_glitch_size = random_offset * glitch_size; + } + + if (local_glitch_size > 0.0) { + random.x = rand(vec2(floor(random_offset + xy.y / local_glitch_size) * local_glitch_size, TIME)); + random.y = rand(vec2(floor(random_offset + xy.x / local_glitch_size) * local_glitch_size, TIME)); + } + else { + random.x = rand(vec2(xy.x, TIME)); + random.y = rand(vec2(xy.y, TIME)); + } + + // if doing a horizontal glitch do a random shift + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + randomDelayShift = clamp(random.x + random.y, 0.0, 2.0); + } + else if (random.x < glitch_horizontal) { + randomDelayShift = clamp(random.x + random.y, 0.0, 2.0); + } + else if (random.y < glitch_vertical) { + randomDelayShift = clamp(random.x + random.y, 0.0, 2.0); + } + + vec4 pixelBuffer = randomDelayShift * inputDelay * 9.0; + + if (randomize_zoom) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + float level = (random.x + random.y) / 3.0 + 0.90; + tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + else if (random.x < glitch_horizontal) { + float level = (random.x) / 2.0 + 0.95; + tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + else if (random.y < glitch_vertical) { + float level = (random.y) / 2.0 + 0.95; + tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + inputDelay.r * random.x, 1.0); + tex.y = mod(tex.y + inputDelay.r * random.y, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + inputDelay.r * random.x, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + inputDelay.r * random.y, 1.0); + } + // apply small random zoom too + } + + if (pixelBuffer.r < 1.0) { + color.r = IMG_NORM_PIXEL(inputImage, tex).r; + } + else if (pixelBuffer.r < 2.0) { + color.r = IMG_NORM_PIXEL(buffer1, tex).r; + } + else if (pixelBuffer.r < 3.0) { + color.r = IMG_NORM_PIXEL(buffer2, tex).r; + } + else if (pixelBuffer.r < 4.0) { + color.r = IMG_NORM_PIXEL(buffer3, tex).r; + } + else if (pixelBuffer.r < 5.0) { + color.r = IMG_NORM_PIXEL(buffer4, tex).r; + } + else if (pixelBuffer.r < 6.0) { + color.r = IMG_NORM_PIXEL(buffer5, tex).r; + } + else if (pixelBuffer.r < 7.0) { + color.r = IMG_NORM_PIXEL(buffer6, tex).r; + } + else if (pixelBuffer.r < 8.0) { + color.r = IMG_NORM_PIXEL(buffer7, tex).r; + } + else { + color.r = IMG_NORM_PIXEL(buffer8, tex).r; + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + random.x * inputDelay.g, 1.0); + tex.y = mod(tex.y + random.y * inputDelay.g, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + random.x * inputDelay.g, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + random.y * inputDelay.g, 1.0); + } + // apply small random zoom too + //float level = inputDelay.g * random.x / 5.0 + 0.9; + //tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + + if (pixelBuffer.g < 1.0) { + color.g = IMG_NORM_PIXEL(inputImage, tex).g; + } + else if (pixelBuffer.g < 2.0) { + color.g = IMG_NORM_PIXEL(buffer1, tex).g; + } + else if (pixelBuffer.g < 3.0) { + color.g = IMG_NORM_PIXEL(buffer2, tex).g; + } + else if (pixelBuffer.g < 4.0) { + color.g = IMG_NORM_PIXEL(buffer3, tex).g; + } + else if (pixelBuffer.g < 5.0) { + color.g = IMG_NORM_PIXEL(buffer4, tex).g; + } + else if (pixelBuffer.g < 6.0) { + color.g = IMG_NORM_PIXEL(buffer5, tex).g; + } + else if (pixelBuffer.g < 7.0) { + color.g = IMG_NORM_PIXEL(buffer6, tex).g; + } + else if (pixelBuffer.g < 8.0) { + color.g = IMG_NORM_PIXEL(buffer7, tex).g; + } + else { + color.g = IMG_NORM_PIXEL(buffer8, tex).g; + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + random.x * inputDelay.b, 1.0); + tex.y = mod(tex.y + random.y * inputDelay.b, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + random.x * inputDelay.b, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + random.y * inputDelay.b, 1.0); + } + // apply small random zoom too + //float level = inputDelay.b * random.x / 5.0 + 0.9; + //tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + + if (pixelBuffer.b < 1.0) { + color.b = IMG_NORM_PIXEL(inputImage, tex).b; + } + else if (pixelBuffer.b < 2.0) { + color.b = IMG_NORM_PIXEL(buffer1, tex).b; + } + else if (pixelBuffer.b < 3.0) { + color.b = IMG_NORM_PIXEL(buffer2, tex).b; + } + else if (pixelBuffer.b < 4.0) { + color.b = IMG_NORM_PIXEL(buffer3, tex).b; + } + else if (pixelBuffer.b < 5.0) { + color.b = IMG_NORM_PIXEL(buffer4, tex).b; + } + else if (pixelBuffer.b < 6.0) { + color.b = IMG_NORM_PIXEL(buffer5, tex).b; + } + else if (pixelBuffer.b < 7.0) { + color.b = IMG_NORM_PIXEL(buffer6, tex).b; + } + else if (pixelBuffer.b < 8.0) { + color.b = IMG_NORM_PIXEL(buffer7, tex).b; + } + else { + color.b = IMG_NORM_PIXEL(buffer8, tex).b; + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + random.x * inputDelay.a, 1.0); + tex.y = mod(tex.y + random.y * inputDelay.a, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + random.x * inputDelay.a, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + random.y * inputDelay.a, 1.0); + } + // apply small random zoom too + //float level = inputDelay.a * random.x / 5.0 + 0.9; + //tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + + if (pixelBuffer.a < 1.0) { + color.a = IMG_NORM_PIXEL(inputImage, tex).a; + } + else if (pixelBuffer.a < 2.0) { + color.a = IMG_NORM_PIXEL(buffer1, tex).a; + } + else if (pixelBuffer.a < 3.0) { + color.a = IMG_NORM_PIXEL(buffer2, tex).a; + } + else if (pixelBuffer.a < 4.0) { + color.a = IMG_NORM_PIXEL(buffer3, tex).a; + } + else if (pixelBuffer.a < 5.0) { + color.a = IMG_NORM_PIXEL(buffer4, tex).a; + } + else if (pixelBuffer.a < 6.0) { + color.a = IMG_NORM_PIXEL(buffer5, tex).a; + } + else if (pixelBuffer.a < 7.0) { + color.a = IMG_NORM_PIXEL(buffer6, tex).a; + } + else if (pixelBuffer.a < 8.0) { + color.a = IMG_NORM_PIXEL(buffer7, tex).a; + } + else { + color.a = IMG_NORM_PIXEL(buffer8, tex).a; + } + + gl_FragColor = color; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Toon.fs b/src/renderer/src/application/sample-modules/isf/Toon.fs new file mode 100644 index 000000000..bb436641d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Toon.fs @@ -0,0 +1,77 @@ + +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "normalEdgeThreshold", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.1, + "DEFAULT": 0.03 + }, + { + "NAME": "qLevel", + "TYPE": "float", + "MIN": 2.0, + "MAX": 64.0, + "DEFAULT": 32.0 + } + ] +}*/ + +// with help from https://github.com/neilmendoza/ofxPostProcessing/blob/master/src/ToonPass.cpp + + +vec3 getNormal(vec2 st){ + vec2 texcoord = clamp(st, 0.001, 0.999); + return IMG_NORM_PIXEL(inputImage,texcoord).rgb; +} + +void main(void){ + float dxtex = 1.0 / RENDERSIZE.x; + float dytex = 1.0 / RENDERSIZE.y; + + vec2 st = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + // access center pixel and 4 surrounded pixel + vec3 center = getNormal(st).rgb; + vec3 left = getNormal(st + vec2(dxtex, 0.0)).rgb; + vec3 right = getNormal(st + vec2(-dxtex, 0.0)).rgb; + vec3 up = getNormal(st + vec2(0.0, -dytex)).rgb; + vec3 down = getNormal(st + vec2(0.0, dytex)).rgb; + + // discrete Laplace operator + vec3 laplace = abs(-4.0*center + left + right + up + down); + // if one rgb-component of convolution result is over threshold => edge + vec4 line = IMG_NORM_PIXEL(inputImage, st); + if(laplace.r > normalEdgeThreshold + || laplace.g > normalEdgeThreshold + || laplace.b > normalEdgeThreshold){ + line = vec4(0.0, 0.0, 0.0, 1.0); // => color the pixel green + } else { + line = vec4(1.0, 1.0, 1.0, 1.0); // black + } + + //end Line; + //gl_FragColor = line; + + vec4 color = IMG_THIS_PIXEL(inputImage); + + // store previous alpha value + float alpha = color.a; + // quantize process: multiply by factor, round and divde by factor + color = floor((qLevel * color)) / qLevel; + // set fragment/pixel color + color.a = alpha; + + gl_FragColor = color * line; + +} + diff --git a/src/renderer/src/application/sample-modules/isf/Trail Mask.fs b/src/renderer/src/application/sample-modules/isf/Trail Mask.fs new file mode 100644 index 000000000..a48c817f9 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Trail Mask.fs @@ -0,0 +1,173 @@ +/*{ + "CATEGORIES": [ + "Glitch" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Pixels update only based on the masking image", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "LABEL": "mask image", + "NAME": "maskImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "LABEL": "mask size mode", + "LABELS": [ + "Fit", + "Fill", + "Stretch", + "Copy" + ], + "NAME": "maskSizingMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3 + ] + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": -1, + "NAME": "bright", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 4, + "MIN": -4, + "NAME": "contrast", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "NAME": "RGB_mode", + "TYPE": "bool" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "PERSISTENT": true, + "TARGET": "bufferVariableNameA" + } + ] +} +*/ + + + +//const vec4 lumcoeff = vec4(0.299, 0.587, 0.114, 0.0); +const vec4 lumcoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + +// 'a' and 'b' are rects (x and y are the originx, z and w are the width and height) +// 'm' is the sizing mode as described above (fit/fill/stretch/copy) +vec4 RectThatFitsRectInRect(vec4 a, vec4 b, int m); + + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + // If we're on the line, update, otherwise use the stale pixel + vec4 stalePixel = IMG_PIXEL(bufferVariableNameA,gl_FragCoord.xy); + + + // get the rect of the mask image after it's been resized according to the passed sizing mode. this is in pixel coords relative to the rendering space! + vec4 rectOfResizedMaskImage = RectThatFitsRectInRect(vec4(0.0, 0.0, _maskImage_imgRect.z, _maskImage_imgRect.w), vec4(0,0,RENDERSIZE.x,RENDERSIZE.y), maskSizingMode); + //vec4 rectOfResizedMaskImage = RectThatFitsRectInRect(vec4(0.0, 0.0, IMG_SIZE(maskImage).x, IMG_SIZE(maskImage).y), vec4(0,0,RENDERSIZE.x,RENDERSIZE.y), maskSizingMode); + // i know the pixel coords of this frag in the render space- convert this to NORMALIZED texture coords for the resized mask image + vec2 normMaskSrcCoord; + normMaskSrcCoord.x = (gl_FragCoord.x-rectOfResizedMaskImage.x)/rectOfResizedMaskImage.z; + normMaskSrcCoord.y = (gl_FragCoord.y-rectOfResizedMaskImage.y)/rectOfResizedMaskImage.w; + + // get the color of the pixel from the mask image for these normalized coords + vec4 tmpColorA = IMG_NORM_PIXEL(maskImage, normMaskSrcCoord); + + // apply bright/contrast to this pixel value + vec4 tmpColorB = tmpColorA + vec4(bright, bright, bright, 0.0); + tmpColorA.rgb = ((vec3(2.0) * (tmpColorB.rgb - vec3(0.5))) * vec3(contrast) / vec3(2.0)) + vec3(0.5); + tmpColorA.a = ((2.0 * (tmpColorB.a - 0.5)) * abs(contrast) / 2.0) + 0.5; + + + vec4 result; + if (RGB_mode) { + result.a = freshPixel.a; + result.rgb = mix(freshPixel.rgb, stalePixel.rgb, clamp(tmpColorA.rgb,0.0,1.0)); + } + // get the luminance of this pixel value: this will be the new alpha for the source pixel + // use the luminance to mix between the fresh pixel and the stale one + else { + float luminance = dot(tmpColorA,lumcoeff); + result = mix(freshPixel, stalePixel, clamp(luminance,0.0,1.0)); + } + + gl_FragColor = result; +} + + +// rect that fits 'a' in 'b' using sizing mode 'm' +vec4 RectThatFitsRectInRect(vec4 a, vec4 b, int m) { + float bAspect = b.z/b.w; + float aAspect = a.z/a.w; + if (aAspect==bAspect) { + return b; + } + vec4 returnMe = vec4(0.0); + // fit + if (m==0) { + // if the rect i'm trying to fit stuff *into* is wider than the rect i'm resizing + if (bAspect > aAspect) { + returnMe.w = b.w; + returnMe.z = returnMe.w * aAspect; + } + // else if the rect i'm resizing is wider than the rect it's going into + else if (bAspect < aAspect) { + returnMe.z = b.z; + returnMe.w = returnMe.z / aAspect; + } + else { + returnMe.z = b.z; + returnMe.w = b.w; + } + returnMe.x = (b.z-returnMe.z)/2.0+b.x; + returnMe.y = (b.w-returnMe.w)/2.0+b.y; + } + // fill + else if (m==1) { + // if the rect i'm trying to fit stuff *into* is wider than the rect i'm resizing + if (bAspect > aAspect) { + returnMe.z = b.z; + returnMe.w = returnMe.z / aAspect; + } + // else if the rect i'm resizing is wider than the rect it's going into + else if (bAspect < aAspect) { + returnMe.w = b.w; + returnMe.z = returnMe.w * aAspect; + } + else { + returnMe.z = b.z; + returnMe.w = b.w; + } + returnMe.x = (b.z-returnMe.z)/2.0+b.x; + returnMe.y = (b.w-returnMe.w)/2.0+b.y; + } + // stretch + else if (m==2) { + returnMe = vec4(b.x, b.y, b.z, b.w); + } + // copy + else if (m==3) { + returnMe.z = float(int(a.z)); + returnMe.w = float(int(a.w)); + returnMe.x = float(int((b.z-returnMe.z)/2.0+b.x)); + returnMe.y = float(int((b.w-returnMe.w)/2.0+b.y)); + } + return returnMe; +} diff --git a/src/renderer/src/application/sample-modules/isf/Trapezoid Distortion.fs b/src/renderer/src/application/sample-modules/isf/Trapezoid Distortion.fs new file mode 100644 index 000000000..f62d0d871 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Trapezoid Distortion.fs @@ -0,0 +1,58 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Warps the video into a trapezoid shape", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "LABEL": "Top Width", + "MAX": 1, + "MIN": 0, + "NAME": "topWidth", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Bottom Width", + "MAX": 1, + "MIN": 0, + "NAME": "bottomWidth", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "LABEL": "Height", + "MAX": 1, + "MIN": 0, + "NAME": "heightScale", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 loc = isf_FragNormCoord.xy; + if (heightScale > 0.0) { + float heightDivisor = 1.0 / heightScale; + loc.y = loc.y * heightDivisor + (1.0 - heightDivisor) / 2.0; + float currentLineWidth = mix(bottomWidth,topWidth,loc.y); + if (currentLineWidth > 0.0) { + float lwDivisor = 1.0 / currentLineWidth; + loc.x = loc.x * lwDivisor + (1.0 - lwDivisor) / 2.0; + + if ((loc.x >= 0.0)&&(loc.x <= 1.0)&&(loc.y >= 0.0)&&(loc.y <= 1.0)) { + inputPixelColor = IMG_NORM_PIXEL(inputImage,loc); + } + } + } + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Triangle Warp.fs b/src/renderer/src/application/sample-modules/isf/Triangle Warp.fs new file mode 100644 index 000000000..855c03e58 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Triangle Warp.fs @@ -0,0 +1,60 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Warps an image to fit in a triangle by fitting the height of the image to the height of a triangle", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": [ + 0.5, + 1 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "peakPoint", + "TYPE": "point2D" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "distortX", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 pt = isf_FragNormCoord; + float val = 0.0; + + if (pt.x < peakPoint.x) { + pt.x = pt.x * 0.5 / peakPoint.x; + val = 2.0 * pt.x * peakPoint.y; + } + else { + pt.x = 0.5 + 0.5 * (pt.x - peakPoint.x) / (1.0 - peakPoint.x); + val = (2.0 - 2.0 * pt.x) * peakPoint.y; + } + if (pt.y <= val) { + pt.x = mix(isf_FragNormCoord.x,pt.x,distortX); + pt.y /= val; + inputPixelColor = IMG_NORM_PIXEL(inputImage, pt); + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Triangle.fs b/src/renderer/src/application/sample-modules/isf/Triangle.fs new file mode 100644 index 000000000..9e905f007 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Triangle.fs @@ -0,0 +1,110 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "by Carter Rosenberg", + "INPUTS": [ + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt1", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0.5, + 1 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt2", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "pt3", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "NAME": "fillColor", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 0 + ], + "NAME": "bgColor", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + +// functions via http://stackoverflow.com/questions/2049582/how-to-determine-a-point-in-a-triangle + +float sign(vec2 p1, vec2 p2, vec2 p3) +{ + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); +} + +bool PointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3) +{ + bool b1, b2, b3; + + b1 = sign(pt, v1, v2) < 0.0; + b2 = sign(pt, v2, v3) < 0.0; + b3 = sign(pt, v3, v1) < 0.0; + + return ((b1 == b2) && (b2 == b3)); +} + + +void main() { + vec2 loc = isf_FragNormCoord; + vec4 outColor = vec4(0.0); + vec2 point1 = pt1; + vec2 point2 = pt2; + vec2 point3 = pt3; + + // determine if we are inside or outside of the triangle + + gl_FragColor = mix(bgColor, fillColor, float(PointInTriangle(loc, point1,point2,point3)));; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Triangles.fs b/src/renderer/src/application/sample-modules/isf/Triangles.fs new file mode 100644 index 000000000..8697feb60 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Triangles.fs @@ -0,0 +1,123 @@ + +/*{ + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Stylize" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "cell_size", + "TYPE": "float", + "MIN": 0.001, + "MAX": 0.5, + "DEFAULT": 0.125 + }, + { + "NAME": "style", + "VALUES": [ + 0, + 1 + ], + "LABELS": [ + "Squared", + "Diamond" + ], + "DEFAULT": 1, + "TYPE": "long" + } + ] +}*/ + + + +void main() +{ +// CALCULATE EDGES OF CURRENT CELL + // At 0.0 just do a pass-thru + if (cell_size == 0.0) { + gl_FragColor = IMG_THIS_PIXEL(inputImage); + } + else { + // Position of current pixel + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + + // Left and right of tile + float CellWidth = cell_size; + float CellHeight = cell_size; + + CellHeight = cell_size * RENDERSIZE.x / RENDERSIZE.y; + + float x1 = floor(xy.x / CellWidth)*CellWidth; + float x2 = clamp((ceil(xy.x / CellWidth)*CellWidth), 0.0, 1.0); + // Top and bottom of tile + float y1 = floor(xy.y / CellHeight)*CellHeight; + float y2 = clamp((ceil(xy.y / CellHeight)*CellHeight), 0.0, 1.0); + + // get the normalized local coords in the cell + float x = (xy.x-x1) / CellWidth; + float y = (xy.y-y1) / CellHeight; + vec4 avgClr = vec4(0.0); + + // style 0, two right triangles making a square + if (style == 0) { + // if above the center line... + if (x < y) { + // Average bottom left, top left, center and top right pixels + vec4 avgL = (IMG_NORM_PIXEL(inputImage, vec2(x1, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x1, y2)))) / 2.0; + vec4 avgR = IMG_NORM_PIXEL(inputImage, vec2(x2, y2)); + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + avgClr = (avgL+avgR+avgC) / 3.0; + } + else { + // Average bottom right, bottom left, center and top right pixels + vec4 avgR = (IMG_NORM_PIXEL(inputImage, vec2(x2, y1))+(IMG_NORM_PIXEL(inputImage, vec2(x2, y2)))) / 2.0; + vec4 avgL = IMG_NORM_PIXEL(inputImage, vec2(x1, y1)); + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + avgClr = (avgL+avgR+avgC) / 3.0; + } + } + // style 1, four triangles making a square + else { + // if above the B2T center line and below the T2B center line... + if ((x > y)&&(x < 1.0 - y)) { + // Average bottom left, bottom right, center + vec4 avgL = IMG_NORM_PIXEL(inputImage, vec2(x1, y1)); + vec4 avgR = IMG_NORM_PIXEL(inputImage, vec2(x2, y1)); + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + avgClr = (avgL+avgR+avgC) / 3.0; + } + else if ((x < y)&&(x < 1.0 - y)) { + // Average bottom left, top left, center + vec4 avgL = IMG_NORM_PIXEL(inputImage, vec2(x1, y1)); + vec4 avgR = IMG_NORM_PIXEL(inputImage, vec2(x1, y2)); + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + avgClr = (avgL+avgR+avgC) / 3.0; + } + else if ((x > 1.0 - y)&&(x < y)) { + // Average top left, top right, center + vec4 avgL = IMG_NORM_PIXEL(inputImage, vec2(x1, y2)); + vec4 avgR = IMG_NORM_PIXEL(inputImage, vec2(x2, y2)); + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + avgClr = (avgL+avgR+avgC) / 3.0; + //avgClr = vec4(0.0,1.0,0.0,1.0); + } + else { + // Average top right, bottom right, center + vec4 avgL = IMG_NORM_PIXEL(inputImage, vec2(x2, y1)); + vec4 avgR = IMG_NORM_PIXEL(inputImage, vec2(x2, y2)); + vec4 avgC = IMG_NORM_PIXEL(inputImage, vec2(x1+(CellWidth/2.0), y2+(CellHeight/2.0))); // Average the averages + centre + avgClr = (avgL+avgR+avgC) / 3.0; + //avgClr = vec4(0.0,0.0,1.0,1.0); + } + } + + gl_FragColor = avgClr; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Trio Tone.fs b/src/renderer/src/application/sample-modules/isf/Trio Tone.fs new file mode 100644 index 000000000..14ad0ea28 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Trio Tone.fs @@ -0,0 +1,95 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "darkColor", + "TYPE": "color", + "DEFAULT": [ + 0.00, + 0.164, + 1.0, + 1.0 + ] + }, + { + "NAME": "midColor", + "TYPE": "color", + "DEFAULT": [ + 0.00, + 1.00, + 0.00, + 1.0 + ] + }, + { + "NAME": "brightColor", + "TYPE": "color", + "DEFAULT": [ + 1.0, + 0.00, + 0.00, + 1.0 + ] + } + ] +}*/ + + +// partly adapted from http://coding-experiments.blogspot.com/2010/10/thermal-vision-pixel-shader.html + + +void main () { + vec4 pixcol = IMG_THIS_PIXEL(inputImage); + /* + // this method doesn't work nicely on certain gpu / os releases due to arrays? + vec4 colors[4]; + colors[0] = vec4(0.0,0.0,0.0,1.0); + colors[1] = darkColor; + colors[2] = midColor; + colors[3] = brightColor; + const vec4 lumacoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + float lum = dot(pixcol, lumacoeff); + //float lum = (pixcol.r+pixcol.g+pixcol.b)/3.; + int ix = 0; + + if (lum > 0.66) { + ix = 2; + } + else if (lum > 0.33) { + ix = 1; + } + + vec4 thermal = mix(colors[ix],colors[ix+1],(lum-float(ix)*0.33)/0.33); + */ + vec4 color1 = vec4(0.0,0.0,0.0,1.0); + vec4 color2 = darkColor; + int ix = 0; + + const vec4 lumacoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + float lum = dot(pixcol, lumacoeff); + + + if (lum > 0.66) { + color1 = midColor; + color2 = brightColor; + ix = 2; + } + else if (lum > 0.33) { + color1 = darkColor; + color2 = midColor; + ix = 1; + } + + vec4 thermal = mix(color1,color2,(lum-float(ix)*0.33)/0.33); + + gl_FragColor = vec4(thermal.rgb, pixcol.a); + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Triple Rotate.fs b/src/renderer/src/application/sample-modules/isf/Triple Rotate.fs new file mode 100644 index 000000000..33306d434 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Triple Rotate.fs @@ -0,0 +1,96 @@ +/*{ + "CATEGORIES": [ + "Geometry Adjustment" + ], + "CREDIT": "by VIDVOX", + "DESCRIPTION": "Performs three different rotations", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle1", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle2", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle3", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "angle4", + "TYPE": "float" + }, + { + "DEFAULT": 0.15, + "MAX": 1, + "MIN": 0, + "NAME": "radius1", + "TYPE": "float" + }, + { + "DEFAULT": 0.15, + "MAX": 1, + "MIN": 0, + "NAME": "radius2", + "TYPE": "float" + }, + { + "DEFAULT": 0.15, + "MAX": 1, + "MIN": 0, + "NAME": "radius3", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +const float pi = 3.14159265359; + +void main() { + // 'loc' is the location in pixels of this vertex. we're going to convert this to polar coordinates (radius/angle) + vec2 loc = RENDERSIZE * vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + // 'r' is the radius- the distance in pixels from 'loc' to the center of the rendering space + float r = distance(RENDERSIZE/2.0, loc); + // 'a' is the angle of the line segment from the center to loc is rotated + float a = atan ((loc.y-RENDERSIZE.y/2.0),(loc.x-RENDERSIZE.x/2.0)); + + // now modify 'a', and convert the modified polar coords (radius/angle) back to cartesian coords (x/y pixels) + float angle = angle1; + float minSide = min(RENDERSIZE.x,RENDERSIZE.y); + if (r > (radius1 + radius2 + radius3)*minSide) + angle = angle4; + else if (r > (radius1 + radius2)*minSide) + angle = angle3; + else if (r > radius1 * minSide) + angle = angle2; + loc.x = r * cos(a + 2.0 * pi * angle); + loc.y = r * sin(a + 2.0 * pi * angle); + + loc = loc / RENDERSIZE + vec2(0.5); + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Truchet Tile.fs b/src/renderer/src/application/sample-modules/isf/Truchet Tile.fs new file mode 100644 index 000000000..3c972764d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Truchet Tile.fs @@ -0,0 +1,110 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "pjkarlik", + "DESCRIPTION": "Creates a Truchet Tile pattern", + "INPUTS": [ + { + "DEFAULT": 0.1, + "LABEL": "Size", + "MAX": 1, + "MIN": 0, + "NAME": "tSize", + "TYPE": "float" + }, + { + "DEFAULT": 0.35, + "LABEL": "Noise Seed", + "MAX": 1, + "MIN": 0, + "NAME": "nSeed", + "TYPE": "float" + }, + { + "DEFAULT": [ + 1, + 1, + 1, + 1 + ], + "LABEL": "Color 1", + "NAME": "color1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "LABEL": "Color 2", + "NAME": "color2", + "TYPE": "color" + }, + { + "DEFAULT": 0, + "NAME": "lineMode", + "TYPE": "bool" + } + ], + "ISFVSN": "2" +} +*/ + + + +// adapted from https://github.com/pjkarlik/TruchetTiles/blob/master/src/shader/truchet/fragmentShader.js + + +#define PI 3.14159265358979323846264 + +vec2 hash2(vec2 p) { + vec2 o = (p+0.5)/256.0; + return o; +} +float goldNoise(vec2 coord, float seed){ + float phi = 1.61803398874989484820459 * 00000.1; + float pi2 = PI * 00000.1; + float sq2 = 1.41421356237309504880169 * 10000.0; + float temp = fract( + sin( + dot( + coord*(seed+phi), vec2(phi, pi2) + ) + ) * sq2 + ); + return temp; +} +vec3 pattern(vec2 uv) { + vec2 grid = floor(uv); + vec2 subuv = fract(uv); + float mult = 0.5; + float dnoise = goldNoise(grid, nSeed); + vec2 rand = hash2(grid); + float shade = 0.; + float df; + float check = dnoise; + if( check <= .25 ) { + df = subuv.x - subuv.y; // tl + } else if( check <= .5 ) { + df = 1. - subuv.y - subuv.x; + } else if( check <= .75 ) { + df = subuv.y - subuv.x; + } else if( check <= 1. ) { + df = subuv.y - 1. + subuv.x; + } + shade = smoothstep(.0, -.02, df); + if (lineMode) + shade += smoothstep(.02, .04, df); + return vec3( shade ); +} +void main() { + float fScale = (tSize == 0.0) ? max(RENDERSIZE.x,RENDERSIZE.y) : 1.0 / tSize; + vec2 uv = (gl_FragCoord.xy - 0.5 * RENDERSIZE.xy) / min(RENDERSIZE.y, RENDERSIZE.x); + uv *= fScale; + vec3 colour = pattern(uv); + vec4 returnMe = (colour.r == 1.0) ? color1 : color2; + gl_FragColor = returnMe; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Twirl.fs b/src/renderer/src/application/sample-modules/isf/Twirl.fs new file mode 100644 index 000000000..79115a2cc --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Twirl.fs @@ -0,0 +1,75 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 5, + "MAX": 1, + "MIN": 0, + "NAME": "radius", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 10, + "MIN": -10, + "NAME": "amount", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + +const float pi = 3.14159265359; + + +void main (void) +{ + vec2 uv = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 texSize = RENDERSIZE; + vec2 tc = uv * texSize; + float radius_sized = radius * max(RENDERSIZE.x,RENDERSIZE.y); + tc -= (center * RENDERSIZE); + float dist = length(tc); + if (dist < radius_sized) { + float percent = (radius_sized - dist) / radius_sized; + float theta = percent * percent * amount * 2.0 * pi; + float s = sin(theta); + float c = cos(theta); + tc = vec2(dot(tc, vec2(c, -s)), dot(tc, vec2(s, c))); + } + tc += (center * RENDERSIZE); + vec2 loc = tc / texSize; + vec4 color = IMG_NORM_PIXEL(inputImage, loc); + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = color; + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/UltimateFlame.fs b/src/renderer/src/application/sample-modules/isf/UltimateFlame.fs new file mode 100644 index 000000000..d9e543066 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/UltimateFlame.fs @@ -0,0 +1,303 @@ +/* + { + "CREDIT": "by mojovideotech", + "DESCRIPTION": "", + "CATEGORIES": [ + "generator", + "flame", + "fire", + "3d noise" + ], + "INPUTS": [ + { + "NAME" : "bgColorIn", + "TYPE" : "color", + "DEFAULT" : [ 0.0, 0.0, 0.0 ] + }, + { + "NAME" : "center", + "TYPE" : "point2D", + "DEFAULT" : [ 0.0, 0.0 ], + "MAX" : [ 1.0, 1.0 ], + "MIN" : [ -1.0, -1.0 ] + }, + { + "NAME" : "scale", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : 0.01, + "MAX" : 2.0 + }, + { + "NAME" : "rate", + "TYPE" : "float", + "DEFAULT" : 1.75, + "MIN" : 0.0, + "MAX" : 3.0 + }, + { + "NAME" : "seed1", + "TYPE" : "float", + "DEFAULT" : 111, + "MIN" : 55, + "MAX" : 233 + }, + { + "NAME" : "seed2", + "TYPE" : "float", + "DEFAULT" : 277, + "MIN" : 98, + "MAX" : 337 + }, + { + "NAME" : "seed3", + "TYPE" : "float", + "DEFAULT" : 497, + "MIN" : 301, + "MAX" : 579 + }, + + { + "NAME" : "freq", + "TYPE" : "float", + "DEFAULT" : 1.5, + "MIN" : 0.1, + "MAX" : 3.0 + }, + { + "NAME" : "flicker", + "TYPE" : "float", + "DEFAULT" : 5.0, + "MIN" : 0.0, + "MAX" : 50.0 + }, + { + "NAME" : "intensity", + "TYPE" : "float", + "DEFAULT" : 0.15, + "MIN" : -0.33, + "MAX" : 2.0 + }, + { + "NAME" : "light", + "TYPE" : "float", + "DEFAULT" : 0.45, + "MIN" : 0.0, + "MAX" : 0.5 + }, + { + "NAME" : "contours", + "TYPE": "float", + "DEFAULT" : 1.05, + "MIN" : 0.0, + "MAX" : 2.0 + }, + { + "NAME" : "bottomedges", + "TYPE" : "float", + "DEFAULT" : 0.05, + "MIN" : 0.0, + "MAX" : 0.667 + }, + { + "NAME" : "topedges", + "TYPE" : "float", + "DEFAULT" : 0.45, + "MIN" : 0.125, + "MAX" : 1.0 + }, + { + "NAME" : "depth", + "TYPE" : "float", + "DEFAULT" : 100.0, + "MIN" : 5.0, + "MAX" : 250.0 + }, + { + "NAME" : "expand", + "TYPE": "float", + "DEFAULT" : 0.8, + "MIN" : 0.1, + "MAX" : 5.0 + }, + { + "NAME" : "cutoff", + "TYPE": "float", + "DEFAULT" : 8.0, + "MIN" : 6.0, + "MAX" : 10.0 + }, + { + "NAME" : "wave", + "TYPE": "float", + "DEFAULT" : 0.15, + "MIN" : 0.1, + "MAX" : 2.0 + }, + { + "NAME" : "fractnoise", + "TYPE": "float", + "DEFAULT" : 0.33, + "MIN" : 0.0, + "MAX" : 1.0 + }, + { + "NAME" : "multiplier", + "TYPE": "float", + "DEFAULT" : 2.0, + "MIN" : 1.0, + "MAX" : 4.9 + }, + { + "NAME": "style", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ], + "LABELS": [ + "EightBit", + "PhotoReal", + "OpArt" + ], + "DEFAULT": 1 + } + ] +} +*/ + +//////////////////////////////////////////////////////////// +// UltimateFlame by mojovideotech +// +// based on : +// The Blue Flame by Hadyn Lander +// shadertoy.com/\lsjcRt +// +// 3D noise from Nikita Miropolskiy +// shadertoy.com/\XsX3zB +// +// License: +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +//////////////////////////////////////////////////////////// + + + +#define pi 3.141592653589793 // pi + +vec3 random3(vec3 c) { + float j = 4231.0*sin(dot(c,vec3(seed1, seed2, seed3))); + vec3 k; + k.z = fract(seed1*j); + j *= .5; + k.x = fract(seed2*j); + j *= .25; + k.y = fract(seed3*j); + return k-0.5; +} + +const float F3 = 0.3333333; +const float G3 = 0.1666667; + +float simplex3d(vec3 p) { + vec3 s = floor(p + dot(p, vec3(F3))); + vec3 x = p - s + dot(s, vec3(G3)); + vec3 e = step(vec3(0.0), x - x.yzx); + vec3 i1 = e*(1.0 - e.zxy); + vec3 i2 = 1.0 - e.zxy*(1.0 - e); + vec3 x1 = x - i1 + G3; + vec3 x2 = x - i2 + 2.0*G3; + vec3 x3 = x - 1.0 + 3.0*G3; + vec4 w, d; + w.x = dot(x, x); + w.y = dot(x1, x1); + w.z = dot(x2, x2); + w.w = dot(x3, x3); + w = max(0.6 - w, 0.0); + d.x = dot(random3(s), x); + d.y = dot(random3(s + i1), x1); + d.z = dot(random3(s + i2), x2); + d.w = dot(random3(s + 1.0), x3); + w *= w; + w *= w; + d *= w; + return dot(d, vec4(depth)); +} + +const mat3 rot1 = mat3(-0.37, 0.36, 0.85,-0.14,-0.93, 0.34,0.92, 0.01,0.4); +const mat3 rot2 = mat3(-0.55,-0.39, 0.74, 0.33,-0.91,-0.24,0.77, 0.12,0.63); +const mat3 rot3 = mat3(-0.71, 0.52,-0.47,-0.08,-0.72,-0.68,-0.7,-0.45,0.56); + +float simplex3d_fractal(vec3 n) { + return 0.5333333*simplex3d(n*rot1) + +0.2666667*simplex3d(2.0*n*rot2) + +0.1333333*simplex3d(4.0*n*rot3) + +0.0666667*simplex3d(8.0*n); +} + +void main() +{ + vec3 finalColor, bgColor, hColor, fColor; + float noise, value, edge, m, v, r, h, o; + float TT = 28.22 + TIME * rate; + float nf = 1.0/freq; + vec2 pos = gl_FragCoord.xy / RENDERSIZE.y; + vec2 wf = vec2(0.0); + float aspect = RENDERSIZE.x/RENDERSIZE.y; + vec2 poc = pos-vec2(0.5*aspect, 0.5) - center; + poc/=scale; + poc.x /= expand; + float pob = 0.5*(poc.y+1.0); + wf.x += pob*sin(4.0*poc.y-4.0*TT); + wf.y += 0.1*pob*sin(4.0*poc.x-1.561*TT); + poc += wave*wf; + poc.x += poc.x / (1.0-(poc.y)); + m = 1.0-pow(1.0-clamp(1.0-length(poc), 0.0, 1.0), 10.1-cutoff); + vec3 p3 = nf*0.25*vec3(pos.x, pos.y, 0.0) + vec3(0.0, -TT*0.1, TT*0.025); + noise = mix(simplex3d(p3*16.0*floor(multiplier)),simplex3d_fractal(p3*8.0*floor(multiplier)),fractnoise); + noise = 0.5 + 0.5*noise; + value = (m*noise)+intensity*m; + + if(style == 0) + { + edge = mix(bottomedges, topedges, pow(0.5*(poc.y+1.0), 1.2) ); + v = smoothstep(edge,edge+0.01, value); + v = mix(0.5*v, 1.0, smoothstep(1.5*edge,1.5*edge+0.01, value)); + v = mix(0.5*v, 1.0, smoothstep(3.0*edge,3.0*edge+0.01, value)); + bgColor = vec3(0.1,0.0,0.2); + finalColor = mix(bgColor, vec3(1.1,0.5,0.0), v); + } + else if(style == 1) + { + edge = mix(bottomedges, topedges, pow(0.5*(poc.y+1.0), 1.2) ); + v = smoothstep(edge,edge+0.1, value); + h = light+.5-clamp(value-edge, 0.0 , 1.0); + p3 = nf*0.1*vec3(pos.x, pos.y, 0.0) + vec3(0.0, -TT*0.01, TT*0.025); + noise = simplex3d(p3*32.0); + noise = 0.5 + 0.5*noise; + r = mix(h, noise, 0.65); + r = 0.5*sin(6.0*pi*(1.0-pow(1.0-r,1.8)) - 0.5*pi)+0.5; + o = smoothstep(0.95, 1.0, pow(r, 8.0)); + o = mix(o, 0.0, (2.1-contours)-noise); + h = max(o, h); + h = pow(h, 2.0); + hColor = mix(vec3(1.0,0.4,0.0), vec3(2.0,0.6,0.0), pos.y); + hColor += vec3(0.9,0.4,0.0) * pow(sin(TIME*flicker), 4.0); + fColor = mix(vec3(0.2,0.2,0.2), vec3(1.0,0.05,0.05), pos.y); + finalColor = hColor*(v*h); + finalColor += fColor*v; + bgColor = bgColorIn.xyz; + finalColor += bgColor; + } + else + { + edge = mix(bottomedges, topedges, pow(0.5*(poc.y+1.0), 1.2) ); + v = smoothstep(edge,edge+0.01, value); + r = 0.5*sin(1.0*pi*(value/edge) + 0.5*pi)+0.5; + v = 1.0-smoothstep(0.5,0.6, 1.0-r); + finalColor = vec3(1.0,1.0,1.0)*v; + } + + gl_FragColor = vec4(finalColor,1.0); + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/UltimateSpiral.fs b/src/renderer/src/application/sample-modules/isf/UltimateSpiral.fs new file mode 100644 index 000000000..2941da64a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/UltimateSpiral.fs @@ -0,0 +1,113 @@ +/*{ + "CREDIT": "by mojovideotech", + "DESCRIPTION": "", + "CATEGORIES": [ + "generator", + "spiral" + ], + "INPUTS": [ + { + "NAME": "shifth", + "TYPE": "float", + "DEFAULT": 0, + "MIN": -3, + "MAX": 3 + }, + { + "NAME": "shiftv", + "TYPE": "float", + "DEFAULT": 0, + "MIN": -2, + "MAX": 2 + }, + { + "NAME": "rate", + "TYPE": "float", + "DEFAULT": 16, + "MIN": 0.1, + "MAX": 24 + }, + { + "NAME": "density", + "TYPE": "float", + "DEFAULT": 64, + "MIN": 6, + "MAX": 256 + }, + { + "NAME": "sectors", + "TYPE": "float", + "DEFAULT": 64, + "MIN": 1, + "MAX": 256 + }, + { + "NAME": "shape", + "TYPE": "float", + "DEFAULT": 32, + "MIN": 16, + "MAX": 256 + }, + { + "NAME": "smooth", + "TYPE": "float", + "DEFAULT": 0.05, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "vanishingpoint", + "TYPE": "float", + "DEFAULT": 0, + "MIN": 0, + "MAX": 2 + } + ] +}*/ + + +//////////////////////////////////////////////////////////// +// UltimateSpiral by mojovideotech +// +// Creative Commons Attribution-NonCommercial-ShareAlike 3.0 +//////////////////////////////////////////////////////////// + +#define AA 2.0 + +float cell (float t, float w, float p) { + return min (step (t-w/2.0,p), 1.0 - step (t+w/2.0,p)); +} + +mat2 rotate (float o) { + float c = cos (o); + float s = sin (o); + return mat2 (-s, c, c, s); +} + +vec4 spiral (vec2 uv) { + float f = length (uv) * density; + float g = atan (uv.y, uv.x); + uv *= rotate (TIME*rate); + uv *= sin (g*floor(sectors)); + uv *= rotate (f); + return mix (vec4 (0.99), vec4 (0.0), min ( + step (smooth, uv.x), + cell (vanishingpoint, f/shape, uv.y))); +} + +void main ( void ) { + float unit = 1.0/min (RENDERSIZE.x, RENDERSIZE.y); + vec2 uv = (2.0*gl_FragCoord.xy - RENDERSIZE.xy) * unit; + vec4 col = vec4 (0.0); + for (float y = 0.0; y < AA; ++y) { + for (float x = 0.0; x < AA; ++x) { + vec2 c = vec2 ( + (x-AA/2.0)*(unit/AA)+shifth, + (y-AA/2.0)*(unit/AA)+shiftv); + col += spiral (uv + c) / (AA*AA); + } + } + gl_FragColor = col; + + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Undulating Burn Out.fs b/src/renderer/src/application/sample-modules/isf/Undulating Burn Out.fs new file mode 100644 index 000000000..9e664a24f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Undulating Burn Out.fs @@ -0,0 +1,121 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/undulatingBurnOut.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0, + 0, + 0, + 1 + ], + "NAME": "color", + "TYPE": "color" + }, + { + "DEFAULT": 0.03, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// License: MIT +// Author: pthrasher +// adapted by gre from https://gist.github.com/pthrasher/8e6226b215548ba12734 + + +const float M_PI = 3.14159265358979323846; + +float quadraticInOut(float t) { + float p = 2.0 * t * t; + return t < 0.5 ? p : -p + (4.0 * t) - 1.0; +} + +float getGradient(float r, float dist) { + float d = r - dist; + return mix( + smoothstep(-smoothness, 0.0, r - dist * (1.0 + smoothness)), + -1.0 - step(0.005, d), + step(-0.005, d) * step(d, 0.01) + ); +} + +float getWave(vec2 p){ + vec2 _p = p - center; // offset from center + float rads = atan(_p.y, _p.x); + float degs = degrees(rads) + 180.0; + vec2 range = vec2(0.0, M_PI * 30.0); + vec2 domain = vec2(0.0, 360.0); + float ratio = (M_PI * 30.0) / 360.0; + degs = degs * ratio; + float x = progress; + float magnitude = mix(0.02, 0.09, smoothstep(0.0, 1.0, x)); + float offset = mix(40.0, 30.0, smoothstep(0.0, 1.0, x)); + float ease_degs = quadraticInOut(sin(degs)); + float deg_wave_pos = (ease_degs * magnitude) * sin(x * offset); + return x + deg_wave_pos; +} + +vec4 transition(vec2 p) { + float dist = distance(center, p); + float m = getGradient(getWave(p), dist); + vec4 cfrom = getFromColor(p); + vec4 cto = getToColor(p); + return mix(mix(cfrom, cto, m), mix(cfrom, color, 0.75), step(m, -2.0)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Unsharp Mask.fs b/src/renderer/src/application/sample-modules/isf/Unsharp Mask.fs new file mode 100644 index 000000000..743d935b8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Unsharp Mask.fs @@ -0,0 +1,66 @@ +/*{ + "CREDIT": "by VIDVOX", + "ISFVSN": "2", + "CATEGORIES": [ + "Sharpen" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "intensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 10.0, + "DEFAULT": 1.0 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +in vec2 left_coord; +in vec2 right_coord; +in vec2 above_coord; +in vec2 below_coord; + +in vec2 lefta_coord; +in vec2 righta_coord; +in vec2 leftb_coord; +in vec2 rightb_coord; +#endif + +float gray(vec4 n) +{ + return (n.r + n.g + n.b)/3.0; +} + +void main() +{ + + vec4 color = IMG_THIS_PIXEL(inputImage); + vec4 colorL = IMG_NORM_PIXEL(inputImage, left_coord); + vec4 colorR = IMG_NORM_PIXEL(inputImage, right_coord); + vec4 colorA = IMG_NORM_PIXEL(inputImage, above_coord); + vec4 colorB = IMG_NORM_PIXEL(inputImage, below_coord); + + vec4 colorLA = IMG_NORM_PIXEL(inputImage, lefta_coord); + vec4 colorRA = IMG_NORM_PIXEL(inputImage, righta_coord); + vec4 colorLB = IMG_NORM_PIXEL(inputImage, leftb_coord); + vec4 colorRB = IMG_NORM_PIXEL(inputImage, rightb_coord); + + vec4 final = color + intensity * (8.0*color - colorL - colorR - colorA - colorB - colorLA - colorRA - colorLB - colorRB); + + gl_FragColor = final; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Unsharp Mask.vs b/src/renderer/src/application/sample-modules/isf/Unsharp Mask.vs new file mode 100644 index 000000000..8f2188f5d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Unsharp Mask.vs @@ -0,0 +1,39 @@ +#if __VERSION__ <= 120 +varying vec2 left_coord; +varying vec2 right_coord; +varying vec2 above_coord; +varying vec2 below_coord; + +varying vec2 lefta_coord; +varying vec2 righta_coord; +varying vec2 leftb_coord; +varying vec2 rightb_coord; +#else +out vec2 left_coord; +out vec2 right_coord; +out vec2 above_coord; +out vec2 below_coord; + +out vec2 lefta_coord; +out vec2 righta_coord; +out vec2 leftb_coord; +out vec2 rightb_coord; +#endif + + +void main() +{ + isf_vertShaderInit(); + vec2 texc = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + vec2 d = 1.0/RENDERSIZE; + + left_coord = clamp(vec2(texc.xy + vec2(-d.x , 0)),0.0,1.0); + right_coord = clamp(vec2(texc.xy + vec2(d.x , 0)),0.0,1.0); + above_coord = clamp(vec2(texc.xy + vec2(0,d.y)),0.0,1.0); + below_coord = clamp(vec2(texc.xy + vec2(0,-d.y)),0.0,1.0); + + lefta_coord = clamp(vec2(texc.xy + vec2(-d.x , d.x)),0.0,1.0); + righta_coord = clamp(vec2(texc.xy + vec2(d.x , d.x)),0.0,1.0); + leftb_coord = clamp(vec2(texc.xy + vec2(-d.x , -d.x)),0.0,1.0); + rightb_coord = clamp(vec2(texc.xy + vec2(d.x , -d.x)),0.0,1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/VHS Glitch.fs b/src/renderer/src/application/sample-modules/isf/VHS Glitch.fs new file mode 100644 index 000000000..501c587c1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/VHS Glitch.fs @@ -0,0 +1,214 @@ +/*{ + "CATEGORIES": [ + "Glitch", + "Retro" + ], + "CREDIT": "David Lublin, original by Staffan Widegarn Åhlvik", + "DESCRIPTION": "VHS Glitch Style", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "NAME": "autoScan", + "TYPE": "bool" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "xScanline", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "xScanline2", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "yScanline", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "xScanlineSize", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": 0, + "NAME": "xScanlineSize2", + "TYPE": "float" + }, + { + "DEFAULT": 0.25, + "MAX": 1, + "MIN": -1, + "NAME": "yScanlineAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 3, + "MIN": 0, + "NAME": "grainLevel", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "NAME": "scanFollow", + "TYPE": "bool" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0, + "NAME": "analogDistort", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 3, + "MIN": 0, + "NAME": "bleedAmount", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "bleedDistort", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": 0, + "NAME": "bleedRange", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.8, + 0, + 0.4, + 1 + ], + "NAME": "colorBleedL", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0, + 0.5, + 0.75, + 1 + ], + "NAME": "colorBleedC", + "TYPE": "color" + }, + { + "DEFAULT": [ + 0.8, + 0, + 0.4, + 1 + ], + "NAME": "colorBleedR", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + + + +// Based on https://github.com/staffantan/unity-vhsglitch +// Converted by David Lublin / VIDVOX + + +const float tau = 6.28318530718; + + +float rand(vec3 co){ + return abs(mod(sin( dot(co.xyz ,vec3(12.9898,78.233,45.5432) )) * 43758.5453, 1.0)); +} + +void main() { + float actualXLine = (!autoScan) ? xScanline : mod(xScanline + ((1.0+sin(0.34*TIME))/2.0 + (1.0+sin(TIME))/3.0 + (1.0+cos(2.1*TIME))/3.0 + (1.0+cos(0.027*TIME))/2.0)/3.5,1.0); + float actualXLineWidth = (!autoScan) ? xScanlineSize : xScanlineSize + ((1.0+sin(1.2*TIME))/2.0 + (1.0+cos(3.91*TIME))/3.0 + (1.0+cos(0.014*TIME))/2.0)/3.5; + vec2 loc = isf_FragNormCoord; + vec4 vhs = IMG_NORM_PIXEL(inputImage, loc); + float dx = 1.0+actualXLineWidth/25.0-abs(distance(loc.y, actualXLine)); + float dx2 = 1.0+xScanlineSize2/10.0-abs(distance(loc.y, xScanline2)); + float dy = (1.0-abs(distance(loc.y, yScanline))); + if (autoScan) + dy = (1.0-abs(distance(loc.y, mod(yScanline+TIME,1.0)))); + + dy = (dy > 0.5) ? 2.0 * dy : 2.0 * (1.0 - dy); + + float rX = (scanFollow) ? rand(vec3(dy,actualXLine,analogDistort)) : rand(vec3(dy,bleedAmount,analogDistort)); + float xTime = (actualXLine > 0.5) ? 2.0 * actualXLine : 2.0 * (1.0 - actualXLine); + + loc.x += yScanlineAmount * dy * 0.025 + analogDistort * rX/(RENDERSIZE.x/2.0); + + if(dx2 > 1.0 - xScanlineSize2 / 10.0) { + float rX2 = (dy * rand(vec3(dy,dx2,dx+TIME)) + dx2) / 4.0; + float distortAmount = analogDistort * (sin(rX * tau / dx2) + cos(rX * tau * 0.78 / dx2)) / 10.0; + //loc.y = xScanline2; + //loc.x += (1.0 + distortAmount * sin(tau * (loc.x) / rX2 ) - 1.0) / 15.0; + loc.x += (1.0 + distortAmount * sin(tau * (loc.x) / rX2 ) - 1.0) / 15.0; + } + if(dx > 1.0 - actualXLineWidth / 25.0) + loc.y = actualXLine; + + loc.x = mod(loc.x,1.0); + loc.y = mod(loc.y,1.0); + + vec4 c = IMG_NORM_PIXEL(inputImage, loc); + float x = (loc.x*320.0)/320.0; + float y = (loc.y*240.0)/240.0; + float bleed = 0.0; + + if (scanFollow) + c -= rand(vec3(x, y, xTime)) * xTime / (5.0-grainLevel); + else + c -= rand(vec3(x, y, bleedAmount)) * (bleedAmount/20.0) / (5.0-grainLevel); + + if (bleedAmount > 0.0) { + IMG_NORM_PIXEL(inputImage, loc + vec2(0.01, 0)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(0.02, 0)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(0.01, 0.01)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(-0.02, 0.02)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(0.0, -0.03)).r; + bleed /= 6.0; + bleed *= bleedAmount; + } + + if (bleed > 0.1){ + float bleedFreq = 1.0; + float bleedX = 0.0; + if (autoScan) + bleedX = x + bleedDistort * (yScanlineAmount + (1.5 + cos(TIME / 13.0 + tau*(bleedDistort+(1.0-loc.y))))/2.0) * sin((TIME / 9.0 + bleedDistort) * tau + loc.y * loc.y * tau * bleedFreq) / 8.0; + else + bleedX = x + (yScanlineAmount + (1.0 + sin(tau*(bleedDistort+loc.y)))/2.0) * sin(bleedDistort * tau + loc.y * loc.y * tau * bleedFreq) / 10.0; + vec4 colorBleed = (bleedX < 0.5) ? mix(colorBleedL, colorBleedC, 2.0 * bleedX) : mix(colorBleedR, colorBleedC, 2.0 - 2.0 * bleedX); + if (scanFollow) + c += bleed * max(xScanlineSize,xTime) * colorBleed; + else + c += bleed * colorBleed; + } + gl_FragColor = c; +} diff --git a/src/renderer/src/application/sample-modules/isf/VHS Glitch.fs.fs b/src/renderer/src/application/sample-modules/isf/VHS Glitch.fs.fs new file mode 100644 index 000000000..fda245d98 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/VHS Glitch.fs.fs @@ -0,0 +1,211 @@ +/*{ + "DESCRIPTION": "VHS Glitch Style", + "CREDIT": "David Lublin, original by Staffan Widegarn Åhlvik", + "CATEGORIES": [ + "Stylize", + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "autoScan", + "TYPE": "bool", + "DEFAULT": 1 + }, + { + "NAME": "xScanline", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "xScanline2", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "yScanline", + "TYPE": "float", + "DEFAULT": 0, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "xScanlineSize", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "xScanlineSize2", + "TYPE": "float", + "DEFAULT": 0.25, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "yScanlineAmount", + "TYPE": "float", + "DEFAULT": 0.05, + "MIN": -1, + "MAX": 1 + }, + { + "NAME": "grainLevel", + "TYPE": "float", + "DEFAULT": 0, + "MIN": 0, + "MAX": 3 + }, + { + "NAME": "scanFollow", + "TYPE": "bool", + "DEFAULT": 1 + }, + { + "NAME": "analogDistort", + "TYPE": "float", + "DEFAULT": 1, + "MIN": 0, + "MAX": 10 + }, + { + "NAME": "bleedAmount", + "TYPE": "float", + "DEFAULT": 1, + "MIN": 0, + "MAX": 3 + }, + { + "NAME": "bleedDistort", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "bleedRange", + "TYPE": "float", + "DEFAULT": 1, + "MIN": 0, + "MAX": 2 + }, + { + "NAME": "colorBleedL", + "TYPE": "color", + "DEFAULT": [ + 0.8, + 0, + 0.4, + 1 + ] + }, + { + "NAME": "colorBleedC", + "TYPE": "color", + "DEFAULT": [ + 0, + 0.5, + 0.9, + 1 + ] + }, + { + "NAME": "colorBleedR", + "TYPE": "color", + "DEFAULT": [ + 0.8, + 0, + 0.4, + 1 + ] + } + ] +}*/ + + + + +// Based on https://github.com/staffantan/unity-vhsglitch +// Converted by David Lublin / VIDVOX + + +const float tau = 6.28318530718; + + +float rand(vec3 co){ + return abs(mod(sin( dot(co.xyz ,vec3(12.9898,78.233,45.5432) )) * 43758.5453, 1.0)); +} + +void main() { + float actualXLine = (!autoScan) ? xScanline : mod(xScanline + ((1.0+sin(0.34*TIME))/2.0 + (1.0+sin(TIME))/3.0 + (1.0+cos(2.1*TIME))/3.0 + (1.0+cos(0.027*TIME))/2.0)/3.5,1.0); + float actualXLineWidth = (!autoScan) ? xScanlineSize : 2.0 * xScanlineSize * ((1.0+sin(1.2*TIME))/2.0 + (1.0+cos(3.91*TIME))/3.0 + (1.0+cos(0.014*TIME))/2.0)/3.5; + vec2 loc = isf_FragNormCoord; + vec4 vhs = IMG_NORM_PIXEL(inputImage, loc); + float dx = 1.0+actualXLineWidth/25.0-abs(distance(loc.y, actualXLine)); + float dx2 = 1.0+xScanlineSize2/10.0-abs(distance(loc.y, xScanline2)); + float dy = (1.0-abs(distance(loc.y, yScanline))); + if (autoScan) + dy = (1.0-abs(distance(loc.y, mod(yScanline+TIME,1.0)))); + + dy = (dy > 0.5) ? 2.0 * dy : 2.0 * (1.0 - dy); + + float rX = (scanFollow) ? rand(vec3(dy,actualXLine,analogDistort)) : rand(vec3(dy,bleedAmount,analogDistort)); + float xTime = (actualXLine > 0.5) ? 2.0 * actualXLine : 2.0 * (1.0 - actualXLine); + + loc.x += yScanlineAmount * dy * 0.025 + analogDistort * rX/(RENDERSIZE.x/2.0); + + if(dx2 > 1.0 - xScanlineSize2 / 10.0) { + float rX2 = (dy * rand(vec3(dy,dx2,dx)) + dx2) / 4.0; + float distortAmount = analogDistort * (sin(rX * tau / dx2) + cos(rX * tau * 0.78 / dx2)) / 10.0; + //loc.y = xScanline2; + loc.x += (1.0 + distortAmount * sin(tau * (loc.x) / rX2 ) - 1.0) / 15.0; + } + if(dx > 1.0 - actualXLineWidth / 25.0) + loc.y = actualXLine; + + loc.x = mod(loc.x,1.0); + loc.y = mod(loc.y,1.0); + + vec4 c = IMG_NORM_PIXEL(inputImage, loc); + float x = (loc.x*320.0)/320.0; + float y = (loc.y*240.0)/240.0; + float bleed = 0.0; + + if (scanFollow) + c -= rand(vec3(x, y, xTime)) * xTime / (5.0-grainLevel); + else + c -= rand(vec3(x, y, bleedAmount)) * (bleedAmount/20.0) / (5.0-grainLevel); + + if (bleedAmount > 0.0) { + IMG_NORM_PIXEL(inputImage, loc + vec2(0.01, 0)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(0.02, 0)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(0.01, 0.01)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(-0.02, 0.02)).r; + bleed += IMG_NORM_PIXEL(inputImage, loc + bleedRange * vec2(0.0, -0.03)).r; + bleed /= 6.0; + bleed *= bleedAmount; + } + + if (bleed > 0.1){ + float bleedFreq = 1.0; + float bleedX = 0.0; + if (autoScan) + bleedX = x + bleedDistort * (yScanlineAmount + (1.5 + cos(TIME / 13.0 + tau*(bleedDistort+(1.0-loc.y))))/2.0) * sin((TIME / 9.0 + bleedDistort) * tau + loc.y * loc.y * tau * bleedFreq) / 8.0; + else + bleedX = x + (yScanlineAmount + (1.0 + sin(tau*(bleedDistort+loc.y)))/2.0) * sin(bleedDistort * tau + loc.y * loc.y * tau * bleedFreq) / 10.0; + vec4 colorBleed = (bleedX < 0.5) ? mix(colorBleedL, colorBleedC, 2.0 * bleedX) : mix(colorBleedR, colorBleedC, 2.0 - 2.0 * bleedX); + if (scanFollow) + c += bleed * max(xScanlineSize,xTime) * colorBleed; + else + c += bleed * colorBleed; + } + gl_FragColor = c; +} diff --git a/src/renderer/src/application/sample-modules/isf/VU Meter.fs b/src/renderer/src/application/sample-modules/isf/VU Meter.fs new file mode 100644 index 000000000..a0d4f8db1 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/VU Meter.fs @@ -0,0 +1,78 @@ +/*{ + "CATEGORIES": [ + "Geometry" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "", + "INPUTS": [ + { + "DEFAULT": 1, + "MAX": 1, + "MIN": 0, + "NAME": "audioLevel", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 1, + 0, + 1 + ], + "NAME": "color1", + "TYPE": "color" + }, + { + "DEFAULT": [ + 1, + 1, + 0, + 1 + ], + "NAME": "color2", + "TYPE": "color" + }, + { + "DEFAULT": [ + 1, + 0, + 0, + 1 + ], + "NAME": "color3", + "TYPE": "color" + } + ], + "ISFVSN": "2" +} +*/ + + +const float divisionCount = 5.0; + +float round(float val) { + if (fract(val) <= 0.5) + return floor(val); + else + return ceil(val); +} + +void main() { + vec4 inputPixelColor = vec4(0.0); + vec2 loc = isf_FragNormCoord; + + float div = floor(divisionCount * audioLevel); + float thisDiv = floor(loc.x * divisionCount); + float nearestDiv = round(loc.x * divisionCount); + + if (loc.x < div / divisionCount) { + if (thisDiv <= divisionCount * 0.5) + inputPixelColor = color1; + else if (thisDiv <= divisionCount - 2.0) + inputPixelColor = color2; + else + inputPixelColor = color3; + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/VVMotionBlur 3.0.fs b/src/renderer/src/application/sample-modules/isf/VVMotionBlur 3.0.fs new file mode 100644 index 000000000..489b7d74b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/VVMotionBlur 3.0.fs @@ -0,0 +1,37 @@ +/*{ + "DESCRIPTION": "this is basically identical to the demonstration of a persistent buffer", + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "blurAmount", + "TYPE": "float", + "DEFAULT": 0.0 + } + ], + "PASSES": [ + { + "TARGET":"bufferVariableNameA", + "PERSISTENT": true, + "FLOAT": true + }, + { + + } + ] + +}*/ + +void main() +{ + vec4 freshPixel = IMG_PIXEL(inputImage,gl_FragCoord.xy); + vec4 stalePixel = IMG_PIXEL(bufferVariableNameA,gl_FragCoord.xy); + gl_FragColor = mix(freshPixel,stalePixel,blurAmount); +} diff --git a/src/renderer/src/application/sample-modules/isf/Vertex Manipulator.fs b/src/renderer/src/application/sample-modules/isf/Vertex Manipulator.fs new file mode 100644 index 000000000..66acebb98 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Vertex Manipulator.fs @@ -0,0 +1,58 @@ +/*{ + "CATEGORIES": [ + "Geometry Adjustment" + ], + "CREDIT": "VIDVOX", + "DESCRIPTION": "Moves the vertex points to the specified locations without correction", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": [ + 0, + 1 + ], + "LABEL": "Top Left", + "NAME": "topleft", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "LABEL": "Bottom Left", + "NAME": "bottomleft", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 1 + ], + "LABEL": "Top Right", + "NAME": "topright", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 1, + 0 + ], + "LABEL": "Bottom Right", + "NAME": "bottomright", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + + +void main() +{ + vec4 test = IMG_THIS_PIXEL(inputImage); + gl_FragColor = test; +} diff --git a/src/renderer/src/application/sample-modules/isf/Vertex Manipulator.vs b/src/renderer/src/application/sample-modules/isf/Vertex Manipulator.vs new file mode 100644 index 000000000..599fdfc18 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Vertex Manipulator.vs @@ -0,0 +1,31 @@ + + +void main() +{ + isf_vertShaderInit(); + + vec4 position = gl_Position; + vec4 coord; + if ((position.x<0.0)&&(position.y<0.0)) { + coord.xy = bottomleft / RENDERSIZE; + coord.x = coord.x * 2.0; + coord.y = coord.y * 2.0; + } + else if ((position.x>0.0)&&(position.y<0.0)) { + coord.xy = bottomright / RENDERSIZE; + coord.x = (1.0-coord.x) * -2.0; + coord.y = coord.y * 2.0; + } + else if (position.x<0.0) { + coord.xy = topleft / RENDERSIZE; + coord.x = coord.x * 2.0; + coord.y = (1.0-coord.y) * -2.0; + } + else { + coord.xy = topright / RENDERSIZE; + coord.x = (1.0-coord.x) * -2.0; + coord.y = (1.0-coord.y) * -2.0; + } + gl_Position.xy = position.xy + coord.xy; + +} diff --git a/src/renderer/src/application/sample-modules/isf/Vertical Tearing.fs b/src/renderer/src/application/sample-modules/isf/Vertical Tearing.fs new file mode 100644 index 000000000..954e8646c --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Vertical Tearing.fs @@ -0,0 +1,51 @@ +/* +{ + "CATEGORIES" : [ + "Glitch" + ], + "DESCRIPTION" : "This introduces a vertical tearing effect similar to when GL VBL sync is off.", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "tearPosition", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0.5, + "MIN" : 0 + } + ], + "PASSES" : [ + { + "TARGET" : "oldImage" + }, + { + "TARGET" : "newImage", + "PERSISTENT" : true + }, + { + + } + ], + "CREDIT" : "by VIDVOX" +} +*/ + +void main() +{ + // write the previous buffer into here + if (PASSINDEX == 0) { + gl_FragColor = IMG_NORM_PIXEL(newImage,isf_FragNormCoord.xy); + } + else if (PASSINDEX == 1) { + gl_FragColor = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord.xy); + } + else if (PASSINDEX == 2) { + vec4 freshPixel = IMG_NORM_PIXEL(inputImage,isf_FragNormCoord.xy); + vec4 stalePixel = IMG_NORM_PIXEL(oldImage,isf_FragNormCoord.xy); + gl_FragColor = (isf_FragNormCoord.y > tearPosition) ? freshPixel : stalePixel; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Vibrance.fs b/src/renderer/src/application/sample-modules/isf/Vibrance.fs new file mode 100644 index 000000000..ed8788ca2 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Vibrance.fs @@ -0,0 +1,54 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Color Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "vibrance", + "TYPE": "float", + "MIN": -0.25, + "MAX": 0.6, + "DEFAULT": 0.0 + } + ] +}*/ + + +vec3 rgb2hsv(vec3 c); +vec3 hsv2rgb(vec3 c); + +void main() { + vec4 tmpColorA = IMG_THIS_PIXEL(inputImage); + vec3 tmpColorB = rgb2hsv(tmpColorA.rgb); + float maxDelta = sqrt(tmpColorB.y) - tmpColorB.y; + tmpColorB.y = (maxDelta * vibrance) + tmpColorB.y; + tmpColorA.rgb = hsv2rgb(tmpColorB.rgb); + gl_FragColor = tmpColorA; +} + + + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Vignette.fs b/src/renderer/src/application/sample-modules/isf/Vignette.fs new file mode 100644 index 000000000..1367728ad --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Vignette.fs @@ -0,0 +1,41 @@ +/* +{ + "DESCRIPTION" : "A simple Vignette", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "intensity", + "TYPE" : "float", + "MAX" : 100, + "DEFAULT" : 15, + "MIN" : 1, + "LABEL" : "Intensity" + }, + { + "NAME" : "extension", + "TYPE" : "float", + "MAX" : 2.5, + "DEFAULT" : 0.25, + "MIN" : 0.05, + "LABEL" : "Extension" + } + ], + "CREDIT" : "2xAA" +} +*/ + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 uv = fragCoord.xy / RENDERSIZE.xy; + uv *= 1.0 - uv.yx; //vec2(1.0)- uv.yx; -> 1.-u.yx; Thanks FabriceNeyret ! + float vig = uv.x * uv.y * intensity; // multiply with sth for intensity + vig = pow(vig, extension); // change pow for modifying the extend of the vignette + fragColor = vec4(IMG_NORM_PIXEL(inputImage, isf_FragNormCoord).rgb * (clamp(vig, 0., 1.)), 1.0); +} + +void main(void) { + mainImage(gl_FragColor, gl_FragCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Water Drop.fs b/src/renderer/src/application/sample-modules/isf/Water Drop.fs new file mode 100644 index 000000000..c634f7c5e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Water Drop.fs @@ -0,0 +1,72 @@ +/*{ + "CATEGORIES": [ + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/WaterDrop.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 30, + "MAX": 100, + "MIN": 0, + "NAME": "speed", + "TYPE": "float" + }, + { + "DEFAULT": 30, + "MAX": 100, + "MIN": 0, + "NAME": "amplitude", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// author: Paweł Płóciennik +// license: MIT + +vec4 transition(vec2 p) { + vec2 dir = p - vec2(.5); + float dist = length(dir); + + if (dist > progress) { + return mix(getFromColor( p), getToColor( p), progress); + } else { + vec2 offset = dir * sin(dist * amplitude - progress * speed); + return mix(getFromColor( p + offset), getToColor( p), progress); + } +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/WaveLines.fs b/src/renderer/src/application/sample-modules/isf/WaveLines.fs new file mode 100644 index 000000000..f0fd39635 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/WaveLines.fs @@ -0,0 +1,115 @@ +// SaturdayShader Week 29 : Wave Lines +// by Joseph Fiola (http://www.joefiola.com) +// 2016-03-05 +//Remix by SilviaFabiani +// Based on "WAVES" Shadertoy by bonniem +// https://www.shadertoy.com/view/4dsGzH + +/*{ + "CREDIT": "Joseph Fiola", + "DESCRIPTION": "", + "CATEGORIES": [ + "Generator" + ], + "INPUTS": [ + { + "NAME": "amp", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": -1.3, + "MAX": 2.3 + }, + { + "NAME": "glow", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": -10, + "MAX": 0.6 + }, + { + "NAME": "mod2", + "TYPE": "float", + "DEFAULT": 0, + "MIN": -1, + "MAX": 1 + }, + { + "NAME": "zoom", + "TYPE": "float", + "DEFAULT": 10, + "MIN": 0, + "MAX": 100 + }, + { + "NAME": "rotateCanvas", + "TYPE": "float", + "DEFAULT": 0, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "scroll", + "TYPE": "float", + "DEFAULT": 0, + "MIN": 0, + "MAX": 1 + }, + { + "NAME": "pos", + "TYPE": "point2D", + "DEFAULT": [ + 0.5, + 0.5 + ], + "MIN": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ] + }, + { + "NAME": "twisted", + "TYPE": "float", + "DEFAULT": -0.050, + "MIN": -0.3, + "MAX": 0.3 + } + ] +}*/ + + +#define PI 3.14159265359 +#define TWO_PI 6.28318530718 + +mat2 rotate2d(float _angle){ + return mat2(cos(_angle),-sin(_angle), + sin(_angle),cos(_angle)); +} + +void main() +{ + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy; + uv -= vec2(pos); + uv.x *= RENDERSIZE.x/RENDERSIZE.y; + uv *= zoom; // Scale the coordinate system + uv = rotate2d(rotateCanvas*-TWO_PI) * uv; + + + // waves + vec3 wave_color = vec3(0.2, 0.1, 0.0); + + float wave_width = 0.01; + for(int i = 0; i < 10; ++i) { + + uv = rotate2d(twisted*-TWO_PI) * uv; + + uv.y += (sin(sin(uv.x + float(i)*1.0 + (scroll * TWO_PI) )/3.) * amp + (mod2 * PI)); + wave_width = abs(1.0 / (50.0 * uv.y * glow)); + wave_color += vec3(wave_width, wave_width, wave_width); + } + + gl_FragColor = vec4(wave_color, 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/White Point Adjust.fs b/src/renderer/src/application/sample-modules/isf/White Point Adjust.fs new file mode 100644 index 000000000..ffdd564af --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/White Point Adjust.fs @@ -0,0 +1,31 @@ +/*{ + "CREDIT": "by zoidberg", + "ISFVSN": "2", + "DESCRIPTION": "Modifies the white point by multiplying the src pixel by the color value", + "CATEGORIES": [ + "Color Adjustment" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "newWhite", + "TYPE": "color", + "DEFAULT": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + } + ] +}*/ + + + +void main() { + vec4 tmpColorA = IMG_THIS_PIXEL(inputImage); + gl_FragColor = tmpColorA * newWhite; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Wind.fs b/src/renderer/src/application/sample-modules/isf/Wind.fs new file mode 100644 index 000000000..7081d9f15 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Wind.fs @@ -0,0 +1,69 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/wind.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.2, + "MAX": 1, + "MIN": 0, + "NAME": "size", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + +// Custom parameters + +float rand (vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 transition (vec2 uv) { + float r = rand(vec2(0, uv.y)); + float m = smoothstep(0.0, -size, uv.x*(1.0-size) + size*r - (progress * (1.0 + size))); + return mix( + getFromColor(uv), + getToColor(uv), + m + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Window Blinds.fs b/src/renderer/src/application/sample-modules/isf/Window Blinds.fs new file mode 100644 index 000000000..ce935060e --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Window Blinds.fs @@ -0,0 +1,60 @@ +/* +{ + "CATEGORIES" : [ + "Dissolve" + ], + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "NAME" : "progress", + "MIN" : 0, + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0 + } + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/windowblinds.glsl", + "DESCRIPTION": "", + "ISFVSN" : "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Fabien Benetou +// License: MIT + +vec4 transition (vec2 uv) { + float t = progress; + + if (mod(floor(uv.y*100.*progress),2.)==0.) + t*=2.-.5; + + return mix( + getFromColor(uv), + getToColor(uv), + mix(t, progress, smoothstep(0.8, 1.0, progress)) + ); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Window Slice.fs b/src/renderer/src/application/sample-modules/isf/Window Slice.fs new file mode 100644 index 000000000..b21d5fa1b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Window Slice.fs @@ -0,0 +1,67 @@ +/*{ + "CATEGORIES": [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/windowslice.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 10, + "MAX": 100, + "MIN": 0, + "NAME": "count", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "smoothness", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + + +vec4 transition (vec2 p) { + float pr = smoothstep(-smoothness, 0.0, p.x - progress * (1.0 + smoothness)); + float s = step(pr, fract(count * p.x)); + return mix(getFromColor(p), getToColor(p), s); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Wipe Down.fs b/src/renderer/src/application/sample-modules/isf/Wipe Down.fs new file mode 100644 index 000000000..c709b3dde --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Wipe Down.fs @@ -0,0 +1,54 @@ +/* +{ + "ISFVSN" : "2", + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "TYPE" : "image", + "NAME" : "endImage" + }, + { + "TYPE" : "float", + "NAME" : "progress", + "MIN" : 0, + "MAX" : 1, + "DEFAULT" : 0 + } + ], + "CATEGORIES" : [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/wipeDown.glsl", + "DESCRIPTION" : "Automatically converted from https://gl-transitions.com/" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Jake Nelson +// License: MIT + +vec4 transition(vec2 uv) { + vec2 p=uv.xy/vec2(1.0).xy; + vec4 a=getFromColor(p); + vec4 b=getToColor(p); + return mix(a, b, step(1.0-p.y,progress)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Wipe Left.fs b/src/renderer/src/application/sample-modules/isf/Wipe Left.fs new file mode 100644 index 000000000..7ac43c618 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Wipe Left.fs @@ -0,0 +1,54 @@ +/* +{ + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "TYPE" : "image", + "NAME" : "endImage" + }, + { + "TYPE" : "float", + "MAX" : 1, + "NAME" : "progress", + "MIN" : 0, + "DEFAULT" : 0 + } + ], + "CATEGORIES" : [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/wipeLeft.glsl", + "DESCRIPTION": "", + "ISFVSN" : "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Jake Nelson +// License: MIT + +vec4 transition(vec2 uv) { + vec2 p=uv.xy/vec2(1.0).xy; + vec4 a=getFromColor(p); + vec4 b=getToColor(p); + return mix(a, b, step(1.0-p.x,progress)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Wipe Right.fs b/src/renderer/src/application/sample-modules/isf/Wipe Right.fs new file mode 100644 index 000000000..e8782b667 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Wipe Right.fs @@ -0,0 +1,54 @@ +/* +{ + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "TYPE" : "image", + "NAME" : "endImage" + }, + { + "TYPE" : "float", + "MAX" : 1, + "NAME" : "progress", + "MIN" : 0, + "DEFAULT" : 0 + } + ], + "CATEGORIES" : [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/wipeRight.glsl", + "DESCRIPTION": "", + "ISFVSN" : "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Jake Nelson +// License: MIT + +vec4 transition(vec2 uv) { + vec2 p=uv.xy/vec2(1.0).xy; + vec4 a=getFromColor(p); + vec4 b=getToColor(p); + return mix(a, b, step(0.0+p.x,progress)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Wipe Up.fs b/src/renderer/src/application/sample-modules/isf/Wipe Up.fs new file mode 100644 index 000000000..3e1f62636 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Wipe Up.fs @@ -0,0 +1,54 @@ +/* +{ + "CATEGORIES" : [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/wipeUp.glsl", + "DESCRIPTION": "", + "ISFVSN" : "2", + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "NAME" : "endImage", + "TYPE" : "image" + }, + { + "MIN" : 0, + "TYPE" : "float", + "NAME" : "progress", + "MAX" : 1, + "DEFAULT" : 0 + } + ] +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: Jake Nelson +// License: MIT + +vec4 transition(vec2 uv) { + vec2 p=uv.xy/vec2(1.0).xy; + vec4 a=getFromColor(p); + vec4 b=getToColor(p); + return mix(a, b, step(0.0+p.y,progress)); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/XYZoom.fs b/src/renderer/src/application/sample-modules/isf/XYZoom.fs new file mode 100644 index 000000000..d679f5b19 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/XYZoom.fs @@ -0,0 +1,60 @@ +/*{ + "CATEGORIES": [ + "Geometry Adjustment" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0.01, + "NAME": "levelX", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0.01, + "NAME": "levelY", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec2 loc; + vec2 modifiedCenter; + + loc = isf_FragNormCoord; + modifiedCenter = center; + loc.x = (loc.x - modifiedCenter.x)*(1.0/levelX) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/levelY) + modifiedCenter.y; + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Y-C Time Blur.fs b/src/renderer/src/application/sample-modules/isf/Y-C Time Blur.fs new file mode 100644 index 000000000..f6224a22a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Y-C Time Blur.fs @@ -0,0 +1,84 @@ +/* +{ + "CATEGORIES" : [ + "Blur", + "Glitch" + ], + "DESCRIPTION" : "This converts to YIQ and does separate feedback blur on the Y and C channels", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "yFeedbackLevel", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + }, + { + "NAME" : "cFeedbackLevel", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0 + } + ], + "PASSES" : [ + { + "TARGET" : "lastFrame", + "PERSISTENT" : true, + "FLOAT" : true + } + ], + "CREDIT" : "VIDVOX" +} +*/ + + +// inspired by (but not exactly the same thing as) y/c delay error +// https://bavc.github.io/avaa/artifacts/yc_delay_error.html + +// shout out to this stackoverflow +// https://stackoverflow.com/questions/9234724/how-to-change-hue-of-a-texture-with-glsl/9234854#9234854 +// (though I'd have gone to HSV for that particular effect, it covers our use case of yiq nicely!) + + +const vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0); +const vec4 kRGBToI = vec4 (0.596, -0.275, -0.321, 0.0); +const vec4 kRGBToQ = vec4 (0.212, -0.523, 0.311, 0.0); + +const vec4 kYIQToR = vec4 (1.0, 0.956, 0.621, 0.0); +const vec4 kYIQToG = vec4 (1.0, -0.272, -0.647, 0.0); +const vec4 kYIQToB = vec4 (1.0, -1.107, 1.704, 0.0); + + +vec3 rgb2yiq(vec3 c) { + float YPrime = dot (c, kRGBToYPrime.rgb); + float I = dot (c, kRGBToI.rgb); + float Q = dot (c, kRGBToQ.rgb); + return vec3(YPrime,I,Q); +} + +vec3 yiq2rgb(vec3 c) { + vec3 yIQ = vec3 (c.r, c.g, c.b); + return vec3(dot (yIQ, kYIQToR.rgb),dot (yIQ, kYIQToG.rgb),dot (yIQ, kYIQToB.rgb)); +} + + +void main() { + // this is a pass-thru1 + vec4 inputPixelColor = IMG_THIS_PIXEL(inputImage); + vec4 lastPixel = IMG_THIS_PIXEL(lastFrame); + inputPixelColor.rgb = rgb2yiq(inputPixelColor.rgb); + lastPixel.rgb = rgb2yiq(lastPixel.rgb); + + inputPixelColor.r = mix(inputPixelColor.r,lastPixel.r,yFeedbackLevel); + inputPixelColor.gb = mix(inputPixelColor.gb,lastPixel.gb,cFeedbackLevel); + + inputPixelColor.rgb = yiq2rgb(inputPixelColor.rgb); + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/Zebre.fs b/src/renderer/src/application/sample-modules/isf/Zebre.fs new file mode 100644 index 000000000..f7dc8ed30 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Zebre.fs @@ -0,0 +1,192 @@ +// SaturdayShader Week 16 : Zebre +// by Joseph Fiola (http://www.joefiola.com) +// 2015-12-05 +// Based on Patricio Gonzalez Vivo's "Wood Texture" example on http://patriciogonzalezvivo.com/2015/thebookofshaders/edit.html#11/wood.frag @patriciogv ( patriciogonzalezvivo.com ) - 2015 + + +/*{ + "CREDIT": "Joseph Fiola", + "DESCRIPTION": "", + "CATEGORIES": [ "Generator","waves", "random" ], + "INPUTS": [ + + { + "NAME": "lineScale", + "TYPE": "float", + "DEFAULT": 2.0, + "MIN": 0.0005, + "MAX": 10.0 + }, + { + "NAME": "harmonic", + "TYPE": "float", + "DEFAULT": 2.0, + "MIN": 0.0, + "MAX": 200.0 + }, + { + "NAME": "lineOffsetSpeed", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 100.0 + }, + { + "NAME": "brightness", + "TYPE": "float", + "DEFAULT": 1.5, + "MIN": 0.1, + "MAX": 10.0 + }, + { + "NAME": "contrast", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": 0.0, + "MAX": 0.5 + }, + { + "NAME": "contrastShift", + "TYPE": "float", + "DEFAULT": 0.0, + "MIN": -0.5, + "MAX": 0.5 + }, + { + "NAME": "randomMultiply", + "TYPE": "float", + "DEFAULT": 43758.5453123, + "MIN": 0.0, + "MAX": 50000.0 + }, + { + "NAME": "randomAmt", + "TYPE": "point2D", + "DEFAULT": [ + 12.9898, + 78.233 + ], + "MIN": [ + 0.0, + 0.0 + ], + "MAX": [ + 100.0, + 100.0 + ] + }, + { + "NAME": "origin", + "TYPE": "point2D", + "DEFAULT": [ + 0.5, + 0.5 + ], + "MIN": [ + 0.0, + 0.0 + ], + "MAX": [ + 1.0, + 1.0 + ] + }, + { + "NAME": "xyStretch", + "TYPE": "point2D", + "DEFAULT": [ + 6.0, + 3.0 + ], + "MIN": [ + 0.0, + 0.0 + ], + "MAX": [ + 100.0, + 100.0 + ] + }, + { + "NAME": "xyNoiseFactor", + "TYPE": "point2D", + "DEFAULT": [ + 10.0, + 12.0 + ], + "MIN": [ + 0.0, + 0.0 + ], + "MAX": [ + 100.0, + 100.0 + ] + } + + ] +}*/ + + +#ifdef GL_ES +precision mediump float; +#endif + + +float random (in vec2 st) { + return fract(sin(dot(st.xy, + vec2(randomAmt.x,randomAmt.y))) + * randomMultiply); +} + +// Value noise by Inigo Quilez - iq/2013 +// https://www.shadertoy.com/view/lsf3WH +float noise(vec2 st) { + + vec2 i = floor(st); + vec2 f = fract(st); + + + vec2 u = f*f*(3.0-2.0*f); + return mix( mix( random( i + vec2(0.0,0.0) ), + random( i + vec2(1.0,0.0) ), u.x), + mix( random( i + vec2(0.0,1.0) ), + random( i + vec2(1.0,1.0) ), u.x), u.y); +} + + +mat2 rotate2d(float angle){ + return mat2(cos(angle *xyNoiseFactor.x),-sin(angle), + sin(angle * xyNoiseFactor.y),cos(angle)); +} + + +float lines(in vec2 pos, float b){ + float scale = lineScale; + pos *= scale; + return smoothstep(0.0, + .5+b*.5, + abs((sin(pos.x*3.1415)+b*2.0))* brightness); +} + + +void main() { + vec2 st = gl_FragCoord.xy/RENDERSIZE.xy; + st -= vec2(origin); + st.y *= RENDERSIZE.y/RENDERSIZE.x; + + vec2 pos = st.yx*vec2(xyStretch); + + float pattern = pos.x; + + // Add noise + pos = rotate2d( noise(pos) ) * pos * harmonic + (TIME * lineOffsetSpeed); + + // Draw lines + pattern = lines(pos,0.5); + + //adjust contrast + pattern += smoothstep(0.0+contrast+contrastShift,1.0-contrast+contrastShift, pattern); + + gl_FragColor = vec4(vec3(pattern),1.0); +} diff --git a/src/renderer/src/application/sample-modules/isf/Zoom In Circles.fs b/src/renderer/src/application/sample-modules/isf/Zoom In Circles.fs new file mode 100644 index 000000000..8605cb525 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Zoom In Circles.fs @@ -0,0 +1,83 @@ +/* +{ + "CATEGORIES" : [ + "Wipe" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/ZoomInCircles.glsl", + "DESCRIPTION": "", + "INPUTS" : [ + { + "TYPE" : "image", + "NAME" : "startImage" + }, + { + "TYPE" : "image", + "NAME" : "endImage" + }, + { + "DEFAULT" : 0, + "TYPE" : "float", + "NAME" : "progress", + "MIN" : 0, + "MAX" : 1 + } + ], + "ISFVSN" : "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + +float ratio = RENDERSIZE.x/RENDERSIZE.y; + +// License: MIT +// Author: dycm8009 +// ported by gre from https://gist.github.com/dycm8009/948e99b1800e81ad909a + +vec2 zoom(vec2 uv, float amount) { + return 0.5 + ((uv - 0.5) * amount); +} + +vec2 ratio2 = vec2(1.0, 1.0 / ratio); + +vec4 transition(vec2 uv) { + // TODO: some timing are hardcoded but should be one or many parameters + // TODO: should also be able to configure how much circles + // TODO: if() branching should be avoided when possible, prefer use of step() & other functions + vec2 r = 2.0 * ((vec2(uv.xy) - 0.5) * ratio2); + float pro = progress / 0.8; + float z = pro * 0.2; + float t = 0.0; + if (pro > 1.0) { + z = 0.2 + (pro - 1.0) * 5.; + t = clamp((progress - 0.8) / 0.07, 0.0, 1.0); + } + if (length(r) < 0.5+z) { + // uv = zoom(uv, 0.9 - 0.1 * pro); + } + else if (length(r) < 0.8+z*1.5) { + uv = zoom(uv, 1.0 - 0.15 * pro); + t = t * 0.5; + } + else if (length(r) < 1.2+z*2.5) { + uv = zoom(uv, 1.0 - 0.2 * pro); + t = t * 0.2; + } + else { + uv = zoom(uv, 1.0 - 0.25 * pro); + } + return mix(getFromColor(uv), getToColor(uv), t); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/Zoom.fs b/src/renderer/src/application/sample-modules/isf/Zoom.fs new file mode 100644 index 000000000..8e8fd3b45 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Zoom.fs @@ -0,0 +1,53 @@ +/*{ + "CATEGORIES": [ + "Geometry Adjustment" + ], + "CREDIT": "by VIDVOX", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 1, + "MAX": 10, + "MIN": 0.01, + "NAME": "level", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0, + 0 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "center", + "TYPE": "point2D" + } + ], + "ISFVSN": "2" +} +*/ + +void main() { + vec2 loc; + vec2 modifiedCenter; + + loc = isf_FragNormCoord; + modifiedCenter = center; + loc.x = (loc.x - modifiedCenter.x)*(1.0/level) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/level) + modifiedCenter.y; + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + gl_FragColor = vec4(0.0); + } + else { + gl_FragColor = IMG_NORM_PIXEL(inputImage,loc); + } +} diff --git a/src/renderer/src/application/sample-modules/isf/Zooming Feedback.fs b/src/renderer/src/application/sample-modules/isf/Zooming Feedback.fs new file mode 100644 index 000000000..755bdd543 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/Zooming Feedback.fs @@ -0,0 +1,230 @@ +/*{ + "CATEGORIES": [ + "Retro", + "Feedback" + ], + "CREDIT": "", + "DESCRIPTION": "Creates a simple zooming feedback loop", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "preShift", + "TYPE": "point2D" + }, + { + "DEFAULT": 0.9, + "MAX": 1, + "MIN": 0, + "NAME": "feedbackLevel", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "rotateAngle", + "TYPE": "float" + }, + { + "DEFAULT": 1.2, + "MAX": 2, + "MIN": 0.25, + "NAME": "zoomLevel", + "TYPE": "float" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "zoomCenter", + "TYPE": "point2D" + }, + { + "DEFAULT": [ + 0.5, + 0.5 + ], + "MAX": [ + 1, + 1 + ], + "MIN": [ + 0, + 0 + ], + "NAME": "feedbackShift", + "TYPE": "point2D" + }, + { + "DEFAULT": 0, + "NAME": "invert", + "TYPE": "bool" + }, + { + "DEFAULT": 3, + "LABELS": [ + "Add", + "Over Black", + "Over Alpha", + "Max", + "Under Black", + "Under Alpha" + ], + "NAME": "blendMode", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + { + "DEFAULT": 0.1, + "MAX": 1, + "MIN": 0, + "NAME": "blackThresh", + "TYPE": "float" + }, + { + "DEFAULT": 1, + "MAX": 2, + "MIN": 0, + "NAME": "satLevel", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "colorShift", + "TYPE": "float" + } + ], + "ISFVSN": "2", + "PASSES": [ + { + "PERSISTENT": true, + "TARGET": "feedbackBuffer" + } + ] +} +*/ + + +const float pi = 3.14159265359; + + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + //vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + //vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + + +void main() { + vec2 loc = isf_FragNormCoord; + vec4 inputPixelColor = IMG_NORM_PIXEL(inputImage,loc + (0.5 - (preShift))); + + vec4 feedbackPixelColor = vec4(0.0); + + // rotate here if needed + loc = loc * RENDERSIZE; + float r = distance(RENDERSIZE/2.0, loc); + float a = atan ((loc.y-RENDERSIZE.y/2.0),(loc.x-RENDERSIZE.x/2.0)); + + loc.x = r * cos(a + 2.0 * pi * rotateAngle - pi) + 0.5; + loc.y = r * sin(a + 2.0 * pi * rotateAngle - pi) + 0.5; + + loc = loc / RENDERSIZE + vec2(0.5); + + vec2 modifiedCenter = zoomCenter; + loc.x = (loc.x - modifiedCenter.x)*(1.0/zoomLevel) + modifiedCenter.x; + loc.y = (loc.y - modifiedCenter.y)*(1.0/zoomLevel) + modifiedCenter.y; + loc += (0.5 - (feedbackShift)); + + if ((loc.x < 0.0)||(loc.y < 0.0)||(loc.x > 1.0)||(loc.y > 1.0)) { + feedbackPixelColor = vec4(0.0); + } + else { + feedbackPixelColor = IMG_NORM_PIXEL(feedbackBuffer,loc); + } + + feedbackPixelColor.rgb = rgb2hsv(feedbackPixelColor.rgb); + feedbackPixelColor.r = mod(feedbackPixelColor.r+colorShift,1.0); + feedbackPixelColor.g *= satLevel; + + feedbackPixelColor.rgb = hsv2rgb(feedbackPixelColor.rgb); + + if (invert) + feedbackPixelColor.rgb = 1.0 - feedbackPixelColor.rgb; + + if (blendMode == 0) { + inputPixelColor = inputPixelColor + feedbackLevel * feedbackPixelColor; + } + else if (blendMode == 1) { + float val = inputPixelColor.a * (inputPixelColor.r + inputPixelColor.g + inputPixelColor.b) / 3.0; + inputPixelColor = (val >= blackThresh) ? inputPixelColor : feedbackLevel * feedbackPixelColor; + inputPixelColor.a = inputPixelColor.a + feedbackPixelColor.a * feedbackLevel; + } + else if (blendMode == 2) { + // apply the alpha to the input pixel as this happens + inputPixelColor.rgb = inputPixelColor.a * inputPixelColor.rgb + (1.0 - inputPixelColor.a) * feedbackLevel * feedbackPixelColor.rgb; + inputPixelColor.a = inputPixelColor.a + feedbackPixelColor.a * feedbackLevel; + } + else if (blendMode == 3) { + inputPixelColor.rgb = max(inputPixelColor.a * inputPixelColor.rgb, feedbackLevel * feedbackPixelColor.rgb); + inputPixelColor.a = inputPixelColor.a + feedbackPixelColor.a * feedbackLevel; + } + else if (blendMode == 4) { + float val = feedbackPixelColor.a * (feedbackPixelColor.r + feedbackPixelColor.g + feedbackPixelColor.b) / 3.0; + inputPixelColor = (val < blackThresh) ? inputPixelColor.a * inputPixelColor : feedbackLevel * feedbackPixelColor; + inputPixelColor.a = inputPixelColor.a + feedbackPixelColor.a * feedbackLevel; + } + else if (blendMode == 5) { + // apply the alpha to the input pixel as this happens + inputPixelColor.rgb = (1.0-feedbackPixelColor.a) * inputPixelColor.a * inputPixelColor.rgb + feedbackLevel * feedbackPixelColor.rgb; + inputPixelColor.a = inputPixelColor.a + feedbackPixelColor.a * feedbackLevel; + } + + gl_FragColor = inputPixelColor; +} diff --git a/src/renderer/src/application/sample-modules/isf/badtv.fs b/src/renderer/src/application/sample-modules/isf/badtv.fs new file mode 100644 index 000000000..e4198afdf --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/badtv.fs @@ -0,0 +1,184 @@ + +/*{ + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "noiseLevel", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "distortion1", + "TYPE": "float", + "MIN": 0.0, + "MAX": 5.0, + "DEFAULT": 1.0 + }, + { + "NAME": "distortion2", + "TYPE": "float", + "MIN": 0.0, + "MAX": 5.0, + "DEFAULT": 5.0 + }, + { + "NAME": "speed", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.3 + }, + { + "NAME": "scroll", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "scanLineThickness", + "TYPE": "float", + "MIN": 1.0, + "MAX": 50.0, + "DEFAULT": 25.0 + }, + { + "NAME": "scanLineIntensity", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.5 + }, + { + "NAME": "scanLineOffset", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + } + ] +}*/ + +// Adapted from http://www.airtightinteractive.com/demos/js/badtvshader/js/BadTVShader.js +// Also uses adopted Ashima WebGl Noise: https://github.com/ashima/webgl-noise + +/* + * The MIT License + * + * Copyright (c) 2014 Felix Turner + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * +*/ + + +// Start Ashima 2D Simplex Noise + +const vec4 C = vec4(0.211324865405187,0.366025403784439,-0.577350269189626,0.024390243902439); + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float snoise(vec2 v) { + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + i = mod289(i); // Avoid truncation effects in permutation + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))+ i.x + vec3(0.0, i1.x, 1.0 )); + + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +// End Ashima 2D Simplex Noise + +const float tau = 6.28318530718; + +// use this pattern for scan lines + +vec2 pattern(vec2 pt) { + float s = 0.0; + float c = 1.0; + vec2 tex = pt * RENDERSIZE; + vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * (1.0/scanLineThickness); + float d = point.y; + + return vec2(sin(d + scanLineOffset * tau + cos(pt.x * tau)), cos(d + scanLineOffset * tau + sin(pt.y * tau))); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + vec2 p = isf_FragNormCoord; + float ty = TIME*speed; + float yt = p.y - ty; + + //smooth distortion + float offset = snoise(vec2(yt*3.0,0.0))*0.2; + // boost distortion + offset = pow( offset*distortion1,3.0)/max(distortion1,0.001); + //add fine grain distortion + offset += snoise(vec2(yt*50.0,0.0))*distortion2*0.001; + //combine distortion on X with roll on Y + vec2 adjusted = vec2(fract(p.x + offset),fract(p.y-scroll) ); + vec4 result = IMG_NORM_PIXEL(inputImage, adjusted); + vec2 pat = pattern(adjusted); + vec3 shift = scanLineIntensity * vec3(0.3 * pat.x, 0.59 * pat.y, 0.11) / 2.0; + result.rgb = (1.0 + scanLineIntensity / 2.0) * result.rgb + shift + (rand(adjusted * TIME) - 0.5) * noiseLevel; + gl_FragColor = result; + +} diff --git a/src/renderer/src/application/sample-modules/isf/block-color.fs b/src/renderer/src/application/sample-modules/isf/block-color.fs new file mode 100644 index 000000000..ed96a3cb0 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/block-color.fs @@ -0,0 +1,49 @@ +/* +{ + "CATEGORIES" : [ + "color" + ], + "DESCRIPTION" : "Solid color block", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "r", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "MIN" : 0, + "LABEL" : "Red" + }, + { + "NAME" : "g", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "LABEL" : "Green", + "MIN" : 0 + }, + { + "NAME" : "b", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "LABEL" : "Blue", + "MIN" : 0 + }, + { + "NAME" : "a", + "TYPE" : "float", + "MAX" : 1, + "DEFAULT" : 0, + "LABEL" : "Alpha", + "MIN" : 0 + } + ], + "VSN" : "1.0", + "CREDIT" : "2xAA" +} +*/ + +void main() { + gl_FragColor = vec4(r, g, b, a); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/cube.fs b/src/renderer/src/application/sample-modules/isf/cube.fs new file mode 100644 index 000000000..84015a582 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/cube.fs @@ -0,0 +1,135 @@ +/*{ + "CATEGORIES": [ + "Wipe", + "Distortion" + ], + "CREDIT": "Automatically converted from https://www.github.com/gl-transitions/gl-transitions/tree/master/cube.glsl", + "DESCRIPTION": "", + "INPUTS": [ + { + "NAME": "startImage", + "TYPE": "image" + }, + { + "NAME": "endImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "progress", + "TYPE": "float" + }, + { + "DEFAULT": 0.4, + "MAX": 1, + "MIN": 0, + "NAME": "reflection", + "TYPE": "float" + }, + { + "DEFAULT": 0.7, + "MAX": 1, + "MIN": 0, + "NAME": "persp", + "TYPE": "float" + }, + { + "DEFAULT": 0.3, + "MAX": 1, + "MIN": 0, + "NAME": "unzoom", + "TYPE": "float" + }, + { + "DEFAULT": 3, + "MAX": 10, + "MIN": 0, + "NAME": "floating", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + + +vec4 getFromColor(vec2 inUV) { + return IMG_NORM_PIXEL(startImage, inUV); +} +vec4 getToColor(vec2 inUV) { + return IMG_NORM_PIXEL(endImage, inUV); +} + + + +// Author: gre +// License: MIT + +vec2 project (vec2 p) { + return p * vec2(1.0, -1.2) + vec2(0.0, -floating/100.); +} + +bool inBounds (vec2 p) { + return all(lessThan(vec2(0.0), p)) && all(lessThan(p, vec2(1.0))); +} + +vec4 bgColor (vec2 p, vec2 pfr, vec2 pto) { + vec4 c = vec4(0.0, 0.0, 0.0, 1.0); + pfr = project(pfr); + // FIXME avoid branching might help perf! + if (inBounds(pfr)) { + c += mix(vec4(0.0), getFromColor(pfr), reflection * mix(1.0, 0.0, pfr.y)); + } + pto = project(pto); + if (inBounds(pto)) { + c += mix(vec4(0.0), getToColor(pto), reflection * mix(1.0, 0.0, pto.y)); + } + return c; +} + +// p : the position +// persp : the perspective in [ 0, 1 ] +// center : the xcenter in [0, 1] \ 0.5 excluded +vec2 xskew (vec2 p, float persp, float center) { + float x = mix(p.x, 1.0-p.x, center); + return ( + ( + vec2( x, (p.y - 0.5*(1.0-persp) * x) / (1.0+(persp-1.0)*x) ) + - vec2(0.5-distance(center, 0.5), 0.0) + ) + * vec2(0.5 / distance(center, 0.5) * (center<0.5 ? 1.0 : -1.0), 1.0) + + vec2(center<0.5 ? 0.0 : 1.0, 0.0) + ); +} + +vec4 transition(vec2 op) { + float uz = unzoom * 2.0*(0.5-distance(0.5, progress)); + vec2 p = -uz*0.5+(1.0+uz) * op; + vec2 fromP = xskew( + (p - vec2(progress, 0.0)) / vec2(1.0-progress, 1.0), + 1.0-mix(progress, 0.0, persp), + 0.0 + ); + vec2 toP = xskew( + p / vec2(progress, 1.0), + mix(pow(progress, 2.0), 1.0, persp), + 1.0 + ); + // FIXME avoid branching might help perf! + if (inBounds(fromP)) { + return getFromColor(fromP); + } + else if (inBounds(toP)) { + return getToColor(toP); + } + return bgColor(op, fromP, toP); +} + + + +void main() { + gl_FragColor = transition(isf_FragNormCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/digital-crystal-tunnel.fs b/src/renderer/src/application/sample-modules/isf/digital-crystal-tunnel.fs new file mode 100644 index 000000000..fadba3e5c --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/digital-crystal-tunnel.fs @@ -0,0 +1,203 @@ +/*{ + "DESCRIPTION": "Fiddling with Abstract Corridor", + "CREDIT": "WilstonOreo", + "CATEGORIES": [ + "tunnel", + "trianglenoise" + ], + "INPUTS": [ + { + "NAME": "modifier", + "TYPE": "float", + "MIN": -2.0, + "MAX": 2.0, + "DEFAULT": 1.0 + } + ] +}*/ + +vec3 iResolution = vec3(RENDERSIZE, 1.); +float iTime = TIME; + +#define PI 3.1415926535898 + +// Non-standard vec3-to-vec3 hash function. +vec3 hash33(vec3 p){ + + float n = sin(dot(p, vec3(7, 157, 113))); + return fract(vec3(2097152, 262144, 32768)*n); +} + +// 2x2 matrix rotation. +mat2 rot2(float a){ + + float c = cos(a); float s = sin(a); + return mat2(c, s, -s, c); +} + +// The triangle function that Shadertoy user Nimitz has used in various triangle noise demonstrations. +// See Xyptonjtroz - Very cool. Anyway, it's not really being used to its full potential here. +vec3 tri(in vec3 x){return abs(x-floor(x)-.5);} // Triangle function. + + +float surfFunc(in vec3 p){ + + return dot(tri(p*0.75 + tri(p*0.4).yzx), vec3(0.5 + 0.4*clamp(3.0*sin(iTime*0.72+5.21),-1.0,1.0))); +} +vec2 path(in float z){ float s = sin(z/24.)*cos(z/3.); return vec2(s*12., -s*6.0); } + +// Standard tunnel distance function with some perturbation thrown into the mix. A floor has been +// worked in also. A tunnel is just a tube with a smoothly shifting center as you traverse lengthwise. +// The walls of the tube are perturbed by a pretty cheap 3D surface function. +float map(vec3 p){ + + // Square tunnel. + // For a square tunnel, use the Chebyshev(?) distance: max(abs(tun.x), abs(tun.y)) + vec2 tun = abs(p.xy - path(p.z))*vec2(0.5 + 0.1* clamp(60.0*sin(iTime*0.04),-1.0,1.0)); + float n = 1.- max(tun.x, tun.y) + (0.6-surfFunc(p)); + return n; +} + +// Surface normal. +vec3 getNormal(in vec3 p) { + + const float eps = 0.001; + return normalize(vec3( + map(vec3(p.x+eps,p.y,p.z))-map(vec3(p.x-eps,p.y,p.z)), + map(vec3(p.x,p.y+eps,p.z))-map(vec3(p.x,p.y-eps,p.z)), + map(vec3(p.x,p.y,p.z+eps))-map(vec3(p.x,p.y,p.z-eps)) + )); + +} + + +// Cool curve function, by Shadertoy user, Nimitz. +// +// I think it's based on a discrete finite difference approximation to the continuous +// Laplace differential operator? Either way, it gives you the curvature of a surface, +// which is pretty handy. +// +// From an intuitive sense, the function returns a weighted difference between a surface +// value and some surrounding values. Almost common sense... almost. :) If anyone +// could provide links to some useful articles on the function, I'd be greatful. +// +// Original usage (I think?) - Xyptonjtroz: https://www.shadertoy.com/view/4ts3z2 +float curve(in vec3 p, in float w){ + + vec2 e = vec2(-1., 1.)*w; + + float t1 = map(p + e.yxx), t2 = map(p + e.xxy); + float t3 = map(p + e.xyx), t4 = map(p + e.yyy); + + return 1.0/(w*w+0.004) *(t1 + t2 + t3 + t4 - 4.*map(p)); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ){ + iTime = iTime * modifier; + + // Screen coordinates. + vec2 uv = (fragCoord - iResolution.xy*0.5)/iResolution.y; + + // Camera Setup. + vec3 lookAt = vec3(0.0, 0.0, iTime); // "Look At" position. + vec3 camPos = lookAt + vec3(0.0, 0.1, -0.5); // Camera position, doubling as the ray origin. + + // Light positioning. One is a little behind the camera, and the other is further down the tunnel. + vec3 light_pos = camPos + vec3(0.0, 0.125, -0.125);// Put it a bit in front of the camera. + + // Using the Z-value to perturb the XY-plane. + // Sending the camera, "look at," and two light vectors down the tunnel. The "path" function is + // synchronized with the distance function. Change to "path2" to traverse the other tunnel. + lookAt.xy += path(lookAt.z); + camPos.xy += path(camPos.z); + light_pos.xy += path(light_pos.z); + + // Using the above to produce the unit ray-direction vector. + float FOV = PI/3.; // FOV - Field of view. + vec3 forward = normalize(lookAt-camPos); + vec3 right = normalize(vec3(forward.z, 0., -forward.x )); + vec3 up = cross(forward, right); + + // rd - Ray direction. + vec3 rd = normalize(forward + FOV*uv.x*right + FOV*uv.y*up); + + // Swiveling the camera from left to right when turning corners. + rd.xy *= rot2( -path(lookAt.z).x/32. ); + + // Standard ray marching routine. I find that some system setups don't like anything other than + // a "break" statement (by itself) to exit. + float t = 0.0, dt; + for(int i=0; i<64; i++){ + dt = map(camPos + rd*t); + if( t>150.){ break; } + t += dt*0.75; + } + + // The final scene color. Initated to black. + vec3 sceneCol = vec3(0.); + + // The ray has effectively hit the surface, so light it up. + if(dt<0.005){ + + t += dt; + + // Surface position and surface normal. + vec3 sp = t * rd+camPos; + vec3 sn = getNormal(sp); + + // Light direction vectors. + vec3 ld = light_pos-sp; + + // Distance from respective lights to the surface point. + float distlpsp = max(length(ld)*0.1, 2.0); + + + // Normalize the light direction vectors. + ld /= distlpsp; + // Light attenuation, based on the distances above. + float atten = min(1./(distlpsp), 1.); + + // Ambient light. + float ambience = 0.25; + + // Diffuse lighting. + float diff = max( dot(sn, ld), 0.0); + + + // Specular lighting. + float spec = pow(max( dot( reflect(-ld, sn), -rd ), 0.0 ), 8.); + + // Curvature. + float crv = clamp(curve(sp, 0.125)*0.5+0.5, .0, 1.); + + // Fresnel term. Good for giving a surface a bit of a reflective glow. + float fre = pow( clamp(dot(sn, rd) + 1., .0, 1.), 0.1); + + // Darkening the crevices. Otherwise known as cheap, scientifically-incorrect shadowing. + float shading = crv*0.5+0.5; + + // Combining the above terms to produce the final color. It was based more on acheiving a + // certain aesthetic than science. + // + // Glow. + + // Shading. + + float gridValue = clamp(4.0*cos(iTime*0.412),-1.0,1.0); + + sceneCol = atten * vec3( fre*crv*4. ) * vec3(0.1*-gridValue,0.8+0.3 * gridValue,1.0 * - gridValue); + + // Drawing the lines on the walls. Comment this out and change the first texture to + // granite for a granite corridor effect. + sceneCol *= clamp((gridValue + 1.0 )*abs(curve(sp, 0.0125)) - gridValue * (1.0 - abs(curve(sp, 0.0125))), .0, 1.); + + + } + + fragColor = vec4(clamp(sceneCol, 0., 1.), 1.0); + +} + +void main(void) { + mainImage(gl_FragColor, gl_FragCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/feedback.fs b/src/renderer/src/application/sample-modules/isf/feedback.fs new file mode 100644 index 000000000..f955dcd4c --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/feedback.fs @@ -0,0 +1,56 @@ +/*{ + "DESCRIPTION": "RGB GLitchMod", + "CREDIT": "by dantheman", + "CATEGORIES": [ + "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "offset", + "TYPE": "float", + "MIN":0.0, + "MAX":0.5, + "DEFAULT": 0.25 + }, + { + "NAME": "offset_right", + "TYPE": "float", + "MIN":0.0, + "MAX":0.1, + "DEFAULT": 0 + }, + { + "NAME": "mix_var", + "TYPE":"float", + "MIN":0.0, + "MAX":1.0, + "DEFAULT": 1.0 + } + ], + "PERSISTENT_BUFFERS": [ + "one" + ], + "PASSES": [ + { + "TARGET":"one", + "WIDTH": "$WIDTH", + "HEIGHT": "$HEIGHT", + "DESCRIPTION": "buffer" + } + ] + +}*/ + + +void main() { + + vec2 pos = isf_FragNormCoord; + vec4 old = IMG_NORM_PIXEL(one, pos); + vec4 new = IMG_NORM_PIXEL(inputImage, pos); + + gl_FragColor = (new + old * mix_var * 10.0) / (1.0 + mix_var * 10.0); +} diff --git a/src/renderer/src/application/sample-modules/isf/film-grain.fs b/src/renderer/src/application/sample-modules/isf/film-grain.fs new file mode 100644 index 000000000..e431e8be4 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/film-grain.fs @@ -0,0 +1,54 @@ +/* +{ + "CATEGORIES" : [ + "XXX" + ], + "DESCRIPTION" : "", + "INPUTS" : [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME" : "strength", + "TYPE" : "float", + "DEFAULT" : 16.0, + "MIN": 0.0, + "MAX": 50.0, + "LABEL" : "Strength" + }, + { + "NAME": "secondaryOperation", + "TYPE": "long", + "VALUES": [ + 0, + 1 + ], + "LABELS": [ + "One", + "Two" + ], + "DEFAULT": 0 + } + ], + "CREDIT" : "" +} +*/ + +void main() { + vec2 uv = gl_FragCoord.xy; + vec4 color = IMG_THIS_PIXEL(inputImage); + + float x = (uv.x + 4.0) * (uv.y + 4.0) * TIME; + vec4 grain = vec4(mod((mod(x, 13.0) + 1.0) * (mod(x, 123.0) + 1.0), 0.01)-0.005) * strength; + + // if(abs(uv.x - 0.5) < 0.002) + // color = vec4(0.0); + + if(secondaryOperation == 0) { + grain = 1.0 - grain; + gl_FragColor = color * grain; + } else { + gl_FragColor = color + grain; + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/hexagons.fs b/src/renderer/src/application/sample-modules/isf/hexagons.fs new file mode 100644 index 000000000..2490def1a --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/hexagons.fs @@ -0,0 +1,266 @@ +/*{ + "CREDIT": "by sheltron3030", + "DESCRIPTION": "", + "CATEGORIES": [ + "XXX" + ], + "INPUTS": [ + { + "NAME": "noise", + "TYPE": "image" + }, + { + "NAME" : "termThres", + "TYPE" : "float", + "DEFAULT" :5, + "MIN" : 0, + "MAX" : 10 + }, + { + "NAME" : "step_p", + "TYPE" : "float", + "DEFAULT" :0.5, + "MIN" : 0, + "MAX" : 1 + }, + { + "NAME" : "rot_z", + "TYPE" : "float", + "DEFAULT" :0.5, + "MIN" : 0, + "MAX" : 6.28 + }, + { + "NAME" : "squiggle", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : -1, + "MAX" : 1 + }, + { + "NAME" : "repeat", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : -1, + "MAX" : 5 + }, + { + "NAME" : "speed", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : -1, + "MAX" : 2 + }, + { + "NAME" : "hex_1", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : 0, + "MAX" : 1 + }, + { + "NAME" : "hex_2", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : 0, + "MAX" : 1 + }, + { + "NAME" : "hex_depth", + "TYPE" : "float", + "DEFAULT" : 0.5, + "MIN" : 0, + "MAX" : 1 + }, + { + "NAME" : "offset_cam", + "TYPE" : "float", + "DEFAULT" : 0, + "MIN" : -1, + "MAX" : 1 + } + ], + "PERSISTENT_BUFFERS": [ + "buffer" + ], + "PASSES": [ + { + "TARGET" : "buffer" + }, + { } + ] +}*/ + +#define MAX_ITER 40 + +vec3 filter() { + vec2 delta = 1. / RENDERSIZE; + + vec3 val = IMG_NORM_PIXEL(buffer, vv_FragNormCoord.xy).xyz; + + + vec3 l = IMG_NORM_PIXEL(buffer, vv_FragNormCoord.xy + vec2(0., delta.y)).xyz; + vec3 r = IMG_NORM_PIXEL(buffer, vv_FragNormCoord.xy - vec2(0., delta.y)).xyz; + vec3 u = IMG_NORM_PIXEL(buffer, vv_FragNormCoord.xy + vec2(delta.x, 0.)).xyz; + vec3 d = IMG_NORM_PIXEL(buffer, vv_FragNormCoord.xy - vec2(delta.x, 0.)).xyz; + + vec3 n = IMG_NORM_PIXEL(noise, fract(vv_FragNormCoord.xy * 2. + TIME )).xyz - 0.5; + + vec3 bloom = max(val, max(max(l, r), max( u, d))) * 1.5; + // bloom = bloom + l + r + u + d; + // bloom /= 5.; // orlando; + return bloom + n/9.; + +} + + mat3 rotationMatrix(vec3 axis, float angle) { + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c); + } + + vec3 opRep( vec3 p, vec3 c ) + { + vec3 q = mod(p, c) - 0.5 * c; + + return q; + } + + float sdCappedCylinder( vec3 p, vec2 h ) + { + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); + } + + float opS( float d1, float d2 ) + { + return max(-d1,d2); + } + + float hex(vec3 p, vec2 h) { + vec3 q = abs(p); + return max(q.z-h.y, max((q.x*0.866025+q.y*0.5),q.y) - h.x); + } + float sdTriPrism( vec3 p, vec2 h ) + { + vec3 q = abs(p); + return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5); + } + vec2 polar(vec2 c) { + return vec2(atan(c.y, c.x), length(c)); + } + vec2 cart(vec2 p) { + return p.y * vec2(cos(p.x), sin(p.x)); + } + + + +vec2 DE(vec3 pos) { + + pos = pos * rotationMatrix(normalize(vec3(0., 0., 1.)), rot_z + TIME/10. + squiggle * sin(pos.z) ); + + // pos -= vec3(0., sin(TIME), cos(TIME)); + pos = opRep(pos, vec3(repeat)); + + + pos.xy = polar(pos.xy); + pos.x += 5.; + pos.xy = cart(pos.xy); + + float a = hex(pos, vec2(hex_1 * repeat, hex_depth/2.)); + float b = hex(pos, vec2(hex_2 * hex_1 * repeat, hex_depth)); + + // float b = sdTriPrism(pos - vec3(-1., 0., 0.), vec2(0.6, 1.)); + float c = sdTriPrism(pos - vec3(0., 0., 0.), vec2(0.6, 1.)); + + float d = max(a,-b); + + return vec2(pos.z, d); +} + +vec3 gradient(vec3 p, float t) { + vec2 e = vec2(0., t); + + return normalize( + vec3( + DE(p+e.yxx).y - DE(p-e.yxx).y, + DE(p+e.xyx).y - DE(p-e.xyx).y, + DE(p+e.xxy).y - DE(p-e.xxy).y + ) + ); + } + +vec3 palette( in float t) +{ + vec3 a = vec3(1.4, 0.5, 0.5); + vec3 b = vec3(0.8, 0.3, 0.3); + vec3 c = vec3(1.31, 1.3, 0.1); + vec3 d = vec3(0.50, 0.20, 1.6); + + return a + b*cos( 6.28318*(c*t+d) ); +} + +vec3 raycast() { + + vec3 camera = vec3( 0., 0., TIME * speed ); + vec2 screenPos = -1.0 + 2.0 * gl_FragCoord.xy /RENDERSIZE; + screenPos.x *= RENDERSIZE.x / RENDERSIZE.y; + vec2 n = IMG_NORM_PIXEL(noise, fract(vv_FragNormCoord.xy + TIME * 10.)).xy - 0.5; + + // screenPos += n/100.; + vec3 ray = normalize(vec3( screenPos, 1.0)); + float thresh = exp(-termThres); + + + // raycasting parameter + float t = 0.; + vec3 point; + int iter = 0; + bool hit = false; + vec2 dist; + // ray stepping + for(int i = 0; i < MAX_ITER; i++) { + point = camera + ray * t; + dist = DE(point); + + thresh = exp(-termThres) * exp(t/4.); + + if (abs(dist.y) < thresh ) { + hit = true; + break; + } + + + t += dist.y * step_p ; + iter ++; + + } + + float shade = dot(gradient(point, 0.01 ), -ray); + float ao = 1. - float(iter) / float(MAX_ITER); + + vec3 color = vec3(0.); + + if ( hit ) + color = palette(point.z/5.) * sqrt(ao); + return color; + + +} + +void main() { + + + if (PASSINDEX == 0) { + gl_FragColor = vec4(raycast(), 1.0); + + } else if (PASSINDEX == 1) { + gl_FragColor = vec4(filter(), 1.0); + } + + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/plasma.fs b/src/renderer/src/application/sample-modules/isf/plasma.fs new file mode 100644 index 000000000..08aa9f75b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/plasma.fs @@ -0,0 +1,47 @@ +/* +{ + "CATEGORIES" : [ + "XXX" + ], + "DESCRIPTION" : "", + "INPUTS" : [ + { + "NAME" : "timeScale", + "TYPE" : "float", + "DEFAULT" : 0.18, + "MIN": 0.001, + "MAX": 2.0, + "LABEL" : "Time Scale" + }, + { + "NAME": "uScale", + "TYPE": "point2D", + "LABEL" : "Scale", + "DEFAULT": [ + 10.5, + 10.5 + ], + "MAX": 50.0, + "MIN": 0.001 + } + ], + "CREDIT" : "" +} +*/ + +const float PI = 3.1415926535897932384626433832795; + +void main() { + float time = TIME / timeScale; + vec2 scale = vec2(RENDERSIZE.x / uScale.x, RENDERSIZE.y / uScale.y); + float v = 0.0; + vec2 c = isf_FragNormCoord * scale - scale/2.0; + v += sin((c.x+time)); + v += sin((c.y+time)/2.0); + v += sin((c.x+c.y+time)/2.0); + c += scale/2.0 * vec2(sin(time/3.0), cos(time/2.0)); + v += sin(sqrt(c.x*c.x+c.y*c.y+1.0)+time); + v = v/2.0; + vec3 col = vec3(1, sin(PI*v), cos(PI*v)); + gl_FragColor = vec4(col*.5 + .5, 1); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/rgbglitchmod.fs b/src/renderer/src/application/sample-modules/isf/rgbglitchmod.fs new file mode 100644 index 000000000..52fe60843 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/rgbglitchmod.fs @@ -0,0 +1,42 @@ +/*{ + "DESCRIPTION": "RGB GLitchMod", + "CREDIT": "by dantheman", + "CATEGORIES": [ + "Distortion Effect" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + } + ], + "PERSISTENT_BUFFERS": [ + "one" + ], + "PASSES": [ + { + "TARGET":"one", + "WIDTH": "$WIDTH", + "HEIGHT": "$HEIGHT", + "DESCRIPTION": "buffer" + } + ] + +}*/ + + + +void main() +{ + vec2 uv = isf_FragNormCoord.xy; + vec2 texCoord = uv; + // if this is the first pass, i'm going to read the position from the "lastPosition" image, and write a new position based on this and the hold variables + + vec4 srcPixel = IMG_NORM_PIXEL(inputImage,texCoord); + vec4 newPixel = IMG_NORM_PIXEL(one, texCoord); + srcPixel = mod((srcPixel+newPixel), vec4(1.0)); + srcPixel *= 0.50; + + // i'm only using the X, which is the last render time we reset + gl_FragColor = vec4(srcPixel.rgb, 1.0); +} diff --git a/src/renderer/src/application/sample-modules/isf/rgbtimeglitch.fs b/src/renderer/src/application/sample-modules/isf/rgbtimeglitch.fs new file mode 100644 index 000000000..42668d70d --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/rgbtimeglitch.fs @@ -0,0 +1,462 @@ +/*{ + "DESCRIPTION": "Buffers 8 recent frames", + "CREDIT": "by VIDVOX", + "CATEGORIES": [ + "Glitch" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "inputDelay", + "LABEL": "Buffer", + "TYPE": "color", + "DEFAULT": [ + 0.25, + 0.5, + 0.75, + 0.5 + ] + }, + { + "NAME": "inputRate", + "LABEL": "Buffer Lag", + "TYPE": "float", + "MIN": 1.0, + "MAX": 20.0, + "DEFAULT": 4.0 + }, + { + "NAME": "glitch_size", + "LABEL": "Size", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.5, + "DEFAULT": 0.1 + }, + { + "NAME": "glitch_horizontal", + "LABEL": "Horizontal Amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.2 + }, + { + "NAME": "glitch_vertical", + "LABEL": "Vertical Amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "randomize_size", + "LABEL": "Randomize Size", + "TYPE": "bool", + "DEFAULT": 1.0 + }, + { + "NAME": "randomize_position", + "LABEL": "Randomize Position", + "TYPE": "bool", + "DEFAULT": 0.0 + }, + { + "NAME": "randomize_zoom", + "LABEL": "Randomize Zoom", + "TYPE": "bool", + "DEFAULT": 0.0 + } + ], + "PERSISTENT_BUFFERS": [ + "lastRow", + "buffer1", + "buffer2", + "buffer3", + "buffer4", + "buffer5", + "buffer6", + "buffer7", + "buffer8" + ], + "PASSES": [ + { + "TARGET":"lastRow", + "WIDTH:": 1, + "HEIGHT": 1, + "DESCRIPTION": "this buffer stores the last frame's odd / even state" + }, + { + "TARGET":"buffer8" + }, + { + "TARGET":"buffer7" + }, + { + "TARGET":"buffer6" + }, + { + "TARGET":"buffer5" + }, + { + "TARGET":"buffer4" + }, + { + "TARGET":"buffer3" + }, + { + "TARGET":"buffer2" + }, + { + "TARGET":"buffer1" + }, + { + + } + ] + +}*/ + + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + + +void main() +{ + // first pass: read the "buffer7" into "buffer8" + // apply lag on each pass + // if this is the first pass, i'm going to read the position from the "lastRow" image, and write a new position based on this and the hold variables + if (PASSINDEX == 0) { + vec4 srcPixel = IMG_PIXEL(lastRow,vec2(0.5)); + // i'm only using the X and Y components, which are the X and Y offset (normalized) for the frame + if (inputRate == 0.0) { + srcPixel.x = 0.0; + srcPixel.y = 0.0; + } + else if (inputRate <= 1.0) { + srcPixel.x = (srcPixel.x) > 0.5 ? 0.0 : 1.0; + srcPixel.y = 0.0; + } + else { + srcPixel.x = srcPixel.x + 1.0 / inputRate + srcPixel.y; + if (srcPixel.x > 1.0) { + srcPixel.y = mod(srcPixel.x, 1.0); + srcPixel.x = 0.0; + } + } + gl_FragColor = srcPixel; + } + if (PASSINDEX == 1) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer8); + } + } + else if (PASSINDEX == 2) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer7); + } + } + else if (PASSINDEX == 3) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer6); + } + } + else if (PASSINDEX == 4) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer5); + } + } + else if (PASSINDEX == 5) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer4); + } + } + else if (PASSINDEX == 6) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer3); + } + } + else if (PASSINDEX == 7) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer2); + } + } + else if (PASSINDEX == 8) { + vec4 lastRow = IMG_PIXEL(lastRow,vec2(0.5)); + if (lastRow.x == 0.0) { + gl_FragColor = IMG_THIS_NORM_PIXEL(inputImage); + } + else { + gl_FragColor = IMG_THIS_NORM_PIXEL(buffer1); + } + } + else if (PASSINDEX == 9) { + // Figure out which section I'm in and draw the appropriate buffer there + vec2 tex = isf_FragNormCoord; + vec4 color = vec4(0.0); + // figure out the "input delay shift" for this pixel... + float randomDelayShift = 0.0; + + vec2 xy; + xy.x = isf_FragNormCoord[0]; + xy.y = isf_FragNormCoord[1]; + + // quantize the xy to the glitch_amount size + //xy = floor(xy / glitch_size) * glitch_size; + vec2 random; + + float local_glitch_size = glitch_size; + float random_offset = 0.0; + + if (randomize_size) { + random_offset = mod(rand(vec2(TIME,TIME)), 1.0); + local_glitch_size = random_offset * glitch_size; + } + + if (local_glitch_size > 0.0) { + random.x = rand(vec2(floor(random_offset + xy.y / local_glitch_size) * local_glitch_size, TIME)); + random.y = rand(vec2(floor(random_offset + xy.x / local_glitch_size) * local_glitch_size, TIME)); + } + else { + random.x = rand(vec2(xy.x, TIME)); + random.y = rand(vec2(xy.y, TIME)); + } + + // if doing a horizontal glitch do a random shift + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + randomDelayShift = clamp(random.x + random.y, 0.0, 2.0); + } + else if (random.x < glitch_horizontal) { + randomDelayShift = clamp(random.x + random.y, 0.0, 2.0); + } + else if (random.y < glitch_vertical) { + randomDelayShift = clamp(random.x + random.y, 0.0, 2.0); + } + + vec4 pixelBuffer = randomDelayShift * inputDelay * 9.0; + + if (randomize_zoom) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + float level = (random.x + random.y) / 3.0 + 0.90; + tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + else if (random.x < glitch_horizontal) { + float level = (random.x) / 2.0 + 0.95; + tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + else if (random.y < glitch_vertical) { + float level = (random.y) / 2.0 + 0.95; + tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + inputDelay.r * random.x, 1.0); + tex.y = mod(tex.y + inputDelay.r * random.y, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + inputDelay.r * random.x, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + inputDelay.r * random.y, 1.0); + } + // apply small random zoom too + } + + if (pixelBuffer.r < 1.0) { + color.r = IMG_NORM_PIXEL(inputImage, tex).r; + } + else if (pixelBuffer.r < 2.0) { + color.r = IMG_NORM_PIXEL(buffer1, tex).r; + } + else if (pixelBuffer.r < 3.0) { + color.r = IMG_NORM_PIXEL(buffer2, tex).r; + } + else if (pixelBuffer.r < 4.0) { + color.r = IMG_NORM_PIXEL(buffer3, tex).r; + } + else if (pixelBuffer.r < 5.0) { + color.r = IMG_NORM_PIXEL(buffer4, tex).r; + } + else if (pixelBuffer.r < 6.0) { + color.r = IMG_NORM_PIXEL(buffer5, tex).r; + } + else if (pixelBuffer.r < 7.0) { + color.r = IMG_NORM_PIXEL(buffer6, tex).r; + } + else if (pixelBuffer.r < 8.0) { + color.r = IMG_NORM_PIXEL(buffer7, tex).r; + } + else { + color.r = IMG_NORM_PIXEL(buffer8, tex).r; + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + random.x * inputDelay.g, 1.0); + tex.y = mod(tex.y + random.y * inputDelay.g, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + random.x * inputDelay.g, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + random.y * inputDelay.g, 1.0); + } + // apply small random zoom too + //float level = inputDelay.g * random.x / 5.0 + 0.9; + //tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + + if (pixelBuffer.g < 1.0) { + color.g = IMG_NORM_PIXEL(inputImage, tex).g; + } + else if (pixelBuffer.g < 2.0) { + color.g = IMG_NORM_PIXEL(buffer1, tex).g; + } + else if (pixelBuffer.g < 3.0) { + color.g = IMG_NORM_PIXEL(buffer2, tex).g; + } + else if (pixelBuffer.g < 4.0) { + color.g = IMG_NORM_PIXEL(buffer3, tex).g; + } + else if (pixelBuffer.g < 5.0) { + color.g = IMG_NORM_PIXEL(buffer4, tex).g; + } + else if (pixelBuffer.g < 6.0) { + color.g = IMG_NORM_PIXEL(buffer5, tex).g; + } + else if (pixelBuffer.g < 7.0) { + color.g = IMG_NORM_PIXEL(buffer6, tex).g; + } + else if (pixelBuffer.g < 8.0) { + color.g = IMG_NORM_PIXEL(buffer7, tex).g; + } + else { + color.g = IMG_NORM_PIXEL(buffer8, tex).g; + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + random.x * inputDelay.b, 1.0); + tex.y = mod(tex.y + random.y * inputDelay.b, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + random.x * inputDelay.b, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + random.y * inputDelay.b, 1.0); + } + // apply small random zoom too + //float level = inputDelay.b * random.x / 5.0 + 0.9; + //tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + + if (pixelBuffer.b < 1.0) { + color.b = IMG_NORM_PIXEL(inputImage, tex).b; + } + else if (pixelBuffer.b < 2.0) { + color.b = IMG_NORM_PIXEL(buffer1, tex).b; + } + else if (pixelBuffer.b < 3.0) { + color.b = IMG_NORM_PIXEL(buffer2, tex).b; + } + else if (pixelBuffer.b < 4.0) { + color.b = IMG_NORM_PIXEL(buffer3, tex).b; + } + else if (pixelBuffer.b < 5.0) { + color.b = IMG_NORM_PIXEL(buffer4, tex).b; + } + else if (pixelBuffer.b < 6.0) { + color.b = IMG_NORM_PIXEL(buffer5, tex).b; + } + else if (pixelBuffer.b < 7.0) { + color.b = IMG_NORM_PIXEL(buffer6, tex).b; + } + else if (pixelBuffer.b < 8.0) { + color.b = IMG_NORM_PIXEL(buffer7, tex).b; + } + else { + color.b = IMG_NORM_PIXEL(buffer8, tex).b; + } + + if (randomize_position) { + if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { + tex.x = mod(tex.x + random.x * inputDelay.a, 1.0); + tex.y = mod(tex.y + random.y * inputDelay.a, 1.0); + } + else if (random.x < glitch_horizontal) { + tex.y = mod(tex.y + random.x * inputDelay.a, 1.0); + } + else if (random.y < glitch_vertical) { + tex.x = mod(tex.x + random.y * inputDelay.a, 1.0); + } + // apply small random zoom too + //float level = inputDelay.a * random.x / 5.0 + 0.9; + //tex = (tex - vec2(0.5))*(1.0/level) + vec2(0.5); + } + + if (pixelBuffer.a < 1.0) { + color.a = IMG_NORM_PIXEL(inputImage, tex).a; + } + else if (pixelBuffer.a < 2.0) { + color.a = IMG_NORM_PIXEL(buffer1, tex).a; + } + else if (pixelBuffer.a < 3.0) { + color.a = IMG_NORM_PIXEL(buffer2, tex).a; + } + else if (pixelBuffer.a < 4.0) { + color.a = IMG_NORM_PIXEL(buffer3, tex).a; + } + else if (pixelBuffer.a < 5.0) { + color.a = IMG_NORM_PIXEL(buffer4, tex).a; + } + else if (pixelBuffer.a < 6.0) { + color.a = IMG_NORM_PIXEL(buffer5, tex).a; + } + else if (pixelBuffer.a < 7.0) { + color.a = IMG_NORM_PIXEL(buffer6, tex).a; + } + else if (pixelBuffer.a < 8.0) { + color.a = IMG_NORM_PIXEL(buffer7, tex).a; + } + else { + color.a = IMG_NORM_PIXEL(buffer8, tex).a; + } + + gl_FragColor = color; + } +} diff --git a/src/renderer/src/application/sample-modules/isf/rotozoomer.fs b/src/renderer/src/application/sample-modules/isf/rotozoomer.fs new file mode 100644 index 000000000..f68df997b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/rotozoomer.fs @@ -0,0 +1,77 @@ +/* +{ + "CATEGORIES" : [ + "zoomer" + ], + "DESCRIPTION" : "Rotozoomer", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "rotationSpeed", + "TYPE" : "float", + "MAX" : 360, + "DEFAULT" : 45, + "MIN" : 0, + "LABEL" : "Rotation Speed" + }, + { + "NAME" : "zoomScale", + "TYPE" : "float", + "MAX" : 10, + "DEFAULT" : 3, + "LABEL" : "Zoom Scale", + "MIN" : 1 + }, + { + "NAME" : "timeScale", + "TYPE" : "float", + "MAX" : 3, + "DEFAULT" : 1, + "MIN" : -3 + } + ], + "PASSES" : [ + { + + } + ], + "CREDIT" : "LukasPukenis " +} +*/ + +vec3 iResolution = vec3(RENDERSIZE, 1.); + +float PI = 3.14; + +vec2 rotate(vec2 v, float angle) { + angle = angle * PI / 180.0; + return vec2(cos(angle)*v.x - v.y*sin(angle), + cos(angle)*v.y + v.x*sin(angle)); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + float time = TIME * timeScale; + float zoom = (2.0+sin(time))/zoomScale; + vec2 as = vec2(iResolution.y / iResolution.x, 1.0); + + vec2 zoomVec = vec2(zoom, zoom); + vec2 coords = fragCoord.xy / iResolution.xy; + coords -= vec2(0.5, 0.5); + coords /= as; + coords += vec2(1.0+sin(time), 1.0+sin(time)); + coords *= zoomVec; + + coords = rotate(coords, rotationSpeed * TIME); + + vec4 pixel = IMG_NORM_PIXEL(inputImage, mod(coords+vec2(0.5, 0.5), 1.0)); + fragColor = pixel; +} + +void main(void) { + mainImage(gl_FragColor, gl_FragCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/scale.fs b/src/renderer/src/application/sample-modules/isf/scale.fs new file mode 100644 index 000000000..fa53fdf5f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/scale.fs @@ -0,0 +1,36 @@ +/* +{ + "CATEGORIES" : [ + "transform" + ], + "DESCRIPTION" : "Scale", + "ISFVSN" : "2", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + }, + { + "NAME" : "scale", + "TYPE" : "float", + "MAX" : 2.0, + "DEFAULT" : 0.0, + "MIN" : -2.0 + } + ], + "CREDIT" : "2xAA" +} +*/ + +void main() { + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy; + + uv -= 0.5; + + uv.x = uv.x / (1.0 + scale); + uv.y = uv.y / (1.0 + scale); + + uv += 0.5; + + gl_FragColor = IMG_NORM_PIXEL(inputImage, uv); +} diff --git a/src/renderer/src/application/sample-modules/isf/spherical-shader-tut.fs b/src/renderer/src/application/sample-modules/isf/spherical-shader-tut.fs new file mode 100644 index 000000000..838ba9bfb --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/spherical-shader-tut.fs @@ -0,0 +1,324 @@ +/*{ + "DESCRIPTION": "This commented shader explains how transform the camera rays so that the output is rendered in equirectangular or fisheye format. Can be used in domes or other immersive environments. Hold mouse for equirectangular view and move mouse to rotate camera. ", + "CREDIT": "WilstonOreo", + "CATEGORIES": [ + "dome", + "raymarching", + "tutorial", + "fisheye", + "equirectangular" + ], + "INPUTS": [ + { + "LABEL": "Mouse X", + "NAME": "mX", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "LABEL": "Mouse Y", + "NAME": "mY", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "LABEL": "Mouse Z", + "NAME": "mZ", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "LABEL": "Mouse W", + "NAME": "mW", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + } + ] +}*/ + +vec3 iResolution = vec3(RENDERSIZE, 1.); +vec4 iMouse = vec4(mX*RENDERSIZE.x, mY*RENDERSIZE.y, mZ*RENDERSIZE.x, mW*RENDERSIZE.y); +float iTime = TIME; + +// Licensed under Creative Commons 3.0 Share Alike License +// Based on dila's ray marching tutorial: +// https://www.shadertoy.com/view/XdKGWm + +// (C) 2016-2017 by WilstonOreo http://omnido.me + + +#define MAP_EQUIRECTANGULAR 0 +#define MAP_FISHEYE 1 + +// Display equirectangular view when clicked. + +const float speed = 0.5; + + +int map_mode() { + return (iMouse.w > 0.0) ? MAP_FISHEYE : MAP_EQUIRECTANGULAR; +} + + +// Camera rotation with mouse + +float camera_yaw() { + return iMouse.x / iResolution.x * 360.0; +} + +float camera_pitch() { + return iMouse.y / iResolution.y * 360.0; +} + + +const float camera_roll = 0.0; +const float sphere_size = 0.25; + + +////////////////////////////////////////////////// +// Code section for spherical translation + +const float PI = 3.14159265358979323846264; + +/// Convert degrees to radians +float deg2rad(in float deg) +{ + return deg * PI / 180.0; +} + +/// Calculates the rotation matrix of a rotation around X axis with an angle in radians +mat3 rotateAroundX( in float angle ) +{ + float s = sin(angle); + float c = cos(angle); + return mat3(1.0,0.0,0.0, + 0.0, c, -s, + 0.0, s, c); +} + +// Calculates the rotation matrix of a rotation around Y axis with an angle in radians +mat3 rotateAroundY( in float angle ) +{ + float s = sin(angle); + float c = cos(angle); + return mat3( c,0.0, s, + 0.0,1.0,0.0, + -s,0.0, c); +} + +// Calculates the rotation matrix of a rotation around Z axis with an angle in radians +mat3 rotateAroundZ( in float angle ) +{ + float s = sin(angle); + float c = cos(angle); + return mat3( c, -s,0.0, + s, c,0.0, + 0.0,0.0,1.0); +} + +// Calculate rotation by given yaw and pitch angles (in degrees!) +mat3 rotationMatrix(in float yaw, in float pitch, in float roll) +{ + return rotateAroundZ(deg2rad(yaw)) * + rotateAroundY(deg2rad(-pitch)) * + rotateAroundX(deg2rad(roll)); +} + +// Get fisheye camera ray from screen coordinates +#ifdef MAP_FISHEYE +float fisheye_direction(out vec3 rd) +{ + // Move screen coordinates to the center, so + // it is bound to [-0.5,-0.5] and [0.5,0.5] + vec2 uv = gl_FragCoord.xy / iResolution.xy - vec2(0.5); + + // Calculate polar coordinates (angle phi and length) + float phi = atan(uv.x,uv.y); + float l = length(uv); + + if (l > 0.5) + { + // Return -1.0 because the calculated polar coordinates are + // outside the half sphere + return -1.0; + } + + // Calculate ray direction + float theta = l * PI; + rd = normalize(vec3(sin(theta)*cos(phi),sin(theta)*sin(phi),cos(theta))); + + // Formulas are on wikipedia: + // https://en.wikipedia.org/wiki/Polar_coordinate_system + return 1.0; +} +#endif + + +// Calculate camera ray in equirectangular direction from screen coordinates +#ifdef MAP_EQUIRECTANGULAR +float equirectangular_direction(out vec3 rd) +{ + vec2 uv = gl_FragCoord.xy / iResolution.xy; + + // Calculate azimuthal and polar angles from screen coordinates + float theta = uv.t * PI, + phi = uv.s * 2.0 * PI; + + // Calculate ray directions from polar and azimuthal angle + rd = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + + // formulas are on wikipedia: + // https://en.wikipedia.org/wiki/Spherical_coordinate_system + return 1.0; +} +#endif + + +// Calculate camera ray direction in equirectangular +float direction(out vec3 rd) +{ + // Select mapping mode based on input parameter +#ifdef MAP_EQUIRECTANGULAR + if (map_mode() == MAP_EQUIRECTANGULAR) + { + return equirectangular_direction(rd); + } +#endif +#ifdef MAP_FISHEYE + if (map_mode() == MAP_FISHEYE) + { + return fisheye_direction(rd); + } +#endif + return -1.0; +} + + +// Calculate camera ray with rotation +float direction(float roll, float pitch, float yaw, out vec3 rd) +{ + if (direction(rd) < 0.0) + { + return -1.0; + } + // Rotate the ray direction to have camera rotation with + // pitch, yaw and roll angles + rd *= rotateAroundZ(yaw)*rotateAroundY(pitch)*rotateAroundX(roll); + return 1.0; +} + +// END Code section for spherical translation +////////////////////////////////////////////////// + + + +////////////////////////////////////////////////// +// Code section from dila's raymarch tutorial + + +// Output resolution + +// Current TIME + +//map function, core of all the ray marching shaders. They return a scalar value, given a 3D point. +float map(vec3 p){ + //instancing: + // you transform the space so it's a repeating coordinate system + vec3 q = fract(p) * 2.0 -1.0; + + //sphere map function is the length of the point minus the radius + //it's negative on the inside of the sphere and positive on the outside and 0 on the surface. + float radius = sphere_size; + return length(q) - radius; +} + +//we use a numerical marching algorithim called trace +//o = origin +//r = ray to march along +//t = intersection along the ray +float trace(vec3 o, vec3 r){ + float t = 0.0; + for (int i =0; i <32; i++){ + //origin + ray*t = where we are along the ray; + // we step along the ray in variable length segments, + vec3 p = o+r*t; + //until we gradual converge on the intersection and evaluate the map function at that point + float d=map(p); + //we add that to t + // the smaller the 0.5 value, the less accurate the map function is + t += d * 0.5; + } + return t; +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + + //////////////////////////////////////// + // This commented out code section explains how setup + // a camera ray for standard perspective camera. + // For a fisheye or equirectangular camera, this is different. + // The calculation is done in the equirectangular_direction or + // fisheye_direction function. + // vec2 uv = fragCoord.xy / iResolution.xy; + //transform the cordinates to -1 to 1, instead of 0 to 1 + // uv = uv * 2.0 - 1.0; + // correct the aspect ratio + // uv.x *= iResolution.x / iResolution.y; + + //r = ray + // it needs to be normalized so it doesn't poke through the geometry when it's really close to the camera + //the z cordinate is 1.0, that's how you project the 2D coordinate into 3D space, + //you just decide the z value, which determines the field of view of the camera + // smaller z = higher fov. 1.0 = 90 degrees + //vec3 r = normalize(vec3(uv,1.0)); + + //rotation around the y axis + //you have to look up on wikipedia what this is + // float the= iTime*.25; + //r.xz *= mat2(cos(the), -sin(the), sin(the), cos(the)); + // END of commented out section for generating a perspective view camera ray + //////////////////////////////////////// + + // Spherical ray generation code + // This is entry point where the generation of + // camera ray in spherical direction happens! + ///////////////////////////////////////////////// + vec3 r; + // Calculate ray direction with camera rotation + if (direction( + deg2rad(camera_roll), + deg2rad(camera_pitch()), + deg2rad(camera_yaw()),r) != 1.0) { + // Transparent pixel if ray direction is not valid for screen coordinates + fragColor = vec4(0.0,0.0,0.0,0.0); + return; + } + + // the sphere is at (0.0,0.0,0.0) + vec3 o = vec3(0.0,0.0, iTime); + + //trace from the origin along the ray to find the intersection from our map function + float t = trace(o, r); + // simple fogging funcition to darken things the further away they are + float fog = 1.0 / (1.0 + t * t * 0.1); + + vec3 fc = vec3(fog); + + fragColor = vec4(fc,1.0); +} + + + + +void main(void) { + mainImage(gl_FragColor, gl_FragCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/st_Ms2SD1.fs.fs b/src/renderer/src/application/sample-modules/isf/st_Ms2SD1.fs.fs new file mode 100644 index 000000000..fc919f3d6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/st_Ms2SD1.fs.fs @@ -0,0 +1,252 @@ +/* +{ + "CATEGORIES": [ + "Automatically Converted" + ], + "DESCRIPTION": "Automatically converted from https://www.shadertoy.com/view/Ms2SD1", + "IMPORTED": [ + + ], + "INPUTS": [ + { + "NAME": "iMouse", + "TYPE": "point2D" + }, + { + "NAME": "SEA_FREQ", + "MIN": 0.0, + "MAX": 1.0, + "TYPE": "float", + "DEFAULT": 0.16 + }, + { + "NAME": "SEA_CHOPPY", + "MIN": 0.0, + "MAX": 8.0, + "TYPE": "float", + "DEFAULT": 4.0 + }, + { + "NAME": "SEA_HEIGHT", + "MIN": 0.0, + "MAX": 3.0, + "TYPE": "float", + "DEFAULT": 0.6 + }, + { + "NAME": "SEA_SPEED", + "MIN": 0.0, + "MAX": 2.0, + "TYPE": "float", + "DEFAULT": 0.8 + }, + { + "NAME": "SEA_BASE", + "TYPE": "color", + "DEFAULT": [ + 0.1, + 0.19, + 0.22, + 1.0 + ] + }, + { + "NAME": "SEA_WATER_COLOR", + "TYPE": "color", + "DEFAULT": [ + 0.8, + 0.9, + 0.6, + 1.0 + ] + } + ] +} +*/ + + +// "Seascape" by Alexander Alekseev aka TDM - 2014 +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +const int NUM_STEPS = 8; +const float PI = 3.1415; +const float EPSILON = 1e-3; +float EPSILON_NRM = 0.1 / RENDERSIZE.x; + +// sea +const int ITER_GEOMETRY = 3; +const int ITER_FRAGMENT = 5; +//const float SEA_HEIGHT = 0.6; +//const float SEA_CHOPPY = 4.0; +//const float SEA_SPEED = 0.8; +//const float SEA_FREQ = 0.16; +//const vec3 SEA_BASE = vec3(0.1,0.19,0.22); +//const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6); +float SEA_TIME = TIME * SEA_SPEED; +mat2 octave_m = mat2(1.6,1.2,-1.2,1.6); + +// math +mat3 fromEuler(vec3 ang) { + vec2 a1 = vec2(sin(ang.x),cos(ang.x)); + vec2 a2 = vec2(sin(ang.y),cos(ang.y)); + vec2 a3 = vec2(sin(ang.z),cos(ang.z)); + mat3 m; + m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x); + m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x); + m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y); + return m; +} +float hash( vec2 p ) { + float h = dot(p,vec2(127.1,311.7)); + return fract(sin(h)*43758.5453123); +} +float noise( in vec2 p ) { + vec2 i = floor( p ); + vec2 f = fract( p ); + vec2 u = f*f*(3.0-2.0*f); + return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ), + hash( i + vec2(1.0,0.0) ), u.x), + mix( hash( i + vec2(0.0,1.0) ), + hash( i + vec2(1.0,1.0) ), u.x), u.y); +} + +// lighting +float diffuse(vec3 n,vec3 l,float p) { + return pow(dot(n,l) * 0.4 + 0.6,p); +} +float specular(vec3 n,vec3 l,vec3 e,float s) { + float nrm = (s + 8.0) / (3.1415 * 8.0); + return pow(max(dot(reflect(e,n),l),0.0),s) * nrm; +} + +// sky +vec3 getSkyColor(vec3 e) { + e.y = max(e.y,0.0); + vec3 ret; + ret.x = pow(1.0-e.y,2.0); + ret.y = 1.0-e.y; + ret.z = 0.6+(1.0-e.y)*0.4; + return ret; +} + +// sea +float sea_octave(vec2 uv, float choppy) { + uv += noise(uv); + vec2 wv = 1.0-abs(sin(uv)); + vec2 swv = abs(cos(uv)); + wv = mix(wv,swv,wv); + return pow(1.0-pow(wv.x * wv.y,0.65),choppy); +} + +float map(vec3 p) { + float freq = SEA_FREQ; + float amp = SEA_HEIGHT; + float choppy = SEA_CHOPPY; + vec2 uv = p.xz; uv.x *= 0.75; + + float d, h = 0.0; + for(int i = 0; i < ITER_GEOMETRY; i++) { + d = sea_octave((uv+SEA_TIME)*freq,choppy); + d += sea_octave((uv-SEA_TIME)*freq,choppy); + h += d * amp; + uv *= octave_m; freq *= 1.9; amp *= 0.22; + choppy = mix(choppy,1.0,0.2); + } + return p.y - h; +} + +float map_detailed(vec3 p) { + float freq = SEA_FREQ; + float amp = SEA_HEIGHT; + float choppy = SEA_CHOPPY; + vec2 uv = p.xz; uv.x *= 0.75; + + float d, h = 0.0; + for(int i = 0; i < ITER_FRAGMENT; i++) { + d = sea_octave((uv+SEA_TIME)*freq,choppy); + d += sea_octave((uv-SEA_TIME)*freq,choppy); + h += d * amp; + uv *= octave_m; freq *= 1.9; amp *= 0.22; + choppy = mix(choppy,1.0,0.2); + } + return p.y - h; +} + +vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) { + float fresnel = 1.0 - max(dot(n,-eye),0.0); + fresnel = pow(fresnel,3.0) * 0.65; + + vec3 reflected = getSkyColor(reflect(eye,n)); + vec3 refracted = SEA_BASE.rgb + diffuse(n,l,80.0) * SEA_WATER_COLOR.rgb * 0.12; + + vec3 color = mix(refracted,reflected,fresnel); + + float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0); + color += SEA_WATER_COLOR.rgb * (p.y - SEA_HEIGHT) * 0.18 * atten; + + color += vec3(specular(n,l,eye,60.0)); + + return color; +} + +// tracing +vec3 getNormal(vec3 p, float eps) { + vec3 n; + n.y = map_detailed(p); + n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y; + n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y; + n.y = eps; + return normalize(n); +} + +float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) { + float tm = 0.0; + float tx = 1000.0; + float hx = map(ori + dir * tx); + if(hx > 0.0) return tx; + float hm = map(ori + dir * tm); + float tmid = 0.0; + for(int i = 0; i < NUM_STEPS; i++) { + tmid = mix(tm,tx, hm/(hm-hx)); + p = ori + dir * tmid; + float hmid = map(p); + if(hmid < 0.0) { + tx = tmid; + hx = hmid; + } else { + tm = tmid; + hm = hmid; + } + } + return tmid; +} + +// main +void main(){ + vec2 uv = gl_FragCoord.xy / RENDERSIZE.xy; + uv = uv * 2.0 - 1.0; + uv.x *= RENDERSIZE.x / RENDERSIZE.y; + float time = TIME * 0.3 + iMouse.x*0.01; + + // ray + vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time); + vec3 ori = vec3(0.0,3.5,time*5.0); + vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15; + dir = normalize(dir) * fromEuler(ang); + + // tracing + vec3 p; + heightMapTracing(ori,dir,p); + vec3 dist = p - ori; + vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM); + vec3 light = normalize(vec3(0.0,1.0,0.8)); + + // color + vec3 color = mix( + getSkyColor(dir), + getSeaColor(p,n,light,dir,dist), + pow(smoothstep(0.0,-0.05,dir.y),0.3)); + + // post + gl_FragColor = vec4(pow(color,vec3(0.75)), 1.0); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/st_lsfGDH.fs b/src/renderer/src/application/sample-modules/isf/st_lsfGDH.fs new file mode 100644 index 000000000..b3ba2ff56 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/st_lsfGDH.fs @@ -0,0 +1,48 @@ +/* +{ + "CATEGORIES" : [ + "zoomer" + ], + "DESCRIPTION" : "simple rotozoomer", + "INPUTS" : [ + { + "NAME" : "inputImage", + "TYPE" : "image" + } + ], + "ISFVSN" : "2", + "CREDIT" : "triggerHLM " +} +*/ + +float pi = 3.14159265; + +vec4 mainImage( vec2 fragCoord ) { + float time= TIME * 0.2; + + vec2 position = vec2(640.0/2.0+640.0/2.0*sin(time*2.0), 360.0/2.0+360.0/2.0*cos(time*3.0)); + vec2 position2 = vec2(640.0/2.0+640.0/2.0*sin((time+2000.0)*2.0), 360.0/2.0+360.0/2.0*cos((time+2000.0)*3.0)); + + + vec2 offset = vec2(640.0/2.0, 360.0/2.0) ; + vec2 offset2 = vec2(6.0*sin(time*1.1), 3.0*cos(time*1.1)); + + vec2 oldPos = (fragCoord.xy); + + float angle = time*2.0; + + vec2 newPos = vec2(oldPos.x *cos(angle) - oldPos.y *sin(angle), + oldPos.y *cos(angle) + oldPos.x *sin(angle)); + + + newPos = (newPos)*(0.0044+0.004*sin(time*3.0))-offset2; + vec2 temp = newPos; + newPos.x = temp.x + 0.4*sin(temp.y*2.0+time*8.0); + newPos.y = (-temp.y + 0.4*sin(temp.x*2.0+time*8.0)); + vec4 final = IMG_NORM_PIXEL(inputImage, newPos); + return final; +} + +void main(void) { + gl_FragColor = mainImage(gl_FragCoord.xy); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/tapestryfract.fs b/src/renderer/src/application/sample-modules/isf/tapestryfract.fs new file mode 100644 index 000000000..d39ff491b --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/tapestryfract.fs @@ -0,0 +1,58 @@ +/*{ + "CREDIT": "by echophons", + "DESCRIPTION": "", + "CATEGORIES": [ "generator" + ], + "INPUTS": [ + { + "NAME": "h", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + }, + { + "NAME": "j", + "TYPE": "float", + "DEFAULT": 0.5, + "MIN": 0.0, + "MAX": 1.0 + } + ] +}*/ + +// edit of http://glslsandbox.com/e#18752.0 +uniform vec2 mouse; + +vec3 iResolution = vec3(RENDERSIZE, 1.0); +float iGlobalTime = TIME; + +float gTime = iGlobalTime*0.5; + +void main( void ) +{ + float f = 3.0; + float g = 3.0; + vec2 res = iResolution.xy; + vec2 mou = mouse.xy; + + //if (mouse.x < 0.5) + //{ + mou.x = sin(gTime * .3)*sin(gTime * .17) * 1. + sin(gTime * .3); + mou.y = (1.0-cos(gTime * .632))*sin(gTime * .131)*1.0+cos(gTime * .3); + mou = (mou+1.0) * res; + //} + vec2 z = ((-res+2.0 * gl_FragCoord.xy) / res.y); + vec2 p = ((-res+2.0+mou) / res.y) * j; + for( int i = 0; i < 25; i++) + { + float d = dot(z,z); + z = (vec2( z.x, -z.y ) / d) + p * h; + z.x = 1.0-abs(z.x); + f = max( f-d, (dot(z-p,z-p) )); + g = min( g*d, sin(dot(z+p,z+p))+1.0); + } + f = abs(-log(f) / 3.5); + g = abs(-log(g) / 8.0); + gl_FragColor = vec4(min(vec3(g, g*f, f), 1.0),1.0); +} diff --git a/src/renderer/src/application/sample-modules/isf/v002 Bleach Bypass.fs b/src/renderer/src/application/sample-modules/isf/v002 Bleach Bypass.fs new file mode 100644 index 000000000..4063ef569 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Bleach Bypass.fs @@ -0,0 +1,55 @@ +/*{ + "CATEGORIES": [ + "Color Adjustment", + "Film", + "v002" + ], + "CREDIT": "by v002", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "amount", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +// Based on v002 bleach bypass – https://github.com/v002/v002-Film-Effects/ + +//constant variables. +const vec4 one = vec4(1.0); +const vec4 two = vec4(2.0); +//const vec4 lumcoeff = vec4(0.2125,0.7154,0.0721,0.0); +const vec4 lumcoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + + +vec4 overlay(vec4 myInput, vec4 previousmix, vec4 amount) +{ + float luminance = dot(previousmix,lumcoeff); + float mixamount = clamp((luminance - 0.45) * 10., 0., 1.); + + vec4 branch1 = two * previousmix * myInput; + vec4 branch2 = one - (two * (one - previousmix) * (one - myInput)); + + vec4 result = mix(branch1, branch2, vec4(mixamount) ); + + return mix(previousmix, result, amount); +} + +void main (void) +{ + vec4 input0 = IMG_THIS_PIXEL(inputImage); + + vec4 luma = vec4(dot(input0,lumcoeff)); + + gl_FragColor = overlay(luma, input0, vec4(amount)); + +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Crosshatch.fs b/src/renderer/src/application/sample-modules/isf/v002 Crosshatch.fs new file mode 100644 index 000000000..46909abf8 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Crosshatch.fs @@ -0,0 +1,114 @@ +/*{ + "CREDIT": "by v002", + "ISFVSN": "2", + "CATEGORIES": [ + "Halftone Effect", "v002" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "invert", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.0 + }, + { + "NAME": "separation", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.5, + "DEFAULT": 0.25 + }, + { + "NAME": "greyscale", + "TYPE": "float", + "MIN": 0.0, + "MAX": 1.0, + "DEFAULT": 0.75 + }, + { + "NAME": "thickness", + "TYPE": "float", + "MIN": 0.5, + "MAX": 1.0, + "DEFAULT": 0.75 + }, + { + "NAME": "front", + "TYPE": "color", + "DEFAULT": [ + 0.5, + 0.0, + 0.25, + 1.0 + ] + }, + { + "NAME": "back", + "TYPE": "color", + "DEFAULT": [ + 0.2, + 0.75, + 0.5, + 1.0 + ] + } + ] +}*/ + + +//Original Source +//http://learningwebgl.com/blog/?p=2858 + +// Based on https://github.com/v002/v002-Half-Tones/ + + + + +const vec4 lumacoeff = vec4(0.2126, 0.7152, 0.0722, 0.0); + + +void main() +{ + vec4 color = IMG_THIS_PIXEL(inputImage); + float lum = dot(color, lumacoeff); + vec2 coord = gl_FragCoord.xy; + + vec4 colora = mix(front, back, invert); + vec4 colorb = mix(back, front, invert); + + colora.a *= color.a; + colorb.a *= color.a; + + vec4 cout = mix(color,colora, greyscale); + + gl_FragColor = colorb; + + if (lum > 1.00) + { + if (mod(coord.x + coord.y, thickness) >= separation) + gl_FragColor = cout; + } + + if (lum > 0.75) + { + if (mod(coord.x - coord.y, thickness) >= separation ) + gl_FragColor = cout; + } + + if (lum > 0.50) + { + if (mod(coord.x + coord.y - (thickness * 0.5), thickness) >= separation) + gl_FragColor = cout; + } + + if (lum > 0.3) + { + if (mod(coord.x - coord.y - (thickness * 0.5), thickness) >= separation) + gl_FragColor = cout; + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Dilate.fs b/src/renderer/src/application/sample-modules/isf/v002 Dilate.fs new file mode 100644 index 000000000..ff24183ea --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Dilate.fs @@ -0,0 +1,58 @@ +/*{ + "DESCRIPTION": "messed up version of v002 dilate", + "CREDIT": "by carter rosenberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur", "v002" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.1, + "DEFAULT": 0.01 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 texcoord0; +varying vec2 texcoord1; +varying vec2 texcoord2; +varying vec2 texcoord3; +varying vec2 texcoord4; +varying vec2 texcoord5; +varying vec2 texcoord6; +varying vec2 texcoord7; +#else +in vec2 texcoord0; +in vec2 texcoord1; +in vec2 texcoord2; +in vec2 texcoord3; +in vec2 texcoord4; +in vec2 texcoord5; +in vec2 texcoord6; +in vec2 texcoord7; +#endif + +void main() +{ + + vec4 dilate = IMG_NORM_PIXEL(inputImage, 0.5 * (texcoord3 + texcoord4)); + + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord0)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord1)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord2)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord3)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord4)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord5)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord6)); + dilate = max(dilate, IMG_NORM_PIXEL(inputImage, texcoord7)); + + gl_FragColor = dilate; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Dilate.vs b/src/renderer/src/application/sample-modules/isf/v002 Dilate.vs new file mode 100644 index 000000000..d47984dea --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Dilate.vs @@ -0,0 +1,40 @@ + +#if __VERSION__ <= 120 +varying vec2 texcoord0; +varying vec2 texcoord1; +varying vec2 texcoord2; +varying vec2 texcoord3; +varying vec2 texcoord4; +varying vec2 texcoord5; +varying vec2 texcoord6; +varying vec2 texcoord7; +#else +out vec2 texcoord0; +out vec2 texcoord1; +out vec2 texcoord2; +out vec2 texcoord3; +out vec2 texcoord4; +out vec2 texcoord5; +out vec2 texcoord6; +out vec2 texcoord7; +#endif + +void main() +{ + isf_vertShaderInit(); + // perform standard transform on vertex + //gl_Position = ftransform(); + + // transform texcoord + vec2 texcoord = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + + // get sample positions + texcoord0 = texcoord + vec2(-amount, -amount); + texcoord1 = texcoord + vec2( 0, -amount); + texcoord2 = texcoord + vec2( amount, -amount); + texcoord3 = texcoord + vec2(-amount, 0); + texcoord4 = texcoord + vec2( amount, 0); + texcoord5 = texcoord + vec2(-amount, amount); + texcoord6 = texcoord + vec2( 0, amount); + texcoord7 = texcoord + vec2( amount, amount); +} diff --git a/src/renderer/src/application/sample-modules/isf/v002 Erode.fs b/src/renderer/src/application/sample-modules/isf/v002 Erode.fs new file mode 100644 index 000000000..b25228cef --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Erode.fs @@ -0,0 +1,57 @@ +/*{ + "DESCRIPTION": "messed up version of v002 erode", + "CREDIT": "by carter rosenberg", + "ISFVSN": "2", + "CATEGORIES": [ + "Blur", "v002" + ], + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "amount", + "TYPE": "float", + "MIN": 0.0, + "MAX": 0.1, + "DEFAULT": 0.01 + } + ] +}*/ + +#if __VERSION__ <= 120 +varying vec2 texcoord0; +varying vec2 texcoord1; +varying vec2 texcoord2; +varying vec2 texcoord3; +varying vec2 texcoord4; +varying vec2 texcoord5; +varying vec2 texcoord6; +varying vec2 texcoord7; +#else +in vec2 texcoord0; +in vec2 texcoord1; +in vec2 texcoord2; +in vec2 texcoord3; +in vec2 texcoord4; +in vec2 texcoord5; +in vec2 texcoord6; +in vec2 texcoord7; +#endif + +void main() +{ + vec4 erode = IMG_NORM_PIXEL(inputImage, 0.5 * (texcoord3 + texcoord4)); + + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord0)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord1)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord2)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord3)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord4)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord5)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord6)); + erode = min(erode, IMG_NORM_PIXEL(inputImage, texcoord7)); + + gl_FragColor = erode; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Erode.vs b/src/renderer/src/application/sample-modules/isf/v002 Erode.vs new file mode 100644 index 000000000..d3fe13ea5 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Erode.vs @@ -0,0 +1,40 @@ + +#if __VERSION__ <= 120 +varying vec2 texcoord0; +varying vec2 texcoord1; +varying vec2 texcoord2; +varying vec2 texcoord3; +varying vec2 texcoord4; +varying vec2 texcoord5; +varying vec2 texcoord6; +varying vec2 texcoord7; +#else +out vec2 texcoord0; +out vec2 texcoord1; +out vec2 texcoord2; +out vec2 texcoord3; +out vec2 texcoord4; +out vec2 texcoord5; +out vec2 texcoord6; +out vec2 texcoord7; +#endif + +void main() +{ + isf_vertShaderInit(); + // perform standard transform on vertex + //gl_Position = ftransform(); + + // transform texcoord + vec2 texcoord = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + + // get sample positions + texcoord0 = texcoord + vec2(-amount, -amount); + texcoord1 = texcoord + vec2( 0, -amount); + texcoord2 = texcoord + vec2( amount, -amount); + texcoord3 = texcoord + vec2(-amount, 0); + texcoord4 = texcoord + vec2( amount, 0); + texcoord5 = texcoord + vec2(-amount, amount); + texcoord6 = texcoord + vec2( 0, amount); + texcoord7 = texcoord + vec2( amount, amount); +} diff --git a/src/renderer/src/application/sample-modules/isf/v002 Light Leak.fs b/src/renderer/src/application/sample-modules/isf/v002 Light Leak.fs new file mode 100644 index 000000000..4818d9d26 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Light Leak.fs @@ -0,0 +1,83 @@ +/*{ + "CATEGORIES": [ + "Film", + "Stylize", + "v002" + ], + "CREDIT": "by v002", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "NAME": "leakImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1.5, + "MIN": 0, + "NAME": "amount", + "TYPE": "float" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "length", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 360, + "MIN": 0, + "NAME": "angle", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +// rotation matrix +#if __VERSION__ <= 120 +varying mat2 rotmat; +#else +in mat2 rotmat; +#endif + + + +void main (void) +{ + // normalized point 0 - 1 texcoords + vec2 point = isf_FragNormCoord; + // our normal image. + vec4 input0 = IMG_NORM_PIXEL(inputImage, point); + // rotate sampling point + point = ((point - 0.5) * rotmat) + 0.5; + //point = clamp(point, 0.0, 1.0); + + // this adjusts the length of the leak + float leakIntensity = pow(point.y, 1.0 + ((1.0 - length) * 19.0)); + + // this adjusts the gamma/brightness of the overall effect. + leakIntensity = pow(leakIntensity, 1.0 / amount); + + // sample the leak // how do we want to handle edge texcoords during rotation? + if (point.x < 0.0) { + point.x = abs(point.x); + } + if (point.x > 1.0) { + point.x = 2.0 - point.x; + } + + vec4 leak = IMG_NORM_PIXEL(leakImage, vec2(mod(point.x,1.0),0.0)); + + leak = pow(leak * leakIntensity, vec4(1.0/(leakIntensity))); // - vec2(0.5, 0.0); + leak += input0; + + gl_FragColor = mix(input0, leak, amount); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Light Leak.vs b/src/renderer/src/application/sample-modules/isf/v002 Light Leak.vs new file mode 100644 index 000000000..c78098f63 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Light Leak.vs @@ -0,0 +1,21 @@ + +#if __VERSION__ <= 120 +varying mat2 rotmat; +#else +out mat2 rotmat; +#endif + +void main() +{ + isf_vertShaderInit(); + + // setup basic rotation matrix here + float theta = radians(angle); + + // dont need to compute this more than once.. + float c = cos(theta); + float s = sin(theta); + + // rotation matrix + rotmat = mat2(c,s,-s,c); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Technicolor.fs b/src/renderer/src/application/sample-modules/isf/v002 Technicolor.fs new file mode 100644 index 000000000..7c693b429 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Technicolor.fs @@ -0,0 +1,109 @@ +/*{ + "CATEGORIES": [ + "Stylize", + "Film", + "Color Effect", + "v002" + ], + "CREDIT": "by v002", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "amount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABELS": [ + "Strip additive", + "Strip subtractive", + "Strip matte" + ], + "NAME": "style", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2 + ] + } + ], + "ISFVSN": "2" +} +*/ + + +// Based on v002 technicolor – https://github.com/v002/v002-Film-Effects/ + +const vec4 redfilter1 = vec4(1.0, 0.0, 0.0, 1.0); +const vec4 bluegreenfilter1 = vec4(0.0, 1.0, 0.7, 1.0); + +const vec4 redfilter2 = vec4(1.0, 0.0, 0.0, 0.0); +const vec4 bluegreenfilter2 = vec4(0.0, 1.0, 1.0, 0.0); + +const vec4 cyanfilter = vec4(0.0, 1.0, 0.5, 0.0); +const vec4 magentafilter = vec4(1.0, 0.0, 0.25, 0.0); + + + +void main (void) +{ + vec4 input0 = IMG_THIS_PIXEL(inputImage); + vec4 result; + + if (style == 0) { + vec4 redrecord = input0 * redfilter1; + vec4 bluegreenrecord = input0 * bluegreenfilter1; + vec4 rednegative = vec4(redrecord.r); + vec4 bluegreennegative = vec4((bluegreenrecord.g + bluegreenrecord.b)/2.0); + + vec4 redoutput = rednegative * redfilter1; + vec4 bluegreenoutput = bluegreennegative * bluegreenfilter1; + + // additive 'projection" + result = redoutput + bluegreenoutput; + + result = mix(input0, result, amount); + result.a = input0.a; + } + else if (style == 1) { + vec4 redrecord = input0 * redfilter2; + vec4 bluegreenrecord = input0 * bluegreenfilter2; + + vec4 rednegative = vec4(redrecord.r); + vec4 bluegreennegative = vec4((bluegreenrecord.g + bluegreenrecord.b)/2.0); + + vec4 redoutput = rednegative + cyanfilter; + vec4 bluegreenoutput = bluegreennegative + magentafilter; + + result = redoutput * bluegreenoutput; + + result = mix(input0, result, amount); + result.a = input0.a; + } + else if (style == 2) { + vec3 redmatte = vec3(input0.r - ((input0.g + input0.b)/2.0)); + vec3 greenmatte = vec3(input0.g - ((input0.r + input0.b)/2.0)); + vec3 bluematte = vec3(input0.b - ((input0.r + input0.g)/2.0)); + + redmatte = 1.0 - redmatte; + greenmatte = 1.0 - greenmatte; + bluematte = 1.0 - bluematte; + + vec3 red = greenmatte * bluematte * input0.r; + vec3 green = redmatte * bluematte * input0.g; + vec3 blue = redmatte * greenmatte * input0.b; + + result = vec4(red.r, green.g, blue.b, input0.a); + + result = mix(input0, result, amount); + result.a = input0.a; + } + gl_FragColor = result; +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002 Vignette.fs b/src/renderer/src/application/sample-modules/isf/v002 Vignette.fs new file mode 100644 index 000000000..9b3a61c4f --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002 Vignette.fs @@ -0,0 +1,66 @@ +/*{ + "CATEGORIES": [ + "Film", + "v002" + ], + "CREDIT": "by v002", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "vignette", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "vignetteEdge", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "MAX": 1, + "MIN": 0, + "NAME": "vignetteMix", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + + +// Based on v002 vignette – https://github.com/v002/v002-Film-Effects/ + + + +// create a black and white oval about the center of our image for our vignette +vec4 vignetteFucntion(vec2 normalizedTexcoord, float vignetteedge, float vignetteMix) +{ + normalizedTexcoord = 2.0 * normalizedTexcoord - 1.0; // - 1.0 to 1.0 + float r = length(normalizedTexcoord); + vec4 vignette = (vec4(smoothstep(0.0, 1.0, pow(clamp(r - vignetteMix, 0.0, 1.0), 1.0 + vignetteedge * 10.0)))); + return clamp(1.0 - vignette, 0.0, 1.0); +} + + + +void main (void) +{ + vec2 normcoord = vec2(isf_FragNormCoord[0],isf_FragNormCoord[1]); + + + // make a vignette around our borders. + vec4 vignetteResult = vignetteFucntion(normcoord, vignetteEdge, vignetteMix); + + // sharpen via unsharp mask (subtract image from blured image) + vec4 input0 = IMG_THIS_PIXEL(inputImage); + + gl_FragColor = mix(input0,vignetteResult * input0, vignette); +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002-CRT-Displacement.fs b/src/renderer/src/application/sample-modules/isf/v002-CRT-Displacement.fs new file mode 100755 index 000000000..f877c8e20 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002-CRT-Displacement.fs @@ -0,0 +1,55 @@ +/*{ + "CATEGORIES": [ + "Distortion Effect", + "Retro", + "v002" + ], + "CREDIT": "by vade", + "DESCRIPTION": "CRT Displacement, emulating the look of curved CRT Displays", + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "Amount", + "TYPE": "float" + } + ], + "ISFVSN": "2" +} +*/ + +void main (void) +{ + vec2 t1, t2; + vec2 ctr = RENDERSIZE / 2.0; + + t1 = gl_FragCoord.xy; + + float a = -0.0; + float b = -.1 * Amount; + float c = -.0; + float d = 1.0 - 1.1 * ( a + b + c ); + float r1, r2; + float unit = length(ctr) / 2.0; + + r1 = distance( t1, ctr )/unit; + r2 = r1 *( r1*( r1 * (a*r1 + b) + c) + d ); + float sc = step( 0.0 , r1) * ( r1/(r2 + .000001)) + (1.0 - step( 0.0 , r1)); + + t2 = ctr + ( t1 - ctr) * sc; + + gl_FragColor = IMG_PIXEL(inputImage, t2); + + if ((t2.x < 0.0) + ||(t2.y < 0.0) + ||(t2.x > RENDERSIZE.x) + ||(t2.y > RENDERSIZE.y)) + { + gl_FragColor = vec4(0.0); + } +} \ No newline at end of file diff --git a/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Shadow.png b/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Shadow.png new file mode 100755 index 0000000000000000000000000000000000000000..5dbf30c34349c71d38d1a28a60eacc642de82352 GIT binary patch literal 6658 zcmcgwc|6qJ_n($WNRKB{T11v7G0eVZY0MCnqNWhiWCoKlqnWX#>}x`{ER_~fN=jdA zB}MkFjVEM@P$^47h`x7*N}hWCzJLAZ^}1*7oO|Bqo^$TK@6Y{AsL9T)%f#1+Lm-f4 zMuwE#;9Ek_L`8ty=VH|h`ZY{4*~ExURxsgm?3gSD1Y#H(7cYjFv0hYOZ=HX-?|^2e zhE)@zR!Xc%I?npyWk#U2Mzsb;mNj@%`t;>q;>`66Xw7R%vQ3pv$Xn+>&UJHojybWK1Gt zz4+ya77|rf(-N5PL&?6G4+l{TC#cOd?_F+@TcS15ewoVBHn;CVnJJf|)gXhDx0+Ko zZJ)p^h13?cth;nEbBI)5_sOm0;tEI=@50vHF1Isp-lB{3iG zmIHEEa<2GYD3TIIP!@a?Ra-1q?0gonLb{G0-Q-hnx*OfMnzh0bn(5c-uM~BvBJq(+ z#OA<+;+Qha>RmDwR`HwMNmrBkCdzJ8j3;(ODN=t#YdkGyoWN#R23Fgcy`d5dhfZ@N z4`=KzO*;_&xpoXanG>BO|4!m<+B@w=c}xxb8Y&rGkUdrKwkG1`Ihb1^(wp(*P=SNB z!*FA6ZRT;eVD08tXz{o#)(7d5&n# zZx8rNf57kf^l*TL3bym_{j*+v{0JXGAA0dr?Cs;cS0OPKqR%(+l}ngqs07tChaTC} z=8brkR*QwzLY*k>P^CoY2vneY+~)9vI$ko)9~Ps#y`%U}m|uLr?LeMkfnxkWy`eo} zG9flm_tZR$ubJF2OmoOj;9BQWGO4eEI*n5;w=vdouE$>AcXe;gOR;M&`DRmXJ!-X+ zmg`v_S4W%+CMe-`DK{cr&cD%axT*h-!SmHRsB1Vj+=KnJ;;HO#adzeV?m9cG;U=>l zjg>bwEX8gZaWdNW=QU+Zw>%@hZ~M@nJCUIHHXC@yXv6WxwuVQqI=^syG`W^RWoOfC+|Ck7XQ)Z{Fx|RxgWAppE@_9b4w~-M zgOMdm&#bz(GEy@T>ZqY>W#L<9vm%rnk+SBAzC`884YF>3JK7yzDYMTgLwPMMVo7~< zUG+obQysCLyL+)p{as=m;l1kJoE5SwUa#0K?Yc2FOiNlngc&+%^fGo`%<1ThtgF%F z%04w^wNZBLz6dJQET)OF=Voh7%IbUFfmqYvJ0n@i3Dc(jE0s2MbvQ}x=rN9~v2wfo zo`op4khK)C?6xe*Vq~@4Y2noWF-Z*0DlDFKCCD|M@3Zne_&Bg-Z@E+6uZ3|%LgC*M@ZJeL4NiT7b3U;ry?rXn`viZo}*c2`6ZQ8%QLU2 zM)_Wust+&-G&p}U^n~$=u+VzEr_Kfq1Y(mq;jPu-ZAafVWUIeb>Ah*Uf^piewaEGF zp%1C6nluPE3U0((4&V8xyGp|9?cIs)X7g8+C+0(MMk=GTp1xYEgj>QMIX%+a59Opg zxtAejVur4;eB{me^^Tl>RCI@L<{dWi)EqpPnl)Vd zA?;24Xziuishd^%BMPys(i|`Mn5o9=#syC4h3QJ;t!+Es)}?294wUF7xbj@w#;hh~ zA75_@?)RL2`?B}AjdIb+Pwi_(C+{3T8+9A^R(I3~uuiGGQTeero*GvyW2oiP=-9ni zmt3!BzVw$c&FQ}1xadPyb2YKS(vdGuN_Ic=7cB&v5ok5UvMe-OL=TfiWi*A!8J|KV z)k;HOY_PwVdpO6-_L<$8!&?IivwF`Rzd}w+fwanL@01_&=GPGvxECV7g!V@+bPL_< z@4n1ql_`Cbl6O|cb66O4{k|)n^aZ08L*3HqG=+8J)ylFjluegCX|Xl8&Em&*NWYLK zcohUBl*QCdK2zzN<{Pow8OYYZrOcEYS-YX0)|r%P$#~WH>(r}lO%zpra;oiJFPvXio_rT*->NQ>J6<=!s$gO;YOfuyg6+^U5dC!~27JRx+{pDS%ntNnZ2RMyChpF@lcQqi$d*(5O-GX_){CqUYBRWB=GzjHL}BSV zCNW)|wszMASr621gd+CoHNL~{%SGS)+^XVvplKK3V}C*;V>@GgHo2t6SEiSHeElhQHlv z+m~a0dk$IOE-pBk?el5Z>V1hhtG3@R6DkKOLxYvR~yx&Q8#ty7+%l5*Nl3W?{#Cav#_OS*df zM@}gsZt7o7W$h|@EbapH-v1uEb&w< z(f62taf<7OL#D=V=ZBzOn@{3<6ney@eJcagqZ8wrq6z~3 zsJ$K8ja6T&o|v1n%YC~c}?prSmt}jCAUxH4|3q=0JQ*(fMCNYMJdIR zzT^5H6!{ZTrCFVk%Ewd!RcyBF8}%8%DydGQQ4P`O;;x_8O*wc&^%6dfT8J=h+FrV4 zub~U+gGNB_AK|xZ`;%NPv$I!Z>+?k|pWIPxH7XRFLip24yizuGy>e>LQ!*~7ad%by z@VqUj`sLH&mXEjgrj2NvS(R(;8s?e*US^#uapHq^(alEYgT!$k#ikOKlALCzm*nPW z4eGU%HzIv*F2lr(HrS{ssNULOIB2qEa9HWm)-G~T!~nrmK5b>PJP9x3o#gyCCJFa9 zYA0rz;pf;H`8q$=INjzc%+8(BWEERySFyU_3%=+})wpAn{_DPoR0CW?HMOdHprSSI znv}P~-W#6{dnSbbNo{v3Uv(<`Le0 zsCdmoMx3LB**UA!&!)X=%9r_vKY=A^y_3f?Y2ANa+p8SFm^lfxkVq?jUgU|^3l2zt9C*^ zY96raG(TaUWL;+&>RYb+z}TbfoxI3CNk~wCT-+ux2;2w)RmseNz6Q-Y$P3%H$b@V$ zwd8U=<~@Yo^f3Kszr&mMudUOI6fZ#j3FnqLf!7-`XG04f1hQ0G&=x?F&dP$99X%%1 zoNsQtgJ{cf(xlNjhZvgfPR_s@0?~3Og02&TPg8JrVzGHdcWp(X1rhWGYM7#e(1h=( zt!Qp+qCn2NEi~S0W36l9&A3%U4zY2nsM^Y4~49#Nii{Zpz^LYS=_+{MA4jev* z=fH9P4*x0Z@58^H7?kYS&~IsfUqi4&T7v2m_t2c2Sqz>DgKNTcXRvh8TEbQNm;T4P z&fqKrwPU#m)?WvQgu@AN7y=GHv+%jY^PxsgG<${_lg@Y0K_F-C=ji5}lQ~Y#Tn3LP zmGjsnO z-fX_V%V}mUek{daHj}UOt)}zTKQfv@nFw|p;0-R*)`-n#a6yszG~s%FbMSkM`M!2> znP6wnpuerrT-|*8oxs$UL1Ef6cpzR37K+5+K*NJZfFiL-C=!PT4e0P_CGp33R{@4h|Zy#R-06z&L0qGzz&{AA!(A=WN2p^m&Xv$p0=VODgp1!r31k!5E3Vm z$>#BCY+HsAMaRX3Nhe}(B$Pe^M}Q-6C<-VeiHt&#@Dzfc9*RW3!9nHzh5HfA54UuV zt&4#FzPUx|lkspYQjbK!VgzpSB#OR1P9I5z8<5Eqguyqrb8tW0YQg@MEP=X^1`ryC<363}p1qeVOfjy7}fy4t#AO&-w z038UVU=|dh34sJyAca^!5@-Y(j0z-(7RVV5PT0o_Bmu+;q|k5KsgXiapfRA{ zBt19?*8ngl8ju>+zyK74fW{JL7xSkTo3osBTcRC@>qO)0Fa^(H;nQ9V_Cq_z^!tT_ z2>yPb{r6o*xJeXtkSSWQUylD0F9aEL=nNgyT-Xow927V&L^78_<1;9r=?Lx?8gQHj z0z*aMiD)Dd3Dd@c&^ zX$s^$Cts!3w?p-;QhH8OUfwAi_SbgDc# zo8J*>8sKNASmXKpUE(+R^ivxI(e5b44*zNZ5LHjEvCOSz9GIsEKX-%+4k2Q1l=~gVL2yZ X(aDyr(&yNMw;3b-os>+ygMR-7L;hUR literal 0 HcmV?d00001 diff --git a/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Staggered.png b/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask-RGB-Staggered.png new file mode 100755 index 0000000000000000000000000000000000000000..c2b42d06685a275c5b34b37763282f53258f3524 GIT binary patch literal 4515 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 Ug3uPHEqwVf)Z0N#(cSO&KQE~)cK`qY literal 0 HcmV?d00001 diff --git a/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask.fs b/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask.fs new file mode 100755 index 000000000..1f09663b6 --- /dev/null +++ b/src/renderer/src/application/sample-modules/isf/v002-CRT-Mask.fs @@ -0,0 +1,77 @@ +/*{ + "CATEGORIES": [ + "Retro", + "v002" + ], + "CREDIT": "by vade", + "DESCRIPTION": "CRT Mask, emulating the look of curved CRT Displays", + "IMPORTED": { + "CRTMask0": { + "PATH": "v002-CRT-Mask-RGB-Staggered.png" + }, + "CRTMask1": { + "PATH": "v002-CRT-Mask-RGB-Shadow.png" + }, + "CRTMask2": { + "PATH": "v002-CRT-Mask-RGB-Straight.png" + }, + "CRTMask3": { + "PATH": "v002-CRT-Mask-Scanline-Staggered.png" + } + }, + "INPUTS": [ + { + "NAME": "inputImage", + "TYPE": "image" + }, + { + "DEFAULT": 0.5, + "MAX": 1, + "MIN": 0, + "NAME": "Amount", + "TYPE": "float" + }, + { + "DEFAULT": 0, + "LABEL": "Style", + "LABELS": [ + "Staggered", + "Shadow", + "Straight", + "Scanline Staggered" + ], + "NAME": "style", + "TYPE": "long", + "VALUES": [ + 0, + 1, + 2, + 3 + ] + } + ], + "ISFVSN": "2" +} +*/ + +void main (void) +{ + vec2 crtcoord = mod(gl_FragCoord.xy, 12.0); + vec4 crt; + + if (style == 0) + crt = IMG_PIXEL(CRTMask0, crtcoord); + else if (style == 1) + crt = IMG_PIXEL(CRTMask1, crtcoord); + else if (style == 2) + crt = IMG_PIXEL(CRTMask2, crtcoord); + else if (style == 3) + crt = IMG_PIXEL(CRTMask3, crtcoord); + + vec4 image = IMG_THIS_PIXEL(inputImage); + vec4 result = mix(image, image * crt, Amount); + + result.a = image.a; + + gl_FragColor = result; +} \ No newline at end of file diff --git a/src/renderer/src/application/setup-beat-detektor.js b/src/renderer/src/application/setup-beat-detektor.js new file mode 100644 index 000000000..f1ef9f466 --- /dev/null +++ b/src/renderer/src/application/setup-beat-detektor.js @@ -0,0 +1,22 @@ +import BeatDetektor from "../../../../lib/BeatDetektor.js"; + +export default function() { + const beatDetektor = new BeatDetektor(85, 169); + const beatDetektorKick = new BeatDetektor.modules.vis.BassKick(); + + this.updateBeatDetektor = (delta, features) => { + if (!features) { + return; + } + + beatDetektor.process(delta / 1000.0, features.complexSpectrum.real); + beatDetektorKick.process(beatDetektor); + const kick = beatDetektorKick.isKick(); + const bpm = beatDetektor.win_bpm_int_lo; + + this.store.commit("beats/SET_KICK", { kick }); + if (this.store.state.beats.bpm !== bpm) { + this.store.dispatch("beats/setBpm", { bpm, source: "beatdetektor" }); + } + }; +} diff --git a/src/renderer/src/application/setup-grandiose.js b/src/renderer/src/application/setup-grandiose.js new file mode 100644 index 000000000..744d145f2 --- /dev/null +++ b/src/renderer/src/application/setup-grandiose.js @@ -0,0 +1,38 @@ +/* globals __dirname */ + +import store from "./worker/store/index"; + +let grandiose = undefined; + +export default function setupGrandiose() { + console.log('setup grandiose') + if (grandiose === undefined) { + /* eslint-disable */ + __dirname = `${__dirname}/node_modules/grandiose`; + __dirname = __dirname.replace("app.asar", "app.asar.unpacked"); + /* eslint-enable */ + + try { + grandiose = require("grandiose"); + } catch (error) { + if (error.message.includes("libndi.so")) { + store.dispatch("errors/createMessage", { + message: + "libndi is not installed, please see \"Ubuntu/Debian\" in the modV README." + }); + } else { + console.error(error); + } + } + + // eslint-disable-next-line + __dirname = __dirname.replace("/node_modules/grandiose", ""); + } + + // Make sure to set grandiose to undefined as it will be an empty object otherwise + if (!grandiose.isSupportedCPU) { + grandiose = undefined; + } + + return grandiose; +} diff --git a/src/renderer/src/application/setup-media.js b/src/renderer/src/application/setup-media.js new file mode 100644 index 000000000..553862b29 --- /dev/null +++ b/src/renderer/src/application/setup-media.js @@ -0,0 +1,224 @@ +import Meyda from "meyda"; +import constants from "./constants"; + +let floatFrequencyDataArray; +let byteFrequencyDataArray; +let byteTimeDomainDataArray; +let analyserNode; + +async function enumerateDevices() { + const { _store: store } = this; + + const devices = await navigator.mediaDevices.enumerateDevices(); + const sources = { + audio: [], + video: [] + }; + + for (let i = 0, len = devices.length; i < len; i++) { + const device = devices[i]; + + if (device.kind === "audioinput") { + sources.audio.push(device); + } else if (device.kind === "videoinput") { + sources.video.push(device); + } + } + + store.commit("mediaStream/CLEAR_AUDIO_SOURCES"); + store.commit("mediaStream/CLEAR_VIDEO_SOURCES"); + + const audioDevices = sources.audio; + for (let i = 0, len = audioDevices.length; i < len; i++) { + store.commit("mediaStream/ADD_AUDIO_SOURCE", { source: audioDevices[i] }); + } + + const videoDevices = sources.video; + for (let i = 0, len = videoDevices.length; i < len; i++) { + store.commit("mediaStream/ADD_VIDEO_SOURCE", { source: videoDevices[i] }); + } + + return sources; +} + +async function getMediaStream({ audioSourceId, videoSourceId }) { + const audioConstraints = {}; + const videoConstraints = {}; + + if (audioSourceId) { + audioConstraints.audio = { + echoCancellation: { exact: false }, + deviceId: audioSourceId + }; + } + + if (videoSourceId) { + videoConstraints.video = { + deviceId: videoSourceId, + frameRate: { + ideal: 60 + } + }; + } + + /* Ask for user media access */ + return [ + audioSourceId && navigator.mediaDevices.getUserMedia(audioConstraints), + videoSourceId && navigator.mediaDevices.getUserMedia(videoConstraints) + ]; +} + +async function setupMedia({ audioId, videoId, useDefaultDevices = false }) { + const { _store: store } = this; + + const mediaStreamDevices = await enumerateDevices.bind(this)(); + + let audioSourceId = audioId; + let videoSourceId = videoId; + + if (!audioId && useDefaultDevices && mediaStreamDevices.audio.length > 0) { + audioSourceId = mediaStreamDevices.audio[0].deviceId; + } + + if (!videoId && useDefaultDevices && mediaStreamDevices.video.length > 0) { + videoSourceId = mediaStreamDevices.video[0].deviceId; + } + + const streams = []; + + if (audioId) { + streams.push(this._audioMediaStream); + } + + if (videoId) { + streams.push(this._videoMediaStream); + } + + for (let i = 0, len = streams.length; i < len; i++) { + const stream = streams[i]; + + if (stream) { + const tracks = stream.getTracks(); + for (let j = 0, jLen = tracks.length; j < jLen; j++) { + const track = tracks[j]; + track.stop(); + } + } + } + + const [audioMediaStream, videoMediaStream] = await Promise.all( + await getMediaStream({ + audioSourceId, + videoSourceId + }) + ); + + // This video element is required to keep the camera alive for the ImageCapture API + // (this._imageCapture, ./index.js) + if (videoMediaStream) { + if (this.videoStream) { + this.videoStream.pause(); + delete this.videoStream; + } + + this.videoStream = document.createElement("video"); + this.videoStream.autoplay = true; + this.videoStream.muted = true; + + this.videoStream.srcObject = videoMediaStream; + this.videoStream.onloadedmetadata = () => { + this.videoStream.play(); + }; + + const [track] = videoMediaStream.getVideoTracks(); + if (track) { + this._imageCapture = new ImageCapture(track); + } + + store.commit("mediaStream/SET_CURRENT_VIDEO_SOURCE", { + videoId: videoSourceId + }); + } + + if (audioMediaStream) { + if (this.audioContext) { + this.audioContext.close(); + } + + // Create new Audio Context + this.audioContext = new window.AudioContext({ + latencyHint: "playback" + }); + + // Create new Audio Analyser + analyserNode = this.audioContext.createAnalyser(); + analyserNode.smoothingTimeConstant = 0; + + // Set up arrays for analyser + floatFrequencyDataArray = new Float32Array(analyserNode.frequencyBinCount); + byteFrequencyDataArray = new Uint8Array(analyserNode.frequencyBinCount); + byteTimeDomainDataArray = new Uint8Array( + analyserNode.frequencyBinCount / 2 + ); + + // Create a gain node + this.gainNode = this.audioContext.createGain(); + + // Default gain + this.gainNode.gain.value = 1; + + // Create the audio input stream (audio) + this.audioStream = this.audioContext.createMediaStreamSource( + audioMediaStream + ); + + // Connect the audio stream to the analyser (this is a passthru) (audio->(analyser)) + this.audioStream.connect(analyserNode); + + // Connect the audio stream to the gain node (audio->(analyser)->gain) + this.audioStream.connect(this.gainNode); + + // Set up Meyda + // eslint-disable-next-line new-cap + this.meyda = new Meyda.createMeydaAnalyzer({ + audioContext: this.audioContext, + source: this.gainNode, + bufferSize: constants.AUDIO_BUFFER_SIZE, + windowingFunction: "rect", + featureExtractors: ["complexSpectrum"] + }); + + store.commit("mediaStream/SET_CURRENT_AUDIO_SOURCE", { + audioId: audioSourceId + }); + } + + this._audioMediaStream = audioMediaStream || this._audioMediaStream; + this._videoMediaStream = videoMediaStream || this._videoMediaStream; + + return [audioMediaStream, videoMediaStream]; +} + +function getFloatFrequencyData() { + analyserNode.getFloatFrequencyData(floatFrequencyDataArray); + + return floatFrequencyDataArray; +} + +function getByteFrequencyData() { + analyserNode.getByteFrequencyData(byteFrequencyDataArray); + return byteFrequencyDataArray; +} + +function getByteTimeDomainData() { + analyserNode.getByteTimeDomainData(byteTimeDomainDataArray); + return byteTimeDomainDataArray; +} + +export { + enumerateDevices, + setupMedia, + getFloatFrequencyData, + getByteFrequencyData, + getByteTimeDomainData +}; diff --git a/src/renderer/src/application/setup-midi.js b/src/renderer/src/application/setup-midi.js new file mode 100644 index 000000000..09fc8d460 --- /dev/null +++ b/src/renderer/src/application/setup-midi.js @@ -0,0 +1,165 @@ +let store; + +const clockHistory = []; +const diffHistory = []; +let nextBpmUpdate = 0; +const TYPE_NOTEON = 144; +const TYPE_CC = 176; + +// create a new audioContext to use intead of modV's +// existing context - we get timing jitters otherwise +const audioContext = new window.AudioContext({ + latencyHint: "playback" +}); + +// hack around chrome's autoplay policy +function resume() { + audioContext.resume(); + window.removeEventListener("click", resume); +} + +window.addEventListener("click", resume); + +function handleInput(message) { + const { + data: [type, channel, data], + currentTarget: { id, name, manufacturer } + } = message; + + const device = store.state.midi.devices[`${id}-${name}-${manufacturer}`]; + + if (!device) { + return; + } + + const { listenForClock, listenForInput, ccLatch, noteOnLatch } = device; + + // clock + if (type === 248 && listenForClock) { + const currentTime = audioContext.currentTime * 1000; + clockHistory.push(currentTime); + + if (clockHistory.length > 1) { + const then = clockHistory[0]; + const now = clockHistory[1]; + + const difference = now - then; + + clockHistory.splice(0, 1); + + diffHistory.push(difference); + + // try to compensate for tempo change + if (diffHistory.length > 1) { + // get the different before the one we just pushed + const lastDifference = diffHistory[diffHistory.length - 2]; + const checkDifference = difference - lastDifference; + if (checkDifference > 50) { + console.warn( + "MIDI: resetting clock detection as difference was too big", + checkDifference + ); + diffHistory.splice(0, diffHistory.length); + clockHistory.splice(0, clockHistory.length); + return; + } + } + + if (diffHistory.length > 94) { + diffHistory.splice(0, 1); + } + + if (currentTime > nextBpmUpdate) { + nextBpmUpdate = currentTime + 500; + + const averageDiff = + diffHistory.reduce((a, b) => a + b) / diffHistory.length; + + const bpm = Math.round((1000 / averageDiff / 24) * 60); + store.dispatch("beats/setBpm", { bpm, source: "midi" }); + } + } + } else if (listenForInput) { + let commitValue = true; + let _data = data; + let _type = type; + + // Overwrite the default behavior for ControlChange messages so that they + // behave like NoteOn + if (_type === TYPE_CC && ccLatch) { + _type = TYPE_NOTEON; + } + + if (_type === TYPE_NOTEON) { + // We want to know when the button was pressed + if (data > 0) { + if (device.channelData !== undefined && device.channelData[channel]) { + _data = device.channelData[channel][_type] === 1 ? 0 : 1; + + // Set the inital value if no value exists yet + } else { + _data = 1; + } + + // Don't commit the value as this is "NoteOff" and we want + // to make sure that the button is not deactivated when it's + // released by the user + } else if (noteOnLatch) { + commitValue = false; + } + } + + if (_type === TYPE_CC) { + _data = _data / 127; + } + + if (commitValue) { + store.commit("midi/WRITE_DATA", { + id: `${id}-${name}-${manufacturer}`, + type: _type, + channel, + data: _data + }); + } + + if (store.state.midi.learning) { + // Make sure to use the overwritten type + message.data[0] = _type; + + store.state.midi.learning(message); + } + } +} + +function handleDevices(inputs) { + // loop over all available inputs and listen for any MIDI input + for(let input of inputs.values()) { // eslint-disable-line + // each time there is a midi message call the onMIDIMessage function + input.removeEventListener("midimessage", handleInput); + input.addEventListener("midimessage", handleInput); + + store.commit("midi/ADD_DEVICE", { + id: input.id, + name: input.name, + manufacturer: input.manufacturer + }); + } +} + +async function setupMidi() { + store = this.store; + + if (navigator.requestMIDIAccess) { + const access = await navigator.requestMIDIAccess({ sysex: false }); + + handleDevices.bind(this)(access.inputs); + + access.addEventListener("statechange", e => { + handleDevices(e.currentTarget.inputs); + }); + } else { + console.warn("MIDI access was unavailable in your browser."); + } +} + +export default setupMidi; diff --git a/src/renderer/src/application/setup-tweens.js b/src/renderer/src/application/setup-tweens.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/renderer/src/application/use.js b/src/renderer/src/application/use.js new file mode 100644 index 000000000..ccdccf070 --- /dev/null +++ b/src/renderer/src/application/use.js @@ -0,0 +1,25 @@ +import installPlugin from "./install-plugin"; + +export default function use(type, extension) { + switch (type) { + case "plugin": { + installPlugin(extension); + break; + } + + case "control": { + this.store.dispatch("controls/addControl", extension); + break; + } + + // case 'renderer': { + // store.commit('renderers/addRenderer', extension) + // break + // } + + default: { + installPlugin(type || extension); + break; + } + } +} diff --git a/src/renderer/src/application/utils/apply-expression.js b/src/renderer/src/application/utils/apply-expression.js new file mode 100644 index 000000000..b3cb08566 --- /dev/null +++ b/src/renderer/src/application/utils/apply-expression.js @@ -0,0 +1,24 @@ +import get from "lodash.get"; +import store from "../worker/store"; + +export function applyExpression({ value, inputId }) { + const expressionAssignment = store.getters["expressions/getByInputId"]( + inputId + ); + + const input = store.state.inputs.inputs[inputId]; + + let dataOut = value; + + if (expressionAssignment) { + const scope = { + value: dataOut, + time: Date.now(), + inputValue: get(store.state, input.getLocation) + }; + + dataOut = expressionAssignment.func.evaluate(scope); + } + + return dataOut; +} diff --git a/src/renderer/src/application/utils/conform-file-path.js b/src/renderer/src/application/utils/conform-file-path.js new file mode 100644 index 000000000..38dfde947 --- /dev/null +++ b/src/renderer/src/application/utils/conform-file-path.js @@ -0,0 +1,6 @@ +import path from "path"; + +export const conformFilePath = (filePath = "") => + filePath + .split(process.platform === "win32" ? path.posix.sep : path.win32.sep) + .join(path.sep); diff --git a/src/renderer/src/application/utils/get-next-name.js b/src/renderer/src/application/utils/get-next-name.js new file mode 100644 index 000000000..e60b0b6ce --- /dev/null +++ b/src/renderer/src/application/utils/get-next-name.js @@ -0,0 +1,29 @@ +function nameTemplate(name, count) { + if (count < 1) { + return name; + } + + return `${name} (${count})`; +} + +export default function findBestName(nameIn, names) { + return new Promise(resolve => { + if (names.indexOf(nameIn) < 0) { + resolve(nameIn); + } + + const nameRe = new RegExp(`\\b^${nameIn}\\s\\((\\d+?)\\)$`, "g"); + + let count = 1; + let newName = nameTemplate(nameIn, count); + + const filteredNames = names.filter(arrName => arrName.match(nameRe)); + + while (filteredNames.indexOf(newName) > -1) { + count += 1; + newName = nameTemplate(nameIn, count); + } + + resolve(newName); + }); +} diff --git a/src/renderer/src/application/utils/get-prop-default.js b/src/renderer/src/application/utils/get-prop-default.js new file mode 100644 index 000000000..a536c3479 --- /dev/null +++ b/src/renderer/src/application/utils/get-prop-default.js @@ -0,0 +1,55 @@ +import Vue from "vue"; +import store from "../worker/store"; + +export default async function getPropDefault( + module, + propName, + prop, + useExistingData +) { + const { random, type } = prop; + let defaultValue = prop.default; + + if (store.state.dataTypes[type]) { + if (!defaultValue && store.state.dataTypes[type].inputs) { + defaultValue = store.state.dataTypes[type].inputs(); + } + + const propData = useExistingData + ? module.props[propName] ?? defaultValue + : defaultValue; + + if (store.state.dataTypes[type].create) { + return await store.state.dataTypes[type].create( + propData, + module.meta.isGallery, + useExistingData + ); + } + } + + if (useExistingData && typeof module.props?.[propName] !== "undefined") { + return module.props[propName]; + } + + if (Array.isArray(defaultValue)) { + defaultValue = Vue.observable(defaultValue); + } + + if ( + typeof defaultValue !== "undefined" && + Array.isArray(defaultValue) && + random + ) { + return defaultValue[Math.floor(Math.random() * defaultValue.length)]; + } + + if ( + typeof defaultValue === "undefined" && + typeof module.data?.[propName] !== "undefined" + ) { + return module.data[propName]; + } + + return defaultValue; +} diff --git a/src/renderer/src/application/utils/lerp.js b/src/renderer/src/application/utils/lerp.js new file mode 100644 index 000000000..6fc46edc0 --- /dev/null +++ b/src/renderer/src/application/utils/lerp.js @@ -0,0 +1,3 @@ +export default function lerp(start, end, amt) { + return (1 - amt) * start + amt * end; +} diff --git a/src/renderer/src/application/utils/map.js b/src/renderer/src/application/utils/map.js new file mode 100644 index 000000000..b90a931e9 --- /dev/null +++ b/src/renderer/src/application/utils/map.js @@ -0,0 +1,4 @@ +export default function map(value, low1, high1, low2, high2) { + // eslint-disable-next-line + return low2 + (high2 - low2) * (value - low1) / (high1 - low1); +} diff --git a/src/renderer/src/application/window-handler.js b/src/renderer/src/application/window-handler.js new file mode 100644 index 000000000..39dfd17b6 --- /dev/null +++ b/src/renderer/src/application/window-handler.js @@ -0,0 +1,140 @@ +export default function windowHandler() { + const windows = {}; + const that = this; + + function createHideMouseTimerhandler(canvas) { + let mouseTimer; + + function hideMouse() { + canvas.ownerDocument.body.style.cursor = "none"; + } + + return function() { + if (mouseTimer) { + clearTimeout(mouseTimer); + } + + canvas.ownerDocument.body.style.cursor = "default"; + mouseTimer = setTimeout(hideMouse, 200); + }; + } + + function configureWindow({ win, canvas, backgroundColor }) { + win.document.body.appendChild(canvas); + win.document.body.style.backgroundColor = backgroundColor; + win.addEventListener("beforeunload", ev => { + // Setting any value other than undefined here will prevent the window + // from closing or reloading + ev.returnValue = true; + }); + + setSize.call(that, win); + } + + function pollToConfigureWindow(args) { + let poll; + + function checkIfDone(args) { + if (args.win.document.readyState === "complete") { + configureWindow(args); + } else { + pollToConfigureWindow(args)(); + } + } + + return function() { + if (poll) { + clearTimeout(poll); + } + + poll = setTimeout(() => checkIfDone(args), 100); + }; + } + + function setSize(win) { + const { innerWidth: width, innerHeight: height } = win; + + this.store.dispatch("size/setSize", { + width, + height + }); + } + + this._store.subscribe(async ({ type, payload }) => { + if (type === "windows/ADD_WINDOW") { + const { width, height, backgroundColor, title, id } = payload; + + const win = window.open( + "./output-window.html", + "modal", + `width=${width}, height=${height}, location=no, menubar=no, left=0` + ); + win.document.title = title; + + if (win === null || typeof win === "undefined") { + console.log( + "Could not create Output Window", + "modV couldn't open an Output Window. Please check you've allowed pop-ups, then reload" + ); + + return; + } + + windows[id] = win; + + const canvas = document.createElement("canvas"); + const offscreen = canvas.transferControlToOffscreen(); + const { id: outputId } = await this.store.dispatch( + "outputs/getAuxillaryOutput", + { + canvas: offscreen, + name: `window-${Object.keys(windows).length}`, + group: "window" + }, + [offscreen] + ); + + this.store.commit("windows/UPDATE_WINDOW", { + id, + key: "outputId", + value: outputId + }); + + canvas.style.backgroundColor = "transparent"; + canvas.style.objectFit = "cover"; + canvas.style.width = "100%"; + + canvas.addEventListener("dblclick", () => { + if (!canvas.ownerDocument.webkitFullscreenElement) { + canvas.webkitRequestFullscreen(); + } else { + canvas.ownerDocument.webkitExitFullscreen(); + } + }); + + canvas.addEventListener("mousemove", createHideMouseTimerhandler(canvas)); + + pollToConfigureWindow({ win, canvas, title, backgroundColor })(); + + let timer; + + win.addEventListener("resize", () => { + if (timer) { + cancelAnimationFrame(timer); + } + + // Setup the new requestAnimationFrame() + timer = requestAnimationFrame(() => { + setSize.call(this, win); + }); + }); + } + }); + + window.addEventListener("unload", () => { + const windowIds = Object.keys(windows); + for (let i = 0, len = windowIds.length; i < len; i++) { + windows[windowIds[i]].close(); + } + }); +} diff --git a/src/renderer/src/application/worker/audio-features.js b/src/renderer/src/application/worker/audio-features.js new file mode 100644 index 000000000..4f3951b2f --- /dev/null +++ b/src/renderer/src/application/worker/audio-features.js @@ -0,0 +1,48 @@ +import lerp from "../utils/lerp"; + +const MAX_SMOOTHING = 1; +const SMOOTHING_STEP = 0.001; + +let features = {}; +const smoothedFeatures = {}; + +function getFeatures() { + return features; +} + +function getFeature(key) { + return features[key]; +} + +function setFeatures(newFeatures) { + features = newFeatures; +} + +function addSmoothingId(id) { + smoothedFeatures[id] = 0; +} + +function removeSmoothingId(id) { + delete smoothedFeatures[id]; +} + +function getSmoothedFeature(feature, id, smoothingValue) { + smoothedFeatures[id] = lerp( + smoothedFeatures[id] || 0, + features[feature], + smoothingValue + ); + + return smoothedFeatures[id]; +} + +export { + getFeature, + getFeatures, + setFeatures, + addSmoothingId, + removeSmoothingId, + getSmoothedFeature, + MAX_SMOOTHING, + SMOOTHING_STEP +}; diff --git a/src/renderer/src/application/worker/frame-counter.js b/src/renderer/src/application/worker/frame-counter.js new file mode 100644 index 000000000..d96134a16 --- /dev/null +++ b/src/renderer/src/application/worker/frame-counter.js @@ -0,0 +1,38 @@ +/* + * In relation to: https://github.com/vcync/modv-3/issues/129 + * + * The frame-counter will not overflow in a normal session of modV. + * + * We can determine the maximum safe integer in JavaScript with `Number.MAX_SAFE_INTEGER`. + * In V8 at the time of writing this is 9007199254740991. + * + * That's nine quadrillion, seven trillion, one hundred and ninety-nine billion, + * two hundred and fifty-four million, seven hundred and forty thousand and nine hundred + * and ninety-one. + * + * We use the following formula to calculate when, in days, this will reach the max safe int: + * max_int / (frames_per_second * (seconds_in_a_minute * minutes_in_an_hour) / hours_in_a_day) + * + * At an average screen refresh of 60fps: + * Number.MAX_SAFE_INTEGER / (60 * (60 * 60) / 24) + * = 1000799917193.4435 + * + * In years, this is: + * 1000799917193.4435 / 365 + * = 2741917581.3519 + * + * Of course this doesn't account for leap years, but we're pretty safe. + * Unless you have a time-machine and somewhere safe to hide a computer running modV… + */ + +let _frames = 0; + +function tick() { + return _frames++; +} + +function frames() { + return _frames; +} + +export { tick, frames }; diff --git a/src/renderer/src/application/worker/index.worker.js b/src/renderer/src/application/worker/index.worker.js new file mode 100644 index 000000000..01ce18744 --- /dev/null +++ b/src/renderer/src/application/worker/index.worker.js @@ -0,0 +1,406 @@ +/* eslint-env worker */ +import constants from "../constants"; +import registerPromiseWorker from "promise-worker/register"; +import fs from "fs"; +import path from "path"; +import store from "./store"; +import loop from "./loop"; +import grabCanvasPlugin from "../plugins/grab-canvas"; +import get from "lodash.get"; +import { tick as frameTick } from "./frame-counter"; +import { getFeatures, setFeatures } from "./audio-features"; + +let lastKick = false; + +function getFilename(path) { + return path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf(".")); +} + +async function start() { + // For Playwright + self._get = get; + + + // const featureAssignmentPlugin = require("../plugins/feature-assignment"); + + let interval = store.getters["fps/interval"]; + + const commitQueue = []; + + store.subscribe(mutation => { + const { type: mutationType, payload: mutationPayload } = mutation; + + if (mutationType === "beats/SET_BPM" || mutationType === "fps/SET_FPS") { + store.dispatch("tweens/updateBpm", { bpm: mutationPayload.bpm }); + } + + if ( + mutationType === "beats/SET_KICK" && + mutationPayload.kick === lastKick + ) { + return; + } else if ( + mutationType === "beats/SET_KICK" && + mutationPayload.kick !== lastKick + ) { + lastKick = mutationPayload.kick; + } + + if (mutationType === "fps/SET_FPS") { + interval = store.getters["fps/interval"]; + } + + if ( + mutationType === "modules/UPDATE_ACTIVE_MODULE" && + (mutationPayload.key !== "props" || mutationPayload.key !== "meta") + ) { + return; + } + + const { + inputs: { inputs, inputLinks } + } = store.state; + + // Update mutation type Input Links + const mutationTypeInputLinks = Object.values(inputLinks).filter( + link => link.type === "mutation" + ); + const inputLinksLength = mutationTypeInputLinks.length; + for (let i = 0; i < inputLinksLength; ++i) { + const link = mutationTypeInputLinks[i]; + const inputId = link.id; + const bind = inputs[inputId]; + + const { type, location, data } = bind; + + const { location: linkLocation, match } = link; + + if (match.type !== mutationType) { + continue; + } + + let payloadMatches = false; + + if (match.payload) { + const matchPayloadKeys = Object.keys(match.payload); + payloadMatches = matchPayloadKeys.every(key => { + const value = match.payload[key]; + return value === mutationPayload[key]; + }); + } else { + payloadMatches = true; + } + + if (!payloadMatches) { + continue; + } + + const value = get(store.state, linkLocation); + + if (type === "action") { + store.dispatch(location, { ...data, data: value }); + } else if (type === "commit") { + store.commit(location, { ...data, data: value }); + } + } + + commitQueue.push(mutation); + }); + + function sendCommitQueue() { + const commits = JSON.stringify(commitQueue); + commitQueue.splice(0, commitQueue.length); + + self.postMessage({ + type: "commitQueue", + payload: commits + }); + } + + store.dispatch("plugins/add", grabCanvasPlugin); + + const rendererModules = import.meta.glob('../renderers/*.js'); + + for (const pathKey in rendererModules) { + const rendererName = getFilename(pathKey); + + rendererModules[pathKey]().then((mod) => { + const { + render, + setupModule, + removeModule, + updateModule, + resizeModule, + tick, + resize, + createPresetData, + loadPresetData, + getModuleData + } = mod.default; + + store.commit("renderers/ADD_RENDERER", { + name: rendererName.replace(/(\.\/|\.js)/g, ""), + render, + resize, + setupModule, + removeModule, + updateModule, + resizeModule, + createPresetData, + loadPresetData, + getModuleData, + tick + }); + }) + + + } + + let modulesToRegister = []; + const sampleModules = import.meta.glob('../sample-modules/*.js'); + + for (const pathKey in sampleModules) { + const mod = await sampleModules[pathKey](); + modulesToRegister.push(mod.default); + } + + const isfModules = import.meta.glob('../sample-modules/isf/*.fs'); + const isfModulesVs = import.meta.glob('../sample-modules/isf/*.vs'); + + const isfModulesVsKeys = Object.keys(isfModulesVs); + + const isfModuleKeys = Object.keys(isfModules); + for (let i = 0, len = isfModuleKeys.length; i < len; i++) { + const key = isfModuleKeys[i] + const fragmentShader = (await isfModules[key]()).default; + let vertexShader = "void main() {isf_vertShaderInit();}"; + const vsIndex = isfModulesVsKeys.indexOf(key.replace(".fs", ".vs")); + if (vsIndex > -1) { + vertexShader = (await isfModulesVs[isfModulesVsKeys[vsIndex]]()).default; + } + + const isfModule = { + meta: { + name: getFilename(isfModuleKeys[i]), + author: "", + version: "1.0.0", + type: "isf" + }, + fragmentShader, + vertexShader + }; + modulesToRegister.push(isfModule); + } + + await Promise.all( + modulesToRegister.map(module => + store.dispatch("modules/registerModule", { module }) + ) + ); + + modulesToRegister = []; + + // store.dispatch("plugins/add", featureAssignmentPlugin); + + const webcamOutput = await store.dispatch("outputs/getAuxillaryOutput", { + name: "webcam", + reactToResize: false, + width: 1920, + height: 1080, + group: "input" + }); + store.dispatch("outputs/setWebcamOutput", webcamOutput.context); + + const fftOutput = await store.dispatch("outputs/getAuxillaryOutput", { + name: "fft", + reactToResize: false, + width: constants.AUDIO_BUFFER_SIZE, + height: 1, + group: "audio", + id: "fft" + }); + + // eslint-disable-next-line + let raf = requestAnimationFrame(looper); + let frames = 0; + let prevTime = 0; + + let now; + let then = Date.now(); + let delta; + + function looper(rafDelta) { + raf = requestAnimationFrame(looper); + + now = Date.now(); + delta = now - then; + + if (delta > interval) { + // update time stuffs + + // Just `then = now` is not enough. + // Lets say we set fps at 10 which means + // each frame must take 100ms + // Now frame executes in 16ms (60fps) so + // the loop iterates 7 times (16*7 = 112ms) until + // delta > interval === true + // Eventually this lowers down the FPS as + // 112*10 = 1120ms (NOT 1000ms). + // So we have to get rid of that extra 12ms + // by subtracting delta (112) % interval (100). + // Hope that makes sense. + + then = now - (delta % interval); + + frameActions(rafDelta); + } + } + + function frameActions(delta) { + sendCommitQueue(); + self.postMessage({ + type: "tick", + payload: delta + }); + + loop(delta, getFeatures(), fftOutput); + + frameTick(); + frames += 1; + + const time = performance.now(); + + if (time >= prevTime + 1000) { + store.commit( + "metrics/SET_FPS_MEASURE", + (frames * 1000) / (time - prevTime) + ); + + prevTime = time; + frames = 0; + } + } + + self.addEventListener("message", async e => { + const message = e.data; + const { type, identifier, payload } = message; + if (Array.isArray(message) && message[1].__async) { + return; + } + + if (type === "canvas") { + if (message.where === "output") { + payload.width = payload.height = 300; + const context = payload.getContext("2d"); + store.dispatch("outputs/setMainOutput", context); + } + + return; + } + + if (type === "meyda") { + setFeatures(payload); + + return; + } + + if (type === "videoFrame") { + const context = store.state.outputs.webcam; + const { canvas } = context; + + canvas.width = payload.width; + canvas.height = payload.height; + + context.drawImage(payload, 0, 0); + + return; + } + + if (type === "loadPreset") { + const fileBuffer = await fs.promises.readFile(payload); + const jsonString = fileBuffer.toString(); + const preset = JSON.parse(jsonString); + + console.log(preset); + + const storeModuleKeys = Object.keys(preset); + for (let i = 0, len = storeModuleKeys.length; i < len; i++) { + const storeModuleKey = storeModuleKeys[i]; + + try { + console.log("Loading from preset…", storeModuleKey); + await store.dispatch( + `${storeModuleKey}/loadPresetData`, + preset[storeModuleKey] + ); + } catch (e) { + console.error(e); + } + } + + store.commit("groups/SWAP"); + store.commit("modules/SWAP"); + store.commit("inputs/SWAP"); + store.commit("expressions/SWAP"); + + return; + } + + store[type](identifier, payload); + }); + + registerPromiseWorker(async message => { + const { type, identifier, payload, __async } = message; + if (__async) { + if (type === "generatePreset") { + const preset = {}; + + const storeModuleKeys = Object.keys(store.state); + for (let i = 0, len = storeModuleKeys.length; i < len; i++) { + const storeModuleKey = storeModuleKeys[i]; + + try { + preset[storeModuleKey] = await store.dispatch( + `${storeModuleKey}/createPresetData` + ); + } catch (e) { + // do nothing + } + } + + return JSON.stringify(preset); + } + + /** + * @todo Don't JSON parse and stringify + */ + const value = await store[type](identifier, payload); + + if (value) { + return JSON.parse(JSON.stringify(value)); + } + + return undefined; + } + }); + + self.store = store; + + self.postMessage({ + type: "worker-setup-complete" + }); +} + +function handleDirname(e) { + const message = e.data; + const { type, payload } = message; + + if (type === "__dirname") { + self.__dirname = payload; + + self.removeEventListener("message", handleDirname); + start(); + } +} + +self.addEventListener("message", handleDirname); diff --git a/src/renderer/src/application/worker/loop.js b/src/renderer/src/application/worker/loop.js new file mode 100644 index 000000000..dd5f76a21 --- /dev/null +++ b/src/renderer/src/application/worker/loop.js @@ -0,0 +1,381 @@ +/* eslint-env worker */ +import get from "lodash.get"; +import store from "./store"; +import map from "../utils/map"; +import constants, { GROUP_DISABLED, GROUP_ENABLED } from "../constants"; +import { applyWindow } from "meyda/dist/esm/utilities"; + +const meyda = { windowing: applyWindow }; + +let bufferCanvas; +let bufferContext; +const fallbackByteFrequencyData = Array(constants.AUDIO_BUFFER_SIZE).fill(0); + +function loop(delta, features, fftOutput) { + if (!bufferCanvas) { + bufferCanvas = new OffscreenCanvas(300, 300); + bufferContext = bufferCanvas.getContext("2d"); + store.dispatch("outputs/addAuxillaryOutput", { + name: "loop-buffer", + context: bufferContext, + group: "buffer" + }); + + return; + } + + const { + beats: { kick }, + modules: { active, registered }, + groups: { groups }, + inputs: { inputs, inputLinks }, + outputs: { main, debug, debugContext, auxillary, webcam: video }, + renderers, + windows + } = store.state; + + const groupIndexRenderOrder = store.getters["groups/groupIndexRenderOrder"]; + + if (!main) { + return; + } + + // Put fftBuffer on fftCanvas + const byteFrequencyData = + features.byteFrequencyData || fallbackByteFrequencyData; + const fftBuffer = byteFrequencyData.reduce((arr, value) => { + arr.push(value, value, value, 255); + return arr; + }, []); + + const uInt8Array = Uint8ClampedArray.from(fftBuffer); + + fftOutput.context.putImageData( + new ImageData(uInt8Array, byteFrequencyData.length), + 0, + 0 + ); + + // Update Input Links + const inputLinkKeys = Object.keys(inputLinks); + const inputLinkKeysLength = inputLinkKeys.length; + for (let i = 0; i < inputLinkKeysLength; ++i) { + const inputId = inputLinkKeys[i]; + const bind = inputs[inputId]; + const link = inputLinks[inputId]; + + const { + type, + location, + data, + data: { path } + } = bind; + + const { + type: linkType, + location: linkLocation, + args: linkArguments, + min, + max, + source + } = link; + + const moduleId = store.state.inputs.inputs[inputId].data.moduleId; + + // check if the module is enabled for meyda sources, and if it is not, skip it + // mutation linkTypes are also skipped as they're handled in index.worker.js in + // the store commit subscription + if ( + (moduleId && + (linkType === "mutation" || + (source === "meyda" && + moduleId && + !store.state.modules.active[moduleId].meta.enabled))) || + linkType === "mutation" + ) { + continue; + } + + let value; + + if (linkType === "getter" && linkArguments) { + value = store.getters[linkLocation](...linkArguments); + } else if (linkType === "state") { + value = get(store.state, linkLocation); + } + + // Coersion with != to also check for undefined + if (min != null || max != null) { + value = map(value, 0, 1, min, max); + } + + if (type === "action") { + store.dispatch(location, { ...data, data: value, path }); + } else if (type === "commit") { + store.commit(location, { ...data, data: value, path }); + } + } + + const preProcessFrameFunctions = + store.getters["plugins/preProcessFrame"] || []; + const preProcessFrameFunctionsLength = preProcessFrameFunctions.length; + + for (let i = 0; i < preProcessFrameFunctionsLength; ++i) { + preProcessFrameFunctions[i].preProcessFrame({ + features, + store, + props: preProcessFrameFunctions[i].$props + }); + } + + const renderersWithTick = store.getters["renderers/renderersWithTick"]; + const renderersWithTickLength = renderersWithTick.length; + for (let i = 0; i < renderersWithTickLength; ++i) { + renderersWithTick[i].tick(); + } + + let lastCanvas = main.canvas; + + const groupsLength = groupIndexRenderOrder.length; + for (let i = 0; i < groupsLength; ++i) { + const group = groups[groupIndexRenderOrder[i]]; + const isGalleryGroup = group.name === constants.GALLERY_GROUP_NAME; + + const groupModulesLength = group.modules.length; + if (group.enabled === GROUP_DISABLED || group.alpha < 0.001) { + continue; + } + + const { clearing, inherit, inheritFrom, pipeline } = group; + + const aux = group.drawToCanvasId && auxillary[group.drawToCanvasId].context; + const groupContext = group.context.context; + let drawTo = groupContext; + + if (aux) { + drawTo = aux; + } + + if (clearing) { + drawTo.clearRect(0, 0, drawTo.canvas.width, drawTo.canvas.height); + } + + if (pipeline && clearing && !isGalleryGroup) { + bufferContext.clearRect( + 0, + 0, + bufferContext.canvas.width, + bufferContext.canvas.height + ); + } + + if (inherit) { + const canvasToInherit = + inheritFrom === -1 + ? lastCanvas + : groups.find(group => group.id === inheritFrom).context.context + .canvas; + + drawTo.drawImage( + canvasToInherit, + 0, + 0, + drawTo.canvas.width, + drawTo.canvas.height + ); + + if (pipeline && !isGalleryGroup) { + bufferContext.drawImage( + canvasToInherit, + 0, + 0, + drawTo.canvas.width, + drawTo.canvas.height + ); + } + } + + let firstModuleDrawn = false; + for (let j = 0; j < groupModulesLength; ++j) { + let canvas = drawTo.canvas; + + const module = active[group.modules[j]]; + + if ( + !module || + !module.meta.enabled || + module.meta.alpha < 0.001 || + module.$status.length + ) { + continue; + } + + if (pipeline && firstModuleDrawn && !isGalleryGroup) { + canvas = bufferContext.canvas; + } else if (pipeline && !isGalleryGroup) { + canvas = drawTo.canvas; + firstModuleDrawn = true; + } else if (isGalleryGroup) { + canvas = aux.canvas; + } + + drawTo.save(); + drawTo.globalAlpha = module.meta.alpha || 1; + drawTo.globalCompositeOperation = + module.meta.compositeOperation || "normal"; + + const { props, data } = module; + const moduleDefinition = registered[module.$moduleName]; + let moduleData = data; + + if (moduleDefinition.update) { + moduleData = renderers[module.meta.type].updateModule({ + moduleDefinition, + props, + data: { ...data }, + canvas, + context: drawTo, + delta + }); + + store.commit("modules/UPDATE_ACTIVE_MODULE", { + id: module.$id, + key: "data", + value: moduleData + }); + } + + if (pipeline && !isGalleryGroup) { + drawTo.clearRect(0, 0, canvas.width, canvas.height); + drawTo.drawImage(bufferCanvas, 0, 0, canvas.width, canvas.height); + } + + renderers[module.meta.type].render({ + canvas, + context: drawTo, + delta, + module: moduleDefinition, + features, + meyda, + video, + props, + data: moduleData, + pipeline, + kick, + fftCanvas: fftOutput.context.canvas + }); + drawTo.restore(); + + if (pipeline && !isGalleryGroup) { + bufferContext.clearRect(0, 0, canvas.width, canvas.height); + bufferContext.drawImage( + drawTo.canvas, + 0, + 0, + canvas.width, + canvas.height + ); + + drawTo.clearRect(0, 0, canvas.width, canvas.height); + drawTo.drawImage( + bufferCanvas, + 0, + 0, + bufferCanvas.width, + bufferCanvas.height + ); + } + } + + if (!group.hidden && group.enabled === GROUP_ENABLED) { + lastCanvas = drawTo.canvas; + } + } + + const windowKeys = Object.keys(windows); + const windowsLength = windowKeys.length; + + main.clearRect(0, 0, main.canvas.width, main.canvas.height); + // minus one here to skip over the gallery group + for (let i = 0; i < groupsLength - 1; ++i) { + const group = groups[groupIndexRenderOrder[i]]; + + const { + compositeOperation, + context: { context }, + alpha, + enabled, + modules + } = group; + const groupModulesLength = modules.length; + if (enabled !== GROUP_ENABLED || groupModulesLength < 1 || !(alpha > 0)) { + continue; + } + + main.save(); + if (compositeOperation) { + main.globalCompositeOperation = compositeOperation; + } + + if (alpha < 1) { + main.globalAlpha = alpha; + } + + main.drawImage(context.canvas, 0, 0); + main.restore(); + } + + for (let i = 0; i < windowsLength; ++i) { + const { outputId } = windows[windowKeys[i]]; + const outputContext = store.state.outputs.auxillary[outputId]; + + if (outputContext) { + const { width, height } = outputContext.context.canvas; + outputContext.context.clearRect(0, 0, width, height); + outputContext.context.drawImage(main.canvas, 0, 0, width, height); + } + } + + if (main.canvas.width !== bufferCanvas.width) { + bufferCanvas.width = main.canvas.width; + } + + if (main.canvas.height !== bufferCanvas.height) { + bufferCanvas.height = main.canvas.height; + } + + if (debug && debugContext) { + const { canvas: debugCanvas } = debugContext; + const canvasToDebug = store.getters["outputs/canvasToDebug"]; + + if (canvasToDebug) { + const { width: dWidth, height: dHeight } = canvasToDebug.context.canvas; + const aspectRatio = dWidth / dHeight; + debugCanvas.height = debugCanvas.width / aspectRatio; + + debugContext.clearRect(0, 0, debugCanvas.width, debugCanvas.height); + debugContext.drawImage( + canvasToDebug.context.canvas, + 0, + 0, + debugCanvas.width, + debugCanvas.height + ); + } + } + + const postProcessFrameFunctions = + store.getters["plugins/postProcessFrame"] || []; + const postProcessFrameFunctionsLength = postProcessFrameFunctions.length; + + for (let i = 0; i < postProcessFrameFunctionsLength; ++i) { + postProcessFrameFunctions[i].postProcessFrame({ + canvas: main.canvas, + features, + store, + props: postProcessFrameFunctions[i].$props + }); + } +} + +export default loop; diff --git a/src/renderer/src/application/worker/store/index.js b/src/renderer/src/application/worker/store/index.js new file mode 100644 index 000000000..bd0db905e --- /dev/null +++ b/src/renderer/src/application/worker/store/index.js @@ -0,0 +1,75 @@ +import Vue from "vue"; +import Vuex from "vuex"; +import createPersistedState from "vuex-persistedstate"; + +import beats from "./modules/beats.js"; +import dataTypes from "./modules/dataTypes.js"; +import errors from "./modules/errors.js"; +import expressions from "./modules/expressions.js"; +import fonts from "./modules/fonts.js"; +import fps from "./modules/fps.js"; +import groups from "./modules/groups.js"; +import images from "./modules/images.js"; +import inputs from "./modules/inputs.js"; +import media from "./modules/media.js"; +import mediaStream from "./modules/mediaStream.js"; +import metrics from "./modules/metrics.js"; +import meyda from "./modules/meyda.js"; +import midi from "./modules/midi.js"; +import modules from "./modules/modules.js"; +import ndi from "./modules/ndi.js"; +import outputs from "./modules/outputs.js"; +import plugins from "./modules/plugins.js"; +import projects from "./modules/projects.js"; +import renderers from "./modules/renderers.js"; +import size from "./modules/size.js"; +import tweens from "./modules/tweens.js"; +import videos from "./modules/videos.js"; +import windows from "./modules/windows.js"; + +Vue.use(Vuex); + +const vuexPlugins = []; + +// createPersistedState doesn't work in the worker store, so don't run it there. +// That's okay as the worker doesn't need to know about mediaStream. +// If we want other persisted items that the worker needs to know about +// we'll need to implement something more complex to commit via postMessage. +if (self.document !== undefined) { + const dataState = createPersistedState({ + paths: ["mediaStream"] + }); + + vuexPlugins.push(dataState); +} + +export default new Vuex.Store({ + modules: { + beats, + dataTypes, + errors, + expressions, + fonts, + fps, + groups, + images, + inputs, + media, + mediaStream, + metrics, + meyda, + midi, + modules, + ndi, + outputs, + plugins, + projects, + renderers, + size, + tweens, + videos, + windows, + }, + plugins: vuexPlugins, + strict: false +}); diff --git a/src/renderer/src/application/worker/store/modules/beats.js b/src/renderer/src/application/worker/store/modules/beats.js new file mode 100644 index 000000000..ba819a570 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/beats.js @@ -0,0 +1,45 @@ +import Vue from "vue"; + +const state = { + bpm: 0, + bpmSource: "beatdetektor", + kick: false, + bpmSources: ["beatdetektor", "midi", "tap"] +}; + +const actions = { + setBpm({ commit, state }, { bpm, source }) { + if (!source) { + throw new Error("Setting the BPM requires a source to be given"); + } + + if (source === state.bpmSource) { + commit("SET_BPM", { bpm }); + } + } +}; + +const mutations = { + SET_BPM(state, { bpm }) { + state.bpm = bpm; + }, + + SET_KICK(state, { kick }) { + state.kick = kick; + }, + + ADD_BPM_SOURCE(state, { source }) { + state.bpmSources.push(source); + }, + + SET_BPM_SOURCE(state, { source }) { + Vue.set(state, "bpmSource", source); + } +}; + +export default { + namespaced: true, + state, + mutations, + actions +}; diff --git a/src/renderer/src/application/worker/store/modules/common/swap.js b/src/renderer/src/application/worker/store/modules/common/swap.js new file mode 100644 index 000000000..9eed32aa5 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/common/swap.js @@ -0,0 +1,89 @@ +import Vue from "vue"; + +/* + * When loading a preset we want to swap the data from the preset with the current state + * and make sure that only the "allowed" properties are moved, see sharedPropertyRestrictions. + * + * The idea is that this makes loading presets smooth and the end user will not see any + * glitches in the render loop. + */ +export function SWAP(swap, getDefault, sharedPropertyRestrictions) { + return function(state) { + const stateKeys = Object.keys(state); + + if (stateKeys.length) { + // eslint-disable-next-line + stateKeys.forEach(key => { + const isArray = Array.isArray(state[key]); + + if (sharedPropertyRestrictions) { + if (typeof sharedPropertyRestrictions[key] === "function") { + const stateChildKeys = Object.keys(state[key]); + + // eslint-disable-next-line + if (isArray) { + state[key] = state[key].filter(sharedPropertyRestrictions[key]); + } else { + const restrictedKeys = sharedPropertyRestrictions[key]( + state[key] + ); + + // eslint-disable-next-line + stateChildKeys.forEach(stateChildKey => { + if (restrictedKeys.indexOf(stateChildKey) < 0) { + delete state[key][stateChildKey]; + } + }); + } + } else if (sharedPropertyRestrictions[key]) { + delete state[key]; + } + } else { + delete state[key]; + } + }); + } + + const swapKeys = Object.keys(swap); + + if (swapKeys.length) { + // eslint-disable-next-line + swapKeys.forEach(key => { + const isArray = Array.isArray(swap[key]); + + if (sharedPropertyRestrictions) { + if (typeof sharedPropertyRestrictions[key] === "function") { + const swapChildKeys = Object.keys(swap[key]); + + if (isArray) { + Vue.set(state, key, [...state[key], ...swap[key]]); + } else { + const restrictedKeys = sharedPropertyRestrictions[key](swap[key]); + + // eslint-disable-next-line + swapChildKeys.forEach(swapChildKey => { + if (restrictedKeys.indexOf(swapChildKey) < 0) { + Vue.set(state[key], swapChildKey, swap[key][swapChildKey]); + } + }); + } + } else if (sharedPropertyRestrictions[key]) { + if (isArray) { + Vue.set(state, key, [...swap[key]]); + } else { + Vue.set(state, key, { ...swap[key] }); + } + } + } else { + if (isArray) { + Vue.set(state, key, [...state[key], ...swap[key]]); + } else { + Vue.set(state, key, { ...state[key], ...swap[key] }); + } + } + }); + } + + Object.assign(swap, getDefault()); + }; +} diff --git a/src/renderer/src/application/worker/store/modules/dataTypes.js b/src/renderer/src/application/worker/store/modules/dataTypes.js new file mode 100644 index 000000000..8dab2f645 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/dataTypes.js @@ -0,0 +1,146 @@ +import store from "../"; +import { frames, advanceFrame } from "./tweens"; + +const state = { + text: { + get: value => value + }, + int: { + get: value => value + }, + bool: { + get: value => value + }, + event: { + get: value => value + }, + vec2: { + get: value => value, + inputs: () => ({ 0: 0, 1: 0 }) + }, + vec3: { + get: value => value, + inputs: () => ({ 0: 0, 1: 0, 2: 0 }) + }, + vec4: { + get: value => value, + inputs: () => ({ 0: 0, 1: 0, 2: 0, 3: 0 }) + }, + float: { + get: value => value + }, + color: { + get: value => value, + inputs: () => ({ r: 0, g: 0, b: 0, a: 0 }) + }, + texture: { + async create(textureDefinition = {}, isGallery, useExistingData = false) { + const { type, options } = textureDefinition; + textureDefinition.location = textureDefinition.location ?? ""; + // textureDefinition.id = textureDefinition.id ?? ""; + + if (type === "image") { + const { path } = options; + let id; + try { + ({ id } = await store.dispatch("images/createImageFromPath", { + path + })); + } catch (e) { + console.error(e); + } + + textureDefinition.location = "images/image"; + textureDefinition.id = id; + } + + if (type === "video" && (useExistingData || !textureDefinition.id)) { + let id; + try { + ({ id } = await store.dispatch( + "videos/createVideoFromPath", + textureDefinition + )); + } catch (e) { + console.error(e); + } + + textureDefinition.location = "videos/video"; + textureDefinition.id = id; + } + + if (type === "canvas" || type === "group") { + const { id } = options; + textureDefinition.location = "outputs/auxillaryCanvas"; + textureDefinition.id = id; + } + + textureDefinition.isGallery = isGallery; + + return Object.defineProperty(textureDefinition, "value", { + enumerable: true, + get() { + return store.state.dataTypes.texture.get(textureDefinition); + } + }); + }, + async destroy(textureDefinition) { + const { type, id } = textureDefinition; + + if (type === "video") { + await store.dispatch("videos/removeVideoById", { + id + }); + } + }, + get: textureDefinition => { + if (!textureDefinition.location.length) { + return false; + } + + return store.getters[textureDefinition.location](textureDefinition.id); + } + }, + enum: { + get: value => value + }, + + tween: { + async create(args, isGallery) { + const tween = await store.dispatch("tweens/createTween", { + ...args, + isGallery + }); + return Object.defineProperty(tween, "value", { + enumerable: true, + get() { + return store.state.dataTypes.tween.get(tween); + } + }); + }, + + get: value => { + const tween = store.state.tweens.tweens[value.id]; + const frame = tween.frames[frames[value.id]]; + advanceFrame(value.id); + return frame; + } + } +}; + +const getters = { + types: state => Object.keys(state) +}; + +const actions = { + createType({ state }, { type, args }) { + return state[type].create(args); + } +}; + +export default { + namespaced: true, + state, + getters, + actions +}; diff --git a/src/renderer/src/application/worker/store/modules/errors.js b/src/renderer/src/application/worker/store/modules/errors.js new file mode 100644 index 000000000..051dd4e8b --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/errors.js @@ -0,0 +1,37 @@ +import Vue from "vue"; +import { v4 as uuidv4 } from "uuid"; + +const state = { + messages: {} +}; + +const actions = { + createMessage({ commit }, { message }) { + if (!message) { + throw new Error("No message given"); + } + + commit("CREATE_MESSAGE", { message, id: uuidv4() }); + }, + + deleteMessage({ commit }, { id }) { + commit("REMOVE_MESSAGE", { id }); + } +}; + +const mutations = { + CREATE_MESSAGE(state, { message, id }) { + Vue.set(state.messages, id, message); + }, + + REMOVE_MESSAGE(state, { id }) { + Vue.delete(state.messages, id); + } +}; + +export default { + namespaced: true, + state, + mutations, + actions +}; diff --git a/src/renderer/src/application/worker/store/modules/expressions.js b/src/renderer/src/application/worker/store/modules/expressions.js new file mode 100644 index 000000000..3aeacfeca --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/expressions.js @@ -0,0 +1,153 @@ +import get from "lodash.get"; +import { v4 as uuidv4 } from "uuid"; +import math from "mathjs/dist/math.js"; +import { SWAP } from "./common/swap"; + +function getDefaultState() { + return { + assignments: {} + }; +} + +const state = getDefaultState(); +const swap = getDefaultState(); + +// getters +const getters = { + getByInputId: state => inputId => { + const assignmentValues = Object.values(state.assignments); + + return assignmentValues.find(assignment => assignment.inputId === inputId); + } +}; + +function compileExpression(expression, scopeItems = {}) { + const scope = { value: 0, time: 0, ...scopeItems }; + + let newFunction; + try { + const node = math.parse(expression, scope); + + newFunction = node.compile(); + newFunction.evaluate(scope); + } catch (e) { + throw e; + } + + return newFunction; +} + +// actions +const actions = { + create( + { rootState, commit }, + { expression = "value", id, inputId, writeToSwap } + ) { + if (!inputId) { + throw new Error("Input ID required"); + } + + if (expression.trim() === "value") { + return null; + } + + const expressionId = id || uuidv4(); + + const input = rootState.inputs.inputs[inputId]; + + const func = compileExpression(expression, { + // We currrently have no way of interacting with swap state. + // This would be something to fix in the future, maybe use an entire store + // for swap, or write a more specific mechanism to look up values in swap + // state. + inputValue: writeToSwap ? 0 : get(rootState, input.getLocation) + }); + + if (!func) { + throw new Error("Unable to compile Expression"); + } + + const assignment = { + id: expressionId, + inputId, + func, + expression + }; + + commit("ADD_EXPRESSION", { assignment, writeToSwap }); + + return expressionId; + }, + + update({ rootState, commit }, { id, expression = "value", writeToSwap }) { + if (!id) { + throw new Error("Expression ID required"); + } + + const existingExpression = state.assignments[id]; + + if (!existingExpression) { + throw new Error(`Existing expression with ID ${id} not found`); + } + + if (expression.trim() === "value") { + commit("REMOVE_EXPRESSION", { id }); + return null; + } + + const input = rootState.inputs.inputs[existingExpression.inputId]; + + const func = compileExpression(expression, { + inputValue: get(rootState, input.getLocation) + }); + + if (!func) { + throw new Error("Unable to compile Expression"); + } + + existingExpression.func = func; + existingExpression.expression = expression; + + commit("ADD_EXPRESSION", { assignment: existingExpression, writeToSwap }); + return existingExpression.id; + }, + + remove({ commit }, args) { + commit("REMOVE_EXPRESSION", args); + }, + + createPresetData() { + return state; + }, + + async loadPresetData({ dispatch }, data) { + const assignments = Object.values(data.assignments); + for (let i = 0, len = assignments.length; i < len; i++) { + const assignment = assignments[i]; + + await dispatch("create", { ...assignment, writeToSwap: true }); + } + } +}; + +// mutations +const mutations = { + ADD_EXPRESSION(state, { assignment, writeToSwap = false }) { + const writeTo = writeToSwap ? swap : state; + writeTo.assignments[assignment.id] = assignment; + }, + + REMOVE_EXPRESSION(state, { id }) { + delete state.assignments[id]; + }, + + SWAP: SWAP(swap, getDefaultState) +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/fonts.js b/src/renderer/src/application/worker/store/modules/fonts.js new file mode 100644 index 000000000..0b2079d14 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/fonts.js @@ -0,0 +1,30 @@ +const state = { + defaultFonts: ["serif", "sans-serif", "cursive", "monospace"], + localFonts: [] +}; + +const getters = { + fonts: state => [ + ...state.defaultFonts, + ...state.localFonts + .filter( + (value, index, self) => + index === self.findIndex(t => t.family === value.family) + ) + .map(font => font.family) + .sort((a, b) => a.localeCompare(b)) + ] +}; + +const mutations = { + SET_LOCAL_FONTS(state, fonts = []) { + state.localFonts = fonts; + } +}; + +export default { + namespaced: true, + state, + getters, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/fps.js b/src/renderer/src/application/worker/store/modules/fps.js new file mode 100644 index 000000000..8ec68b852 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/fps.js @@ -0,0 +1,31 @@ +const state = { + fps: 60 +}; + +const getters = { + interval: state => 1000 / state.fps +}; + +const actions = { + setFPS({ commit }, { fps }) { + if (!fps) { + throw new Error("No FPS given"); + } + + commit("SET_FPS", { fps }); + } +}; + +const mutations = { + SET_FPS(state, { fps }) { + state.fps = fps; + } +}; + +export default { + namespaced: true, + state, + getters, + mutations, + actions +}; diff --git a/src/renderer/src/application/worker/store/modules/groups.js b/src/renderer/src/application/worker/store/modules/groups.js new file mode 100644 index 000000000..47851f1c8 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/groups.js @@ -0,0 +1,430 @@ +import { SWAP } from "./common/swap"; +import store from "../"; +import constants, { GROUP_DISABLED } from "../../../constants"; +import { v4 as uuidv4 } from "uuid"; +import { applyExpression } from "../../../utils/apply-expression"; + +/** + * @typedef {Object} Group + * + * @property {String} id UUID of the Group + * + * @property {String} name Name of the Group + * + * @property {Boolean} hidden Indicated whether this group is hidden or not + * + * @property {Array} modules The draw order of the Modules contained within the Group + * + * @property {Number} enabled Indicates whether the Group should be drawn. 0: not drawn, + * 1: drawn, 2: not drawn to output canvas + * + * @property {Number} alpha The level of opacity, between 0 and 1, the Group should + * be drawn at + * + * @property {Boolean} inherit Indicates whether the Group should inherit from another + * Group at redraw + * + * @property {String|Number} inheritFrom The target Group to inherit from, -1 being the previous + * Group in modV#Groups, UUID4 string being the ID of + * another Group within modV.store.state.groups.groups + * + * @property {Boolean} pipeline Indicates whether the Group should render using pipeline + * at redraw + * + * @property {Boolean} clearing Indicates whether the Group should clear before redraw + * + * @property {String} compositeOperation The {@link Blendmode} the Group muxes with + * + * @property {String} drawToCanvasId The ID of the auxillary Canvas to draw the Group to, + * null indicates the Group should draw to the main output + * + * @property {String} alphaInputId The Input ID for the Alpha control + * + * @property {String} enabledInputId The Input ID for the Enabled control + * + * @property {String} clearingInputId The Input ID for the Clearing control + * + * @property {String} inheritInputId The Input ID for the Inherit control + * + * @property {String} compositeOperationInputId The Input ID for the Composite Operation control + * + * @property {String} pipelineInputId The Input ID for the Pipeline control + * + * @example + * const Group = { + * name: 'Group', + * + * position: 0, + * + * modules: [ + * 'uuid1', + * 'uuid2', + * ], + * + * enabled: true, + * + * alpha: 1, + * + * inherit: true, + * + * inheritFrom: -1, + * + * pipeline: false, + * + * clearing: false, + * + * compositeOperation: 'normal', + * + * drawToCanvasId: null, + * }; + */ + +function getDefaultState() { + return { groups: [] }; +} + +const state = getDefaultState(); +const swap = getDefaultState(); + +// Any keys marked false or arrays with keys given +// will not be moved from the base state when swapped +const sharedPropertyRestrictions = { + groups: ( + // As state.groups is an Array, we return a function which checks the item in the Array, + // returns a boolean. True to remove, false to keep. + // Objects return an Array of keys to remove. + // This keeps gallery group in place + group + ) => group.name === constants.GALLERY_GROUP_NAME +}; + +const getters = { + groupIndexRenderOrder: state => { + const galleryGroupIndex = state.groups.findIndex( + group => group.name === constants.GALLERY_GROUP_NAME + ); + const indexes = [...state.groups.keys()]; + + if (galleryGroupIndex > -1) { + indexes.splice(galleryGroupIndex, 1); + indexes.push(galleryGroupIndex); + } + + return indexes; + } +}; + +const actions = { + async createGroup({ commit }, args = {}) { + const name = args.name || "New Group"; + const writeTo = args.writeToSwap ? swap : state; + const inherit = args.inherit === undefined ? true : args.inherit; + + const existingGroupIndex = writeTo.groups.findIndex( + group => group.id === args.id + ); + + if (existingGroupIndex > -1) { + return writeTo.groups[existingGroupIndex]; + } + + const id = args.id || uuidv4(); + + const group = { + ...args, + name, + id, + clearing: args.clearing ?? false, + enabled: Number(args.enabled) ?? GROUP_DISABLED, + hidden: args.hidden ?? false, + modules: args.modules ?? [], + inherit, + inheritFrom: args.inheritFrom ?? -1, + alpha: args.alpha ?? 1, + pipeline: args.pipeline ?? 0, + compositeOperation: args.compositeOperation || "normal", + context: await store.dispatch("outputs/getAuxillaryOutput", { + name, + group: "group", + id + }) + }; + + const inputs = [ + "alpha", + "enabled", + "clearing", + "inherit", + "compositeOperation", + "pipeline" + ]; + for (let i = 0; i < inputs.length; i += 1) { + const inputName = inputs[i]; + const idKey = `${inputName}InputId`; + + if (args[idKey] !== undefined) { + group[idKey] = args[idKey]; + } else { + group[idKey] = ( + await store.dispatch("inputs/addInput", { + type: "commit", + location: "groups/UPDATE_GROUP_BY_KEY", + data: { groupId: group.id, key: inputName } + }) + ).id; + } + } + + commit("ADD_GROUP", { group, writeToSwap: args.writeToSwap }); + + return group; + }, + + async removeGroup({ commit }, { groupId, writeToSwap }) { + const group = state.groups.find(group => group.id === groupId); + + const inputIds = [ + group.alphaInputId, + group.enabledInputId, + group.clearingInputId, + group.inheritInputId, + group.compositeOperationInputId, + group.pipelineInputId + ]; + + for (let i = 0; i < inputIds.length; i += 1) { + const inputId = inputIds[i]; + + await store.dispatch("inputs/removeInput", { + inputId + }); + } + + for (let i = 0; i < group.modules.length; i += 1) { + const moduleId = group.modules[i]; + + await store.dispatch("modules/removeActiveModule", { moduleId }); + } + + commit("REMOVE_GROUP", { id: groupId, writeToSwap }); + }, + + orderByIds({ commit }, { ids }) { + const newGroups = ids.map(id => { + return state.groups.find(group => group.id === id); + }); + + commit("REPLACE_GROUPS", { groups: newGroups }); + }, + + createPresetData() { + return state.groups + .filter(group => group.name !== constants.GALLERY_GROUP_NAME) + .map(group => { + const clonedGroup = { ...group }; + delete clonedGroup.context; + return clonedGroup; + }); + }, + + updateGroupName({ commit }, { groupId, name }) { + const group = state.groups.find(group => group.id === groupId); + + store.commit("outputs/UPDATE_AUXILLARY", { + auxillaryId: group.context.id, + data: { + name + } + }); + + commit("UPDATE_GROUP", { + groupId, + data: { + name + } + }); + }, + + async loadPresetData({ dispatch }, groups) { + for (let i = 0, len = groups.length; i < len; i++) { + const group = groups[i]; + await dispatch("createGroup", { ...group, writeToSwap: true }); + } + + return; + }, + + updateGroupInput({ commit }, { groupId, key, data, writeToSwap }) { + let dataOut = data; + + const group = state.groups.find(group => group.id === groupId); + const inputId = group[`${key}InputId`]; + dataOut = applyExpression({ inputId, value: dataOut }); + + commit("UPDATE_GROUP_BY_KEY", { groupId, key, data: dataOut, writeToSwap }); + }, + + async duplicateModule({ commit }, { groupId, moduleId }) { + const group = state.groups.find(group => group.id === groupId); + const position = + group.modules.findIndex(moduleListId => moduleListId === moduleId) + 1; + const existingModule = store.state.modules.active[moduleId]; + + const existingInputIds = store.getters["modules/activeModuleInputIds"]( + existingModule.$id + ); + + const existingInputLinks = existingInputIds.reduce((obj, id) => { + obj[id] = store.state.inputs.inputLinks[id]; + return obj; + }, {}); + + const duplicateModule = await store.dispatch("modules/makeActiveModule", { + moduleName: existingModule.meta.name, + existingModule, + generateNewIds: true + }); + + const newInputIds = store.getters["modules/activeModuleInputIds"]( + duplicateModule.$id + ); + + for (let i = 0; i < newInputIds.length; i += 1) { + const newInputId = newInputIds[i]; + const existingInputId = existingInputIds[i]; + + if (existingInputLinks[existingInputId]) { + await store.dispatch("inputs/createInputLink", { + ...existingInputLinks[existingInputId], + inputId: newInputId + }); + } + + const existingExpression = store.getters["expressions/getByInputId"]( + existingInputId + ); + + if (existingExpression) { + await store.dispatch("expressions/create", { + expression: existingExpression.expression, + inputId: newInputId + }); + } + } + + commit("ADD_MODULE_TO_GROUP", { + moduleId: duplicateModule.$id, + groupId, + position + }); + } +}; + +const mutations = { + ADD_GROUP(state, { group, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + writeTo.groups.push(group); + }, + + REMOVE_GROUP(state, { id, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + const index = writeTo.groups.findIndex(group => group.id === id); + + if (index > -1) { + writeTo.groups.splice(index, 1); + } + }, + + REPLACE_GROUPS(state, { groups, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + const oldGroups = writeTo.groups.splice(0); + + const groupsLength = groups.length; + let hasWrittenGalleryId = false; + for (let i = 0; i < groupsLength; i += 1) { + writeTo.groups.push(groups[i]); + if (groups[i].name === constants.GALLERY_GROUP_NAME) { + hasWrittenGalleryId = true; + } + } + + if (!hasWrittenGalleryId) { + writeTo.groups.push( + oldGroups.find(group => group.name === constants.GALLERY_GROUP_NAME) + ); + } + }, + + REPLACE_GROUP_MODULES(state, { groupId, modules, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + const index = writeTo.groups.findIndex(group => group.id === groupId); + + if (index > -1) { + writeTo.groups[index].modules = modules; + } + }, + + ADD_MODULE_TO_GROUP(state, { moduleId, groupId, position, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + const groupIndex = writeTo.groups.findIndex(group => group.id === groupId); + + if (groupIndex < 0) { + return false; + } + + const positionActual = + typeof position === "undefined" + ? writeTo.groups[groupIndex].modules.length + : position; + writeTo.groups[groupIndex].modules.splice(positionActual, 0, moduleId); + }, + + REMOVE_MODULE_FROM_GROUP(state, { moduleId, groupId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + const groupIndex = writeTo.groups.findIndex(group => group.id === groupId); + const moduleIndex = writeTo.groups[groupIndex].modules.findIndex( + module => module === moduleId + ); + + if (groupIndex < 0 || moduleIndex < 0) { + return false; + } + + writeTo.groups[groupIndex].modules.splice(moduleIndex, 1); + }, + + UPDATE_GROUP(state, { groupId, data, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + const index = writeTo.groups.findIndex(group => group.id === groupId); + + if (index > -1) { + const dataKeys = Object.keys(data); + const dataKeysLength = dataKeys.length; + for (let i = 0; i < dataKeysLength; i += 1) { + const key = dataKeys[i]; + const value = data[key]; + writeTo.groups[index][key] = value; + } + } + }, + + UPDATE_GROUP_BY_KEY(state, { groupId, key, data, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + const index = writeTo.groups.findIndex(group => group.id === groupId); + + if (index > -1) { + writeTo.groups[index][key] = data; + } + }, + + SWAP: SWAP(swap, getDefaultState, sharedPropertyRestrictions) +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/images.js b/src/renderer/src/application/worker/store/modules/images.js new file mode 100644 index 000000000..b3ca197e9 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/images.js @@ -0,0 +1,62 @@ +import streamToBlob from "stream-to-blob"; +import fs from "fs"; +import path from "path"; + +import Vue from "vue"; +import { v4 as uuidv4 } from "uuid"; + +import store from "../"; +import { conformFilePath } from "../../../utils/conform-file-path"; + +const state = {}; + +const getters = { + image: state => id => state[id] +}; + +const actions = { + async createImageFromPath({ commit }, { path: filePath }) { + let stream; + let joinedFilePath; + + try { + joinedFilePath = path.join( + store.state.media.path, + conformFilePath(filePath) + ); + } catch (e) { + console.log(e); + } + + try { + stream = fs.createReadStream(joinedFilePath); + } catch (error) { + throw error; + } + + if (!stream) { + return {}; + } + + const blob = await streamToBlob(stream); + const imageBitmap = await createImageBitmap(blob); + + const id = uuidv4(); + commit("SAVE_IMAGE", { id, imageBitmap }); + return { id }; + } +}; + +const mutations = { + SAVE_IMAGE(state, { id, imageBitmap }) { + Vue.set(state, id, imageBitmap); + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/inputs.js b/src/renderer/src/application/worker/store/modules/inputs.js new file mode 100644 index 000000000..bcd13786d --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/inputs.js @@ -0,0 +1,269 @@ +import Vue from "vue"; +import { v4 as uuidv4 } from "uuid"; +import { SWAP } from "./common/swap"; + +/** + * InputLinkType enum string values. + * @enum {String} + */ +// eslint-disable-next-line +const InputLinkType = { + getter: "getter", + mutation: "mutation", + state: "state" +}; + +/** + * @typedef {Object} InputLink + * + * @property {String} inputId The ID of the @Input to link to + * + * @property {InputLinkType} type The type of the @InputLink + * + * @property {String} source Identifies what created and/or manages this input link + * + * @property {Number} [min] Minimum expected value, will scale the incoming value to + * the input's min/max if min and max are set + * + * @property {Number} [max] Maximum expected value, will scale the incoming value to + * the input's min/max if min and max are set + */ + +/** + * @typedef {Object} InputLinkGetterType + * + * @property {String} location location of the getter in the store, e.g. meyda/getFeature + * + * @property {Array} [args] arguments for a getter that is a function, e.g. ["energy"] + * + * + * ⚠️ updates every frame + * Reads a getter in the store, possibly with arguments, defined in ?args, and applies that to the + * input link defined with inputId + * @typedef {InputLink & InputLinkGetterType} InputLinkGetter + */ + +/** + * @typedef {Object} InputLinkMutationType + * + * @property {String} location location of the value to read in the store, e.g. + * midi.devices[1].channelData[1][144] + * + * @property {Object} match parameters of the mutation to match + * + * @property {String} match.type Mutation type from the store, e.g. midi/WRITE_DATA + * + * @property {String} [match.payload] Key-value pairs to match in the mutation payload, e.g. + * { + id: `${id}-${name}-${manufacturer}`, + channel: 1, + type: 144, + } + * + * Waits for a mutation type, defined with match.type, possibly checks the mutation payload for + * specific unique parameters, defined on match.?payload, and applies a store value, specified + * with location, to the input link defined with inputId + * @typedef {InputLink & InputLinkMutationType} InputLinkMutation + */ + +/** + * @typedef {Object} InputLinkStateType + * + * @property {String} location location of the getter in the store, e.g. + * midi.devices[1].channelData[1][144] + * + * ⚠️ updates every frame + * Reads a location in the store and applies that to the input link defined with inputId + * @typedef {InputLink & InputLinkStateType} InputLinkState + */ + +function getDefaultState() { + return { + focusedInput: { id: null, title: null }, + inputs: {}, + inputLinks: {} + }; +} + +const state = getDefaultState(); +const swap = getDefaultState(); + +const getters = { + inputsByActiveModuleId: state => moduleId => + Object.values(state.inputs).filter( + input => input.data.moduleId === moduleId + ) +}; + +const actions = { + setFocusedInput({ commit }, { id, title, writeToSwap }) { + commit("SET_FOCUSED_INPUT", { id, title, writeToSwap }); + }, + + clearFocusedInput({ commit }) { + commit("SET_FOCUSED_INPUT", { id: null, title: null }); + }, + + addInput( + { commit }, + { type, getLocation, location, data, id = uuidv4(), writeToSwap } + ) { + const input = { type, getLocation, location, data, id }; + commit("ADD_INPUT", { input, writeToSwap }); + return input; + }, + + removeInput({ commit }, { inputId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + if (!writeTo.inputs[inputId]) { + console.warn( + "Did not remove input. Could not find input with id", + inputId + ); + + return false; + } + + commit("REMOVE_INPUT", { inputId, writeToSwap }); + return true; + }, + + createInputLink( + { commit }, + { + inputId, + location, + type = "state", + args, + min = 0, + max = 1, + source, + match, + writeToSwap + } + ) { + const writeTo = writeToSwap ? swap : state; + + if (!source) { + console.warn("Did not create inputLink. Require source", inputId); + + return false; + } + + const inputLink = { + id: inputId, + location, + type, + args, + min, + max, + source, + match + }; + if (!writeTo.inputs[inputId]) { + console.warn( + "Did not create inputLink. Could not find input with id", + inputId + ); + + return false; + } + + commit("ADD_INPUT_LINK", { inputLink, writeToSwap }); + return true; + }, + + updateInputLink({ commit }, { inputId, key, value, writeToSwap }) { + commit("UPDATE_INPUT_LINK", { inputId, key, value, writeToSwap }); + }, + + removeInputLink({ commit }, { inputId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + if (!writeTo.inputs[inputId]) { + return false; + } + + commit("REMOVE_INPUT_LINK", { inputId, writeToSwap }); + return true; + }, + + createPresetData() { + return state; + }, + + async loadPresetData({ dispatch }, data) { + await dispatch("setFocusedInput", data.focusedInput); + + const inputs = Object.values(data.inputs); + for (let i = 0, len = inputs.length; i < len; i++) { + const input = inputs[i]; + + await dispatch("addInput", { ...input, writeToSwap: true }); + } + + const inputLinks = Object.values(data.inputLinks); + + for (let i = 0, len = inputLinks.length; i < len; i++) { + const link = inputLinks[i]; + + await dispatch("createInputLink", { + inputId: link.id, + ...link, + writeToSwap: true + }); + } + } +}; + +const mutations = { + SET_FOCUSED_INPUT(state, { id, title, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + writeTo.focusedInput.id = id; + writeTo.focusedInput.title = title; + }, + + ADD_INPUT(state, { input, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + writeTo.inputs[input.id] = input; + }, + + REMOVE_INPUT(state, { inputId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + Vue.delete(writeTo.inputs, inputId); + }, + + ADD_INPUT_LINK(state, { inputLink, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + Vue.set(writeTo.inputLinks, inputLink.id, inputLink); + }, + + UPDATE_INPUT_LINK(state, { inputId, key, value, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + if (!writeTo.inputLinks[inputId]) { + return; + } + + Vue.set(writeTo.inputLinks[inputId], key, value); + }, + + REMOVE_INPUT_LINK(state, { inputId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + Vue.delete(writeTo.inputLinks, inputId); + }, + + SWAP: SWAP(swap, getDefaultState) +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/media.js b/src/renderer/src/application/worker/store/modules/media.js new file mode 100644 index 000000000..a937ba0df --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/media.js @@ -0,0 +1,65 @@ +import store from "../"; +import streamToBlob from "stream-to-blob"; +import fs from "fs"; +import path from "path"; +import media from "../../../../../../main/media-manager/store/modules/media.js"; + +/** + * Holds processed media + * + * @type {Object} + */ +const state = media.state; + +const getters = media.getters; + +const actions = { + ...media.actions, + + async addMedia({ commit }, { project, folder, item }) { + if (folder === "module" || folder === "isf") { + const stream = fs.createReadStream( + path.join(store.state.media.path, item.path) + ); + const blob = await streamToBlob(stream); + + let text; + let module; + + try { + text = await blob.text(); + } catch (e) { + console.error(`Could not load module`, item.name); + console.error(e); + return; + } + + try { + module = eval(text).default; + } catch (e) { + console.error(`Could not load module`, item.name); + console.error(e); + } + + try { + await store.dispatch("modules/registerModule", { module, hot: true }); + } catch (e) { + console.error(`Could not load module`, item.name); + console.error(e); + return; + } + } + + commit("ADD", { project, folder, item }); + } +}; + +const mutations = media.mutations; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/mediaStream.js b/src/renderer/src/application/worker/store/modules/mediaStream.js new file mode 100644 index 000000000..043c6850a --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/mediaStream.js @@ -0,0 +1,39 @@ +const state = { + audio: [], + video: [], + + currentAudioSource: null, + currentVideoSource: null +}; + +const mutations = { + ADD_AUDIO_SOURCE(state, { source }) { + state.audio.push(source); + }, + + ADD_VIDEO_SOURCE(state, { source }) { + state.video.push(source); + }, + + CLEAR_AUDIO_SOURCES(state) { + state.audio = []; + }, + + CLEAR_VIDEO_SOURCES(state) { + state.video = []; + }, + + SET_CURRENT_AUDIO_SOURCE(state, { audioId }) { + state.currentAudioSource = audioId; + }, + + SET_CURRENT_VIDEO_SOURCE(state, { videoId }) { + state.currentVideoSource = videoId; + } +}; + +export default { + namespaced: true, + state, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/metrics.js b/src/renderer/src/application/worker/store/modules/metrics.js new file mode 100644 index 000000000..40866e99b --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/metrics.js @@ -0,0 +1,15 @@ +const state = { + fps: 0 +}; + +const mutations = { + SET_FPS_MEASURE(state, fps) { + state.fps = fps; + } +}; + +export default { + namespaced: true, + state, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/meyda.js b/src/renderer/src/application/worker/store/modules/meyda.js new file mode 100644 index 000000000..de06b704e --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/meyda.js @@ -0,0 +1,96 @@ +import { + getFeature, + addSmoothingId, + removeSmoothingId, + getSmoothedFeature, + MAX_SMOOTHING, + SMOOTHING_STEP +} from "../../audio-features"; +import { v4 as uuidv4 } from "uuid"; + +const state = { + features: [ + "buffer", + "complexSpectrum", + "rms", + "zcr", + "energy", + "spectralCentroid", + "spectralFlatness", + "spectralSlope", + "spectralRolloff", + "spectralSpread", + "spectralSkewness", + "spectralKurtosis", + "perceptualSpread", + "perceptualSharpness" + ], + smoothingIds: [], + MAX_SMOOTHING, + SMOOTHING_STEP +}; + +const getters = { + getFeature: () => (feature, smoothingId, smoothingValue) => { + if (smoothingId && smoothingValue) { + return getSmoothedFeature(feature, smoothingId, smoothingValue); + } + + return getFeature(feature); + } +}; + +const actions = { + addFeature({ commit }, feature) { + commit("ADD_FEATURE", feature); + }, + + getSmoothingId() { + const id = uuidv4(); + addSmoothingId(id); + return id; + }, + + // eslint-disable-next-line no-empty-pattern + removeSmoothingId({}, id) { + removeSmoothingId(id); + } +}; + +const mutations = { + ADD_FEATURE(state, feature) { + const index = state.features.indexOf(feature); + + if (index < 0) { + state.features.push(feature); + } + }, + + REMOVE_FEATURE(state, feature) { + const index = state.features.indexOf(feature); + + if (index > -1) { + state.features.splice(index, 1); + } + }, + + ADD_SMOOTHING_ID(state, id) { + state.smoothingIds.push(id); + }, + + REMOVE_SMOOTHING_ID(state, id) { + const index = state.smoothingIds.indexOf(id); + + if (index > -1) { + state.smoothingIds.splice(index, 1); + } + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/midi.js b/src/renderer/src/application/worker/store/modules/midi.js new file mode 100644 index 000000000..46c30aa85 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/midi.js @@ -0,0 +1,78 @@ +import Vue from "vue"; + +const state = { + devices: {}, + learning: false +}; + +const actions = { + learn({ commit }) { + let resolve; + const promise = new Promise(r => { + resolve = message => { + commit("SET_LEARNING", false); + r(message); + }; + }); + + commit("SET_LEARNING", resolve); + return promise; + }, + + createPresetData() { + return state; + }, + + async loadPresetData({ commit }, newState) { + commit("SET_STATE", newState); + return; + } +}; + +const mutations = { + ADD_DEVICE(state, { id, name, manufacturer }) { + Vue.set(state.devices, `${id}-${name}-${manufacturer}`, { + id, + name, + manufacturer, + channelData: {}, + listenForInput: true, + listenForClock: false, + ccLatch: false, + noteOnLatch: false + }); + }, + + UPDATE_DEVICE(state, { id, key, value }) { + Vue.set(state.devices[id], key, value); + }, + + WRITE_DATA(state, { id, channel, type, data }) { + if (!state.devices[id].channelData[channel]) { + Vue.set(state.devices[id].channelData, channel, {}); + } + + Vue.set(state.devices[id].channelData[channel], type, data); + }, + + SET_LEARNING(state, value) { + Vue.set(state, "learning", value); + }, + + SET_STATE(state, newState) { + const keys = Object.keys(newState); + + for (let i = 0, len = keys.length; i < len; i++) { + const key = keys[i]; + + state[key] = newState[key]; + } + } +}; + +export default { + namespaced: true, + state, + mutations, + actions +}; diff --git a/src/renderer/src/application/worker/store/modules/modules.js b/src/renderer/src/application/worker/store/modules/modules.js new file mode 100644 index 000000000..77a333bca --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/modules.js @@ -0,0 +1,823 @@ +import Vue from "vue"; +import { SWAP } from "./common/swap"; +import getNextName from "../../../utils/get-next-name"; +import getPropDefault from "../../../utils/get-prop-default"; +import store from ".."; +import get from "lodash.get"; +import set from "lodash.set"; + +import { v4 as uuidv4 } from "uuid"; +import { applyExpression } from "../../../utils/apply-expression"; + +// Any keys marked false or arrays with keys given +// will not be moved from the base state when swapped +const sharedPropertyRestrictions = { + registered: false, // will not move + active: ( + // keeps gallery item modules in place + value + ) => + Object.values(value) + .filter(module => module.meta.isGallery) + .map(module => module.$id), + propQueue: true, // will move + metaQueue: true // will move +}; + +function getDefaultState() { + return { + registered: {}, + active: {}, + propQueue: {}, + metaQueue: {} + }; +} + +const state = getDefaultState(); +const swap = getDefaultState(); + +// this function either creates module properties from an existing module +// (e.g. loading a preset) or initialises the default value +async function initialiseModuleProperties( + props, + module, + isGallery = false, + useExistingData = false, + existingData = {}, + writeToSwap = false, + generateNewIds = false +) { + const propKeys = Object.keys(props); + const propsWithoutId = []; + + if (useExistingData) { + for (let i = 0, len = propKeys.length; i < len; i += 1) { + const prop = propKeys[i]; + const propDidExist = !!existingData.$props[prop]; + + if (propDidExist) { + module.$props[prop].id = existingData.$props[prop].id; + } else { + propsWithoutId.push(prop); + } + } + } + + for (let i = 0, len = propKeys.length; i < len; i++) { + const propKey = propKeys[i]; + + const prop = props[propKey]; + + module.props[propKey] = await getPropDefault( + module, + propKey, + prop, + useExistingData + ); + + if ( + (!isGallery && !useExistingData) || + (propsWithoutId.length && propsWithoutId.indexOf(propKey) > -1) || + generateNewIds + ) { + const inputBind = await store.dispatch("inputs/addInput", { + type: "action", + getLocation: `modules.active["${module.$id}"].props["${propKey}"]`, + location: "modules/updateProp", + data: { moduleId: module.$id, prop: propKey }, + writeToSwap + }); + + if ( + prop.type in store.state.dataTypes && + store.state.dataTypes[prop.type].inputs + ) { + const dataTypeInputs = store.state.dataTypes[prop.type].inputs(); + const dataTypeInputsKeys = Object.keys(dataTypeInputs); + + for (let i = 0; i < dataTypeInputsKeys.length; i += 1) { + const key = dataTypeInputsKeys[i]; + await store.dispatch("inputs/addInput", { + type: "action", + getLocation: `modules.active["${module.$id}"].props["${propKey}"]["${key}"]`, + location: "modules/updateProp", + data: { + moduleId: module.$id, + prop: propKey, + path: `[${key}]` + }, + id: `${inputBind.id}-${key}`, + writeToSwap + }); + } + } + + module.$props[propKey].id = inputBind.id; + } + } + + return module; +} + +const getters = { + activeModuleInputIds: state => activeModuleId => { + const activeModule = state.active[activeModuleId]; + return [ + activeModule.meta.alphaInputId, + activeModule.meta.enabledInputId, + activeModule.meta.compositeOperationInputId, + ...store.getters["inputs/inputsByActiveModuleId"](activeModule.$id).map( + input => input.id + ) + ]; + } +}; + +const actions = { + async registerModule( + { commit, rootState }, + { module: moduleDefinition, hot = false } + ) { + const { renderers } = rootState; + + if (!moduleDefinition) { + console.error("No module to register."); + return; + } + + if (!moduleDefinition.meta) { + console.error("Malformed module."); + return; + } + + const { name, type } = moduleDefinition.meta; + + const existingModuleWithDuplicateName = Object.values( + state.registered + ).findIndex(registeredModule => registeredModule.meta.name === name); + + if (!hot && existingModuleWithDuplicateName > -1) { + console.error(`Module registered with name "${name}" already exists.`); + return; + } + + if (renderers[type].setupModule) { + try { + moduleDefinition = await renderers[type].setupModule(moduleDefinition); + } catch (e) { + console.error( + `Error in ${type} renderer setup whilst registering "${name}". This module was ommited from registration. \n\n${e}` + ); + + return false; + } + } + + commit("ADD_REGISTERED_MODULE", { module: moduleDefinition }); + + if (hot) { + const activeModuleValues = Object.values(state.active).filter( + activeModule => activeModule.meta.name === name + ); + + for (let i = 0, len = activeModuleValues.length; i < len; i += 1) { + const existingActiveModule = activeModuleValues[i]; + const activeModule = { ...existingActiveModule }; + + const { canvas } = rootState.outputs.main || { + canvas: { width: 0, height: 0 } + }; + + const { props } = moduleDefinition; + + activeModule.$props = JSON.parse(JSON.stringify(props)); + + const initialisedModule = await initialiseModuleProperties( + props, + { ...activeModule }, + false, + true, + existingActiveModule + ); + + commit("ADD_ACTIVE_MODULE", { module: initialisedModule }); + + if ("init" in moduleDefinition) { + const { data } = activeModule; + const returnedData = moduleDefinition.init({ + canvas, + data: { ...data }, + props: activeModule.props + }); + + if (returnedData) { + commit("UPDATE_ACTIVE_MODULE", { + id: activeModule.$id, + key: "data", + value: returnedData, + writeToSwap: false + }); + } + } + } + } + }, + + async makeActiveModule( + { commit, rootState }, + { + moduleName, + moduleMeta = {}, + existingModule, + generateNewIds = false, + writeToSwap + } + ) { + const writeTo = writeToSwap ? swap : state; + const expectedModuleName = existingModule + ? existingModule.$moduleName + : moduleName; + const moduleDefinition = state.registered[expectedModuleName]; + + const { props = {}, data = {} } = moduleDefinition || {}; + + let module = {}; + + if (moduleDefinition) { + module = { + meta: { ...moduleDefinition.meta, ...moduleMeta }, + ...(existingModule && JSON.parse(JSON.stringify(existingModule))), + $status: [] + }; + } else { + module = { + meta: { ...moduleMeta }, + ...existingModule, + $status: [] + }; + + console.error( + `Could not find registered module with name ${expectedModuleName}.` + ); + + module.$status.push({ + type: "error", + message: `Module "${expectedModuleName}" is not registered. modV will skip this while rendering` + }); + } + + if (moduleMeta.isGallery) { + const existingModuleWithDuplicateNameInGallery = Object.values( + writeTo.active + ).find( + activeModule => + activeModule.meta.isGallery && activeModule.meta.name === moduleName + ); + + if (existingModuleWithDuplicateNameInGallery) { + console.warn( + `Module active in gallery with name "${moduleName}" already exists.` + ); + return existingModuleWithDuplicateNameInGallery; + } + } + + module.$props = JSON.parse(JSON.stringify(props)); + + if (!existingModule || generateNewIds) { + module.$id = uuidv4(); + + if (!moduleMeta.isGallery) { + const alphaInputBind = await store.dispatch("inputs/addInput", { + type: "action", + getLocation: `modules.active["${module.$id}"].meta.alpha`, + location: "modules/updateMeta", + data: { id: module.$id, metaKey: "alpha" } + }); + + module.meta.alphaInputId = alphaInputBind.id; + + const enabledInputBind = await store.dispatch("inputs/addInput", { + type: "action", + getLocation: `modules.active["${module.$id}"].meta.enabled`, + location: "modules/updateMeta", + data: { id: module.$id, metaKey: "enabled" } + }); + + module.meta.enabledInputId = enabledInputBind.id; + + const coInputBind = await store.dispatch("inputs/addInput", { + type: "action", + getLocation: `modules.active["${module.$id}"].meta.compositeOperation`, + location: "modules/updateMeta", + data: { moduleId: module.$id, metaKey: "compositeOperation" } + }); + + module.meta.compositeOperationInputId = coInputBind.id; + } + } + + if (!existingModule) { + module.$moduleName = moduleName; + module.props = {}; + + await initialiseModuleProperties(props, module, moduleMeta.isGallery); + + const dataKeys = Object.keys(data); + module.data = {}; + + for (let i = 0, len = dataKeys.length; i < len; i++) { + const dataKey = dataKeys[i]; + + const datum = data[dataKey]; + module.data[dataKey] = datum; + } + + module.meta.name = await getNextName( + `${moduleName}`, + Object.keys(state.active) + ); + module.meta.alpha = 1; + module.meta.enabled = false; + module.meta.compositeOperation = "normal"; + + const { presets } = module; + + if (moduleMeta) { + const moduleMetaKeys = Object.keys(moduleMeta); + for (let i = 0, len = moduleMetaKeys.length; i < len; i++) { + const key = moduleMetaKeys[i]; + + const value = moduleMeta[key]; + + module.meta[key] = value; + } + } + + if (presets) { + module.presets = {}; + + const presetKeys = Object.keys(presets); + for (let i = 0, len = presetKeys.length; i < len; i++) { + const key = presetKeys[i]; + + const value = presets[key]; + module.presets[key] = value; + } + } + } else { + const { renderers } = rootState; + const { type } = module.meta; + + if (renderers[type].setupModule) { + try { + const newDef = await renderers[type].setupModule(moduleDefinition); + module.data = newDef.data; + } catch (e) { + console.error( + `Error in ${type} renderer setup whilst registering "${name}". This module was ommited from registration. \n\n${e}` + ); + + return false; + } + } + + await initialiseModuleProperties( + props, + module, + moduleMeta.isGallery, + true, + existingModule, + writeToSwap, + generateNewIds + ); + } + + // We're done setting up the module, we can commit now + + commit("ADD_ACTIVE_MODULE", { module, writeToSwap }); + + if ("audioFeatures" in module.meta) { + if (Array.isArray(module.meta.audioFeatures)) { + const audioFeatureKeys = module.meta.audioFeatures; + for (let i = 0, len = audioFeatureKeys.length; i < len; i++) { + store.dispatch("meyda/addFeature", audioFeatureKeys[i]); + } + } + } + + const { canvas } = rootState.outputs.main || { + canvas: { width: 0, height: 0 } + }; + + if (moduleDefinition && "init" in moduleDefinition) { + const { data } = writeTo.active[module.$id]; + const returnedData = moduleDefinition.init({ + canvas, + data: { ...data }, + props: module.props + }); + + if (returnedData) { + commit("UPDATE_ACTIVE_MODULE", { + id: module.$id, + key: "data", + value: returnedData, + writeToSwap + }); + } + } + + if (moduleDefinition && "resize" in moduleDefinition) { + const { renderers } = rootState; + if ("resizeModule" in renderers[module.meta.type]) { + const { data, props } = module; + let returnedData; + + try { + returnedData = renderers[module.meta.type].resizeModule({ + moduleDefinition, + canvas, + data: { ...data }, + props + }); + } catch (error) { + console.error( + `module#resize() in ${module.meta.name} threw an error: ${error}` + ); + } + + if (returnedData) { + commit("UPDATE_ACTIVE_MODULE", { + id: module.$id, + key: "data", + value: returnedData, + writeToSwap + }); + } + } + } + + return module; + }, + + async updateProp( + { state, commit, rootState }, + { moduleId, prop, data, path = "", writeToSwap } + ) { + if (!state.active[moduleId]) { + console.error(`The module with the moduleId ${moduleId} doesn't exist.`); + return; + } + + const moduleName = state.active[moduleId].$moduleName; + const inputId = state.active[moduleId].$props[prop].id; + const propData = state.registered[moduleName].props[prop]; + const currentValue = get( + state.active[moduleId][prop], + path, + state.active[moduleId][prop] + ); + const { type } = propData; + + if (data === currentValue) { + return; + } + + let dataOut = data; + + if (store.state.dataTypes[type] && store.state.dataTypes[type].create) { + dataOut = await store.state.dataTypes[type].create(dataOut); + } + + dataOut = applyExpression({ inputId, value: dataOut }); + + if (!Array.isArray(dataOut)) { + const { strict, min, max, abs } = propData; + + if (strict && typeof min !== "undefined" && typeof max !== "undefined") { + dataOut = Math.min(Math.max(dataOut, min), max); + } + + if (abs) { + dataOut = Math.abs(dataOut); + } + + if (type === "int") { + dataOut = Math.round(dataOut); + } + } + + commit("UPDATE_ACTIVE_MODULE_PROP", { + moduleId, + prop, + data: { + value: dataOut, + type: propData.type, + path + }, + + writeToSwap + }); + + const registeredModule = state.registered[moduleName]; + + if ("set" in registeredModule.props[prop]) { + const { renderers } = rootState; + + const { getModuleData = () => ({}) } = renderers[ + registeredModule.meta.type + ]; + + const newData = registeredModule.props[prop].set.bind(registeredModule)({ + ...getModuleData(registeredModule.meta.name), + data: { ...state.active[moduleId].data }, + props: state.active[moduleId].props + }); + + if (newData) { + commit("UPDATE_ACTIVE_MODULE", { + id: moduleId, + key: "data", + value: newData + }); + } + } + }, + + async updateMeta({ commit }, { id: moduleId, metaKey, data, writeToSwap }) { + if (!state.active[moduleId]) { + console.error(`The module with the moduleId ${moduleId} doesn't exist.`); + return; + } + + const currentValue = state.active[moduleId].meta[metaKey]; + const inputId = state.active[moduleId].meta[`${metaKey}InputId`]; + + if (data === currentValue) { + return; + } + + let dataOut = data; + + dataOut = applyExpression({ inputId, value: dataOut }); + + commit("UPDATE_ACTIVE_MODULE_META", { + id: moduleId, + metaKey, + data: dataOut, + writeToSwap + }); + }, + + resize({ commit, state, rootState }, { moduleId, width, height }) { + const module = state.active[moduleId]; + const moduleName = module.$moduleName; + const moduleDefinition = state.registered[moduleName]; + const { renderers } = rootState; + + if ( + "resize" in moduleDefinition && + "resizeModule" in renderers[module.meta.type] + ) { + const { data, props } = module; + let returnedData; + + try { + returnedData = renderers[module.meta.type].resizeModule({ + moduleDefinition, + canvas: { width, height }, + data: { ...data }, + props + }); + } catch (error) { + console.error( + `module#resize() in ${module.meta.name} threw an error: ${error}` + ); + } + + if (returnedData) { + commit("UPDATE_ACTIVE_MODULE", { + id: moduleId, + key: "data", + value: returnedData + }); + } + } + }, + + init({ commit, state }, { moduleId, width, height }) { + const module = state.active[moduleId]; + const moduleName = module.$moduleName; + const moduleDefinition = state.registered[moduleName]; + + if ("init" in moduleDefinition) { + const { data, props } = module; + const returnedData = moduleDefinition.init({ + canvas: { width, height }, + data: { ...data }, + props + }); + + if (returnedData) { + commit("UPDATE_ACTIVE_MODULE", { + id: moduleId, + key: "data", + value: returnedData + }); + } + } + }, + + createPresetData({ rootState }) { + const { renderers } = rootState; + + return Object.values(state.active) + .filter(module => !module.meta.isGallery) + .reduce((obj, module) => { + const { + meta: { type }, + data + } = module; + + obj[module.$id] = { ...module }; + delete obj[module.$id].$status; + + if (renderers[type].createPresetData) { + module.data = { + ...data, + ...renderers[type].createPresetData(module) + }; + } + + return obj; + }, {}); + }, + + async loadPresetData({ dispatch }, modules) { + const moduleValues = Object.values(modules); + + for (let i = 0, len = moduleValues.length; i < len; i++) { + const module = moduleValues[i]; + + await dispatch("makeActiveModule", { + moduleName: module.$moduleName, + existingModule: module, + writeToSwap: true + }); + } + + return; + }, + + async removeActiveModule({ commit, rootState }, { moduleId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + const module = writeTo.active[moduleId]; + const { + meta, + meta: { type } + } = module; + + if (!module) { + throw new Error(`No module with id "${moduleId}" found`); + } + + const { renderers } = rootState; + if (renderers[type].removeActiveModule) { + renderers[type].removeActiveModule(module); + } + + const metaInputIds = [ + meta.alphaInputId, + meta.compositeOperationInputId, + meta.enabledInputId + ]; + const moduleProperties = Object.entries(module.$props).map( + ([key, prop]) => ({ + key, + id: prop.id, + type: prop.type + }) + ); + const inputIds = [...moduleProperties, ...metaInputIds.map(id => ({ id }))]; + + for (let i = 0, len = moduleProperties.length; i < len; i++) { + const { key, type: propType } = moduleProperties[i]; + + // destroy anything created by datatypes we don't need anymore + if (store.state.dataTypes[propType].destroy) { + store.state.dataTypes[propType].destroy(module.props[key]); + } + } + + for (let i = 0, len = inputIds.length; i < len; i++) { + const { id: inputId, type: propType } = inputIds[i]; + + await store.dispatch("inputs/removeInputLink", { + inputId, + writeToSwap + }); + + await store.dispatch("inputs/removeInput", { + inputId + }); + + // clear up datatypes with multiple inputs + if ( + propType in store.state.dataTypes && + store.state.dataTypes[propType].inputs + ) { + const dataTypeInputs = store.state.dataTypes[propType].inputs(); + const dataTypeInputsKeys = Object.keys(dataTypeInputs); + + for (let j = 0; j < dataTypeInputsKeys.length; j += 1) { + const key = dataTypeInputsKeys[j]; + await store.dispatch("inputs/removeInputLink", { + inputId: `${inputId}-${key}`, + writeToSwap + }); + + await store.dispatch("inputs/removeInput", { + inputId: `${inputId}-${key}` + }); + } + } + } + + commit("REMOVE_ACTIVE_MODULE", { moduleId, writeToSwap }); + } +}; + +const mutations = { + ADD_REGISTERED_MODULE(state, { module, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + Vue.set(writeTo.registered, module.meta.name, module); + }, + + REMOVE_REGISTERED_MODULE(state, { moduleName, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + Vue.delete(writeTo.registered, moduleName); + }, + + ADD_ACTIVE_MODULE(state, { module, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + Vue.set(writeTo.active, module.$id, module); + }, + + REMOVE_ACTIVE_MODULE(state, { moduleId, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + delete writeTo.active[moduleId]; + }, + + UPDATE_ACTIVE_MODULE(state, { id, key, value, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + Vue.set(writeTo.active[id], key, value); + }, + + async UPDATE_ACTIVE_MODULE_PROP( + state, + { moduleId, prop, data, data: { path }, group, groupName, writeToSwap } + ) { + const writeTo = writeToSwap ? swap : state; + const value = data.value; + + // if (data.type === "texture") { + // value = await textureResolve(data.value); + // } else { + // value = data.value; + // } + + if (typeof group === "number") { + Vue.set(writeTo.active[moduleId][groupName].props[prop], group, value); + } else if (path) { + const tempValue = writeTo.active[moduleId].props[prop]; + set(tempValue, path, value); + if (Array.isArray(tempValue)) { + Vue.set(writeTo.active[moduleId].props, prop, [...tempValue]); + } else { + Vue.set(writeTo.active[moduleId].props, prop, tempValue); + } + } else { + Vue.set(writeTo.active[moduleId].props, prop, value); + } + }, + + UPDATE_ACTIVE_MODULE_META(state, { id, metaKey, data, writeToSwap }) { + const writeTo = writeToSwap ? swap : state; + + if (id) { + Vue.set(writeTo.active[id].meta, metaKey, data); + } + }, + + SWAP: SWAP(swap, getDefaultState, sharedPropertyRestrictions) +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/ndi.js b/src/renderer/src/application/worker/store/modules/ndi.js new file mode 100644 index 000000000..e4b093317 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/ndi.js @@ -0,0 +1,207 @@ +import { v4 as uuidv4 } from "uuid"; +import Vue from "vue"; +import store from "../"; +import grandiose from "../../../setup-grandiose"; + +const state = { + discovering: false, + timeout: 30 * 1000, + + receivers: { + // "receiver-uuidv4": { + // reciver: {}, // the receiver instance, + // outputId: "output-uuidv4", // id of an output canvas + // enabled: false // whether we should do anything with the incoming data from this receiver + // } + }, + + sources: [ + // { + // name: "GINGER (Intel(R) HD Graphics 520 1)", + // urlAddress: "169.254.82.1:5962" + // }, + // { name: "GINGER (Test Pattern)", urlAddress: "169.254.82.1:5961" }, + // { + // name: "GINGER (TOSHIBA Web Camera - HD)", + // urlAddress: "169.254.82.1:5963" + // } + ], + + discoveryOptions: { + showLocalSources: true + } +}; + +function checkCpu() { + if (!grandiose().isSupportedCPU()) { + throw new Error("Your CPU is not supported for NDI"); + } +} + +async function waitForFrame(receiverContext) { + const { receiver, outputId } = receiverContext; + const { + context, + context: { canvas } + } = store.state.outputs.auxillary[outputId]; + + let dataFrame; + + try { + dataFrame = await receiver.data(); + } catch (e) { + console.error(e); + + if (receiverContext.enabled) { + waitForFrame(receiverContext); + } + } + + if (dataFrame.type === "video") { + const { xres, yres, data: uint8array } = dataFrame; + const ui8c = new Uint8ClampedArray( + uint8array.buffer, + uint8array.byteOffset, + uint8array.byteLength / Uint8ClampedArray.BYTES_PER_ELEMENT + ); + const image = new ImageData(ui8c, dataFrame.xres); + + if (canvas.width !== xres || canvas.height !== yres) { + canvas.width = xres; + canvas.height = yres; + } + + context.putImageData(image, 0, 0); + } + + if (receiverContext.enabled) { + waitForFrame(receiverContext); + } +} + +const actions = { + async discoverSources({ commit }) { + checkCpu(); + + commit("SET_DISCOVERING", true); + + try { + const sources = await grandiose().find( + state.discoveryOptions, + state.timeout + ); + commit("SET_SOURCES", sources); + } catch (e) { + console.log(e); + } + + commit("SET_DISCOVERING", false); + }, + + setDiscoveryOptions({ commit }, options) { + commit("SET_DISCOVERY_OPTIONS", { ...state.discoveryOptions, ...options }); + }, + + async createReceiver({ commit }, receiverOptions) { + receiverOptions.colorFormat = grandiose().COLOR_FORMAT_RGBX_RGBA; + receiverOptions.bandwidth = grandiose().BANDWIDTH_LOWEST; + + const receiver = await grandiose().receive(receiverOptions); + + const outputContext = await store.dispatch("outputs/getAuxillaryOutput", { + name: receiverOptions.source.name, + group: "NDI", + reactToResize: false + }); + + const receiverId = uuidv4(); + const receiverContext = { + id: receiverId, + outputId: outputContext.id, + receiver, + enabled: false + }; + + commit("ADD_RECIEVER", receiverContext); + + return receiverContext; + }, + + async enableReceiver({ commit }, { receiverId }) { + const receiverContext = state.receivers[receiverId]; + + if (!receiverContext) { + throw new Error(`No receiver found with id ${receiverId}`); + } + + receiverContext.enabled = true; + + commit("UPDATE_RECIEVER", receiverContext); + + waitForFrame(receiverContext); + }, + + disableReceiver({ commit }, { receiverId }) { + const receiverContext = state.receivers[receiverId]; + + if (!receiverContext) { + throw new Error(`No receiver found with id ${receiverId}`); + } + + receiverContext.enabled = false; + + commit("UPDATE_RECIEVER", receiverContext); + }, + + async removeReceiver({ commit }, { receiverId }) { + const receiverContext = state.receivers[receiverId]; + + if (!receiverContext) { + throw new Error(`No receiver found with id ${receiverId}`); + } + + await store.dispatch("ndi/disableReceiver", { + receiverId: receiverContext.id + }); + + await store.dispatch( + "outputs/removeAuxillaryOutput", + receiverContext.outputId + ); + + commit("DELETE_RECIEVER", receiverContext); + } +}; + +const mutations = { + SET_SOURCES(state, sources) { + state.sources = sources; + }, + + SET_DISCOVERING(state, discovering) { + state.discovering = discovering; + }, + + SET_DISCOVERY_OPTIONS(state, options) { + state.discoveryOptions = options; + }, + + ADD_RECIEVER(state, receiverContext) { + Vue.set(state.receivers, receiverContext.id, receiverContext); + }, + + UPDATE_RECIEVER(state, receiverContext) { + Vue.set(state.receivers, receiverContext.id, receiverContext); + }, + + DELETE_RECIEVER(state, receiverContext) { + Vue.delete(state.receivers, receiverContext.id); + } +}; + +export default { + namespaced: true, + state, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/outputs.js b/src/renderer/src/application/worker/store/modules/outputs.js new file mode 100644 index 000000000..c1bb5d37b --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/outputs.js @@ -0,0 +1,196 @@ +import Vue from "vue"; +import { v4 as uuidv4 } from "uuid"; + +const state = { + main: null, + webcam: null, + auxillary: {}, + + debug: false, + debugId: "main", + debugContext: null +}; + +const getters = { + canvasToDebug: state => { + if (state.debugId === "main") { + return { context: state.main }; + } + + return state.auxillary[state.debugId]; + }, + + resizable: state => + Object.values(state.auxillary).filter(aux => aux.reactToResize), + + auxillaryCanvas: state => id => state.auxillary[id].context.canvas +}; + +/** + * @typedef {Object} outputContext + * + * @property {CanvasRenderingContext2D|WebGLRenderingContext|WebGL2RenderingContext|ImageBitmapRenderingContext} context + * @property {String} name + * @property {String} id + * @property {String} group + * @property {Boolean} reactToResize + */ + +const actions = { + setMainOutput({ commit }, context) { + commit("SET_MAIN_OUTPUT", context); + }, + + setWebcamOutput({ commit }, context) { + commit("SET_WEBCAM_OUTPUT", context); + }, + + async getAuxillaryOutput( + { dispatch }, + { + canvas = new OffscreenCanvas(300, 300), + context, + name, + type = "2d", + options = {}, + group = "", + id = "", + reactToResize = true, + width = state.main ? state.main.canvas.width : 300, + height = state.main ? state.main.canvas.height : 300 + } + ) { + if (type === "2d") { + options.storage = "discardable"; + } + + if (!context) { + canvas.width = width; + canvas.height = height; + } + + const canvasContext = context || canvas.getContext(type, options); + + const outputContext = await dispatch("addAuxillaryOutput", { + name, + context: canvasContext, + reactToResize, + group, + id + }); + + return outputContext; + }, + + addAuxillaryOutput({ commit }, outputContext) { + outputContext.id = outputContext.id || uuidv4(); + commit("ADD_AUXILLARY", outputContext); + return outputContext; + }, + + removeAuxillaryOutput({ commit }, outputContextId) { + commit("REMOVE_AUXILLARY", outputContextId); + }, + + setDebugContext({ commit }, debugCanvas) { + const context = debugCanvas.getContext("2d"); + commit("SET_DEBUG_CONTEXT", context); + }, + + resize({ commit, getters }, { width, height }) { + const resizable = getters.resizable; + + commit("RESIZE_MAIN_OUTPUT", { width, height }); + + for (let i = 0, len = resizable.length; i < len; i++) { + const outputContext = resizable[i]; + + commit("RESIZE_AUXILLARY", { id: outputContext.id, width, height }); + } + }, + + resizeDebug({ commit }, { width, height }) { + commit("RESIZE_DEBUG", { width, height }); + } +}; + +const mutations = { + SET_MAIN_OUTPUT(state, outputContext) { + state.main = outputContext; + }, + + SET_WEBCAM_OUTPUT(state, outputContext) { + state.webcam = outputContext; + }, + + ADD_AUXILLARY(state, outputContext) { + Vue.set(state.auxillary, outputContext.id, outputContext); + }, + + REMOVE_AUXILLARY(state, id) { + Vue.delete(state.auxillary, id); + }, + + UPDATE_AUXILLARY(state, { auxillaryId, data }) { + if (state.auxillary[auxillaryId]) { + const dataKeys = Object.keys(data); + const dataKeysLength = dataKeys.length; + for (let i = 0; i < dataKeysLength; i += 1) { + const key = dataKeys[i]; + const value = data[key]; + state.auxillary[auxillaryId][key] = value; + } + } + }, + + RESIZE_MAIN_OUTPUT(state, { width, height }) { + if (!state.main.canvas) { + return; + } + + state.main.canvas.width = width; + state.main.canvas.height = height; + }, + + RESIZE_AUXILLARY(state, { id, width, height }) { + if (!state.auxillary[id].context.canvas) { + return; + } + state.auxillary[id].context.canvas.width = width; + state.auxillary[id].context.canvas.height = height; + }, + + TOGGLE_DEBUG(state, debug) { + state.debug = debug; + }, + + RESIZE_DEBUG(state, { width, height }) { + if (!state.debugContext.canvas) { + return; + } + + if (width) { + state.debugContext.canvas.width = width; + } + + if (height) { + state.debugContext.canvas.height = height; + } + }, + + SET_DEBUG_ID(state, id) { + state.debugId = id; + }, + + SET_DEBUG_CONTEXT(state, context) { + state.debugContext = context; + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/plugins.js b/src/renderer/src/application/worker/store/modules/plugins.js new file mode 100644 index 000000000..735c45e67 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/plugins.js @@ -0,0 +1,154 @@ +import store from "../"; +import getPropDefault from "../../../utils/get-prop-default"; + +import { v4 as uuidv4 } from "uuid"; + +function camelize(str) { + return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => { + if (+match === 0) { + return ""; + } // or if (/\s+/.test(match)) for white spaces + return index === 0 ? match.toLowerCase() : match.toUpperCase(); + }); +} + +const state = []; + +const getters = { + preProcessFrame: state => { + return state.filter(plugin => !!plugin.preProcessFrame && plugin.enabled); + }, + + postProcessFrame: state => { + return state.filter(plugin => !!plugin.postProcessFrame && plugin.enabled); + } +}; + +const actions = { + async add({ commit }, plugin) { + if (!("name" in plugin)) { + throw new Error("Plugin requires a name"); + } + + if ("store" in plugin) { + const storeName = plugin.storeName || camelize(plugin.name); + store.registerModule(storeName, plugin.store); + } + + plugin.$props = {}; + + if ("props" in plugin) { + const keys = Object.keys(plugin.props); + const keysLength = keys.length; + + for (let i = 0; i < keysLength; i += 1) { + const key = keys[i]; + const prop = plugin.props[key]; + + plugin.$props[key] = await getPropDefault(plugin, key, prop, false); + + if (!plugin.$props[key]) { + plugin.$props[key] = null; + } + } + } + + plugin.id = uuidv4(); + plugin.enabled = false; + commit("ADD_PLUGIN", plugin); + }, + + setEnabled({ commit }, { pluginId, enabled }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + if (enabled) { + if ("init" in plugin) { + plugin.init({ store, props: plugin.$props }); + } + } else { + if ("shutdown" in plugin) { + plugin.shutdown({ store, props: plugin.$props }); + } + } + + commit("SET_PLUGIN_ENABLE", { pluginId, enabled }); + }, + + async updateProp({ commit }, { pluginId, prop, data }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + let dataOut = data; + + const propData = plugin.props[prop]; + const currentValue = plugin.$props[prop]; + const { type } = propData; + + if (data === currentValue) { + return; + } + + if (store.state.dataTypes[type] && store.state.dataTypes[type].create) { + dataOut = await store.state.dataTypes[type].create(dataOut); + } + + if (!Array.isArray(dataOut)) { + const { strict, min, max, abs } = propData; + + if (strict && typeof min !== "undefined" && typeof max !== "undefined") { + dataOut = Math.min(Math.max(dataOut, min), max); + } + + if (abs) { + dataOut = Math.abs(dataOut); + } + + if (type === "int") { + dataOut = Math.round(dataOut); + } + } + + commit("UPDATE_PROP", { pluginId, prop, data: dataOut }); + } +}; + +const mutations = { + ADD_PLUGIN(state, plugin) { + state.push(plugin); + }, + + SET_PLUGIN_ENABLE(state, { pluginId, enabled }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + plugin.enabled = enabled; + }, + + UPDATE_PROP(state, { pluginId, prop, data }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + plugin.$props[prop] = data; + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/projects.js b/src/renderer/src/application/worker/store/modules/projects.js new file mode 100644 index 000000000..9f0fd42f9 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/projects.js @@ -0,0 +1,22 @@ +const state = { + currentProject: "default" +}; + +const actions = { + setCurrentProject({ commit }, projectName) { + commit("SET_CURRENT_PROJECT", projectName); + } +}; + +const mutations = { + SET_CURRENT_PROJECT(state, currentProject) { + state.currentProject = currentProject; + } +}; + +export default { + namespaced: true, + state, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/renderers.js b/src/renderer/src/application/worker/store/modules/renderers.js new file mode 100644 index 000000000..7ed4fdd1d --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/renderers.js @@ -0,0 +1,26 @@ +const state = {}; + +const getters = { + renderersWithTick: state => { + const keys = Object.keys(state); + return keys + .map(key => state[key].tick && state[key]) + .filter(renderer => renderer); + } +}; + +const mutations = { + ADD_RENDERER(state, renderer) { + state[renderer.name] = renderer; + }, + REMOVE_RENDERER(state, name) { + delete state[name]; + } +}; + +export default { + namespaced: true, + getters, + state, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/size.js b/src/renderer/src/application/worker/store/modules/size.js new file mode 100644 index 000000000..58ab9f473 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/size.js @@ -0,0 +1,51 @@ +import store from "../index"; + +const state = { + width: 0, + height: 0, + dpr: 1 +}; + +const getters = { + area: state => state.width * state.height +}; + +const actions = { + async setSize({ commit }, { width, height, dpr }) { + await store.dispatch("outputs/resize", { width, height }); + + const modulesValues = Object.values(store.state.modules.active); + const modulesLength = modulesValues.length; + for (let i = 0; i < modulesLength; ++i) { + const module = modulesValues[i]; + store.dispatch("modules/resize", { moduleId: module.$id, width, height }); + } + + const renderersValues = Object.values(store.state.renderers); + const renderersLength = renderersValues.length; + for (let i = 0; i < renderersLength; ++i) { + const renderer = renderersValues[i]; + if (renderer.resize) { + renderer.resize({ width, height }); + } + } + + commit("SET_SIZE", { width, height, dpr }); + } +}; + +const mutations = { + SET_SIZE(state, { width, height, dpr = 1 }) { + state.width = width; + state.height = height; + state.dpr = dpr; + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/tweens.js b/src/renderer/src/application/worker/store/modules/tweens.js new file mode 100644 index 000000000..e1c421984 --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/tweens.js @@ -0,0 +1,259 @@ +import Vue from "vue"; +import anime from "animejs"; +import store from "../"; +import { v4 as uuidv4 } from "uuid"; + +if (typeof window === "undefined") { + // shims for anime in a worker + self.NodeList = function() {}; + self.HTMLCollection = function() {}; + self.SVGElement = function() {}; + self.window = { Promise }; +} + +const frames = {}; +const progress = {}; + +const state = { + tweens: {}, + easings: Object.keys(anime.penner).map(easing => ({ + value: easing, + label: easing + .replace(/([a-z])([A-Z])/g, "$1 $2") + .replace(/^\w/, c => c.toUpperCase()) + })) +}; + +const getters = { + progress: () => progress, + frames: () => frames +}; + +async function buildFrames({ + id, + loop, + data, + duration, + direction, + easing, + bpmDivision, + useBpm, + durationAsTotalTime, + steps +}) { + let newDuration = 1000; + + if (useBpm) { + const bpm = store.state.beats.bpm || 120; + const calculatedBpm = ((60 * 60) / bpm) * bpmDivision; + + newDuration = durationAsTotalTime + ? calculatedBpm + : calculatedBpm / data.length; + } else { + newDuration = durationAsTotalTime ? duration : duration / data.length; + } + + let seek = 0; + + if (state.tweens[id]) { + seek = frames[id] / state.tweens[id].frames.length; + } + + return new Promise(resolve => { + const objOut = {}; + const mapped = {}; + + const newData = data; + + for (let i = 0, len = newData.length; i < len; i++) { + const datum = newData[i]; + + let index = 0; + for (let j = 0, lenj = datum.length; j < lenj; j++) { + const value = datum[j]; + + if (!(index in objOut)) { + objOut[index] = value; + mapped[index] = []; + } + + mapped[index].push(value); + index++; + } + } + + const animation = anime({ + targets: objOut, + ...mapped, + duration: newDuration, + easing: steps ? `steps(${steps})` : easing || "linear", + direction, + autoplay: false, + loop, + update(anim) { + progress[id] = anim.progress; + } + }); + + const animationCache = []; + let frame = 0; + const framesRequired = + Math.round(newDuration / (1000 / store.state.fps.fps)) || 1; + + let frameRecordingCompleted = false; + let frameRecordingDirection = 1; + + if (loop) { + while (!frameRecordingCompleted) { + animation.seek((frame / framesRequired) * newDuration); + animationCache.push(Object.assign({}, objOut)); + + if (frame === framesRequired) { + frameRecordingDirection = -1; + } + + frame += frameRecordingDirection; + + if (frame === 0) { + frameRecordingCompleted = true; + } + } + } else { + while (frame < framesRequired) { + animation.seek((frame / framesRequired) * newDuration); + animationCache.push(Object.assign({}, objOut)); + frame++; + } + } + + frames[id] = Math.floor(animationCache.length * seek); + + resolve(animationCache); + }); +} + +const actions = { + async createTween( + { commit }, + { + id = uuidv4(), + data, + duration = 1000, + direction = "alternate", + easing = "linear", + loop = true, + useBpm = true, + bpmDivision = 16, + durationAsTotalTime = false, + isGallery, + steps = 0 + } + ) { + const animationCache = await buildFrames({ + id, + loop, + data, + duration, + direction, + easing, + useBpm, + bpmDivision, + durationAsTotalTime, + steps + }); + + const tween = { + id, + frames: animationCache, + data, + loop, + easing, + duration, + direction, + useBpm, + bpmDivision, + durationAsTotalTime, + isGallery, + steps + }; + + commit("ADD_TWEEN", tween); + + return tween; + }, + + async updateBpm({ commit }, { bpm }) { + const tweenKeys = Object.keys(state.tweens); + const tweenKeysLength = tweenKeys.length; + + for (let i = 0; i < tweenKeysLength; ++i) { + const tween = state.tweens[tweenKeys[i]]; + + if (tween.useBpm) { + const calculatedBpm = ((60 * 60) / bpm) * tween.bpmDivision; + + commit("UPDATE_TWEEN_VALUE", { + id: tween.id, + key: "duration", + value: tween.durationAsTotalTime + ? calculatedBpm + : calculatedBpm * tween.data.length + }); + + const frames = await buildFrames(tween); + + commit("UPDATE_TWEEN_VALUE", { + id: tween.id, + key: "frames", + value: frames + }); + } + } + }, + + createPresetData() { + return Object.values(state.tweens).filter(tween => !tween.isGallery); + }, + + async loadPresetData(context, tweens) { + const tweenValues = Object.values(tweens); + for (let i = 0, len = tweenValues.length; i < len; i++) { + const tween = tweenValues[i]; + + await store.dispatch("dataTypes/createType", { + type: "tween", + args: tween + }); + } + } +}; + +const mutations = { + ADD_TWEEN(state, tween) { + Vue.set(state.tweens, tween.id, tween); + frames[tween.id] = 0; + }, + + UPDATE_TWEEN_VALUE(state, { id, key, value }) { + state.tweens[id][key] = value; + } +}; + +function advanceFrame(id) { + if (frames[id] < state.tweens[id].frames.length - 1) { + ++frames[id]; + } else { + frames[id] = 0; + } +} + +export { frames, advanceFrame }; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/videos.js b/src/renderer/src/application/worker/store/modules/videos.js new file mode 100644 index 000000000..a359531ab --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/videos.js @@ -0,0 +1,126 @@ +import Vue from "vue"; +import { v4 as uuidv4 } from "uuid"; +import path from "path"; +import store from "../"; +import { conformFilePath } from "../../../utils/conform-file-path"; + +const state = {}; + +const getters = { + video: state => id => state[id]?.outputContext?.context.canvas +}; + +const actions = { + createVideoFromPath({ rootState, commit }, textureDefinition) { + const { + id = uuidv4(), + options: { path: filePath } + } = textureDefinition; + + const url = `modv://${path.join( + rootState.media.path, + conformFilePath(filePath) + )}`; + + if (typeof window !== "undefined") { + self.postMessage({ + type: "createWebcodecVideo", + id, + url, + textureDefinition + }); + } + + commit("CREATE_VIDEO", { id, path: filePath }); + return { id }; + }, + + async assignVideoStream({ commit }, { id, stream, width, height }) { + const frameReader = stream.getReader(); + const outputContext = await store.dispatch("outputs/getAuxillaryOutput", { + name: state[id].path, + options: { + desynchronized: true + }, + group: "videos", + reactToResize: false, + width, + height + }); + + frameReader.read().then(function processFrame({ done, value: frame }) { + const { stream, needsRemoval } = state[id]; + if (done) { + return; + } + + // NOTE: all paths below must call frame.close(). Otherwise, the GC won't + // be fast enough to recollect VideoFrames, and decoding can stall. + + if (needsRemoval) { + // TODO: There might be a more elegant way of closing a stream, or other + // events to listen for - do we need to use frameReader.cancel(); somehow? + frameReader.releaseLock(); + stream.cancel(); + + frame.close(); + + commit("REMOVE_VIDEO", { id }); + + if (typeof window !== "undefined") { + self.postMessage({ + type: "removeWebcodecVideo", + id + }); + } + return; + } + + // Processing on 'frame' goes here! + // E.g. this is where encoding via a VideoEncoder could be set up, or + // rendering to an OffscreenCanvas. + + outputContext.context.drawImage(frame, 0, 0); + frame.close(); + + frameReader.read().then(processFrame); + }); + + commit("UPDATE_VIDEO", { + id, + stream, + width, + height, + frameReader, + outputContext, + needsRemoval: false + }); + }, + + async removeVideoById({ commit }, { id }) { + commit("UPDATE_VIDEO", { id, needsRemoval: true }); + } +}; + +const mutations = { + CREATE_VIDEO(state, { id, path }) { + Vue.set(state, id, { path }); + }, + + UPDATE_VIDEO(state, video) { + const { id } = video; + state[id] = { ...state[id], ...video }; + }, + + REMOVE_VIDEO(state, { id }) { + delete state[id]; + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/renderer/src/application/worker/store/modules/windows.js b/src/renderer/src/application/worker/store/modules/windows.js new file mode 100644 index 000000000..f7418f83f --- /dev/null +++ b/src/renderer/src/application/worker/store/modules/windows.js @@ -0,0 +1,46 @@ +import Vue from "vue"; + +import { v4 as uuidv4 } from "uuid"; + +const state = {}; + +const actions = { + createWindow({ commit }) { + const win = { + width: 300, + height: 300, + title: "modV Output", + pixelRatio: 1, + x: 0, + y: 0, + fullscreen: false, + backgroundColor: "#000", + outputId: "" + }; + + win.id = uuidv4(); + commit("ADD_WINDOW", win); + + return win.id; + } +}; + +const mutations = { + ADD_WINDOW(state, window) { + Vue.set(state, window.id, window); + }, + + UPDATE_WINDOW(state, { id, key, value }) { + Vue.set(state[id], key, value); + } + + // REMOVE_WINDOW(state, id) {} +}; + +export default { + namespaced: true, + state, + // getters, + actions, + mutations +}; diff --git a/src/renderer/src/assets/fonts/Inter-italic.var.woff2 b/src/renderer/src/assets/fonts/Inter-italic.var.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..b826d5af84b3bd70535b6bb993f443a2deb46894 GIT binary patch literal 245036 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�LzqE;f#83j;-svHFcR`yUq8K)}}#m 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 C+W+PN literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/fonts/Inter-roman.var.woff2 b/src/renderer/src/assets/fonts/Inter-roman.var.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..6a256a068f0dce7b44f8314bdc735ef91b35e2e3 GIT binary patch literal 227180 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�J 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%0DEZbsBGRd~gwj?vfc1&?NalonLq)8f1 zUc*dnnx=s=rfFW8UYpW`U;AD;FJ66f=H9(ptz|opzQ3&tdue9QoH^&rnF9@}Y6^v9 zJmU9)Kym?0r+sm8B)2mBwgalqLh@wdZOcO4dGLE2kelN89QeJ#=;;Mx*4pj z7E^&iM=_jdX0Ce0%oWe_oe8lRI1#d&+PY@Wl-tUU`mw&vw%&kf#0|?%cYU(!G+4Z5 zp4vOw(N7&`&VGnbXLc%Wno#^Um&k4MGZ!8S=X?9eP7W~tQoVg9a~_p%)0!JtbmdS) zb%V(B@rhunf!wUraf_{w)^UezN7rfm0(GLrrRF(z1%@qDszmiQO=1&Ha zv_M}p7L91Ng%BosE>@|`q7(Fon$H05)MAZPXgXE&!obG6>vl-sM%jwW*%+?~0?7Q(G9w27gZawnl+Cimq?4WA# zpuvEU!DsL~Z1h20RMg7$_HxzcvV!{4$ey+RbSo_vbtD`NREU1Bo8Z^i@b~Cbu%Ids&20kV6kTW?)l^6m(;VOK~i^9=1i;!f{nak2i?-t(#wd zwI}m!&$akW|3-_aHF~)C#YaAS_KnORZoeIy@mDwB{%AtYmBQc^zj&^d{9G;wPydkq zI7Q!&Knolk5iy2D0U9g{Se)bu0ib9hYL{OajD%_JJB#PKRgoND*OB zRysx!p+roQa@KOwS=nIbJe(y2Glpd^9E*;7N2@FAMM4r4E9@&OD>Z~vS1xN_I)2Bl zgX{Zncl*4idYvv1?p?Nh#c-WPk^CKEZF?05_ z_zfBujr(k-9r|4^17a6qIeuQv0+e$d#)cY)PtA338Ld+xWKPs#fd%J40*8f3xk?PO zB4#*Em+O*Xc1y8Y$&Hfje<$M&6g4OcoH|pv@)Nh;WH0yOLZ8+rIK%eN>e_;`v1&-m z@k{K>d>9Q2l=3D{I1g9Uzm@qRtskogtKS{+*d3Jd^(WIGkS#10Teobw<)jr-1{|}e z1$~*Hxjy9FpqFH|syr6QUWvgd6r>_`6jEfB2xyoi1q%`_g)w{sO{%cVUb`wjQXe`T zedH5Aq}d-k4rIQyDjuNlQ*#p#G&U2!!2SHWwz#0 z_*{bf{DapYGu~ii`-ymi-Xyd<%=R7C{wJRQmSJ>MK9COGqRz39tH+%^D!E?N7=8qO zlEG#n;0pciU_5{z#1piYlYt}Swl(Sq1BS#Rvy`N%8hN$#3bC?l{RW-yOjxs|rM!V= z`uK};)$-DUnq8uGO;Pnhy*0Cd zBGN9i`TPl|Jk3@FTO&0*tP(%Z&G-yqu&gm%`Y2r;SrPs0?pKjWHv-}+AKtT z2-6<`k#nCVvK;}a@GR_~BW%}8fgGqfXTXvRn(ON4$)G7HD%?zAlh}V~eb0J%YYYK= z91(zzpaB3W&4KN66iAShV{1;%5XyqCQIH6dpYWjZK{;?rsX3&i$mgDrhxq;{Jy$D$ zUvN8BrlPL zuis*>h6-PNylhp3y!zpmk;Mnv50q3`3XS35yNH$DN_Ty7ocSCXa4Ku3lP?3hokowz zqso9F$*6V6P2%_bC3dV$jI4mAOq2^mT|xQ%FfB%8(GO>IAIlolDR6 z2m_jY&Rop>pgXd7i=yG8m^T)u4RBDw&`ViT8%e8xi~wgc5{aJV2-WVmBV6nb;F6@H zXYrBkA6s8%^psZwYWq$25pR^XD`|=CvGqNC>Qf)C?7!wT=^4H4`3-^EgT=KrUt#@^ zSziO3S^KQW4^eRuhcp7G*$Vt#Qu#ehUiq~O!?)XfMK-_P574G4ty39)PGvc@o-)j< zmRFrf>HzL{RxhtUfiF?uuBUL%{2T`v>~TrvdwY@;_V$49$7rz;(b5+q} z<^y_Eb2c+R=|T~RR>6$u`XwyVc8O}fiaeRu?RH1p;V|ujQc4QFAkwiJnvC?aj0>V2 zPR}R~EcI>ncmQ`Un75|~oA;k7t2-3zuAYxS5n2L7noA@PkNbU+XdIotXm=Z(S?FG> zjN{9`P?EGb%znR&Bhx8{BO!=_OIE!|Y{uO&l45!g|D(1?XsrHGd?T))q4^<5zwBpC`>F!^3h0yrBOZJb z1?VfD-iB08ihQAT70}U;Fw<4mQlL|^KcPBR<|1+xW}H_dIN4d`UNgTSt|={PDK9;< zpVcbFgl1mycyIVdc}aBPfbedWKODDY&Qh(-SE>9Qt&g6(59Gx6s9Z(GB9uU#?ehW! z1jC|$4kN5XCp1{(FxH7!)=yA5QLh))G9`siKbVNeqD6&SMTOZwg}{)ij<|rz0)+Z+ zRO(BpPNH=3>J>A3ixR50D4Ee)2xVoPvwDg1YA#M@KFI1UwlbYX71;d*Fvt5b;Sv}V zm=AHYbrvkK-$qq+!43LARS^fl(G~ybx(YZ%-fSB4UQ}DLb)EZqQCnMlVSUB2Bj#IJ zTWqu~2g$2)zH$pjGA}>^kXZqVg1Av8#z>yiCfVc!P&5{oH38=;3AhNAS7KVw1?yD>nDk-L2PyU82}IpEfpkbhI_ZRfIW0yyT0>E;8e!yv2QnXoS(T%Q1Ba{IK7F$iM-(CQF$Tv}dzB801OpC;JQ3xv80s`s z*dSqfxZ6S{P)m*EXPlLRaSm0>ll$ygQ@P;O?N@|aaFJKE3$$XLskGkLU40vwcZ7lQ zY?oHl#AAnohf|%;Wqyd1dFoa+Pc^X_2amAG<4I;!ByE)EpCo58qf{jnvCZ@<^yMAn z)%UWVytk6hE@}U%$#Fq*nVnPKoTYDS7AuaEa~P_U#4P&80fkcr@RpNRN2_qycE|dY z)kmvO0-$kV@7Yg*04e9D-^z3%eiUmDd)#`R9AB0oj&K`Q84(XUo4ax&6))97)cmxR3?=!7e&9Q zB(jttoKC4q8IV-z>i(u$u*^W8SIFJ1ezr_Omx9z^}I)L#HC=9NmWihREPQC)r z;+>F3>Lz|xpIRYt0YH0>#088nK3RQE@`6>PaAQ;PZ0%XUn zv+0C_P*!TIs}w$$Nt%jVC8Djtzj{5>xfB=ICL0)z{Y7WU>Rl)&!NUyGRM~YKo4+l3 z#bMNQyc$%*spv9!KD*GVOy2C-$+q$UT&C(mhsEx6fHEjOQ#BVoFgsPKG=r=kSkYfyz5?G4~n6`=*44-PLT0p2jD8foUCi*2*iek(iO1| zq-v(Cw58^fr>~<0%~nN`SO4N%xgx8I$~-fN{4eYMneOgZS$Ef?t_dl5F-fkzm|Y*? zPOI0G%5{)apJm_m%&ar0x$o8Qs(F_Px;Cf$(`xz2o?M$VxPC^y-k7VeKBVfaD`x4j zPpj9VUKh@nspw_#t2sK%L#ht5q7&s@KdoL*&bt0}^?JE-o#_+V^PiNTZ)VqJT?V`U zRBk)fd=f;C$BruK@0H{a$d4+~=9jFPCt1ZmI9*E{YpSbOw8()NYQb@S zXFcqhuDC(*TXdiZs)6}Kq=0b15FwK=$E#@tql`1)^@1##N2ByoO|-hSB|)nV3X9KO~@%NN=WdYfG%m<{ntB`?1daw>o?rV%3? zDj)=f!Q8h$s|ARvkm3w+u zZl=Fd(O6k>q^NJ*8AIQ~uGOXjZK1OyJy257*WIzoT%Z9n4ZmkVjw`_G$n4{Op7s4v zbA`2%Tjy9C4a~7N3S9XijNO-){(k;x!k@}ywjqLwna|iCSiIzp_BInp?HY)1o)vYc zkP`GdAC-#`f~d#QFzB`;v6zad%#1|txpWpDbFG8lzO!?8=cHZg>ZvJrR5zWtY+icc z(s_*qI!z>5*=Ep-rE$Za^&4;L+fux8)Oft$)U~&MD?PBIr9Bn*2P3V`^&xYzT#b9n z#^?7g8aFpTE-zcBqCXDOHrJ!B_I4f$Sy5hAnhZHDut*hhn&g#M+R(a4nl)Wc?+=%l2?mT^YW^?De3J*_#uKlC<4K4mQHVL-V<&`*U`%uzIp1n*G=DWViArM8P%M8n zc4mGvTN@@R#&R$2OfK_wRn*o9*jBj1JQ^$J@o;&&zprxF(E6#iR=lBg-;klKd|5E$ zj~Fc0^+hg2wLdseQQgy0JHJ3MFHQ}~{ipiv5!^AS?^|^oo!@5-VWz9^Cj9x<7W+rP?V*)QYZKT-X4HLaWVoVC)x`PNDUbF7ud=UOWjWfar*gc3#{KC$fd{7Hko zDjw*otK1u#T=on#M!Idmp~LZjAF27nhFA>HV7t$t*AgBAl3@lSPElW3Y%*a{lD4Pi zEbdZd?deX<$&*VDCfu4QM>K{69alg2!PyUgHy5cWTtHn! z&snXK#9-O32O6R*LdZW@|Hb*iuCA`pu95y;>gk+}#iN!GC`7PqAUP9mM2^4WC_;@&bR>Vcf$m#7bC!=j`hrd`Jh$LUyZ}<6!s)bL%r6XT}OA{7- zNn=k%slUh>h|O2e9xxk9C~Q?)^G7NAA3}Uch!`T!MS8yw;mP{a_9;5qv=K z_tnz`+uqY2Asncz*A`Nw`7m9zusslZ^ftev&{ti1*VzwQc_F&SxsjGvhqkxdswg>k z2m)V#iNF?sx$FWk5QTv14SAsj;QS|*1z^Yt;)psD@wpa&mDuU|?oq10uo6c!f^X|r z*M|4%qkK`Uyq*Zbg-r|F^kt_jrCPVop3sz@Za39;*rNsM^4AOf%6jo?&e2d&VDIi6 z>56w^Df8Q;qt#JZTZM7vI8UE`sx>9&bff*TiSVc#t$sNfBm!3B0dg240b$_;PY4WE zjU;lQ@BpDU{~n?@=mUh-Syb9zj>@7II|TcK_B-=@!1CM)bLN(CqM19i-+|cy01Y(0 zAKsLCA{*ZiH_&qy-w)Gxv}zPUnZ^;?@5^aB7or30whk-`o$WrSQ6Qorx8FldQ5j&i zm1G0Yk!;s*(Ce~Y??)JZV6CX ziJO-$&}(*xn!03R*?sy5Z*lv>SSVSz5DWIkWUNG@`QCYt(`44x?9ZNDGG8C4EASPh zD_?g9DTdTFYQw@x&QemW*KHXt(A3mcS4NX~S>|Q8HRko!;j)~w3C^E3o?`eyD6ef$ zeChpY(@Rc_)c}${g{NZ7ZIC^En0v%p1nUWqE>F47Zh*%rbMwi4W(;D!f?pU&nBZ2g zVa`7JsH5Tw=+zJ8`_#*R-qPhommB7Ct*8FL{(BKYJrwWVEbdGQ)wicK>I|QkXZQix zrh`&#wialoooWx+gGCP73KZCJ-eEBl*H`j)?t&yr{tZ2HOneurnRd^cgp}SuxKO> zVV9E#90dK4e-rjwjr&mwD}aa-PN?y7q5xv$bhd*PEXCzMjg*HKYyNy^tZ*2iaCNvU zSq$q9F?+aBcE*yYL^GxwK%XU7K1|}OJ4?OMQw99&?{#*oG+x8*6jzU)X$wAUv$H>|G5sUypdJZciG_QHfHCJpyi zbz=16(&Uv(4mK0>*$-EYhpZlVc{FTqf;Go4)Ase#_JvR-dYq0sk%!h9SnGHs5?-9j zhT#f;O(-EbORlnnS$6J&Xy&mwmTBjHe2!&Wx=zcGqcU7kk^tn0+AVR1x=x!`Mbfk^ zrOq{CWDwz|e!Ztdv9PO&!tGBA+zL@KI`onWOKOzYC=!r_1ZgX* zlV~_J2@-ce@$S-!SuhmO{TM)jJq!L{3fptBIdXAPA{+z?7G7lBcaBX~f)%L+`nzy>*NK^fKTUkw@yrFX*Qe$H| zS>5W|YqT1|=o>G`Z4>xh4^hRhh$$niV3eWE7cOL$5&6Y~cx@~dURASg@g!7m`9{ZH zLwU5eL;Da;iH7lDvZ@Y7VPb|+bD?Is?sB6k&Nw)%fmiw zxkaK%!1kQDzwfi0X@-Cj_gBN>{<`I)VI0Tro{5&kRzjrpNRreJ&}iZsmO72+_^U7e zB$H~u8!IZ?K9=bu|9OJ`su@f!8vJ+tOeT{H2LV=dny7(*xtw^v4io^rZr0X9qRmhU zfhO13!s&$Izs`WuU+6YEjCQk$8C+=3F}NU7iZzItTjYfRu5|r^q$>Fi9sVaSRhbh4 z7{7X{SyNq=d9okd{A&LYm}VHiPNLE9X57R)v}l-qM) zXknZmFpbYI@X6CsoKM*UyCI|D=eW&RKORE>M=MV>oi0Z0|N%8gjcNA>uOUqR8v_T34@-}0<|{MSx7Wb z3z_;z%F8sqVwN(p`$D?Q(UK{5c$cb@Jg?sJZ)ap(<$gy0OHo^U^Tl+Pc~%%vZqUnA zl*|g_BdRE&qh4R%aSmxjq!elr*2qJHJ{J!BB0n^$aXI_ zww@|06!UbA6xjIslneBU%Vwz>i$;@|TU#&tX0DcTQ3j!GO#d|upPzZjf)SH&bj;fw zreXYEq?0hx?Un5tWH(4CV{iKZW=e6Ys%x%jt11Jtq5+0w78M)^bO6K9=L2}v-5J%ykq&f8G;uK7F-K;>8HAgb)Z)U zza1XWD6J78A-Xp2uz&~RjFpBId1c!jeut)i{h7?~K&AYJM<4yd*$=MjnQ~R0zvlX?sw>CAV zE27>alb|I+D@Fuz&xC)3p4=idz?0cbBQ!WZBwejhCf)7SRBAn zK;aBZjc~qQ2~#>B60}}}d2PY)uv`WAT6d+Ph>po$nL_#v1Kpi+EnRGwwf2&^i%GcFg^8|fWT{}&yxmnfks(CdDqH8%Wz+M=ub!aPByj7s;WZtN{^Y&Kk}))is;oh2niF*Vk}i^?E?LF0V(x^B?5G zN7gUFb%Jy2p~gcZc&qxmHYXNPbul`Bj2whfnE46v$caYVwOWlN@^ZAaeEit3^Rx<-ykX<>71pw=G@vzQeK>9HF67%H?|5~O>(StBz0){#F6C# zxUsAA#L5i^iniLj8fsf5lCJD?;7=y}R&6Lgvbw%;W&ixu7D3nF+BwnG+cHpESzcl= zMoW&$@%R+q79BD|j}>4LGX&;N&J#wqA*Qj?Uj{vWPDgo%MFPmCLut&|gcPHFhm+3C z7oB+R3w>j@F=yr{uT^TUxdK0*nYZtnzhjT$yAHlq@=5@~JP!CN;ROdFWw9DmpezYv zv+5tzNd|3k8sNzcQE0KE(VPo506trYG-p0rs|oeCE!jH%>NS&>_%5-`Pt|v5xSH~| zh6AhdGoInukozS?T4s*?9I9`-WX^Hroleexv44HZQgLX|3mjmJvHFpqqTJCT16 zf|}548l<5t5JKRma6|%9hIit{|KvDcg8$;oe1>#f_P5K=eo3ZLZl>q`A}T_qs2Q|T zHi1Ak0SR-6r?e`ERDo(kB~@i*UN1sr&1FrsHQrJ$9R-j&xlAQhiZ&`A|E+nDBYD5? zII(gFmsd9*TCr_!e>bjc?Yv^u_C|@TEU#jF+$M1~m33@ySU=n|X_c(qsoL@8RsF52 z+>&V^-8j*KA1N!b7KEbmKJ&Gzq}38BmiI75S`cD%8StXAy-dS21Ok?W6@U^$8R#wKOfHMloK>ZOaKnuF#Q339Uo+|U=n~&(Et%Bc+X*dLpiK( zF&^~$AwG;h;g3f`IZgXCLjmEv9>V#= z6-^UY!PXxu?R1%)PUW!7Zg$W=7=yRb-Q9Q((!pE?bXy9u8(?!wZY>P5K`?A&RMTz| z^d4YyX@$$(RxlhM@*mndPZ}+oY945wH~*m}tEro*fu}Kf?1A+ysnU*^A}8&q z{qX`VyBPJhFU;j{KFkYVOnXFSaZ_rYR2H`wSX?4TlV5lkiz~*(lHOSwn~TLcSOG+32{40BK5w`%C?22WnD{qw}$82Q_cWKs*4MLY0(>q(OlZ%^QTHfl^SAd zPj&TFSJ*5Dupl7lAcGzBi4h`-sGMwq^$XDNLEvP@0+o{i{L+zZaD6U9f#%RZR`A)K z@p41CzDAl@**)^Z_ZM|z{vg8WIEByK^!ovrS7OW|I<4dgNBWJJ;66ZUg19NrWU`<) z1L|7dVw`n!jYne)F(Qv$vtGvOpm2VozyJqNk?N%M*vw#r9%v zTi@ZNC0tq`AzaweP@d>2S>$#asO%^25k_~x91ZW0`I&7Fem1~(*5I7{Ou3iG&tSCK zWPZjy`{EA$h#Q9rp2X44Q$v}b0`0k6p*^r>*#NZ1dLCXjr1CP>k0m9r09n#dlCG|t zS%xgt>Q!Dg7b{cymccBa1H9EyRf4NS^HVMD$q4rO5`DExMdGwo!jAWF#AWlay;xHo zuQ71WbYXR%CK0SG;I%QozajjKm_aNsv)w10W`k(7$a_T4WeDNV;)8$>AlIn;ONq@3 znoKqM3k+sPTik9yhLTu`Riq;%DpEMMk#xdHF2Kg{XU|21&Yr7tP~qWxJm_sJ$;AYf zCp?Fjg1$-ieT@2Lo`4Wo4^FAPLFEXb`1E>#BX|?ucsOV%GEhqE`!JIJ0K>=`tu9j7o!`7Zmv{(x&<^8Z;Yj(gU)6Y|McPk!3 zI*5hAb41|@2wzR`Ou!kiCIyYd3^Rz_wJbR5+_k2xPyhGsHxey%nZIM&f91p6Xe*_-7M$vpf+Hyw(H2zHy}Me~cdGY3^eV|sL$X*&R^S37(=U2vYC^Rx(?2aY^ z7L#ROz(5S9k{X}4G9E0aoHypHY?1AGC}G17F!&q68rgsegFnGnivm$+HB2w7*XxaX zBRHCwp_Va~2hfvF#s1{u(r|?^!FY zwj;|w2s0I)$a=RqRpya<1CnT~T0og)MDWTD`W`1147abt1zlJ7P(}figJ3kv z`P4tb1i-e*n*lti6zO~lgl1hcL1s25r0ko4WP(*tOMt< zj?vky13#X_J5(4#{I8vJ@{Uj*?@%DAMx_8U7S3ZGb3Ak|=dfOcbNu)MoC9y3n{~iS z25rwjlUIRGzzUSiJaSlqfC*1F!*E1{b8!T85srZM=V1wW^96ar&uO3kC)1I2$UMQB zm$n5vUnq3EOxbikZnC-sH7byB!zPOTJ1Pv9;}U*7mH>7>R~oz%)XbW#RqDgMYh zsf8EONs+P(E2Y?&d78oR)j9Q2%b6l(MV?*?V3?YuHb!aXyZDS*>U6iy#7zS7j5 zhV>Pn*UZ84iV3o7<*%_USJzlpl0l}FGK&T;5V{MhrSQMLcz(4MnV3r}g%E);MG$%Q zVmhhiIXbD8bLgZDGPk(6P73QUqLjj27u8AuOuvnLh>pB^e!bN40XC%O)JrM%^7K+L z;<9?F-^}QxdQ{C6(4bGrH0a{Gsj(d0)XMYgrho?hcXd;L|ERht-1T2oPO-7^GHv^x z$Qu{ePbmW%=uEDDijEZY(ezV)x|n_n|Jy|sRAl9e3?ytGyoR72tej_wM9V)y&m_7} zM#z!r8ie3`1ln^1eVdJSDCcdK z%Rq~2+AA@0Y~-`;YMzPh+d>9K5g}14YBds=BU?piYJxevL4yknp$Ee28fdfs_~F;S z_F)IKouE_Gz4$r8BaVLml9x0}rd%(Z=gp-2K(aGIZgPJ+A_8W!XZ0}z`Yj%-24l2l z%d>A1Ziot`RvzY#T3#!OtT#j@i3vy9woAg#(aKGQ*VP%qgn$0|vkSg<_UzXXLA0Mq zJzWEJBQNm60)uQNgG9^;h|94Yk(6a5u)gqk5b}6EH2XAt&mD_fG<3Z_PsGadZ5cve z^ekVYB^8la+Pv3yczo0ThPBOx0$QbizJsr@kF&!B~0YyD22SOGBLb4wdRI{D=D~l?ylt**!gBJ}0mzOVc+KmMUJp-9W z0U41kCuAF7F~{KvzfG%rm{tDt>=rSgHYxR=)g~FIzl?r>@547ROpT)-AkA|W$A18b ziqUoGA^Z#c7UIAko9Dzo^Q5w+g=nf!5?Xd@(8}og|8kDA{g; z#^|&fi1~>PsE4_@yhI-Z3{wCUeS7P!*5{vZ-P>`h`*uWV3lO=K#SjaCwGqTfF`5fF32CHBlSq)j5f%hNkF{Db z>0pBfAxQa1-=V3uz&7jgu6udr`5WR;h3w&D0HES)=*?CbM>?h_1_&P*gC8n*T{uNA zzH?2c>Lho?hsWWu2c{3;H~%}|61bEN_da&;G5J&Q*yGa&$rAc43yRC%(xco@X=U{_ zeCb7x4Je~kG2dZ0=!#H1;N$(551!g{r`0U1>bSCf1h4$c1GS64x_zxbIQ~XQ+gJ9m zatfyp;?K`22d-X&FmA#-VF(+(i4+yY@aOXia-35TMjI)dM`$?>)GNFk zV@BQF%B&g-;JYD+;9b6ma(M*0j)&K$Q7S+R%>4RtzgG~ltMs>aM?jjeW@Ri|0d z{+#6%o!)3NcXg31fpDQm(7a+kS(dP+%4!21zu8r|Axw|n62Gyfrg1|{fy?W16}zgM zC5@pcSQ#yfhpIOK9NZKRk_rccXW)Pl;W3#&Fs0x&W}Apw1%Lyb2^h@t)W-wCXh<9S-Dg|5ZR>bb!L4pTa>x3uFM; zpk0iQDLj?opGOIplFP-|V?*-PL_(5c0_I|ICR5UZ+i|=3YnOb@LdVjEvz<^@fR4*Q z&n^oyoXmcXYb8(?JwpUTGB8F~kxEtb%feg;tUlBD&$nmZV1<#teRzBWd3^&LQ#YW0 z!9T+X5r_QkMLBW|qXxMo8eE-kaN$R0dH6ytF`nAvQV1gUO)|;LpUixf=+%^qqJ_xz(Fz~)sd>@&FJon_z8$tfM6Wzpk z;tG2G3VJ<4?wUS<;LcqLj!?{Vlw1CY-s6z1-JC-v<&rTi8Smb<^EL8*<}>X1@6&IQ z3qQZ$wb!@9^Ri7lDkY8h&$LeWoPv_?L07WtetJDduSdwe)5lmF?q#&@AykTArq@Lz zzQ7?|1JWOGPCW3q^Sg;Vww6A6#r>;qI*Az1pwGUjJS%ea*)*_PNqXRptgluH3LNUFP1_!b zw+by+p|;z;nP_hkmFs{n!{$^4 zqF1h1cF&gkyAA|bY$^Sj>3zfdrk|3QhLTR->Z%xu1 zJr6x*qr5;p`J+*nE1HNV;xSjDtI+0S z4r`*fRG`v{4WtsMs+R*Y%Tc+dJ|W3$ll_Qif8pZYw&(Y~VB2l$A6?vktZ93X5Dhj2 ze_0s_h6I88NvI+8_h2McTrLXyo;~E%J^Q4kU5i$k1kdh{4fXi+8*e8;FOtGq9=EHfmda$tt>UvyR$p(;vg>=FMZaQL!;yD+zqEqlW1m_8j9^j+!YdkgkMvRc1e6Y^#X2HbvAqY|s;6j?=K<446{9~%m~T~>>+K(8f8 zv3&u&xP~FVY|>37)IzJ12YRNM{jwH->so@ND}gei)Am_iHc1x79&TzHxNU;-?~QIW z9~R7(Goqoi-gY!`<)(tb-gd{Sg6bBdLn`ncS$XW8!XhDrj0d<1Qw!y|ZQ*?=txNNK? z*kbd$7y8o;H(l#q=!SFOi02ZGX`(DR5ufPtEIfF?8oI>keqF!0-Qf@o{M2Q>BV#ZB zhiBVne(FeUsy61#47ytCy7~&XkuPPe0{(wOX0-=DmN;=V8j#16m9}w6X=4Rw(aK6= zQMb!sw@@m?70l`dDvT+;00#fAbVALQ#hM|xvPf^GH>rST1)@;*Zs%stN@sU*(Eh-Q zU9?-Ohg_?v3g5VR`DJ@8yNkVHcWLQoe8IxylD0m%I#8^6U97xv%^~f?4qa7fWu@_H z-m_%>#>?~*q~EJQqbUny{_Hb6Xd9C2UyKmrqlG9*>;L0+T>;kM0M=@<5e}*iSPQb}J*iDuYZjag)bUyzj}=#GglHr)ZN*=+epp^R*uVF1ZTa<+ zsnld<^?1TvuXTsZV{W4_lF4k{%KU6!m@Z>E-8>Mx1lF`fiXG?1DrjaoH5Ncl4Om76 zc4~2HsN0BBjv$bev6a=Hwrb)0WPJU4>&8>IkNeCUhyKC#YUB~yGn6`Zyw}vx+M`%1 zWQir>vbaQD5!D3bkQ9c*Oz$y^;<@B0#69}jy1rYd0*6XhTMkIx);C@Dt9Kjx``Vm0 z6x5~*?3x|Mk|wP*q!ayzmR<49LuB)z#KGcv&zDc`KQ3&-RzH4+=+}%Ks&enaVrxTE zmi64=XUB3fENvgLV_vXfjxWPHRF3sJaAV>m%w%{C#*jS#;LP(ZXHy45p7jQbJe_&~ zHstz-7^W6SG-?T1>;<|2lVKR9)P>Ta+;uj_rl1R?kQ7oAv5S${cj)IEcRZ1yrp?Q^ zWN}+-YkYIlyfro2g~siV(>fS4pVU_*dwW_-c6J!1hQprWCE~EbSdvVyi4|82RuuZ+ zg56t^?AlOVD$mIgBB#MF0erwko)E1>Fd}$T9s(Mfx!SB*^@v$VE1qKELDckUEa3jL zXR1^76ip?P`0o8bJ#9aE^?g^A;*tZnDDx}W4rIQEOO$x98z@Zc(aCnZ2lFC}VoOA@ z>~Lcd_sIq)aS8LFnB=($Ev#kAAs0kZ0j@z$6cHhL;RitvJRtW1=+;>1+`3{aN?F3R zAM6&J4LX8P0QIkjzM%8d1U;z2CG8VA?u64tcc3){nOU|n0H;7$zrc5m-(fiBTxr^4 z+#nFl_Ze#!9PByMwbz=8*UWp-c$c1BVLakY@&Bf`Y}6UFjcG%)Y2>D8_mD@cwGL^z zx3^rjFcNKTz(eKcp_ozXEf_2GN6PGVi`2eZ2)ZqZ&u=p7bpkJf8Wa;XLh=cDB?V({ zoxGe9wwAbIIVCO4BlM4|(M*K?fuxfHabZipwNFpC*vZ1DEl=y8vOMi>Sukk6+I-Jd z?|wNr8SJFn&+qS>?CYf4ElW4u@_%C%p5uI7us8p zge<&dq5UoBuv|1YAw*1wYWXhwI*{?O*Bc3kg5H3aDgjjm;0%J>7&*^X{karc&bA1b zh)6);v1c-|Jzj3A&{UWz<1ct8igSL&{JP{qYsP=09B}L3|NiN}gE0ro zy?qIVryKd{c`^EA-?J5vq}^f=IIch>jILOX5{Jzif=n1q*k~iQI+Jz+>Ge$E%@|aR zj)J6BpzS8yatk)FR}HlNW`xbl*#|WE!tal@7e^z3fX9=FM~b7xfpCC=$K&@<=up_$ zA*pN#0Aq)Y-YQUP}%sUDhK~S@wy93f=S-mn9Zroob*WUV5fWZP^uZ=+fL-z>jRV*;=eETDsN! zb$nCH-oc&PZ(1MdNmXk^E|Kipw{PFJ`fa#*?Z~2CZS=&DD34k-exIC+>pv-N+ehnK zMJsW8yS@lZ#H(Qe5oMxQrK49kB5@}m@G~c1u&?C=SOU;!(Aw-!<{{!6&v$}Svi2jq zqJ`w^@{5SQM(2z*)$~qdbxTbPtxc6J87VCe=_#9!C`|`>P*zhW;<+`pIBOl!&`9>?f}d zEa=$neW9eJ(CrGCe0BcHp5h`JAHiARFlSU&(w9KNyT9jas+!MVarGBSelv zJ`PyZ_h4A~(P1spUV{V?20RN6rNMKm&se(CLU5Q4!Jl5Fb3h>IH8OFRQ-#U2T$F6M z_0EIV%hw0~Wcs~+@T8Rp)^~A*;U?2@{b1W8f8E}*x{KSsUC`IBZeHrh{2lrjc2~0e zECHCuv7q-NR(g&Nt2S_t=hjNH_h`gM>k)GVVUi)k9)fGh0Hr4>hJEw^$hhOvi&yat zYtvj=Vr=u4|F^bxX+@`YbjzrD%fQdbtNpzmSLS&<;4l=&&fZ7tneMs<{HKg@!xogo zSF5P4z1@ikUjkfC=Bp0OXZfmEz(iD-q+|Hjqm7$nKCDNG98>x5T{1td)MBiYsl14Ru$zqr4G$9;*7&voo5jkR zOgXcl^to>kgMm@eHrm(hRXoR=W9{)^(CZCGf|Q4zhlS>I3Mq$uD%sWOE=0El&87x@ z+EnX0?*F6zNTk-3HZ_`B>R&8%PrZ2EXiXF5nrcGWxT=5kW6wVO*y{dOG?4pinfc>* zZRP>k5Jq5Z@&L%MAS2O=bGTo{;Yps!uo6Z{vzF4!77dFtWP>VdX%*!Pa!QIfR>4sG zG4rFg%Z}eZrg`JO4^G$~Ge2g_gm-V+jSpnLfm4~2^i;_i)JDoll=jDBc`oY|v69u7 z$4I~|VqzO%F`5Y`SF3k{TmhP}tiKp71`A|4$OHV7VBRw`dz8Krc*g#8;CSF!`!j*I zPw)RU{XG%3$C!*ij$A^ zKY4KN!N>ZZIJox5x2!mQYWS90mY=?5SjN3pR7l(0%4~Y{V85HQlr3T4N)s9^@KmtV zD2ws}KcvyXaMS2Dbhz;npB->o)e0bFqGJL-TXbL1dr-~C=?Q7Q_ukB_`}fbg_g)y= zNpuxy$3JD|0?kD{#suMWtqq{0oH9gZPz!Wh4BtMRfj!>3fB&y3Fc=(@C`Q_8n`~%r z-?K(JP#LHwf#D$S9+poN7|o<4cb&utiv|O)fq<-_pOl%?1s|1@PTP>W&Hy77FR z`GXT*@5+>Z{2t9cSN`hcZ|L*GNFpuS=LysO$0Yi=H0;9fxig=|*yQ}TlfOE3kLKPh z0p_K27bc_7)1dp41RjzQ_MT%Y<0hiJ)TWdT)73FtgEPOk<6B*s1_3{9%Zy;l25J4F zd#=C7zZaqGw_D_IgQaZ#w^c0GXaH?IfW;WTPD=sp2}xwWg|XS5+2O=rKKpwcEd;;2 zL0WhC-s|r<`_x`2gs_}hZRAo~f(hhCHU>4c6cGvHiJUTuNM4M)JFUwG+Y^d9%pf$7b>2nhAcBl{nrf3E;e$+1`0lEs8Waq=bGJ}iH(J->;!S;(Eik10I z%ZAPDP{b}B@9ErhuxVlI{OoaaERd*<2i*m(LU|9rL8tc(s?$46^`|z76(%yv84xiD zCfDSTLq=Szp(HPY^Pn2gl`X^V)?g|P!imrLjS5wy9eVss_ z?i$(~JbUSV*}RJ@s2mvwZ?7IKDMI=Gog`h?dusFE1Dn>6QVN? zH~EBpaoIt4(dIk0Slz|*%ugV~a(Vg{+c?Jg2)Y^(j2RyQU=Tb+B@vGHW}0G=6v7cM zWkS>MwA;`A0Z(dP`0+8)diL#Oq$1Oo&8@eE;b)Mgk|3}^O9+E4NOFh_F$+}@5?RU8 z%k}stmwas9XV!fT62X&>v!4UF{eHTE=|>7ckERnijw1|mgaZQ#4k2#2;sC$_wmaAt zg-{v;2b4lKi3{OxWWrAD!OuAGPwko4AF%udH+}g89^i5HiAyu@ug&}m!YF|dxrfD$ zs)FS-4&i=A+j!`YIff{ZGKa+?P8biej9@@ho-&PY6Gzy0Y?Qn8l)xJ7Rt>eb45INuqmZkv1I% z^PTg>*}D5c`|bskQH|+}OTN5p_rP^4n%i|+uDYyBJ{a~+EV+Jg9Y1(Ochj?nF1deb z$GoPrwV<>@IWYbAUn%@M36DJ43j~|?8dY=`io{=;JIv$0ym&@o17_v1^gKh z6-zzYpwFw^hdV4&1Wd8jXXM-5+PQC!$+DdXc_LKp%Vaw#FS|;FCEZ9wT$1+C=@i%Q zC^h?~D##omR*hFsaGAeMzMgrO)_7SG0lBB&bG zp@ryWxmnGK6GVX%PaqA}VU6xY0Tv7xYXm%nM5NP;Iytu*!Jv9KVZB~juQd{wGfWwR)|#Mfr>|J5x;7*GHpPD?;@P>le&xg($$$>L^{KwMMMrB9Fza3`d`dp7YG%MdeExI$(zn?E1>CYrP`YJ6%5caJ_o-U+F#k zqgglc=-~XWEe&jkCo=C9MI)LvE!STFI!Z9y2lrqKT`jN42?%A@n>ejjTyKN~Hl5;Ne}7X`I1Hjm z|9JoErK3%KO}$+m;l^-7ZB0d4aXiO|%A(8np~{Dj6mtp66S*u7INTgh*PL@@Yv=gl zHoAoa0+yT(w|v-zf2GY7&F>uV@9=4C=|Ho37Mu0l&u9I@AM0uwo`7vI_kTJ5#yK+N)id`VTK#| z49C)728M$pb@cN%f$2U{7a|pVc&BcOq!PB5$s*sM4H)!dXATEBE4uQSxowo z5#r+-Svds;f_pJOS4aw_K-dNYpM(^u3%Sz3=J9&D@_)hxx$--6n}$$sTVVF2&9(*i zDkNRyc1+9zo?%2rPY6VbbX|&;g43Ap>r$2<&#M10G!KWCWqtg;~# zQTu_~Cecq>QWnWYPo>t}y8e0+M<|(BPy7%xgI!R%0hWSkKeVX*z{;V!Ug(RSd|^xO2b&9&S|LY>B9==^ z{8g^KA@e7>_N0HNZTW2h-mT5H1@|zsFaSOx%Q-Bk(IjYAM_@;U7K0^B_l}4KADz_% zvNI3UJwM0gcK<2pML-XXm-NafJ-p z-{<~d?QEv)ybO6k2LxdCviVa{A>n#dS9>PA%IRZ3a;>-6yFdg}8e78%U0pV*tU%2OqT4eSk>OXssd`6R~MSowFnbjh&kc~$ry1tg<55i4kI^0Btpld8RU60 zCJ6lK31P^M=8|?pmK399GF&LdRA7N6R`@RuPbn~Xl@)sSZ}{eHX;5B0LZoA+JfM_x z*H23&y|VcuLvy5Jxl*3ea9Uoru_LU!sl&gN%lk1aF0+?tTrony<+jw!>RG+b(6E|uru9QU+(j@ zuCG6|{=iHfT$F|b?#Dr!25#9QyNv@Xi#VpAIw4@5O%5lRa5Scb^dlIwRB;sdl$BUS zu~1=TYUEyKrRD-6=agx1LS}HJV{sEPwax23HZpp=ySa7ANOEE6f+g{!i5GX1S9k0A zg8HKyHs0LVcf;z@s~Z;#9&27vF}0y}UD8Bam*9nHR-M5HDTFzm>IJ}Ui-469OlnUc z`U~;g1QR626sIywhAp(ZWQfa%4$sbNBJl zkz?KS+DxQrFAT5d z&W{oV;+(&WCGsPBy+YOOhgTV=_~yMj~8Yf*a2xl*s^&Wo%hNMIpv6GI1S-W z(69@0x`cxmdq?=g*lrK+l`qpH0mQ5=gpVnH1kpUTnDts!N3cqXZ? ztc=W4m!#t9b8_mQIe66p+dMSAbr;{d$H+TsqUq|A-s*UTz>9oiecxKQLtM1@aKP!& z7_r9{h-fribkB{aR!!j*yVexCGiOH1s|NcAk1eFOFD_R@YWL~m(dv=W*S*22rM%=Q ztg5VXMpCl=RY%9watiMNsz6Psr+q<9b-W0i=rjUDCLW?poPcDF3cNHJQqqbXVTR3> zmBAoFl}(k6!HQrx1m7u(*|oZCLi=1TqDzSuFHcdevUNh%os+Q8CNfM`434k&RIP4b zynbSQk!Ai!mA9#6EGB9ew!9KBnz-U(Q3w^C3>P#;3pvj3F|1v&{+KwBP7PPC8yekZ z`3k?bdAKTmTh|ELS{e$pm1s@nQM$Al%Q)=e$MjXPU{N53*xK*(c5;M#2ALpsAp)Ci zL{5NP;Z@nx*v-tRJ#H4Lx+j(uIGbde8{Wx-hEi1kh z?Xb3g?i+xP+E~QzjfFsm&#U3W6-pe>2|BGNO;IdF)X)e79!!YJW!gD)_?-jWT6aeV z9gE_{w2K#Yc6RrWNO;bR^`Ur=&JZqC4vQwE(G`Whn9mh3dt6;5E&aBF!uhdfb6>`~ zq`}1ruDHjOJyF)ktz?osO8Y2;TG1M|?l9WiR7P7UafI#Fm5@FxiNa+ zSf(Hj4~N5MGeY6ka7#ly4GEtxhs;5bi+JrB zjJ$oIbL^gt{cPW}x6h#8GBIC1q*vCjX_~jdywXtRO9*=6D|E-q!);U6`6rg`d}_VC zuW#PIpw+wnN_uGVt-o@3RbTVuJUZ}eW4;cv==K$PO1IEvp2jN)VR}LzDn%*up9~_p zO03Z)u%sdV^2%qUfnJcb8tDX*c&)Ts#9*2Syom?^F9B&af_6$TgGtZ8q+?(zlId*I z><{qA{|jGeZ=UOAEzceQ2ycT`UwIh>>#nIPOO>ZelO;4xRKRca8NE(BQ!*AP-398e z^9P*=0~UqyMCdsLUW-Je-;dXgH!Xx+>#$B}YH4A6YeQglAmDcRS{=?nA%~rA4PH66 zC_SELyW*}Uzc$?=?`uEpb$i0vj&!kIZ?1%KK7j8ed)c~!8zLk+>{dMwYIdz8YIsoP z3p`=MI20nMh$5tMYusiNxM3(>f;uMfMdUzbIZ5#)%DJT-@ddh{>$>Bss#b=@ z!KGD;%r1VSme-i^o$t4{zK=uu_GNyyVf^&z@ePdr+>M8bhvJF}6`=-(C*|3&je-n# zK?#u#fQQjxE;1K7?QBel3XoCWm{8P+48+(zP};D*sfO+A8j|61y5XU+!K$V;b@G0^ zqasue8^Y)*>QDK(|CY}*)viBv`_bF8ZqA70d`m)u^4HXe20YGn1uZLND`uOT( zthl_j@d6WyeTVzjR+SbwQ!U;g^l=|+B>F%wM2N><#GjFfv7DW z4ND@mVsK{rDV#GnL+LtsS=;exTU)q$O}KW&1H;GW&p$Ry_n9NVrWeC)wxw3r!Vcd< z`3p66qOo8u}s3qcr5o-+? z>$G?Z=}cJ9>GTtb69lIJ64n+-MBs#>sw%%9$VpYIDpgbMuk=?WVrHM2PIqPVGOLA= z7_%tH{Zn#^G0$=~_)>Hiu~8ulowL3uQPAVJYqiGqrfRl7`=|X+Qz3BUa`}M2X_E|s z$!M1kHyVCyzzaH>OREQ#cQhw!29{@DrNM}v%lw@zJG%f6Vd?DS_;jRXz-u&FmBTar zN?w93qyU#O|JWd-o{n==D(0-E10cnh)|cEv3ce@5`&-h5Kg|{9yqmAeP~XLncx?TO zy1Ti;?=c_eA<~O}Nq$Qq>dm}|5WDso=|cy|@8~t3dhJ@$j23fodd;t1dy_PwPqJ$+ z_1Xr~j4>Cc*8=LbVfx&6I19a2q+Z)hy3lW+&YVZRwuwIXTh2(YxpT^mqvyCzdM&J8 zTSFG3eh!FnNhyRkb zJDR>E^GCE``l}xW*MMv-yZh1=_1~l4Ju+Q}jv+s7SE1V05_-*xZ1h?v+h^$CNE3b+ zaEi|93HYUB(0g_*+qaiARlMH9p1FlI5j$suDCAHFh}}+P-GNHV7_%Q{D+M7jj0N(X2t26Xs)zojxUEuI!TSA6rc+ zSBbr7zN@$^z&Y%`_)zP6h4wnH$>1jpL#7-1C)`uSix`Pr>=0#!$v63NJ z)q`O9`8zQAq))s+n(jY5K&2_YaCBff`IqL;CuIJ3GifH}T$ssonwLbLAVQy` z9q6Llj|aDJZ>1Y)j=$OP4Be0%1D#CbUz2Zs6uh^Eq|i&a+LHMe`6jcmUrPIb5!hLw z8uo6&%+~Ns8i|`5X|2domTjV}+@KW`nf(WF;ydSeFXWr;3(MnSYkhL@d*#K64x2@^ zC9AE0qVjsy#urE&|Af-bf{!GR!xYF^WGr;p*rZ21|D;Dv^PlM}AE-#fF68IMQV;A% zd{J#hPYK)gdRX%Ff%c^ zTrQ>s)&N~jfq?!imER+q=*DJn^|S!Yhkuuf;tIa)iEgN#@I zLf*&kaPQ>NyAGgt@6XV?|D=6+Gc7pXUYlFT3&ZW=6Q?RCR zae6DcO7m##t9i8c{d`(`iu@a~aGysa^2|+VC1>;0a* z_p@~BV+x)6Ob(s8l>CVpxYH=X`KCXH9s~Y|wxY+78Fb>0$)t)M>9fBgf5G>0XW*IX zv*-yq_SqA1?6c>{ukc~$&s_TR9Hl?pZ7Th_k^G93a$ldD{`@dYe||NK{yfN8@%!AN z^Tizmkpt+?3ykh?hqgdD&yjcVkGOwjG@$CgH2$FjFEI8$oNMgAbKbFk9r=Lrl2gco z(*K=t5BPijF~95lWB!BljQKO9iT1S-nZb(OpyxRv%#F-uWQxnktYWjQSN>wew2#+? zjK-igyvy%f-9(xW)|77Jc%Iu<+Sr(u`|LCwGdenE{`=*8{+x2&Kc}298k~;Oatx?| zLM(==Q7h^}i_uE72~DEyXdgO=j-boXmFQY@13HCnMIT3>K=+}C&?6LgpFuC6SI}qB z=g`~ei|DKP2kp~Z3V!Ud5lLLLj2H^IejOnK?)xHc?O9;z=8RUm)o4F~PzS~w!eEjh zh#crck;ID<=tqf$CsR7C>C$&{+5(MsYCfiDWhpRBAPX|vEanN|hDMup0@)X0htuwu za${$YXA$Qrbh;+IxUkRH%N6+xizb5DKM?BY0%3n(qBtJq!owwt6GO2`MeO2{DQv<1A-rTs1Pq);kC+4-0me$sm<#23iT|3sk zf@^JQ9s1Ij-gx7+*Is<_*=HYp^x=o^zWa_l&b0sInOknT@y6?}yXvYFCypLHbZGzn z9XnR78XoTLZELHkLFh|g{nA&z^5rjl{*AZac>Aq4U;Er^pL_kYpLzAupL+3?7hie# zrDtDw_J!x4d*oWk!$S`~aR1%+-F@G^_uTP`J3evOoo7CN z=Hs{DcFV1|+J9OmG zk;9knKe+$kflGGm+p%}|uI<~lPEAg1*}Q7gs*S@dhsSys_YMv8xAn9wn%`N|TBE3( zBOzar%V9CG0lgAtzoJwpj~)jdOHQV=mw$_Zaf+rb$AG;~%rOeA$vGFDt_Xb&Zl}|L zra`L9cd=WENL|W7H<8-y(i>78Q!6@zyrV>)N+nXRYEW*}$-hAlnSU;vdk&|V>+v{x z%gTBko}Q8`hO4WGtyZ@GO;sdP1zUx~Y<9qAv)PPBx-C$S&WQGeHe&y5sOZwT;Ircf zpAf&aDCAmwhW=Y4-#jxZ_KTC5@9&ptr2Uz*tHnZb^-mi_u}YGvM6uzg4Tfc6?`nE< zw@Fn)4Vkm!v8d?o8qa)xyxJh9$H`CW+DN>-Jnr>J-fC#5sGu8E3pS_~Z2xF0D6r*j zI=jVU|60)HvyI$rce(60kJx;!;Of|}*e6<1CyB;`a6=>2cITo_;1{In5TvZ>;+s_mS>Z09O}KGI+^Fq9<$qF<4wG=m?>6i zO=snxNvD}Vfjxy+R^Qw)jr-OwY@%D6#8uSMjf*#2GL(6=v8uHPw(c@#g#O9?*-J?Z z{f>JbVzGherWw|%36|!dZcxw`9Iq-85pDhJd4NdI$(<)1^)sqbQtHYk&lP8{rBJU_2Y?&|cL@dsxYsh0T+(#zTI z-7b?+s}aF@WLAX3u^cXKFhJg)ggXdaI#=gm&8x=9&BY~&(r9brC6WBfJVPl}OIa+i zA@jSuDyen4lf^F|YVWsWf*o1uAsQ(vq@0lNpAjEKtc1oD1b!?VAeR@0X9v5FMxz>9 zU+auikt0VNge#U)P z9*;2R0OZEK0>^V~t_vr*>oz<;;{xdr6}*xlz&+hcDLy?(XTSI`-I4E5M0)04`VY$K zqvd?SeGbg-kr8|@vof`S!S#i5YK|ErvlGIW5(pmB>m>;x$iGuykaUtxs{z?rLuF@< z$=)U+7i>7xz3rxsM+cznOQGz4Ju`1=YTg;QTs)M|+WFdlzn$DnX)7oSo3cf5S2esD z$4WV5dW6*AWvm<>L>s1ULA}g98;gqkPsfLj*JbAqf;9ZM$C9Zr1MRl>(npl$`4pDy6FUup96}_OyrZa|lmJg2-`% zaLd^VN7nL~a3l{SDCA+lav>??QgDBq&2;vuXt8@sJs^eBKJ@S0!^nezsIPsI1|!a? zB?LJzCR{&+a4r(~9uf1H2V*?ikAf0ab!uiE1l;{-cLbGLhy?T^cj6JwosuY>YX~}A zVJn?}#GUPNd+v`lhZpU-rsE&Dhjlv0Z`J7yt0zxgGjH9xdDkEgJxRWc|H2>ppYuQI zg8XmAoc!;d^Ygz^@)PtMe)50L|0?F-f86mo`JY_omj5~bo0^0F@s)G(Ke^1Y{}cYV zq=qLQd{|^5vq4@NKN&$}L@;@l& zZ~WB%5&tvh@jveNeEui5bIbn<|67^M|M-eo{BJS&F3qKStkV?YlVt!!!JE8qF|3zA~2qDlqHf$Nrc2-&8*T0{{mA0ssI20001Z0b^if zU|?bV_m+WyDdIoFzg0{pnO`t0XFvkXZUCmk2blnP0kzfxjBH65#qoM$TW$B)d|2DI zjbLrtwrv}4ZQHhO*ZzBQl2p3yEPK-V&8hmXQFW(abrXdtSlzxljeZAA0z*^qp~=Z~ z9`$-?azwF)@}CMh0y69&#kK#T<9JEtpM5_={nFUlu`$dNO<+Q&AOd9JsmPO|jYB8UOfjmm0I+y*8EcqJ^SyI!-{+P?uE8}t+&ZYJ$ z=|chw3hbjwTFsV#o2+Lq$iEHdHrsw<(V44udJgPmfdKLQNbLVDf zz27r!B+-W!YR~CB8)^SS&V-I-hOjJPU)K1W2l`HMjm~#X*WT%I?MYeKv6zmLMjs~7 zE6bomHLhL~xkQ zivm6Ko%Np2gVk{jUcwN=v;9k3fR&+2m=jbm^T_P}oV2-i8|W)<}hl}~ZBmLG-P zunXSDH8>pSs(mny$FW!zVXT2GIfqrUEQzILMXZJbwI^$tu}H3@-x8C*o&}m+^Ic7eD6Z@``ztyn0?MubVf>8|QuU!T0?-{w)7e zkSpjDj0mO#3xZD}gnqaw+!vmT@TP#?4h zy}@uW8O#T(!FF&MoCmjsib7psy_i8fES?u{i_gW+QcM~y&6GaMOwKEpl&i`O<+gH9 zd8i^Q5#_9MQ)OyLwXZr-{jMd`a%ye0{@Q45y0%h#skhfJ>-UYSMnj{m@!a?fbHn1W zGOQ0oDAo~)o?pJ49}zbs5R=22BYz4Hd>B02Ydk-Z~|9@RfB6oEYv?VIy61B zIP^1|BAg|>HoP-@G}1eA9_PfRadq4n@5LwaRs0aY#Xm_3l7$Q<6Ukh%l58ah$ysvK zY-{#3hnknI!d7{!w$1+C%rDEBb$_R_I!tA@7%+2JA?nU>m`_lcIkR+jcG=Fqu^mnX#Y;Wu+ zPsKBFi|6EZ`Fy^dZ|3{sp7{TQ-~q64H2?tc!#(9}+cwfU$=aDo<}FpKe=gI$`nFtB z%{be(ZQHhOexG&Hs!7`>9fF&|o#1R(1_M|N*TOU41@H>^2K+=YP7o|85}Xix3ZjCz zphLk>a6<5okY*w6LS!N9Lbin*3ONRf`ERB$b3x{XtU+1MtZmt&vf1nhIW2Nx zau(!V$whMK z@*i{yx(7Xi4y6<5T)Kd^(mr|yy@K9BpP+BhFX$hN7K%X%M1d=4gmt% zUQsnuNmQ(Ah3byFgSt`ELxXEPnnjvbnq!(r;9t-K3;`1W2C{(`*ntPk0h_=+a0}eZ zACrHkfGXHh_-`Roc%>*%bfH*KysY?M2~)DGR9X5>o1*=o!*l_CE4@(fHZ(It8Cb(S z!ydyA<1iy@oMqf(d|>KiB2B=w$#jkRn_-xB<|gK0<|MP*yus4S!u_9OZDpNg1=bnX zbJlORQ8tOK(RRb$!j9Xg+0Qv32jVa~7CEjsA!o4D>fGh(;wo`*t~ss~t{3hW?gNvt z$=b3GWi!f^UA^>Ut1^@y8v;ign zjQ{`v=nfSC0{{Ye0W8KPPz3-GhQaUdaCdhtxNE_+4;Jx0BpT9Z$VjKt0~P5NZ?A`k z{hJnOurmw*=mgy;pi_6Unbd=37W5QqIMh?g{)lpETIQ;p01}b;;VWpReq|c(c@Qo z24()JXR_n3dNzHrtmn`Y$Nx8eexuvZ?{%jwKJ}m}C0|crAf&0MVx%~H!G%ZyuY$%$&Lw8}|N?*pYATSn6$HP3u3mDR4O>*{a6 z-*sItqfwcBdUIbs8knrybfPIMtHt_Ghq;Pc)Qip{S``bOi)+e^o7Ot`$|~a?HV<9@9|OmFi}eZT4J@;knPbdv?>=D7&MjrwO&2^O z&)^YJrrnyI0yUosf6?dDrKgAN_M-C9Jw41>m2W5vHRU}MS(jvNy5F+bF$1xvRR!yu9fyXREOCN$iavQLy16$AMWRl*V_v_xQ8Q9QqW%x=vm-Ki0c?a-K>z^& z#kM=#-JNqolh6R`K?I6G4gAPK0NnZKy=?^W{dB^2%Mt%9-1`d=R|bZ5*lCyDT-al; zefB%RfrFemo<)NZ;(<2D=axcfs8JTT+|0ALt=pIAIg%(j(l-f|yj^9~zqHK>W2gdO%c;HY>_RtHY%#F=ThFr67nV5Yj6#cW)0 z!yOMi@xog*_^3s#nj(v)$_8Kj@RuzC8rClY2_l#f*%3+@;Y7$@Jw&QkiA1T7Xkv&Z zjycR_9`jkiLKd-D4ss-(1QJP-lbmHDSruc ziYZYlrAnrZaw?QgrP9<$mE7d6zepJ@VW~W1WEsd)nJj08vRTP0s#(n%<*=4@%3?hm z*hmeVbsxYo_ zm1_#;x<>Sy8{Aa2BDlqE?r>L;D(9XmRmFW(@PLOr;xSKXr-M$q=%$BWMJbv-`gzJT z#VA&0p6dcHc*!eXD~>lB<1GUWGQ=<=j55YJ28@_6W5LQh-t&QveBv`-_{ulF^MjxK z;x~Wzt82QhI@R-^Ab2o900000R?D_++x-8l+I|9p2nmabiit}|N=eJe%E>DzDk-a| zs;O&eYH91}>ggL88X23InweWzT3OrJ+SxleIyt+zy19FJdU^Z!{t1EyBL@Hg0AP1n z>}}h&?b^26VLvfs*oaYM#!Z+sW!j8cbLK5rv}DFB9sbh#?5=rxpcfIF* zAF$qsB>RXeAN$0oKJ&RReCaD+`^L9y@SX4dz+SR=!Ap9W$2R6Omp$xe7uBqxf@SRF zupj-zK@L#Kem|4KlwbVnH^2KMh-qO&Bt&8)MRKG>YNSPaWJG3UMRw#wZsbLN6wn)m zQN%)0*~~_MWedNtj#WHlZ4@&WB|iy*2de-BFaUtxdlzQNw(a_nZ5so`Eo#1uO}bN} zRGD%WDpjdgqgI`I4H`9R)}mFLb{#r(#dCbbUwp@Fyu@eZ@e}Xy8(;CJTaR9S`VAN~ zWY~yNW5!LGG$oPBxmv=W$H^u*C zlZSGnJQzjy-?hd3X8kHV^fH|yr}v(zTCBw+L}VywFQ0<0jT}!qpn5+^rxS%qBPtnt zpaEK-gKTHhYV{M^|kJIYZe5g2GY*kMQRKz7=qoOAtaQ6q8;+c5Rr@0-~k+3!z< z?Dv(TH6HDMMj(f>>rm-(++S4WBU+N7r3emc~#?!uUASg`U@jhGWOLF`~RzX!E4?W z4Y|r|_Qds@p%Ys>~HPGA?V(6cw`};9RQ6rGs2`5OEV9G(j75C;YpEAsA~MiTIj>sl0JF)bkA&?MXhk zxcG8$F^nOFDq;+VKZ`r9z)l5REc&aJ8`-{e(u;a0?>3zDf8Xad=b~g{USy<{kGoU_ zO-ALHph6y_V2@SP*45*J6QRoL{o*1gl`5+VhM*0);PPNoa5k1^scS$caj4sGP=N3eKk8TILRS z*Fny%m$Q>{KFgTTik#0YIiIDRuN!i@FXf|`*FFfn+uQo{cAaS}yH!v< ze|d8O+n&N!eXF$9se1Zy-J5NxT_=0p6c+Khp!jI09$udgO+UM)w@bdB3^#?f9=*SH zAD+Me?|OK<+2zeUzGl4r_4V(|_v#Dh&2Fz>l=0S}MTdtpSk`(TgER)Mv=I|#EYftk z(v3cGb;p5II)fG+uJawV$AAeVW-QW7x?#mrnt~Mrdg(eHF=572x`9&~Cli*X$A}p# zHdzfi^jLAo)1bqM8LMmtt?ULJdW@K{;gG|j$A}q+oCY0w%n}Cd_zBKXA(6 zWIAM7225D+#4fAF0|TBo3IOVi>V8HVD@-Tdb zIh(Bx)t|1*LP=5}2%_hE%({HX1uo&(*+;Cx_|O-~+Sgx@l?*+Ey{v4hs?lQhUe;5wkzkvbhz%4 z9_f<-8Ilobc@F849_f<-8IlnhlO~yvDVdQuX-S=NMw(ypu2u+g=cg2v4D?Q))K*nH zRh8{S{r06$)q+bl$v)%k%XU>i(2QyNj4d*wA2N;@$BaY9Imsq%AGA%MFS2*^P}l0E z`zPyBFhgATZx^l5fK{lMicTcXwww=pd~qICyEJ%V!QF90#=^uTEz%|(FfnuTr_eIV zVZToX0RsvA_GC;Z!qg>wFt8k<%lFZ8jIwE0yN9+c=*Wv>t|gcNcmb?d*LK@X4j!txSCtf|fSJg@*BmNNcP|{%+H}`*$gQL$ z=YIDp&%k{dQ0aI3XbX^%d-==Sk}!iZ!1n3`G-NL zX0WG&1%-JVtJ>#$Skt=eU_u-4zgt+C`b%fp7|~wK!;%ATWt79M&xq zJ3fjx>}=8Ae1rFRb?;5|V_dY@<3~{(1VK9r(6ED`iy(kD;gN3OU{L{8P^d?QsZVeD z*e6}=mo9aHLP7^iDc!^bw3EIA7ud*f&8&R>#Zu_CusCCpQ9}$dU1TU5mw7X;rlN*9y;@S~m z)LI#BWGO~23CD*NB`E5mpiz|q`aCi$@*ZU|r4`K35Ra)Om-Db4Z8$it$Q1y`^lLtV z7Stge=iLtN#?yXHC|aeY0CCWw%s8P#h(o{$qRqic^=8jc3hj;HA&wl%PY#yVn{EHi zZl*LX(w9?xMI~ZV@B7K*q(&ohal#H5Qm*bMWBfBtKWNk>5RxrFVfqNK8^^$D$7d`_ zVZF7P*(rp+sU{#KO{%|1+SARtq=ktPWl4N#6e}IWFw$;Sl}JR++w*Z!y5Qlcba6z| z$hm+O(u7pQ_nS8jF(JyrbzNqnqC>VNm3|xlW1A0%w;F-1+78}OiNW$F5zq3LO7bjk ztE9lvR7sKL9hH<=-c?B-%X=#6XL(;G11xPNdySx4h;j)HH2%()jf3ihv36wqS7vlh z7^jYm|IUn{1diF40cNaUGpr2sBtDDq0oC<@@UfJHkL5$c$FffNSWXc>mcI}_meYif z<(~*2%NfGQvY}upo!m!?(%uX~OWJDgElF(f@XjiHWaA^cFdr*`CS1~18O=$)!(ZuP zGWsXIUMHg^k4Fz5&m={o>iM%lit=ej&y_yU6g*EWf38H=$2*@B4V!AYSTAZ{1pjTU z$hqX1QDv+zh!1F9Qa=hE%{ll|HEt|9ICq^Z8oEX9^VA|yt3v~tC?sDtuj05Vn=%6b zfcQW|x}7=0s8)#K1x01y4RR~g>65x8!@|WOZ6C%B39uB?nTx00ppl)2CF%CO0YkpI ztZnB zq%BT$xsXdl({j&5m{a?vY;scW;!4k!g2|XhvFA5nNm?dpdP+LQYx~Xai>{!ehwkd`@M|M>s6q3_1^}ge7Rmx$gO! zDj6))T?+X7Th(s`(k1neUQ2Xd%gpQ&R+tG^nF-dI z3D#+Pg$){zrU9EYV2gP{r?kyHuN~%j?MilSkItceIS2l@()rF1Oos7cmXwuMMA|;5Cr?#l0u9pNQVK&k(@q zbtOAl+Vm19n?Adv*R?)BX@ml!TLsrOzIxF+U@~#ytj58=MEVAw;{(*e8+ehT^!g|E fd+5o_YmU)dzpL#3|NnnQ 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

    xdtBx59o-c02@6Z=} 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/fonts/iaw-mono-Italic.woff b/src/renderer/src/assets/fonts/iaw-mono-Italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..a93ecb0e0e3a9fe82aef52c5030970504b3011bd GIT binary patch literal 51240 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 X%$mZ+;Nk)RcoZ^;0000100000t#8#Z literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/fonts/iaw-mono-Italic.woff2 b/src/renderer/src/assets/fonts/iaw-mono-Italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c76d4ce4d66425b7f6bb4cf8448d54d450d65d6f GIT binary patch literal 40708 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 z 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 A2mk;8 literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/graphics/Arrow-vertical.svg b/src/renderer/src/assets/graphics/Arrow-vertical.svg new file mode 100644 index 000000000..ca98bc8a3 --- /dev/null +++ b/src/renderer/src/assets/graphics/Arrow-vertical.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/src/assets/graphics/Arrow.svg b/src/renderer/src/assets/graphics/Arrow.svg new file mode 100644 index 000000000..a789885b1 --- /dev/null +++ b/src/renderer/src/assets/graphics/Arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/src/components/ABSwap.vue b/src/renderer/src/components/ABSwap.vue new file mode 100644 index 000000000..246a70a44 --- /dev/null +++ b/src/renderer/src/components/ABSwap.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/renderer/src/components/ActiveModule.vue b/src/renderer/src/components/ActiveModule.vue new file mode 100644 index 000000000..66c7ee5d9 --- /dev/null +++ b/src/renderer/src/components/ActiveModule.vue @@ -0,0 +1,322 @@ + + + + + + + diff --git a/src/renderer/src/components/CollapsibleRow.vue b/src/renderer/src/components/CollapsibleRow.vue new file mode 100644 index 000000000..53075f4bd --- /dev/null +++ b/src/renderer/src/components/CollapsibleRow.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/renderer/src/components/Control.vue b/src/renderer/src/components/Control.vue new file mode 100644 index 000000000..6f49b1192 --- /dev/null +++ b/src/renderer/src/components/Control.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/src/renderer/src/components/Controls/CollapsibleControl.vue b/src/renderer/src/components/Controls/CollapsibleControl.vue new file mode 100644 index 000000000..502555a2b --- /dev/null +++ b/src/renderer/src/components/Controls/CollapsibleControl.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/renderer/src/components/Controls/ColorControl.vue b/src/renderer/src/components/Controls/ColorControl.vue new file mode 100644 index 000000000..d9f861782 --- /dev/null +++ b/src/renderer/src/components/Controls/ColorControl.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/src/renderer/src/components/Controls/FontControl.vue b/src/renderer/src/components/Controls/FontControl.vue new file mode 100644 index 000000000..6f889d667 --- /dev/null +++ b/src/renderer/src/components/Controls/FontControl.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/renderer/src/components/Controls/PaletteControl.vue b/src/renderer/src/components/Controls/PaletteControl.vue new file mode 100644 index 000000000..56433ded9 --- /dev/null +++ b/src/renderer/src/components/Controls/PaletteControl.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/src/renderer/src/components/Controls/RangeControl.vue b/src/renderer/src/components/Controls/RangeControl.vue new file mode 100644 index 000000000..a769a130e --- /dev/null +++ b/src/renderer/src/components/Controls/RangeControl.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/renderer/src/components/Controls/TextureControl.vue b/src/renderer/src/components/Controls/TextureControl.vue new file mode 100644 index 000000000..a69495198 --- /dev/null +++ b/src/renderer/src/components/Controls/TextureControl.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/src/renderer/src/components/Controls/TweenControl.vue b/src/renderer/src/components/Controls/TweenControl.vue new file mode 100644 index 000000000..33630630d --- /dev/null +++ b/src/renderer/src/components/Controls/TweenControl.vue @@ -0,0 +1,181 @@ +