I'm building a drawing application in as3 and am having problems with feathered or blurred edges on a brush. I'm using bitmapData.draw() to draw the brush but I'm seeing darker colored areas around the edges when drawing slowly.
I've tried numerous options including setting all display objects involve开发者_如何学编程d to cacheAsBitmap=true, using copyPixels instead of draw, blur filter vs. gradient fill ... all to no avail.
The following class illustrates my problem. I have included a solid() method that works correctly but without feathered edges, a gradient() method and a filter() method which both show the same issue, and also onMove2 uses copyPixels() and again has the same issue.
Is there anything I can do to fix this?! I really don't want to get into using pixelbender shaders for something so simple...
package test {
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
public class Sucks extends Sprite {
private var brush:Sprite;
private var brushMap:BitmapData;
private var bmd:BitmapData;
private var radius:Number = 50;
private var color:uint = 0x990000;
public function Sucks() {
brush = new Sprite();
brushMap = new BitmapData(radius*2, radius*2, true, 0x00ffffff);
bmd = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x0);
var bmp:Bitmap = new Bitmap(bmd, PixelSnapping.ALWAYS, true);
addChild(bmp);
//solid();
gradient();
//filter();
brushMap.draw(brush, new Matrix(1, 0, 0, 1, radius, radius));
stage.addEventListener(Event.ENTER_FRAME, onMove);
}
private function solid():void {
brush.graphics.beginFill(color, 1);
brush.graphics.drawCircle(0, 0, radius);
brush.graphics.endFill();
}
private function gradient():void {
var m:Matrix = new Matrix();
m.createGradientBox(radius*2, radius*2, 0, -radius, -radius);
brush.graphics.beginGradientFill(GradientType.RADIAL, [color, color], [1, 0], [0, 250], m);
brush.graphics.drawCircle(0, 0, radius);
brush.graphics.endFill();
}
private function filter():void {
solid();
brush.filters = [new BlurFilter(8, 8, 3)];
}
private function onMove(e:Event):void {
var mp:Matrix = new Matrix();
mp.tx = mouseX;
mp.ty = mouseY;
bmd.draw(brush, mp);
//bmd.applyFilter(bmd, new Rectangle(0, 0, stage.stageWidth, stage.stageHeight), new Point(), new BlurFilter(2, 2, 3));
}
private function onMove2(e:Event):void {
bmd.copyPixels(brushMap, new Rectangle(0, 0, radius*2, radius*2), new Point(mouseX-radius, mouseY-radius), null, null, true);
}
}
}
Your problem is caused by the fact that Flash uses pre-multiplied alpha. Usually this is not a big issue, but it becomes very apparent if you have pixel values with very low alpha values which unfortunately are very common when you use a blurred soft-feathered brush.
The pre-multiplication causes an information loss in the pixels - and when you have low alpha values the color values are effectively getting rounded down which when you are drawing slowly in your app and the same pixels are getting drawn over each other again and again will cause the area to get darker and darker.
For some more details on the pre-multiplication issue check an old post of mine: http://www.quasimondo.com/archives/000665.php
There is nothing you can do about this except if you handle the alpha channel and the rgb channel separately. But then you will also have to do all the compositing yourself which is not a trivial task and might slow down your app too much.
I'm surprised that the BlurFilter
didn't work. I've had luck with that in the past. Might be worth playing around with the Flex Filter Explorer to see if the settings need tweaking.
Another thing you could try is drawing several concentric circles with transparency and decreasing radius to simulate having blurry edges.
If the brush color is a solid one you may try with Glow filter as well, with precise settings it could be achieved
I believe this is more an issue with enterframe than it is with your logic. I noticed that if you change the enter_frame handler to a mouse_move handler, it worked much better. The problem with using enter_frame is that you're drawing the same item over and over -- which naturally has it's own artifacts -- so after 600 draws or so, it'll start showing those artifacts. I think it'd be better to use mouse_move.
If you have to use enter_frame, I would suggest listing to both enter_frame and mouse_move, and reducing the opacity on the draw for enter_frames considerably.
This worked nicely for me:
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMove);
I don't know if it have something to do with it, but you are setting transparency on, but your BitmapData isn't transparent.
brushMap = new BitmapData(radius*2, radius*2, true, 0x00ffffff);
To
brushMap = new BitmapData(radius*2, radius*2, true, 0);
Also, try changing the PixelSnapping.ALWAYS value to PixelSnapping.NEVER, but I guess you already tried that.
Change brush's cacheAsBitmap
the property to true.
brush.cacheAsBitmap = true;
the sprite will be kept in memory as a bitmap rather than vector, so when drawn it will map the pixels more precisely.
精彩评论