pepe pepe - 4 months ago 22
Javascript Question

How to draw on an image using Javascript and snap tracing to a grid?

I'd like to implement a front end tool that allows the user to:


  • trace a freehand area on an image

  • the tracing must be closed

  • the tracing should be snapped to a grid (e.g. 32 x 32 px)



I've found this JS library (http://ianli.com/sketchpad/) that seems very nice and could fit the bill.

I presume there's no ready made library specifically for my need, so please feel free to give any suggestions on how this could be tackled.

The final objective is to output a JSON (or other list of coordinates) that maps to an ara on the image, but with 32 x 32 px "resolution".

The end result of the tracing should look something like this:

enter image description here

Answer

I hope you will find this script useful. Try it on JSFiddle. Press Enter key for options.

window.addEventListener('load', function(){
    var O = {
        doc: document,
        body: document.body,
        wd: window,
        ce: function(a, b){
            return a.appendChild(a = O.doc.createElement(b)), a;
        },
        cc: function(){
            var c, g, gn;
            c = O.ce(O.body, 'canvas');
            c.width = O.w;
            c.height = O.h;
            g = c.getContext('2d');
            gn = {
                sfc: function(a){ // Set fill color
                    g.fillStyle = a;
                },
                slc: function(a){ // Set line color
                    g.strokeStyle = a;
                },
                slw: function(a){ // Set line width
                    g.lineWidth = a;
                },
                bp: function(){ // Begin path
                    g.beginPath();
                },
                mt: function(a, b){ // Move to
                    g.moveTo(gn.tp(0, a), gn.tp(1, b));
                },
                t: { // Transform
                    x: 0,
                    y: 0,
                    sx: 1,
                    sy: 1
                },
                tp: function(a, b){ // Tranform point
                    return O.mfl(a ? b * gn.t.sy + gn.t.y : b * gn.t.sx + gn.t.x) + .5;
                },
                tps: function(a, b){ // Tranform distance between points
                    return O.mfl(a ? b * gn.t.sy : b * gn.t.sx);
                },
                lt: function(a, b){ // Line to
                    g.lineTo(gn.tp(0, a), gn.tp(1, b));
                },
                cp: function(){ // Close path
                    g.closePath();
                },
                fill: function(){
                    g.fill();
                },
                line: function(){
                    g.stroke();
                },
                arc: function(a, b, c, d, e, f){
                    g.arc(gn.tp(0, a), gn.tp(1, b), c, d, e, f);
                },
                rc: function(a, b, c, d){ // Rectangle
                    g.rect(gn.tp(0, a) - .5, gn.tp(1, b) - .5, gn.tps(0, c), gn.tps(1, d));
                },
                frc: function(a, b, c, d){ // Fill rectangle
                    g.fillRect(gn.tp(0, a) - .5, gn.tp(1, b) - .5, gn.tps(0, c), gn.tps(1, d));
                },
                st: function(a, b, c, d){
                    gn.t.x = a;
                    gn.t.y = b;
                    gn.t.sx = c;
                    gn.t.sy = d;
                    gn.slw(1 / O.min(c, d));
                },
                cls: function(){
                    gn.sfc(O.cols.white);
                    gn.slc(O.cols.black);
                    gn.frc(0, 0, O.w, O.h);
                }
            };
            gn.cls();
            return gn;
        },
        init: function(){
            O.body.style.margin = '0px';
            O.body.style.padding = '0px';
            O.cols = {
                red: O.rgb(1, 0, 0),
                green: O.rgb(0, 1, 0),
                blue: O.rgb(0, 0, 1),
                white: O.rgb(1, 1, 1),
                black: O.rgb(0, 0, 0),
                yellow: O.rgb(1, 1, 0),
                lightBlue: O.rgb(0, 1, 1),
                purple: O.rgb(1, 0, 1),
                gray: O.rgb(.5, .5, .5)
            };
            O.w = O.wd.innerWidth;
            O.h = O.wd.innerHeight;
            O.wh = O.w / 2;
            O.hh = O.h / 2;
            O.pih = O.pi / 2;
            O.pi2 = O.pi * 2;
            main(O);
        },
        rf: function(b, c){
        var a = new XMLHttpRequest();
        a.open('get', b, true);
        a.onreadystatechange = function(){
          a.readyState != 4 || a.status && a.status != 200 || c(a.responseText);
        };
        a.send();
        },
        ca: function(a, b){
            return (Array(a) + '').split(',').map(b);
        },
        pad: function(a, b, c){
            return (a += '').length >= b ? a : Array(b - a.length + 1).join(c) + a;
        },
        rgb: function(a, b, c){
            return '#' + O.rgbf(a) + O.rgbf(b) + O.rgbf(c);
        },
        rgbf: function(a){
            return O.pad(O.mro(a * 255).toString(16), 2, '0');
        },
        cols: null,
        ael: function(a, b){
            O.wd.addEventListener(a, b);
        },
        rel: function(a, b){
            O.wd.removeEventListener(a, b);
        },
        w: null,
        h: null,
        raf: function(a){ // Request animation frame
            O.wd.requestAnimationFrame(a);
        },
        rand: function(a){ // Random integer
            return O.mfl(O.mra * a);
        },
        randf: function(a){ // Random float
            return O.mra() * a;
        },
        rq: function(a, b){ // Require
            O.rf('/' + a + '/1.js', function(a){
                eval(a);
                b();
            });
        },
        wh: null,
        hh: null,
        pi: Math.PI,
        pih: null,
        pi2: null,
        mfl: function(a){ // Math floor
            return Math.floor(a);
        },
        mra: function(){ // Math random
            return Math.random();
        },
        mro: function(a){ // Math round
            return Math.round(a);
        },
        mce: function(a){ // Math ceil
            return Math.ceil(a);
        },
        max: function(a, b){
            return Math.max(a, b);
        },
        min: function(a, b){
            return Math.min(a, b);
        }
    };
    O.init();
});

