as3 draw line with LineStyle and then edit with transparent (invisible) pixels - actionscript-3

I have MC with external loaded image (jpg).
Next layer is masked Sprite where I am drawing lines in different colors, opacity ,etc with graphics.lineStyle . Everything works fine, so I can see image and drawings over. Now I want eraser to make, and trying to make some kind of (transparent) line which will "erase" exising line.
If I for example draw (with mouse) red line, and then switch color to yellow and intersect on some point with previous line, it is ok, I see yellow color. Now I don't have idea how to make "transparent" color? So when mouse move over existing drawing, just see again part of background image. If I set one color background everything is OK, but I have image, end eraser leaves some color, and I need to be transparent. Tried blend modes, cached as bmp, everything I found but still no solution.

Seemingly you use Graphics objects that overlay the image. In order to erase something, you should give your sprite a topmost shape that will have BlendMode.ERASE as blend mode, and draw on it with alpha component of color being 0xFF. The sprite should have its blendMode to be BlendMode.LAYER. Here's a test to play with:
public class TestErase extends Sprite
{
public function TestErase() {
var b:Sprite = new Sprite();
var c:Shape = new Shape();
b.blendMode = BlendMode.LAYER;
c.blendMode = flash.display.BlendMode.ERASE;
c.graphics.beginFill(0, 1);
c.graphics.drawRect(50, 50, 50, 50);
c.graphics.endFill();
b.graphics.beginFill(0xff0000, 1);
b.graphics.drawRect(0, 0, 150, 150);
b.graphics.endFill();
b.addChild(c);
addChild(b);
this.graphics.beginFill(0x00c000, 1);
graphics.drawRect(75, 75, 100, 100);
graphics.endFill();
}
}
This makes a 50x50 hole, defined by "c" sprite in "b" sprite, that does not affect "this" sprite.
Okay you asked for an example. Here.
public class Main extends Sprite
{
public var sh:Shape;
public var drawing:Boolean;
public var sp:Sprite;
public var bd:BitmapData;
public var erasing:Boolean;
[Embed(source = '../lib/093.jpg')]
public static const Background:Class;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
var ba:Bitmap = new Background();
ba.scaleX = 0.5;
ba.scaleY = 0.5;
addChild(ba);
bd = new BitmapData(640, 400, true, 0);
var bm:Bitmap = new Bitmap(bd);
sp = new Sprite();
sp.addChild(bm);
sp.blendMode = BlendMode.LAYER;
var tf:TextField = new TextField();
tf.y = 401;
tf.background = true;
tf.backgroundColor = 0x0;
tf.defaultTextFormat = new TextFormat('Arial', 12, 0xe00000, true, false, false);
tf.selectable = false;
tf.text = 'Erase';
addChild(tf);
addChild(sp);
tf.addEventListener(MouseEvent.CLICK, toggleErasing);
sp.addEventListener(MouseEvent.MOUSE_DOWN, startDrawing);
sp.addEventListener(MouseEvent.ROLL_OUT, endDrawing);
sp.addEventListener(MouseEvent.MOUSE_UP, endDrawing);
}
private function toggleErasing(e:MouseEvent):void {
erasing = !erasing;
if (erasing) (e.currentTarget as TextField).text = 'Erasing...'; else (e.currentTarget as TextField).text = 'Erase';
e.stopPropagation();
}
private function startDrawing(e:MouseEvent):void {
if (drawing) return;
sh = new Shape();
sh.graphics.lineStyle(3, Math.floor(Math.random() * 0x100000), 1,true);
if (erasing) sh.blendMode = BlendMode.ERASE;
sh.graphics.moveTo(e.localX, e.localY);
addEventListener(MouseEvent.MOUSE_MOVE, trackMouse);
sp.addChild(sh);
drawing = true;
}
private function endDrawing(e:MouseEvent):void {
if (!drawing) return;
removeEventListener(MouseEvent.MOUSE_MOVE, trackMouse);
if (erasing) bd.draw(sh, null, null, BlendMode.ERASE);
else bd.draw(sh);
sp.removeChild(sh);
sh = null;
drawing = false;
}
private function trackMouse(e:MouseEvent):void {
sh.graphics.lineTo(e.localX, e.localY);
}
}
Tested by me, and should work as is, just change the background :)

Related

AS3: How to make a stamp-like effect [specifically with the coding I am using]?

