在画布上逐个像素地手绘圆圈

时间:2014-11-07 03:01:00

标签: javascript html5 canvas

我正在努力做一些复杂的效果,为了做到这一点,我必须把它分解成它的组件,我可以在此基础上,希望它们会聚集在一起。

现在在画布上制作圆圈很容易。但我想自己做。所以我想编写一个函数,给出一个点为中心,半径,然后它将绘制一个1 px笔画宽度的圆。

我该怎么办?如果我从数学角度来看,我想到的是使用圆距离公式并按小值增加,如.3度,并在圆周上做一个点。但是如果我的圆圈太小,比如2 px半径。然后它将浪费大量时间绘制不重要的东西,如果它足够大,你会看到点之间的空间。

所以我想要我的圆绘图功能来绘制

  • dot如果半径为1px。
  • 如果半径为2px,则在中心周围有4个点。
  • ..等等。
  • 如果这会使我的圆圈看起来很僵硬,我也希望在那里进行抗锯齿处理:D

我想,一旦我知道如何使轮廓填充它就不会成为一个问题......我要做的就是缩小半径并保持绘制直到半径为1px。 / p>

2 个答案:

答案 0 :(得分:0)

您的中心为x0, y0,半径为r。基本上你需要圆的参数方程:

x = x0 + r * cos(t) y = y0 + r * sin(t)

其中t是径向段和标准化x轴之间的角度,您需要根据需要将其分开。例如,对于您的四点情况,您可以

360/4 = 90

所以使用0,90,180,270得到四个点。

答案 1 :(得分:0)

好的,我已将我之前的代码重新计算为名为" canvasLens"的jQuery插件。它接受一系列选项来控制图像src,镜头大小和边框颜色等内容。您甚至可以在两种不同的镜头效果之间进行选择," fisheye"或" scaledSquare"。

我试图通过标题栏和其他许多评论尽可能地使其变得不言自明。

/*
 *  Copyright (c) 2014 Roamer-1888
 *  "canvasLens"
 *  a jQuery plugin for a lens effect on one or more HTML5 canvases
 *  by Roamer-1888, 2014-11-09
 *  http://stackoverflow.com/users/3478010/roamer-1888
 * 
 *  Written in response to aa question by Muhammad Umer, here
 *      http://stackoverflow.com/questions/26793321/
 * 
 *  Invoke on a canvas element as follows
 *  $("#canvas").lens({
 *    imgSrc: 'path/to/image',
 *    imgCrossOrigin: '' | 'anonymous' | 'use-credentials', //[1]
 *    drawImageCoords: [ //[2]
 *      0, 0, //(sx,st) Source image sub-rectangle Left,Top.
 *      1350, 788, //(sw/sh) Source image sub-rectangle Width,Height.
 *      0, 0, //(dx/dy) Destination Left,Top.
 *      800, 467 //(dw/dh) Destination image sub-rectangle Width,Height.
 *    ], 
 *    effect: 'fisheye' | 'scaledSquare',
 *    scale: 2 //currently affects only 'scaledSquare'
 *    size: 100, //diameter/side-length of the lens in pixels
 *    hideCursor: true | false,
 *    border: [0, 0, 0, 255] //[r,g,b,alpha] (base-10) | 'none'
 *  });
 * 
 * Demo: http://jsfiddle.net/7z6by3o3/1/
 * 
 * Further reading :
 * [1] imgCrossOrigin - 
 *     https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes
 * [2] drawImageCoords -
 *     https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D
 * 
 * Licence: MIT - http://en.wikipedia.org/wiki/MIT_License
 * 
 * Please keep this header block intact, with amendments 
 * to reflect any changes made to the code.
 * 
 */
