4

当我使用时:

var shape:Shape = new new Shape();
shape.graphics.lineStyle(2,0);
shape.graphics.lineTo(10,10);
addChild(shape);

我得到了我想要的黑线,但我也得到了在它们旁边漂浮的灰色像素。有没有办法关闭任何平滑/抗锯齿添加模糊像素?

4

3 回答 3

3

是的,即使启用了抗锯齿,也可以绘制像素完美的形状。像素提示是必须的。等式的另一半是实际发出具有全像素坐标的绘图命令。

例如,您可以使用以下代码绘制一个具有 4px 半径曲线的像素完全对称的圆角矩形。仔细注意代码在做什么,特别是偏移量与边框厚度的关系。

首先,请记住,当您绘制填充形状时,会发生光栅化,但不包括轮廓的右/下边缘。因此,要绘制一个 4x4 像素填充的正方形,您只需调用 drawRect(0,0,4,4)。这涵盖了像素 0、1、2、3、4(5 像素),但由于它没有栅格化右边缘和下边缘,因此最终为 4 像素。另一方面,如果只绘制轮廓(不填充),则需要调用 drawRect(0,0,3,3),它将覆盖像素 0,1,2,3,即 4像素。所以你实际上需要稍微不同的尺寸来填充和轮廓以获得像素完美的尺寸。

假设您要绘制一个 50px 宽、20px 高的按钮,其圆角边缘的半径为 4px,厚度为 2px。为了确保准确地覆盖 50x20 像素,并且 2px 粗线的外边缘与边缘像素相抵而不会溢出,您必须像这样发出绘图命令。您必须使用像素提示,并且必须在所有边上将矩形向内偏移 1px(不是半个像素,而是正好 1 个像素)。这会将线的中心正好放在像素 0 和 1 之间,这样它最终会通过像素 0 和 1 绘制 2px 宽的线。

这是您可以使用的示例方法:

public class GraphicsUtils
{
    public static function drawFilledRoundRect( g:Graphics, x:Number, y:Number, width:Number, height:Number, ellipseWidth:Number = 0, ellipseHeight:Number = 0, fillcolor:Number = 0xFFFFFF, fillalpha:Number = 1, thickness:Number = 0, color:Number = 0, alpha:Number = 1, pixelHinting:Boolean = false, scaleMode:String = "normal", caps:String = null, joints:String = null, miterLimit:Number = 3 )
    {
        if (!isNaN( fillcolor))
        {
            g.beginFill( fillcolor, fillalpha );
            g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
            g.endFill();
        }
        if (!isNaN(color))
        {
            g.lineStyle( thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit );
            g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
        }
    }
}

你想这样称呼它:

var x:Number = 0;
var y:Number = 0;
var width:Number = 50;
var height:Number = 20;
var pixelHinting:Boolean = true;
var cornerRadius:Number = 4;
var fillColor:Number = 0xffffff; //white
var fillAlpha:Number = 1;
var borderColor:Number = 0x000000; //black
var borderAlpha:Number = 1;
var borderThickness:Number = 2;
GraphicsUtils.drawFilledRoundRect( graphics, x + (borderThickness / 2), y + (borderThickness / 2), width - borderThickness, height - borderThickness, cornerRadius * 2, cornerRadius * 2, fillColor, fillAlpha, borderThickness, borderColor, borderAlpha, pixelHinting );

这将产生一个像素完全对称的 2px 厚的填充圆角矩形,该矩形恰好覆盖 50x20 像素区域。

非常重要的是要注意,使用零的边界厚度有点无意义,并且会导致矩形过大 1 个像素,因为它仍在绘制一个 1 像素宽的线,但它没有减去宽度(因为它为零),因此你会得到一个超大的矩形。

总之,使用上面的算法,将一半的边框厚度添加到 x 和 y 坐标,并从宽度和高度中减去整个边框厚度,并且始终使用最小厚度 1。这将始终产生一个矩形具有占据且不溢出与给定宽度和高度等效的像素区域的边框。

如果您想查看它的实际效果,只需将以下代码块复制并粘贴到主时间线上的新 AS3 Flash 项目中并按原样运行它,因为它包含运行所需的所有内容:

import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.events.Event;
import flash.utils.getTimer;
import flash.display.Sprite;
import flash.display.Graphics;

stage.scaleMode = flash.display.StageScaleMode.NO_SCALE;
stage.align = flash.display.StageAlign.TOP_LEFT;
stage.frameRate = 60;
draw();

