在画布上逐个像素地手绘圆圈 [英] Draw a circle in canvas manually pixel by pixel

查看:89
本文介绍了在画布上逐个像素地手绘圆圈的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to do some complicated effect, and to do it i have to break it down into its components, upon which i can build on and hopefully they will come together.

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

Now to make a circle in canvas is easy. But i want to make it myself. So I want to write a function that would be given a point that's center, radius, and then it will draw a circle with 1 px stroke width.

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

How would i go about it? If i look at from math perspective, what comes to mind is use circle distance formula and increment by small values, like .3 degrees, and make a dot at circumference. But if my circle is too small, like 2 px radius. Then it will waste lot of time drawing that won't matter, and if it's big enough you will see spaces between dots.

所以我希望我的圆绘图功能能够绘制一个

so i want my circle drawing function to draw a


  • dot如果半径是1px。

  • 如果半径为2px,则在中心周围有4个点。

  • ..依此类推。

  • 如果这会使我的圆圈看起来很僵硬,我也想要抗锯齿:D

  • dot if radius is 1px.
  • 4 dots around the center if radius is 2px.
  • ..and so on.
  • also if this gonna make my circle look rigid, i want there to be antialiasing too :D

我想我曾经知道如何填写它的轮廓将不会是一个问题。我所要做的就是缩小半径并保持绘图直到半径为1px。

I suppose once i know how to make outline filling it in won't be a problem..all i'd've to do is reduce the radius and keep drawing until radius is 1px.

推荐答案

好吧,我把我早期的代码重新分解为一个名为canvasLens的jQuery插件。它接受一系列选项来控制图像src,镜头大小和边框颜色等内容。您甚至可以选择两种不同的镜头效果,fisheye或scaledSquare。

OK, I've re-factored my earlier code as a jQuery plugin named "canvasLens". It accepts a bunch of options to control things like image src, lens size and border color. You can even choose between two different lens effects, "fisheye" or "scaledSquare".

我试图通过标题块使其尽可能不言自明还有很多其他意见。

I've tried to make it as self-explanatory as possible with a header block and plenty of other comments.

/*
 *  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);

这里有 演示

And here's a Demo.

修改

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

Here's an attempt at explaining the fisheye (barrel distortion) calculations ...


  • 以空白开头镜头 wxh 像素。

  • 代码循环遍历所有像素(目标像素)。

  • 对于每个目标像素,从背景图像中选择一个像素(源像素)。

  • 源像素始终从与目标相同(或接近)相同径向光线的像素中选择,但在镜头中心的较小径向距离(使用桶形失真公式)。

  • 这是通过计算源像素的极坐标(nr,theta)来实现的,然后应用程序是一个标准的数学公式用于将极性转换回矩形坐标 nxn = nr * Math.cos(theta) nxn = nr * Math.sin(theta)。到目前为止,所有计算都是在标准化的-1 ... 0 ... 1空间中进行的。

  • 块中的其余代码重新定义(重新定位),(我必须大量调整的位)实际上通过索引到一维源来实现源到目标像素映射和目标数据。

  • Starting with a blank lens of w x h pixels.
  • The code loops through all pixels (target pixels).
  • For each target pixel, chooses a pixel (source pixel) from the background image.
  • The source pixel is always selected from those on (or close to) the same radial ray as the target, but at a smaller radial distance (using a formula for barrel distortion) from the lens's center.
  • This is mechanised by calculation of the polar coordinates (nr, theta) of the source pixel, then the application a standard math formula for converting polar back to rectangular coordinates nxn = nr * Math.cos(theta) and nxn = nr * Math.sin(theta). Up to this point, all calculations have been made in normalised -1...0...1 space.
  • The rest of the code in the block denormaises (rescales), and (the bit I had to heavily adapt) actually implements the source to target pixel mapping by indexing into the 1-dimensional source and target data.

这篇关于在画布上逐个像素地手绘圆圈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