I am VERY new to Flash and Actionscript. I am having a pretty bad time grasping some elements.
I am currently making a program based on a beginner's tutorial I found online and I have edited it to how I have wanted so far with the exception of making a stamp-like effect.
What happen's so far is simple: it generates a smiley face vector image, if the user clicks on it, it follows the cursor until the user clicks again where it will stay and no longer follow the cursor.
What I am trying to do is modify the click event to somehow not only place down the vector but essentially make a new one that will follow the cursor and allow another one to be places, so on and so forth.
The code I have so far is below:
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
/**
* ...
* #author Lyon's Den Gaming
*/
public class Main extends Sprite
{
public var happyface:Sprite;
public var stampcount:Number = 0;
public var happyfacecursor:Boolean;
public var mouseListener:Object = new Object();
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
happyface = new Sprite();
addChild(happyface);
//This will draw the main circle O
happyface.graphics.beginFill(0xf2f75b);
happyface.graphics.lineStyle(6, 0x000000);
happyface.graphics.drawCircle(800 / 2, 600 / 2, 200);
happyface.graphics.endFill();
//This will draw the eyes ^^
happyface.graphics.lineStyle(8);
happyface.graphics.moveTo(325, 170);
happyface.graphics.lineTo(325, 245);
happyface.graphics.lineStyle(8);
happyface.graphics.moveTo(466, 170);
happyface.graphics.lineTo(466, 245);
//This will draw the mouth :D
happyface.graphics.lineStyle(8);
happyface.graphics.moveTo(275, 340);
happyface.graphics.lineTo(525, 340);
happyface.graphics.lineStyle(8);
happyface.graphics.beginFill(0xdb3d3d);
happyface.graphics.moveTo(275, 340);
happyface.graphics.cubicCurveTo(280, 500, 520, 500, 525, 340);
happyface.graphics.endFill();
//this scales it down and places it where I want
happyface.scaleX = .15;
happyface.scaleY = .15;
happyface.x = 335;
happyface.y = 265;
//This will add mouse input!
stage.addEventListener(MouseEvent.MOUSE_MOVE, MouseMoveHappyFace);
happyface.addEventListener(MouseEvent.CLICK, SelectHappyFace);
}
public function MouseMoveHappyFace(e:MouseEvent):void
{
if (happyfacecursor)
{
happyface.x = mouseX - happyface.width;
happyface.y = mouseY - happyface.height / (1.4);
} else {
happyfacecursor = false;
addChild(happyface);
}
}
public function SelectHappyFace(e:MouseEvent):void
{
if (happyfacecursor)
happyfacecursor = false;
else
happyfacecursor = true;
}
}
}
why don't you use the drag and drop methods? When you click the happyface end or start the drag:
Edited: you won't need your MouseMoveHappyFace function with this:
Edited 2: here you have a new method that creates a happy face at a time. I haven't tried this but it should work.
public var happyface:Sprite;
public var stampcount:int = 0;
public var happyfacecursor:Boolean;
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
// Create the first stamp
addNewHappyFace();
}
public function addNewHappyFace():void {
// This method creates a new happyFace, adds it and asigns it to
// the global happyface variable
var aux = new Sprite();
this.addChild(aux);
stampcount++;
//This will draw the main circle O
aux.graphics.beginFill(0xf2f75b);
aux.graphics.lineStyle(6, 0x000000);
aux.graphics.drawCircle(800 / 2, 600 / 2, 200);
aux.graphics.endFill();
//This will draw the eyes ^^
aux.graphics.lineStyle(8);
aux.graphics.moveTo(325, 170);
aux.graphics.lineTo(325, 245);
aux.graphics.lineStyle(8);
aux.graphics.moveTo(466, 170);
aux.graphics.lineTo(466, 245);
//This will draw the mouth :D
aux.graphics.lineStyle(8);
aux.graphics.moveTo(275, 340);
aux.graphics.lineTo(525, 340);
aux.graphics.lineStyle(8);
aux.graphics.beginFill(0xdb3d3d);
aux.graphics.moveTo(275, 340);
aux.graphics.cubicCurveTo(280, 500, 520, 500, 525, 340);
aux.graphics.endFill();
//this scales it down and places it where I want
aux.scaleX = .15;
aux.scaleY = .15;
aux.x = 335;
aux.y = 265;
//This will add mouse input!
aux.addEventListener(MouseEvent.CLICK, SelectHappyFace);
happyface = aux;
}
public function SelectHappyFace(e:MouseEvent):void{
if (happyfacecursor) }
happyface.startDrag(); // To make the object follow the mouse
happyfacecursor = false;
} else {
happyface.stopDrag(); // To drop the object where you want
// This will prevent that when clicking on an already placed stam i would
// make the last one created to move
happyface.removeEventListener(MouseEvent.CLICK, SelectHappyFace);
happyfacecursor = true;
// Now create and add a new HappyFace clip here
addNewHappyFace()
}
}

