AS3像素级绘图?

4

当我在使用时:

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

我得到了我想要的黑线,但是它们旁边还有灰色像素漂浮。是否有一种方法可以关闭平滑/抗锯齿功能,以消除这些模糊像素?


1
首先,你发布的代码有问题:var shape:Shape = new new Shape(); 你不能使用 "new new"。其次,你正在绘制矢量图像,线条应该清晰而锐利。我在 CS4 Flash 中运行了你的示例代码,看起来很好。所以,请发布实际导致问题的代码。 - The_asMan
3个回答

3
是的,即使开启了抗锯齿,也有可能绘制出完美无误的像素形状。像素提示是必须的。另一半方程式则是使用整数像素坐标发出绘图命令。
例如,您可以使用以下代码绘制一个像素完美对称的带有4像素半径曲线的圆角矩形。请特别留意代码执行的内容,特别是偏移量与边框厚度之间的关系。
首先,请记住,当您绘制填充形状时,光栅化会发生在轮廓的右/下边缘之前,但不包括它们。因此,要绘制一个4x4像素的填充正方形,您只需要调用drawRect(0,0,4,4)。这涵盖了像素0、1、2、3、4(5个像素),但由于它不会光栅化右侧和下侧的边缘,所以它最终是4个像素。另一方面,如果您仅绘制轮廓(而不是填充它),则需要调用drawRect(0,0,3,3),这将涵盖像素0、1、2、3,即4个像素。因此,为了获得像素完美大小,实际上需要针对填充和轮廓使用略微不同的尺寸。
假设您想绘制一个按钮,它的宽度为50像素,高度为20像素,带有4像素半径的圆角,并且边缘厚度为2像素。为了确保恰好覆盖50x20像素,且2像素厚的外边缘紧贴边缘像素而不会溢出,您必须完全按照以下方式发出绘图命令。您必须使用像素提示,并在所有侧面都向内偏移1像素(而不是半个像素,而是确切地1像素)。这将使线的中心正好位于像素0和1之间,从而使其通过像素0和1绘制2像素宽的线条。
下面是一个示例方法,您可以使用它:
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 );

那将产生一个完美对称的2像素宽的填充圆角矩形,恰好覆盖了一个50x20像素区域。
非常重要的是注意,使用零的borderThickness有点没有意义,并且会导致一个超过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 );
    }
}

我接受了这个答案,但实际上并没有亲自测试过它,但我依稀记得说服自己,类似这样的东西确实是解决方案,并选择了一条不同的道路而不是实现这个。我很高兴这在这里。 - Weavermount

0

尝试打开pixelHinting:

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

更多关于pixelHinting的信息在这里


像素提示并不会关闭抗锯齿。它仍然是抗锯齿的,但是在可能的情况下,线条会被捕捉到整个像素上,因此有时会减少模糊像素的数量。如果您运行您的代码,然后再运行 OP 的代码,视觉上没有任何区别。 - Peter Hall

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接