function draw():void
{
    var x:Number = 10;
    var y:Number = 10;
    var width:Number = 50;
    var height:Number = 20;
    var pixelHinting:Boolean = true;
    var cornerRadius:Number = 4;
    var fillColor:Number = 0xffffff; //white
    var fillAlpha:Number = 1;
    var borderColor:Number = 0x000000; //black
    var borderAlpha:Number = 1;
    var borderThickness:Number = 2;

    var base:Number = 1.6;
    var squares:int = 10;
    var rows = 4;
    var thicknessSteps:Number = 16;
    var thicknessFactor:Number = 4;

    var offset:Number;
    var maxBlockSize:Number = int(Math.pow( base, squares ));
    var globalOffset:Number = maxBlockSize; //leave room on left for animation
    var totalSize:Number = powerFactorial( base, squares );
    var colors:Array = new Array();
    for (i = 1; i <= squares; i++)
        colors.push( Math.random() * Math.pow( 2, 24 ) );

    for (var j:int = 0; j < thicknessSteps; j++)
    {
        var cycle:Number = int(j / rows);
        var subCycle:Number = j % rows;
        offset = cycle * totalSize;
        y = subCycle * maxBlockSize;
        borderThickness = (j + 1) * thicknessFactor;
        for (var i:int = 0; i < squares; i++)
        {
            cornerRadius = Math.max( 8, borderThickness );
            borderColor = colors[i];
            x = globalOffset + offset + powerFactorial( base, i ); //int(Math.pow( base, i - 1 ));
            width = int(Math.pow( base, i + 1 ));
            height = width;
            if (borderThickness * 2 > width) //don't draw if border is larger than area
                continue;
            drawFilledRoundRect( graphics, x + (borderThickness / 2), y + (borderThickness / 2), width - borderThickness, height - borderThickness, cornerRadius * 2, cornerRadius * 2, fillColor, fillAlpha, borderThickness, borderColor, borderAlpha, pixelHinting );
        }
    }

    var start:uint = flash.utils.getTimer();
    var duration:uint = 5000;
    var sprite:Sprite = new Sprite();
    addChild( sprite );
    var gs:Graphics = sprite.graphics;
    addEventListener( flash.events.Event.ENTER_FRAME,
    function ( e:Event ):void
    {
        var t:uint = (getTimer() - start) % duration;
        if (t > (duration / 2))
            borderThickness = ((duration-t) / (duration/2))  * thicknessSteps * thicknessFactor;
        else
            borderThickness = (t / (duration/2)) * thicknessSteps * thicknessFactor;
        //borderThickness = int(borderThickness);
        cornerRadius = Math.max( 8, borderThickness );
        borderColor = colors[squares - 1];
        x = 0;
        y = 0;
        width = int(Math.pow( base, squares ));
        height = width;
        if (borderThickness * 2 > width) //don't draw if border is larger than area
            return;
        gs.clear();
        drawFilledRoundRect( gs, x + (borderThickness / 2), y + (borderThickness / 2), width - borderThickness, height - borderThickness, cornerRadius * 2, cornerRadius * 2, fillColor, fillAlpha, borderThickness, borderColor, borderAlpha, pixelHinting );
    }, false, 0, true );
}

function powerFactorial( base:Number, i:int ):Number
{
    var result:Number = 0;
    for (var c:int = 0; c < i; c++)
    {
        result += int(Math.pow( base, c + 1 ));
    }
    return result;
}

function drawFilledRoundRect( g:Graphics, x:Number, y:Number, width:Number, height:Number, ellipseWidth:Number = 0, ellipseHeight:Number = 0, fillcolor:Number = 0xFFFFFF, fillalpha:Number = 1, thickness:Number = 0, color:Number = 0, alpha:Number = 1, pixelHinting:Boolean = false, scaleMode:String = "normal", caps:String = null, joints:String = null, miterLimit:Number = 3 )
{
    if (!isNaN( fillcolor))
    {
        g.beginFill( fillcolor, fillalpha );
        g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
        g.endFill();
    }
    if (!isNaN(color))
    {
        g.lineStyle( thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit );
        g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
    }
}
于 2014-06-12T19:35:28.783 回答
0

尝试开启pixelHinting:

shape.graphics.lineStyle(2, 0, 1, true);

更多关于 pixelHinting的信息

于 2012-07-26T23:29:59.037 回答
0

您不能完全关闭抗锯齿。如果您想要一条清晰的像素化线,那么不幸的是,您必须使用 Bitmap 和 setPixel() 逐个像素地绘制

于 2012-07-26T23:47:48.160 回答