As3 hittesting doesn't really work (nested)

I got a tank.
I got a hero (controllable character).
The tank has a nested movieclip which has a very thin surface area.
Yet, the thing detects a collision when it's not even touching the hero.
public class tank_sight extends MovieClip
{
private var _root:MovieClip;
public function tank_sight()
{
addEventListener(Event.ADDED, beginClass);
}
private function beginClass(event:Event):void
{
_root = MovieClip(root);
addEventListener(Event.ENTER_FRAME, loop);
}
private function loop(event:Event):void
{
if(this.hitTestObject(_root.hero.hitbox))
{
this.gotoAndStop(2);
trace("HIT");
fire();
}
else
{
this.gotoAndStop(1);
}
}
private function fire():void
{
var shell:Shell = new Shell(x, y, rotation - 180);
_root.addChild(shell);
}
}
What's wrong? I don't get it.
EDIT: Sight is rotating, so that's probably why. I tried using this code on the player class:
point = _root.tanks.barrel.sight.localToGlobal(new Point());
if(this.hitTestPoint(point.x, point.y, false))
{
trace("HIT");
}
But it doesn't work.. It never traces "HIT", unless I stand in some weird location at certain times.
hitTestObject works with nested objects also (display objects that have different parents), you should check logic of your game, and do some tests.
Simple example:
var squareHolder:Sprite = new Sprite();
var squareInner:Shape = new Shape();
var hitHolder:Sprite = new Sprite();
var hitCircle:Shape = new Shape();
hitCircle.graphics.beginFill(0x990000);
hitCircle.graphics.drawCircle(0, 0, 20);
squareInner.graphics.beginFill(0x009900);
squareInner.graphics.drawRect(0, 0, 40, 40);
addChild(squareHolder);
squareHolder.addChild(squareInner);
squareHolder.x = 200;
squareHolder.y = 100;
squareInner.x = 50;
squareInner.y = 50;
stage.addChild(hitHolder);
hitHolder.addChild(hitCircle);
hitCircle.transform.matrix = new Matrix(1, 0.5, 0.5, 1, 30, 30);
stage.addEventListener(MouseEvent.MOUSE_MOVE, function (e:MouseEvent):void {
hitHolder.x = e.stageX;
hitHolder.y = e.stageY;
if (hitCircle.hitTestObject(squareInner)) {
trace("Ding!");
}
});
Whatever you do with hitCircle (visible=false, trasparent fill, transformations) it will still work.

AS3-animate position of filtered area (Bitmapdata)

I know it's simple to animate the amount of any given filter, but lets say you have a filter on a specific region(Rectangle) of the stage.
How would you animate(tween) the position of this filtered area so that the effect moves on the stage?
One way to achieve the effect that comes to mind is to draw the contents of the stage to a BitmapData and then use BitmapData.applyFilter(). applyFilter() lets you specify a sourceRect and a destPoint. So in your case you can animate the properties of sourceRect. destPoint's x and y should be the same as sourceRect's x and y in this case.
Here's a working example:
package {
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
public class Main extends Sprite {
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
var bitmapData : BitmapData = new BitmapData(this.stage.stageWidth, this.stage.stageHeight);
var sourceRectVelocity : Point = new Point(3, 2);
var sourceRect : Rectangle = new Rectangle(50, 100, 200, 150);
var bitmap : Bitmap = new Bitmap(bitmapData);
// draw some random circles on the stage
for (var i:int = 0; i < 100; i++) {
this.graphics.beginFill((Math.floor(Math.random()*255) << 16) + (Math.floor(Math.random()*255) << 8) + Math.floor(Math.random()*255), 1);
this.graphics.drawCircle(Math.random()*this.stage.stageWidth, Math.random()*this.stage.stageHeight, 50 + Math.random()*50);
}
this.addChild(bitmap);
this.addEventListener(Event.ENTER_FRAME, function(event : Event):void {
sourceRect.x = Math.min(Math.max(sourceRect.x + sourceRectVelocity.x, 0), bitmapData.width - sourceRect.width);
sourceRect.y = Math.min(Math.max(sourceRect.y + sourceRectVelocity.y, 0), bitmapData.height - sourceRect.height);
if (sourceRect.right >= bitmapData.width) {
sourceRectVelocity.x = -Math.abs(sourceRectVelocity.x);
} else if (sourceRect.left <= 0) {
sourceRectVelocity.x = Math.abs(sourceRectVelocity.x);
}
if (sourceRect.bottom >= bitmapData.height) {
sourceRectVelocity.y = -Math.abs(sourceRectVelocity.y);
} else if (sourceRect.top <= 0) {
sourceRectVelocity.y = Math.abs(sourceRectVelocity.y);
}
// clear the bitmap with white (not needed if the stage doesn't have any transparency)
bitmapData.fillRect(bitmapData.rect, 0xffffff);
// draw the stage to the bitmapData, but make sure the Bitmap display object showing the BitmapData isn't visible
bitmap.visible = false;
bitmapData.draw(stage);
bitmap.visible = true;
// apply greyscale filter
bitmapData.applyFilter(bitmapData, sourceRect, new Point(sourceRect.x, sourceRect.y), new ColorMatrixFilter([0.3, 0.59, 0.11, 0, 0,0.3, 0.59, 0.11, 0, 0,0.3, 0.59, 0.11, 0, 0,0, 0, 0, 1, 0]));
});
}
}
}
This example draws the whole stage to a BitmapData but then only apply the filter to a region. A more optimized approach (especially if the region never/rarely change size) is to only draw the region you want to filter to a BitmapData with the size of the region and then apply the filter to the whole BitmapData.

