Created with PulpCore

Compositing.java  particle.svg 

// Compositing
// Shows image tinting, additive blending (colors blend to white) and
// and multiplicative blending (colors blend to black).
import pulpcore.animation.BindFunction;
import pulpcore.animation.Easing;
import pulpcore.animation.event.TimelineEvent;
import pulpcore.animation.Property;
import pulpcore.animation.PropertyListener;
import pulpcore.animation.Timeline;
import pulpcore.image.CoreGraphics;
import pulpcore.image.CoreImage;
import pulpcore.image.BlendMode;
import pulpcore.scene.Scene2D;
import pulpcore.sprite.FilledSprite;
import pulpcore.sprite.Group;
import pulpcore.sprite.ImageSprite;
import pulpcore.sprite.Sprite;
import pulpcore.Stage;
import static pulpcore.image.Colors.*;
import static pulpcore.math.CoreMath.rand;

public class Compositing extends Scene2D {
    
    BlendMode nextBlendMode = BlendMode.Add();
    int prevBackgroundColor = BLACK;
    int border = 100;
    int particles = 4;
    int moves = 4;
    
    @Override 
    public void load() {
        // Add background and set up blend mode
        FilledSprite background;
        Group particleLayer = new Group();
        if (nextBlendMode == BlendMode.Add()) {
            background = new FilledSprite(prevBackgroundColor);
            background.fillColor.animateTo(BLACK, 500);
            particleLayer.setBlendMode(nextBlendMode);
            prevBackgroundColor = BLACK;
            nextBlendMode = BlendMode.Multiply();
        }
        else {
            background = new FilledSprite(prevBackgroundColor);
            background.fillColor.animateTo(WHITE, 500);
            particleLayer.setBlendMode(nextBlendMode);
            prevBackgroundColor = WHITE;
            nextBlendMode = BlendMode.Add();
        }
        add(background);
        particleLayer.alpha.set(0);
        addLayer(particleLayer);
        
        // Add particles
        for (int i = 0; i < particles; i++) {
            int[] x = new int[moves];
            int[] y = new int[moves];
            for (int j = 0; j < moves; j++) {
                x[j] = rand(border, Stage.getWidth() - border*2);
                y[j] = rand(border, Stage.getHeight() - border*2);
            }
            
            Timeline moveTimeline = new Timeline();
            Sprite sprite = makeParticle();
            particleLayer.add(sprite);
            
            int startTime = 0;
            for (int j = 0; j < moves; j++) {
                int lastX = x[j];
                int lastY = y[j];
                int newX = x[(j+1)%moves];
                int newY = y[(j+1)%moves];
                
                int dx = newX - lastX;
                int dy = newY - lastY;
                int moveDur = (int)(Math.sqrt(dx * dx + dy * dy) * 50); 
                moveTimeline.at(startTime).move(sprite, lastX, lastY, newX, newY, moveDur, 
                    Easing.ELASTIC_IN_OUT);
                startTime += moveDur;
            }
            moveTimeline.loopForever();
            addTimeline(moveTimeline);
        
            // Create 3 mirror objects
            Sprite mirror = makeParticle();
            mirror.x.bindTo(mirrorX(sprite));
            mirror.y.bindTo(mirrorY(sprite));
            particleLayer.add(mirror);
            
            mirror = makeParticle();
            mirror.x.bindTo(mirrorX(sprite));
            mirror.y.bindTo(sprite.y);
            particleLayer.add(mirror);
            
            mirror = makeParticle();
            mirror.x.bindTo(sprite.x);
            mirror.y.bindTo(mirrorY(sprite));
            particleLayer.add(mirror);
        }
        
        // Transition to the next blend mode
        int changeTime = 30000;
        Timeline timeline = new Timeline();
        timeline.at(500).animate(particleLayer.alpha, 0, 255, 2000);
        timeline.at(changeTime-2500).animate(particleLayer.alpha, 255, 0, 2000);
        timeline.add(new TimelineEvent(changeTime) {
            public void run() {
                reload();
            }
        });
        addTimeline(timeline);
    }
    
    Sprite makeParticle() {
        CoreImage image = CoreImage.load("particle.png");
        int color = hue(rand(0, 255));
        ImageSprite sprite = new ImageSprite(image.tint(color), 0, 0);
        sprite.setAnchor(Sprite.CENTER);
        return sprite;
    }
    
    BindFunction mirrorX(final Sprite source) {
        return new BindFunction() {
            public Number f() {
                return (Stage.getWidth() - source.x.get());
            }
        };
    }
    
    BindFunction mirrorY(final Sprite source) {
        return new BindFunction() {
            public Number f() {
                return (Stage.getHeight() - source.y.get());
            }
        };
    }
   
}