function main(O){
    var gridX = 32;
    var gridY = 32;

    var coords, pressed, cx, cy, g;

    (function(){
        g = O.cc();
        coords = [];
        pressed = 0;
        O.ael('keydown', onKeyDown);
        O.ael('mousedown', onMouseDown);
        O.ael('mousemove', onMouseMove);
        O.ael('mouseup', onMouseUp);
    })();

    function onKeyDown(a){
        switch(a.keyCode){
            case 13:
                switch(+prompt('0 - Clear\n1 - Get JSON\n2 - Paste JSON')){
                    case 0:
                        coords.length = 0;
                        g.cls();
                        break;
                    case 1:
                        prompt('', JSON.stringify(coords));
                        break;
                    case 2:
                        coords = JSON.parse(prompt('Pase JSON:'));
                        drawCoords();
                        break;
                }
                break;
        }
    }

    function onMouseDown(a){
        if(!a.button){
            cx = snapToGrid(0, a.clientX);
            cy = snapToGrid(1, a.clientY);
            pressed = 1;
        }
    }

    function onMouseMove(a){
        var x, y;
        if(pressed){
            x = snapToGrid(0, a.clientX);
            y = snapToGrid(1, a.clientY);
            if(x != cx){
                g.bp();
                g.mt(cx, cy);
                g.lt(x, cy);
                g.line();
                cx = x;
                coords.push(cx, cy);
            }
            if(y != cy){
                g.bp();
                g.mt(cx, cy);
                g.lt(cx, y);
                g.line();
                cy = y;
                coords.push(cx, cy);
            }
        }
    }

    function onMouseUp(a){
        if(!a.button){
            pressed = 0;
            coords.push(null);
        }
    }

    function snapToGrid(a, b){
        return a ? O.mro(b / gridY) * gridY : O.mro(b / gridX) * gridX;
    }

    function drawCoords(){
        var a = coords.slice(), q;
        g.cls();
        g.bp();
        g.mt(a.shift(), a.shift());
        while(a.length){
            if((q = a.shift()) == null){
                g.mt(a.shift(), a.shift());
            }else{
                g.lt(q, a.shift());
            }
        }
        g.line();
    }
}
Comments