Change Alpha on Rollover; Not Changing Back on Mouse Out?

So I have a Tile class, with a sprite variable that holds the graphic for the tile. On mouse over, I ColorTransform the graphic. Seems to work fine. On mouse out, I try to change it back. Nada. And in fact, rolling over the same tile twice will increase the alpha until eventually it fades totally. Not sure how to fix it. Here's my code.
In the Tile class:
this.addEventListener(MouseEvent.MOUSE_OVER, thisMouseOver);
this.addEventListener(MouseEvent.MOUSE_OUT, thisMouseOut );
public function thisMouseOver( e:Event ):void {
tileGraphic.bitmapData = setAlpha(tileGraphic.bitmapData);
}
public function thisMouseOut( e:Event ):void {
tileGraphic.bitmapData = resetAlpha(tileGraphic.bitmapData);
}
private function setAlpha( bmd:BitmapData ):BitmapData {
var rec:Rectangle = new Rectangle( 0, 0, bmd.width, bmd.height );
var ct:ColorTransform = new ColorTransform();
ct.alphaMultiplier = .65;
bmd.colorTransform( rec, ct );
return bmd;
} //end function setAlpha
private function resetAlpha( bmd:BitmapData ):BitmapData {
var rec:Rectangle = new Rectangle( 0, 0, bmd.width, bmd.height );
var ct:ColorTransform = new ColorTransform();
ct.alphaMultiplier = 1;
bmd.colorTransform( rec, ct );
return bmd;
} //end function resetAlpha
Can anyone point me in the right direction? Thanks!
Replace the resetAlpha with
private function resetAlpha( bmd:BitmapData ):BitmapData {
var rec:Rectangle = new Rectangle( 0, 0, bmd.width, bmd.height );
var ct:ColorTransform = new ColorTransform();
ct.alphaOffset = 255
bmd.colorTransform( rec, ct );
return bmd;
} //end function resetAlpha
You should better change alpha of the container instead of playing with the BitmapData pixels. For example, in your case if your tile bitmap will initially have transparent pixels (fill it with 0x00ff0000 prior to drawing a thing and check if so), they will become opaque with codingbuddha's answer. So, change the listeners to the following:
public function thisMouseOver( e:Event ):void {
tileGraphic.alpha=0.65;
}
public function thisMouseOut( e:Event ):void {
tileGraphic.alpha=1;
}

How to change the pixels in an image

