git @ Cat's Eye Technologies Colourring / master src / colourring.js
master

Tree @master (Download .tar.gz)

colourring.js @masterraw · history · blame

// SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense

var QUADRANT = Math.PI / 2;
var TWO_PI = Math.PI * 2;

Colourring = function() {
    this.init = function(cfg) {
        this.canvas = cfg.canvas;
        this.ctx = this.canvas.getContext('2d');
        this.mode = "Complementary Rings";
        this.scale = 0.0;
        this.numSegments = 100;
        this.numLevels = 3;
        this.randomnessOn = [0.0, 1.0];
        this.segmentWidth = 0.01;
        this.initModes();
        this.reset();
        return this;
    };

    this.initModes = function() {
        this.modes = {};
        this.modeNames = [];

        this.addMode("Complementary Rings", function(cx, cy, rz, step) {
            for (var i = 0.0; i < 1.0; i += step) {
                var baseHue = 360 * i;
                var levelDepth = (rz / this.numLevels) * 3;
                for (var d = this.numLevels - 1; d >= 0; d--) {
                    var hue;
                    if (d % 2 === 0) {
                        hue = Math.random() > this.randomnessOn[0] ? baseHue : baseHue + 180;
                    } else {
                        hue = Math.random() > this.randomnessOn[1] ? baseHue : baseHue + 180;
                    }
                    this.setFillHSL(hue, 1.0, -1);
                    this.fillSegment(this.ctx,
                        cx, cy,
                        rz + levelDepth * (d + 1) + 1, rz + levelDepth * d,
                        i * TWO_PI, (i + step + 0.001) * TWO_PI
                    );
                }
            }
        });

        this.addMode("Spurious Spokes", function(cx, cy, rz, step) {
            for (var i = 0; i < this.numSegments; i += 1) {
                var ang = Math.random();
                this.setFillHSL(360 * ang, 1.0, -1);
                this.fillSegment(
                    this.ctx, cx, cy,
                    rz * 4, rz * 1,
                    ang * TWO_PI, (ang + this.segmentWidth) * TWO_PI
                );
            }
        });

        this.addMode("Random ArcAreas", function(cx, cy, rz, step) {
            for (var i = 0; i < this.numSegments; i += 1) {

                // Pick angle.  (I'd call this theta, but it's not in radians.  It is in range 0.0 — 1.0.
                var ang = Math.random();

                // Pick (1/2) width of segment, in same units as angle.
                var spread = this.segmentWidth / 2;

                // Pick radii
                var r1 = Math.random();
                var r2 = Math.random();
                if (r1 < r2) { var t = r1; r1 = r2; r2 = t; }

                this.setFillHSL(360 * ang, 1.0, -1);
                this.fillSegment(
                    this.ctx, cx, cy,
                    r1 * (rz * 4), r2 * (rz * 4),
                    (ang - spread) * TWO_PI, (ang + spread) * TWO_PI
                );
            }
        });

        this.addMode("Complementary Randompatch Rings", function(cx, cy, rz, step) {
            for (var i = 0; i < this.numSegments; i += 1) {

                // Pick angle.  (I'd call this theta, but it's not in radians.  It is in range 0.0 — 1.0.
                var ang = Math.random();

                // Pick width of segment, in same units as angle.
                var width = this.segmentWidth;

                // Pick distance from centre.
                var dist = Math.random() * (rz * 3) + rz;

                // Pick thickness of segment.
                var thickness = rz / 2;

                // Pick hue, saturation, luminance.
                var hue = 360 * ang;
                if (dist > rz * 2 && dist < rz * 3) {
                    hue = 360 * ang + 180;
                }
                var sat = 1.0;

                this.setFillHSL(hue, sat, -1);
                this.fillSegment(
                    this.ctx, cx, cy,
                    dist - thickness / 2, dist + thickness / 2,
                    (ang - width / 2) * TWO_PI, (ang + width / 2) * TWO_PI
                );
            }
        });

        this.addMode("Complementary Spokes", function(cx, cy, rz, step) {
            for (var i = 0.0; i < 1.0; i += step) {
                var hue = (Math.floor(360 * i) % 2 === 0) ? 360 * i : 360 * i + 180;
                this.setFillHSL(hue, 1.0, -1);
                this.fillSegment(this.ctx, cx, cy, rz * 4 + 1, rz * 1, i * TWO_PI, (i + step + 0.001) * TWO_PI);
            }
        });
    };

    this.reset = function() {
        this.ctx.fillStyle = "black";
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

        this.draw();
    };

    this.addMode = function(modeName, modeFun) {
        this.modeNames.push(modeName);
        this.modes[modeName] = modeFun;
    };

    this.getModes = function() {
        return this.modeNames;
    };

    this.setMode = function(mode) {
        console.log("Set mode to " + JSON.stringify(mode) + ", which is " + this.modes[mode])
        this.mode = mode;
        this.reset();
    };

    this.setLuminanceVariability = function(scale) {
        this.scale = scale;
        this.reset();
    };

    this.setNumRadialSegments = function(n) {
        this.numSegments = n;
        this.reset();
    };

    this.setNumLevels = function(n) {
        this.numLevels = n;
        this.reset();
    };

    this.setRandomnessOn = function(index, value) {
        this.randomnessOn[index] = value;
        this.reset();
    };

    this.setSegmentWidth = function(n) {
        this.segmentWidth = n;
        this.reset();
    };

    /* Utilities */

    this.pathSegment = function(ctx, cx, cy, r1, r2, th1, th2) {
        ctx.arc(cx, cy, r1, th1, th2, false);
        ctx.arc(cx, cy, r2, th2, th1, true);
    };

    this.fillSegment = function(ctx, cx, cy, r1, r2, th1, th2) {
        ctx.beginPath();
        this.pathSegment(ctx, cx, cy, r1, r2, th1, th2);
        ctx.fill();
    };

    this.setFillColour = function(r, g, b) {
        this.ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')';
    };

    this.setFillHSL = function(h, s, l) {
        if (l === -1) {
            l = 0.5 + (Math.random() - 0.5) * this.scale;
        }
        h = Math.floor(h) % 360;
        s = '' + (s * 100) + '%';
        l = '' + (l * 100) + '%';
        this.ctx.fillStyle = 'hsl(' + h + ',' + s + ',' + l + ')';
    };

    /* Draw */

    this.draw = function() {
        var cx = this.canvas.width / 2;
        var cy = this.canvas.height / 2;
        var rz = this.canvas.width / 10;
        var step = 1.0 / this.numSegments;

        var fun = this.modes[this.mode];
        fun.call(this, cx, cy, rz, step);
    };
};