【问题标题】:Bresenham concentric circles leaving empty pixelsBresenham 同心圆留下空像素
【发布时间】:2012-08-30 17:00:01
【问题描述】:

我正在使用中点圆算法(也称为 Bresenham 算法)来绘制同心圆。每个圆的半径与下一个圆的半径差始终为 1,因此最终结果应该是一个完整的圆形区域。

但是,有些像素是空白的,如附图所示。

我正在使用 Javascript 在 HTML5 画布上绘画,操作 canvas.getContext("2d").getImageData(...).data 数组。

圆圈为白色和红色,空白像素为黑色。您可能需要放大才能正确理解我的意思。

我正在尝试向算法添加一些代码,以便在绘制相应的弧线时填充这些像素。这些像素中的任何一个似乎都没有理由属于一个弧而不是下一个,所以我不在乎它们是否与具有偶数半径的弧或具有奇数半径的弧一起填充(我希望我说清楚)。

像素似乎遵循某种模式,但我不知道那可能是什么。谁能帮我找到它?

function drawCircles(radius, x, y){
    var f = 1 - radius;
    var ddF_x = 1;
    var ddF_y = -2 * radius;
    var x = 0;
    var y = radius;

    //Colors
    var red = 255;       
    var green = radius%2==0?255:0;       
    var blue = radius%2==0?255:0;        

    paintPixel(x, y + radius, red, green, blue);
    paintPixel(x, y - radius, red, green, blue);
    paintPixel(x + radius, y, red, green, blue);
    paintPixel(x - radius, y, red, green, blue);    

    while(x < y){
        // ddF_x == 2 * x + 1;
        // ddF_y == -2 * y;
        // f == x*x + y*y - radius*radius + 2*x - y + 1;
        if(f >= 0) 
        {
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;    
        paintPixel(x + x, y + y, red, green, blue);
        paintPixel(x - x, y + y, red, green, blue);
        paintPixel(x + x, y - y, red, green, blue);
        paintPixel(x - x, y - y, red, green, blue);
        paintPixel(x + y, y + x, red, green, blue);
        paintPixel(x - y, y + x, red, green, blue);
        paintPixel(x + y, y - x, red, green, blue);
        paintPixel(x - y, y - x, red, green, blue);
    }

}

function paintPixel(x, y, red, green, blue){
    imageData.data[grid[y][x]] = red;
    imageData.data[grid[y][x]+1] = green;
    imageData.data[grid[y][x]+2] = blue;
    imageData.data[grid[y][x]+3] = 255; //Alpha
}

【问题讨论】:

  • 这是一个预期的结果,如果不清楚的话。
  • 由于您是在画布上绘图,您可能会看看是否有助于使笔触超过一个像素宽。
  • 如果你向外画,你可以填充上述弧线。
  • 您是否在以整数坐标为中心绘制圆?如果是这样,当您将中心在 x 和 y 上移动 0.5 时会发生什么?
  • @Ted Hopp 该算法是为离散环境设计的。坐标必须是整数。你想到了什么?

标签: javascript algorithm geometry bresenham


【解决方案1】:

Bresenham's 旨在使用一个像素乘 n 个像素的区域绘制一条线。在 45 度时,它将绘制一个像素,然后绘制另一个 (+1,+1) 像素。这给出了两个像素中心之间的平均厚度 1/√2。一个像素粗线的精确图的粗细为 1。黑点是由于 Bresenham 算法线的粗细与真实粗细之间的差异造成的。

如果您将绘制的像素扩展为包括真实线中心交叉的所有像素,则它不应有任何间隙,因为它的厚度永远不会小于 1。这样做的一种方法是使用 Bresenham 的两次内半径和外半径,并根据两者之间的差异绘制像素。

【讨论】:

  • 再补充一下——如果你要绘制一系列同心圆形状,每个形状正好是一个像素宽,你最终会得到一组菱形,而不是圆形。
  • @Pete Kirkham 实际上,我的目标是找出确定那些空像素位置的模式,以尽可能少地增加复杂性。我的问题有一个解决方法,包括重新遍历算法,将 +1 添加到其中一个坐标,但我想避免这种情况。您的建议不会大大增加必要的计算吗?
【解决方案2】:

如果您将 Bresenham 样式的圆形抽屉设计为计算边界轮廓而不是像素,则可以生成完美嵌套的圆形。从概念上讲,边界轮廓是像素边缘的列表,而不是像素中心。这非常适合 Bresenham 风格的操作:在增加 x 坐标时注册水平边缘,在增加 y 坐标时注册垂直边缘。

对于每个圆,计算两个轮廓:一个用于outer_radius,另一个用于(outer_radius - pen_diameter)。绘制两个轮廓之间的像素:稍微聪明一点,您应该能够在同一个循环中运行两个轮廓生成器,并在线绘制像素。

当然,使用这种边界技术绘制的圆看起来与直接生成的圆不同。但是,IIRC,边界技术可能比直接技术更强大,无论如何......

【讨论】:

    【解决方案3】:

    这看起来肯定是一个别名问题。由于丢失的像素在 45° 角处似乎更密集,我怀疑根本问题与距离计算有关。沿着对角线,一个像素的距离比沿着轴测量的距离大约多 41%。这会导致像素中心远离任一圆。没有看到你的代码,很难说更多。

    一种解决方法可能是简单地用一种圆形颜色填充圆形,然后只绘制另一种圆形颜色。

    【讨论】:

    • Well Bresenham 的算法是一个整数逐像素地绘制圆形曲线的方法。我认为没有涉及距离计算,但我的记忆很模糊。
    • 先填充圆圈是个好主意,虽然它在其他情况下不起作用,我希望颜色从中心向外淡出。
    【解决方案4】:
    <canvas width="500" height="500" style="background:#000;">
    </canvas>​
    
    var canvas = $("canvas")[0];
    var cen = $("canvas").width()/2;
    var len = cen, i = len;
    var ctx = canvas.getContext("2d");
    var red = "#f00";
    var white = "#fff";
    
    
    for (; i > 0; i--){
        ctx.beginPath();
        ctx.arc(cen, cen, i, 0, 2 * Math.PI, false);
        ctx.fillStyle = i % 2 ? red : white;
        ctx.fill();
    }​
    

    http://jsfiddle.net/RmHC3/

    没有黑点。 :)

    【讨论】:

    • 当然,输出可能与您的算法所期望的不同。
    • 问题是圆圈是作为动画的一部分绘制的(我之前应该说过......)。如果我使用上下文绘图工具,则确实可以正确渲染圆圈,但是,由于必须在每一帧上处理所有圆圈,因此性能会下降很多。那是因为图像数据是在每一帧上绘制的。
    • @broncoAbierto 这样的事情? jsfiddle.net/RmHC3/2我只是简单的交替着每一行的颜色,这是一个比较简单的过程。动画更复杂吗?
    • 圆圈向外增长。脚本的性质要求在每一帧都绘制整个图像,因此我必须利用这一点并尝试以这种方式绘制所有内容。使用 context 方法会慢得多。
    • @broncoAbierto 或许您可以设置一个小提琴来展示您的尝试。这样我们也许可以对其进行优化。
    【解决方案5】:

    嗯,我在洪都拉斯科技大学 (UTH) 教汇编语言,出于某种原因,我试图画线和圆,但我试图找到一种不同于 Bresenham 的算法,我找到了这些(用于线和当你用同心圆填充圆形或用斜线填充矩形时,它可以解决原始 Bresenham 中的孔。

    注意 1:此算法与 Supercover 算法不同,但您可以将其用于同一目的。

    注意2:我只使用整数算术和逻辑函数来完成工作。

    这是一个屏幕截图(在 Windows XP VirtualBox 中使用 Emu8086 并将程序编译为 .exe 文件)。

    这段代码应该优化,但由于是出于教学目的,所以我只是以学生容易理解的方式编程。

                data segment
                    ; Las variables que comienzan con _ son variables usadas en los procedimientos
                    _migaja         dw ?
                    _x              dw ?
                    _y              dw ?
                    _x2             dw ?
                    _y2             dw ?
                    _color          dw ?
                    _deltaX         dw ?
                    _deltaY         dw ?
                    _deltaX_abs     dw ?
                    _deltaY_abs     dw ?
                    _error          dw ?
                    _error_x        dw ?
                    _error_y        dw ?
                    _error_xy       dw ?
                    _error_x_abs    dw ?
                    _error_y_abs    dw ?
                    _error_xy_abs   dw ?
                    _cambio_y       dw ?
                    _color_inicial  db ?
                    _color_relleno  db ?
                    _xc             dw ?
                    _yc             dw ?
                    _radio          dw ?
                    ; Variables usadas en la parte principal
                    i               dw ?   
                    xcentro        dw 160
                    ycentro        dw 100
                    radio          dw 1
                    color          dw 0
                ends
    
                stack segment
                    dw   32767  dup(0)
                ends
    
                code segment
                start:
                    mov ax, data
                    mov ds, ax
                    mov es, ax
                    call videoMode
    
                    mov color, 10
    
                    pre_ciclo:
                        mov radio, 0
    
    
                    ciclo:
                        cmp radio, 100
                        jge salir_ciclo
                        push xcentro
                        push ycentro
                        push radio
                        push color
                        call circulo
                        inc radio
                        jmp ciclo        
                    salir_ciclo:
    
    
                    mov ah, 1
                    int 21h
    
                    mov ax, 4c00h
                    int 21h    
                ends
    
                    videoMode PROC
                        mov ah, 0
                        mov al, 13h
                        int 10h
                        ret
                    ENDP
    
                    setPixel PROC
                        pop _migaja
                        pop ax
                        pop dx
                        pop cx
                        push _migaja
                        mov ah, 0Ch
                        int 10h        
                        ret
                    ENDP
    
                    circulo PROC
                        ; Este procedimiento dibuja un circulo en (x,y) de radio r
                        ; Hecho por Ing. Yury Euceda© para los alumnos de UTH Agosto 2014
                        pop _migaja
                        pop _color
                        pop _radio
                        pop _yc
                        pop _xc
                        push _migaja
    
                        ; Defino el error inicial
                        pre_ciclo_circle:
                            mov _error, 0
                            mov _x, 0
                            mov ax, _radio
                            mov _y, ax
    
                        ciclo_circulo:
                            push cx
    
    
                            mov cx, _x
                            add cx, _xc
                            mov dx, _yc
                            add dx, _y
                            mov ax, _color
                            mov ah, 0Ch
                            int 10h
                            push dx
                            mov dx, _yc
                            sub dx, _y
                            int 10h
                            push cx
                            mov cx, _xc
                            sub cx, _x
                            int 10h
                            pop cx
                            pop dx
                            mov cx, _xc
                            sub cx, _x            
                            int 10h
    
    
                            pop cx
                            cmp _y, 0
                            je salir_ciclo_circulo
                            ; Calculo error si suben ambos
                            mov ax, _x
                            shl ax, 1
                            inc ax
                            add ax, _error
                            mov _error_x, ax
                            mov _error_x_abs, ax
                            mov _error_xy, ax
                            mov _error_xy_abs, ax
                            mov ax, _y
                            shl ax, 1
                            neg ax
                            inc ax
                            add _error_xy, ax
                            add _error_xy_abs, ax
                            add ax, _error
                            mov _error_y, ax
                            mov _error_y_abs, ax
    
                            ; Calculo los valores absolutos de los errores
                            cmp _error_x_abs, 0
                            jge continuar1_circulo
                            neg _error_x_abs
                            continuar1_circulo:
                            cmp _error_y_abs, 0
                            jge continuar2_circulo
                            neg _error_y_abs
                            continuar2_circulo:
                            cmp _error_xy_abs, 0
                            jge continuar3_circulo
                            neg _error_xy_abs
                            continuar3_circulo:
                            ; Ahora voy a decidir que error absoluto es el menor
                            inc _x            
                            dec _y
                            mov ax, _error_xy
                            mov _error, ax
                            mov ax, _error_xy_abs
    
                            compare_a_b_circulo:
                                cmp ax, _error_y_abs    ; compare a con b
                                jg compare_b_c_circulo          ; si a > b compare b con c
                                cmp ax, _error_xy_abs   ; sino compare a con c
                                jg continuar_loop_circulo       ; si es mayor continue loop
                                inc _y
                                mov ax, _error_x
                                mov _error, ax                
                                jmp continuar_loop_circulo
                            compare_b_c_circulo:
                                mov ax, _error_y_abs
                                cmp ax, _error_xy_abs
                                jg continuar_loop_circulo
                                dec _x
                                mov ax, _error_y
                                mov _error, ax                
                            continuar_loop_circulo:
                        jmp ciclo_circulo
                        salir_ciclo_circulo:
                        ret
                    ENDP
    
    
                    linea PROC
                        ; Este procedimiento dibuja una linea desde (x1,y1) hasta (x2,y2)
                        ; Hecho por Ing. Yury Euceda© para los alumnos de UTH Agosto 2014
                        pop _migaja
                        pop _color
                        pop _y2
                        pop _x2
                        pop _y
                        pop _x
                        push _migaja
    
                        mov ax, _x
                        cmp ax, _x2
                        jle calcular_deltaX
                        xchg ax, _x2
                        mov _x, ax
                        mov ax, _y
                        xchg ax, _y2
                        mov _y, ax 
    
    
    
                        calcular_deltaX:
                            ; Calculo deltaX = X2 - X
                            mov ax, _x2
                            sub ax, _x
                            mov _deltaX, ax
                            mov _deltaX_abs, ax
                            cmp ax, 0
                            jge calcular_deltaY
                            neg _deltaX_abs
    
                        calcular_deltaY:        
                            ; Calculo deltaY = Y2 - Y
                            mov ax, _y2
                            sub ax, _y
                            mov _deltaY, ax
                            mov _deltaY_abs, ax
                            cmp ax, 0
                            jge calcular_cambio
                            neg _deltaY_abs
    
                        calcular_cambio:
                            mov _cambio_y, 1
                            cmp _deltaY, 0
                            jge pre_ciclo_linea
                            neg _cambio_y        
    
                        ; Defino el error inicial
                        pre_ciclo_linea:
                            mov _error, 0
                            mov ax, _deltaY_abs
                            cmp _deltaX_abs, ax
                            jge asignar_deltaX
                            mov cx, _deltaY_abs
                            inc cx
                            jmp ciclo_linea
    
                            asignar_deltaX:
                            mov cx, _deltaX_abs
                            inc cx
    
                        ciclo_linea:
                            push cx
                            push _x
                            push _y
                            push _color
                            call setPixel
                            pop cx
                            ; Calculo error si suben ambos
                            mov ax, _error
                            add ax, _deltaY_abs         ; ax  = error + deltaY
                            mov _error_x, ax
                            mov _error_x_abs, ax
                            sub ax, _deltaX_abs         ; ax = error + deltaY - deltaX
                            mov _error_xy, ax
                            mov _error_xy_abs, ax
                            sub ax, _deltaY_abs         ; ax = error - deltaX
                            mov _error_y, ax
                            mov _error_y_abs, ax
                            ; Calculo los valores absolutos de los errores
                            cmp _error_x_abs, 0
                            jge continuar1
                            neg _error_x_abs
                            continuar1:
                            cmp _error_y_abs, 0
                            jge continuar2
                            neg _error_y_abs
                            continuar2:
                            cmp _error_xy_abs, 0
                            jge continuar3
                            neg _error_xy_abs
                            continuar3:
    
                            comparar_x_con_y:
                                mov ax      , _error_y_abs
                                cmp _error_x_abs, ax
                                jge comparar_y_con_xy
                                mov ax      , _error_xy_abs
                                cmp _error_x_abs, ax
                                jg cambiar_xy
                                inc _x
                                mov ax, _error_x
                                mov _error, ax
                                jmp continuar_loop
    
                            comparar_y_con_xy:
                                mov ax      , _error_xy_abs
                                cmp _error_y_abs, ax
                                jge cambiar_xy
                                mov ax, _cambio_y
                                add _y, ax
                                mov ax, _error_y
                                mov _error, ax
                                jmp continuar_loop
    
                            cambiar_xy:
                                inc _x
                                mov ax, _cambio_y
                                add _y, ax
                                mov ax, _error_xy
                                mov _error, ax
    
    
                            continuar_loop:
    
                        loop ciclo_linea            
                        ret
                    ENDP
    
    
                    rellenar PROC
                        pop _migaja
                        pop ax
                        pop dx
                        pop cx
                        push _migaja
                        mov _color_relleno, aL
                        mov ah, 0Dh
                        int 10h
                        mov _color_inicial, aL 
                        ; Llamo la recursiva
                        push cx
                        push dx
                        call rellenar_recursiva
                        pop dx
                        pop cx
                        ret
                    ENDP
    
                    rellenar_recursiva PROC
                        pop _migaja
                        ; Saco los parametros de la pila
                        pop dx
                        pop cx
                        ; Vuelvo a meterlos a la pila :)
                        push cx
                        push dx
                        push _migaja
    
                        ; valido que el punto este en rango
                        cmp cx, 0
                        jl salir_rellenar
                        cmp cx, 319
                        jg salir_rellenar
                        cmp dx, 0
                        jl salir_rellenar
                        cmp dx, 199
                        jg salir_rellenar
                        ; Extraigo el color del pixel en CX,DX
                        mov ah, 0Dh
                        int 10h
                        ; Lo comparo con el color inicial
                        cmp _color_inicial, aL
                        ; Si no es igual salgase
                        jne salir_rellenar
                        ; Si es igual entonces lo pinto
                        mov aL, _color_relleno
                        mov ah, 0Ch
                        int 10h
    
                        ; Pinto el norte
                        dec dx
                        push cx
                        push dx
                        call rellenar_recursiva
                        pop dx
                        pop cx
                        inc dx
                        ; Pinto el este
                        inc cx
                        push cx
                        push dx
                        call rellenar_recursiva
                        pop dx
                        pop cx
                        dec cx
                        ; Pinto el sur
                        inc dx
                        push cx
                        push dx
                        call rellenar_recursiva
                        pop dx
                        pop cx
                        dec dx
                        ; Pinto el oeste
                        dec cx
                        push cx
                        push dx
                        call rellenar_recursiva
                        pop dx
                        pop cx
                        inc cx
                        salir_rellenar:
                        ret
                    ENDP
    
    
                end start
    

    【讨论】:

    • 我必须承认,在这三年中,我对这项挑战的兴趣有所减弱。尽管如此,我怎么能不赞成这个!格拉西亚斯。
    猜你喜欢
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2019-01-24
    • 2015-06-10
    • 1970-01-01
    • 2015-04-23
    • 1970-01-01
    • 2022-08-03
    相关资源
    最近更新 更多