i actually try to do the following: I have loaded an external image in a bitmapdata object and create a bitmap from it which i attach it to a sprite/MovieClip in order to have mouse events on it. Now under the previous logic i loaded two images (let's say circles) of the same size one that has a particular color and is covered by its black foreground circle. When i press left mouse button and hold it down i want while the mouse is moved to erase the foreground circle's pixels and so the background image starting to appear. I tried this to achieve but had no luck. In my best attempt i achieve to draw a line in the foreground image but i cannot reveal the background!
package
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.display.BlendMode;
public class Test2 extends MovieClip
{
// properties - state
// to attach the image and have mouse events
private var frontImage:Sprite;
private var backImage:Sprite;
// to load the image
private var myLoader:Loader;
// to get the bitmap data of the image
private var frontBitmapData:BitmapData;
private var frontBitmap:Bitmap;
// test
private var frontMask:Bitmap;
// constructor
function Test2():void
{
// load the background image
backImage = new Sprite();
attachImageToSprite1(new URLRequest("btest.jpg"));
backImage.mouseEnabled = false;
this.addChild( backImage );
// load the front image
frontImage = new Sprite();
attachImageToSprite2(new URLRequest("test.jpg"));
frontImage.mouseEnabled = true; // enable mouse
frontImage.buttonMode = true; // set button mode
this.addChild(frontImage); // load to stage
this.frontImage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
this.frontImage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
// methods
private function attachImageToSprite1(Name:URLRequest):void
{
this.myLoader = new Loader();
this.myLoader.load(Name);
this.myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete1);
}
private function attachImageToSprite2(Name:URLRequest):void
{
this.myLoader = new Loader();
this.myLoader.load(Name);
this.myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete2);
}
private function getImageBitmapDataFromSprite(srcImage:Sprite):BitmapData
{
var tmpBitmapData:BitmapData = new BitmapData(frontImage.width, frontImage.height, true, 0xFFCCCCCC);
tmpBitmapData.lock();
tmpBitmapData.draw(frontImage);
tmpBitmapData.unlock();
return tmpBitmapData;
}
private function isPixelAlpha(bitmapdata:BitmapData):Boolean
{
var pixelValue:uint = bitmapdata.getPixel32(mouseX, mouseY);
var alphaValue:uint = pixelValue >> 24 & 0xFF;
//var red:uint = pixelValue >> 16 & 0xFF;
//var green:uint = pixelValue >> 8 & 0xFF;
//var blue:uint = pixelValue & 0xFF;
return (alphaValue == 0x00) ? true : false;
}
private function deletePixelUnderMouse(bitmapdata:BitmapData, bitmap:Bitmap):void
{
bitmapdata.lock();
if ( !isPixelAlpha(bitmapdata) ) {
bitmapdata.setPixel32(mouseX, mouseY, 0xFF << 24); // how to make the current pixel's alpha
} // equal to zero.
bitmap = new Bitmap(bitmapdata);
bitmap.x = frontImage.x;
bitmap.y = frontImage.y;
this.frontImage.addChild(bitmap);
bitmapdata.unlock();
}
// events
public function onLoadComplete1(e:Event):void
{
frontImage.addChild(this.myLoader.content);
}
public function onLoadComplete2(e:Event):void
{
backImage.addChild(this.myLoader.content);
}
public function onMouseDown(e:MouseEvent):void
{
// delete a pixel from the sprite under the mouse
frontBitmapData = getImageBitmapDataFromSprite(frontImage);
deletePixelUnderMouse(frontBitmapData, frontBitmap);
frontImage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseDown);
trace("start");
}
public function onMouseUp(e:MouseEvent):void
{
frontImage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseDown);
trace("stop")
}
}
}
Not sure if I got it right, but if you want a 'reveal' effect, as in you draw a mask to display a hidden image for example, this could be achieved slightly easier:
var bitmapToReveal:BitmapData = new BitmapToReveal(0,0);
var brush:BitmapData = new Brush(0,0);
var canvasData:BitmapData = new BitmapData(bitmapToReveal.width,bitmapToReveal.height,true,0x00FFFFFF);
var cursor:Point = new Point();//used as destination point when painting
var zero:Point = new Point();//reused for painting
var reveal:Bitmap = new Bitmap(bitmapToReveal);
var canvas:Bitmap = new Bitmap(canvasData);
reveal.cacheAsBitmap = canvas.cacheAsBitmap = true;
addChild(reveal);
addChild(canvas);
reveal.mask = canvas;
stage.addEventListener(MouseEvent.MOUSE_DOWN, brushDown);
stage.addEventListener(MouseEvent.MOUSE_UP, brushUp);
function brushDown(event:MouseEvent):void {
this.addEventListener(Event.ENTER_FRAME, paint);
}
function brushUp(event:MouseEvent):void {
this.removeEventListener(Event.ENTER_FRAME, paint);
}
function paint(event:Event):void {
cursor.x = mouseX-brush.width*.5;
cursor.y = mouseY-brush.height*.5;
canvasData.copyPixels(brush,brush.rect,cursor,brush,zero,true);
}
I'm using two Bitmaps form the library(bitmapToReveal and brush).
The main thing to look at is the copyPixels() method. I copy
the brush bitmap into the canvas(an empty transparent bitmap data),
using the offset cursor position(so the brush centered), and using the
alpha channel to do that. Note that I've set cacheAsBitmap to true
for both mask and maskee. You need to do that to get a transparent mask,
which is key to the effect.
Here is the result:
You can 'paint' the mask here. CS4 Source is here.
HTH,
George