(function($){
    // *******************************
    // ***** Start: Private vars *****
    // *******************************
    var pluginName = 'canvasLens';
    // *****************************
    // ***** Fin: Private vars *****
    // *****************************

    // **********************************
    // ***** Start: Private Methods *****
    // **********************************
    // Note that in all private methods,
    // `this` is the canvas on which 
    // the plugin is invoked.
    // Most private methods are called 
    // with `methodName.call(this)`.
    // **********************************
    function animate() {
        var data = $(this).data(pluginName);
        if(data) {
            draw.call(this);
            requestAnimationFrame(animate.bind(this));
        }
    }
    function draw() {
        var data = $(this).data(pluginName);
        data.ctx.drawImage(data.m_can, 0, 0);
        if(data.showLens) {
            if(data.settings.effect == 'scaledSquare') {
                scaledSquare.call(this);
            } else {
                fisheye.call(this);
            }
        }
    }
    function putBg() {
        var data = $(this).data(pluginName);
        data.m_ctx.drawImage.apply(data.m_ctx, [data.img].concat(data.settings.drawImageCoords));
    }
    function scaledSquare() {
        var data = $(this).data(pluginName),
            xt = data.settings.scale,
            h = data.settings.size;
        data.ctx.drawImage(data.m_can,
            data.mouse.x - h/xt/2, data.mouse.y - h/xt/2, //sx,st Source image sub-rectangle Left,Top coordinates.
            h/xt, h/xt, //sw/sh Source image sub-rectangle Width,Height.
            data.mouse.x - h/2, data.mouse.y - h/2, //dx/dy Destination Left,Top coordinates.
            h, h //dw/dh The Width,Height to draw the image in the destination canvas.
        );
    }
    function fisheye() {
        var data = $(this).data(pluginName),
            d = data.settings.size,
            mx = data.mouse.x, my = data.mouse.y,
            srcpixels = data.m_ctx.getImageData(mx - d/2, my - d/2, d, d);
        fisheyeTransform.call(this, srcpixels.data, data.xpixels.data, d, d);
        data.ctx.putImageData(data.xpixels, mx - d/2, my - d/2);
    }
    function fisheyeTransform(srcData, xData, w, h) {
        /*
         *    Fish eye effect (barrel distortion)
         *    *** adapted from ***
         *    tejopa, 2012-04-29
         *    http://popscan.blogspot.co.ke/2012/04/fisheye-lens-equation-simple-fisheye.html
         */
        var data = $(this).data(pluginName),
            y, x, ny, nx, ny2, nx2, r, nr, theta, nxn, nyn, x2, y2, pos, srcpos;
        for (var y=0; y<h; y++) { // for each row
            var ny = ((2 * y) / h) - 1; // normalize y coordinate to -1 ... 1
            ny2 = ny * ny; // pre calculate ny*ny
            for (x=0; x<w; x++) { // for each column
                pos = 4 * (y * w + x);
                nx = ((2 * x) / w) - 1; // normalize x coordinate to -1 ... 1
                nx2 = nx * nx; // pre calculate nx*nx
                r = Math.sqrt(nx2 + ny2); // calculate distance from center (0,0)
                if(r > 1) {
                    /* 1-to-1 pixel mapping outside the circle */
                    /* An improvement would be to make this area transparent. ?How? */
                    xData[pos+0] = srcData[pos+0];//red
                    xData[pos+1] = srcData[pos+1];//green
                    xData[pos+2] = srcData[pos+2];//blue
                    xData[pos+3] = srcData[pos+3];//alpha
                }
                else if(data.settings.border && data.settings.border !== 'none' && r > (1-3/w) && r < 1) { // circular border around fisheye
                    xData[pos+0] = data.settings.border[0];//red
                    xData[pos+1] = data.settings.border[1];//green
                    xData[pos+2] = data.settings.border[2];//blue
                    xData[pos+3] = data.settings.border[3];//alpha
                }
                else if (0<=r && r<=1) { // we are inside the circle, let's do a fisheye transform on this pixel
                    nr = Math.sqrt(1 - Math.pow(r,2));
                    nr = (r + (1 - nr)) / 2; // new distance is between 0 ... 1
                    if (nr<=1) { // discard radius greater than 1.0
                        theta = Math.atan2(ny, nx); // calculate the angle for polar coordinates
                        nxn = nr * Math.cos(theta); // calculate new x position with new distance in same angle
                        nyn = nr * Math.sin(theta); // calculate new y position with new distance in same angle
                        x2 = Math.floor(((nxn + 1) * w) / 2); // map from -1 ... 1 to image coordinates
                        y2 = Math.floor(((nyn + 1) * h) / 2); // map from -1 ... 1 to image coordinates
                        srcpos = Math.floor(4 * (y2 * w + x2));
                        if (pos >= 0 && srcpos >= 0 && (pos+3) < xData.length && (srcpos+3) < srcData.length) { // make sure that position stays within arrays
                            /* get new pixel (x2,y2) and put it to target array at (x,y) */
                            xData[pos+0] = srcData[srcpos+0];//red
                            xData[pos+1] = srcData[srcpos+1];//green
                            xData[pos+2] = srcData[srcpos+2];//blue
                            xData[pos+3] = srcData[srcpos+3];//alpha
                        }
                    }
                }
            }
        }
    }
    // ********************************
    // ***** Fin: Private methods *****
    // ********************************

    // *********************************
    // ***** Start: Public Methods *****
    // *********************************
    var methods = {
        'init': function(options) {
            //"this" is a jquery object on which this plugin has been invoked.
            return this.each(function(index) {
                var can = this,
                    $this = $(this);
                var data = $this.data(pluginName);
                if (!data) { // If the plugin hasn't been initialized yet
                    data = {
                        target: $this,
                        showLens: false,
                        mouse: {x:0, y:0}
                    };
                    $this.data(pluginName, data);

                    var settings = {
                        imgSrc: '',
                        imgCrossOrigin: '',
                        drawImageCoords: [
                            0, 0, //sx,st Source image sub-rectangle Left,Top coordinates.
                            500, 500, //sw/sh Source image sub-rectangle Width,Height.
                            0, 0, //dx/dy Destination Left,Top coordinates.
                            500, 500 //(dw/dh) Destination image sub-rectangle Width,Height.
                        ],
                        effect: 'fisheye',
                        scale: 2,
                        size: 100,
                        border: [0, 0, 0, 255], //[r,g,b,alpha] base-10
                        hideCursor: false
                    };
                    if(options) {
                        $.extend(true, settings, options);
                    }
                    data.settings = settings;

                    if(settings.hideCursor) {
                        data.originalCursor = $this.css('cursor');
                        $this.css('cursor', 'none');
                    }

                    $this.on('mouseenter.'+pluginName, function(e) {
                        data.showLens = true;
                    }).on('mousemove.'+pluginName, function(e) {
                        data.mouse.x = e.offsetX;
                        data.mouse.y = e.offsetY;
                    }).on('mouseleave.'+pluginName, function(e) {
                        data.showLens = false;
                    });
                    data.m_can = $("<canvas>").attr({
                        'width': can.width,
                        'height': can.height
                    })[0];
                    data.ctx = can.getContext("2d"); // lens effect
                    data.m_ctx = data.m_can.getContext('2d'); // background image
                    data.xpixels = data.ctx.getImageData(0, 0, settings.size, settings.size);
                    data.img = new Image();
                    data.img.onload = function() {
                        putBg.call(can);
                        animate.call(can);
                    };
                    data.img.crossOrigin = settings.imgCrossOrigin;
                    data.img.src = settings.imgSrc;
                }
            });
        },
        'destroy': function() {
            return this.each(function(index) {
                var $this = $(this),
                    data = $this.data(pluginName);
                $this.off('mouseenter.'+pluginName)
                    .off('mousemove.'+pluginName)
                    .off('mouseleave.'+pluginName);
                if(data && data.originalCursor) {
                    $this.css('cursor', data.originalCursor);
                }
                $this.data(pluginName, null);
            });
        }
    };
    // *******************************
    // ***** Fin: Public Methods *****
    // *******************************

    // *****************************
    // ***** Start: Supervisor *****
    // *****************************
    $.fn[pluginName] = function( method ) {
        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || !method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
        }
    };
    // ***************************
    // ***** Fin: Supervisor *****
    // ***************************
})(jQuery);

这是一个 Demo

修改

这是尝试解释鱼眼(桶形失真)计算......

  • w x h像素的空白镜头开始。
  • 代码遍历所有像素(目标像素)。
  • 对于每个目标像素,从背景图像中选择一个像素(源像素)。
  • 源像素总是从与目标相同的径向光线上(或接近)的像素中选择,但是距离镜头中心的径向距离较小(使用桶形失真公式)。
  • 通过计算源像素的极坐标(nr, theta)来机械化,然后应用标准数学公式,将极坐标转换为直角坐标nxn = nr * Math.cos(theta)nxn = nr * Math.sin(theta)。到目前为止,所有计算都是在标准化的-1 ... 0 ... 1空间中进行的。
  • 块中的其余代码重新定义(重新定位),(我必须大量调整的位)实际上通过索引到1维源和目标数据来实现源到目标像素映射。