首页 文章

在画布上逐个像素地画一个圆圈[关闭]

提问于
浏览
0

我正在尝试做一些复杂的效果,为了做到这一点,我必须把它分解成它的组件,我可以继续构建,希望它们会聚集在一起 .

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

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

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

  • dot如果半径是1px .
    如果半径为2px,则中心周围有4个点 .

  • ..等等 .

  • 如果这会让我的圆圈看起来很僵硬,我也希望它也能抗锯齿:D

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

2 回答

  • 0

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

    我试图通过 Headers 块和大量其他注释使其尽可能不言自明 .

    /*
     *  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 .

    Edit

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

    • w x h 像素的空白镜头开始 .

    • 代码遍历所有像素(目标像素) .

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

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

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

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

  • 0

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

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

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

    360/4 = 90

    因此使用0,90,180,270来获得四个点 .

相关问题