AS3 - How to use pixel/point detection with mouse event instead of object detection - actionscript-3

This seems like it should be so easy that I'm embarrassed to ask, but I just can't get it.
I have a large round MovieClip (being used as a button). This MovieClip contains a PNG with a transparent background inserted into the MovieClip.
Due to its size there are large empty registration areas on the 4 corners (the bounding box).
How can I have the mouse register as being over only the circle pixels and not the blank space (of Alpha channel pixels) in the square boundary box?
Simple sample code:
public function simpleSample () : void
{
mc1.buttonMode = true;
mc1.addEventListener(MouseEvent.CLICK, doStuff);
}
public function doStuff (event:MouseEvent) : void
{
mc2.gotoAndStop(2);
}

Here are 3 different ways to accomplish this.
EDIT Since you've later explained that your button is an image, this first option won't work for you
If the shape flag on hitTestPoint works with your button (eg it's a shape), you can use hitTestPoint inside your mouse click handler to figure out if the click is actually over the object:
public function doStuff(event:MouseEvent){
//only continue if hit test point is true,
//the x and y values are global (not relative to the mc your testing as one might suppose)
//the third parameter should be true, so it takes into account the shape of object and not just it's bounds
if(mc1.hitTestPoint(stage.mouseX, stage.mouseY, true)){
mc2.gotoAndStop(2);
}
}
If the above doesn't work because you have bimtap data in your button, then an easy way to accomplish this is to just add a shape mask to the button.
So, either inside your button using FlasPro, mask everything with a circle shape, or, do it via code by doing the following when you first show the button:
var s:Shape = new Shape();
s.graphics.beginFill(0);
s.graphics.drawCircle(mc1.x + (mc1.width * .5), mc1.y + (mc1.height * .5), mc1.width / 2);
addChild(s);
mc1.mask = s;
If using an image as the button, or you want to set a threshold of how transparent to consider a click, then you can check the transparency of the pixel under the mouse:
function doStuff(event:MouseEvent){
//only continue if pixel under the mosue is NOT transparent
//first, you need a bitmap to work with
//if you know for sure the position of your bitmap, you can do something like this:
var bm:Bitmap = mc1.getChildAt(0) as Bitmap;
//annoyingly though, FlashPro makes timeline bitmaps shapes,
//so the above won't work UNLESS you take your bitmap in the FlashPro Library
//and export it for actionscript, giving it a class name, then it will be an actual bitmap on the timeline.
//As an alternative, you could (very CPU expensively) draw the whole button as a bitmap
var bmd:BitmapData = new BitmapData(mc1.width,mc1.height,true,0x00000000);
bmd.draw(mc1);
var bm:Bitmap = new Bitmap(bmd);
//we get the 32bit pixel under the mouse point
var pixel:uint = bm.bitmapData.getPixel32(bm.x + event.localX,bm.y + event.localY);
//then we grab just the Alpha part of that pixel ( >> 24 & 0xFF ).
//if the value is 0, it's totally transparent, if it's 255, it's totally opaque.
//for this example, let's say anything greater than 0 is considered good to be a click
if((pixel >> 24 & 0xFF) > 0){
mc2.gotoAndStop(2);
}
}

Related

Save un-scaled canvas with image background after changes were applied

I've got a big issue and it's almost a week trying to make it work so any help I would really appreciate - I am trying to create a simple image editor in html5, so I upload an image, load it into canvas and then paint on it -
I also want to be able to zoom in and zoom out- just that I can't figure out how should I save the canvas state - for the paint mouseevents I am using an array which saves canvas.toDataUrl, but this one will save only what it is visible in canvas, only a part of the scaled image, and not the entire one -
if anyone knows how can I un-scale the canvas together with the painting over it and save it in the stack from where I can retrieve it for other painting events, I'll appreciate a lot! Thanks
Saving state
The canvas' save() and restore() is not related to the pixels in the canvas at all. Save() only saves current pen color, fill color, transform, scale, rotation and so forth - parameter values only, not actual pixel data.
And so, the restore() will only restore these parameter values to the previous ones.
The canvas element is passive, meaning it only holds the pixels that you see on the screen. It does not keep a backup of anything so if you change its size, re-size browser window or open dialogs in the browser causing it to clear, you will need to update the canvas yourself.
This also applies when you change a parameter value such as scale. Nothing on the canvas will change setting a new value. The only thing that happens is that your next draw of what-ever will use these parameter values for the drawing (in other words: if you apply rotation nothing rotates, but the next thing you draw will be rotated).
Drawing on existing image
As you need to maintain the content it also means you need to store the image you draw on as well as what you draw.
When you draw for example lines you need to record every stroke to arrays. When the canvas needs an update (ie. zoom) you redraw the original image first at the new scale, then iterate through the arrays with lines and re-render them too.
Same for points, rectangles, circles and what have you..
Think of canvas as just a snapshot of what you have stored elsewhere (image object, arrays, objects) . Canvas is just a view-port for that data.
I would recommend to store as this:
var backgroundImage; //reference to your uploaded image
var renderStack = []; //stores all drawing objects (see below)
//example generic object to hold strokes, shapes etc.
function renderObject() {
this.type = 'stroke'; //or rectangle, or circle, or dot, ...
this.x1;
this.y1;
this.x2;
this.y2;
this.radius;
this.penWidth;
this.penColor;
this.fillColor;
this.points = [];
//... extend as you need or use separate object for each type
}
When you then draw a stroke (pseudo):
var currentRenderObject;
function mouseDown(e) {
//get a new render object for new shape/line etc.
currentRenderObject = new renderObject();
//get type from your selected tool
currentRenderObject.type = 'stroke'; //for example
//do the normal draw operations, mouse position etc.
x =..., y = ...
}
function mouseMove(e) {
//get mouse positions, draw as normal
x = ..., y = ...
//store the points to the array, here:
//we have a line or stroke, so we push the
//values to ourpoint-array in the renderObject
currentRenderObject.points.push(x);
currentRenderObject.points.push(y);
}
function mouseUp(e) {
//when paint is done, push the current renderObject
//to our render stack
renderStack.push(currentRenderObject);
}
Now you can make a redraw function:
function redraw() {
clearCanvas();
drawBackgroundImage();
for(var i = 0, ro; ro = renderStack[i]; i++) {
switch(ro.type) {
case 'stroke':
//... parse through point list
break;
case 'rectangle':
//... draw rectangle
break;
...
}
}
}
function zoom(factor) {
//set new zoom, position (scale/translate if you don't
//want to do it manually). Remember that mouse coords need
//to be recalculated as well to match the zoom factor.
redraw();
}
//when canvas is scaled for some reason, or the window
canvas.onresize = windows.onresize = redraw;
A bonus doing this here is you can use your render stack as a undo/redo stack as well...
Hope this helped to better understand how canvas works.

Canvas Animation Not Rendering

I'm new to the canvas tag and am playing around with some animation. Basically, I'm trying to setup a "ground" section composed of multiple images (similar to an 8bit side scroller game like Mario Brothers). The ground will be composed of multiple images, which I've built a constructor function to load these and tile them across the bottom.
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
img=new Image();
img.onload=function(){context.drawImage(img,ImgX,ImgY,ImgW,ImgH);};
img.src='images/'+ImageName;};
This seems to work out just fine. I've then setup the rest of the animation, including a basic setup for Key Left/Right events, like so:
window.onload=function(){
var canvas=document.getElementById('canvas'),
context=canvas.getContext('2d'),
Grounds=[],
ImgX=-150; // INITIAL STARTING X FOR FIRST GROUND TILE
// INSERT GROUND ELEMENTS
for(var i=0,l=8; i<l; i++){
var ImgX+=150;
Grounds[i]=new Ground(context,"ground.png",ImgX,650,150,150);
};
// ASSIGN LEFT/RIGHT KEYS
window.addEventListener('keyup',function(event){
switch(event.keyCode){
case 37:
for(var i=0,l=Grounds.length; i<l; i++){
Grounds[i].x+=10;
};
break;
case 39:break;
};
});
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
}());
};
I know exactly what my problem is, but don't know how to solve it. The animation loop is clearing the canvas every frame, but not redrawing the updated position (if any) when the user presses the left arrow key.
I'm missing the redraw part here and I'm not exactly sure how to handle this or if I'm approaching this entirely wrong. Any help is very appreciated! Thanks!
First of all you're incrementing the property x of the ground tiles but that property is not even used anywhere in your code. Modify your code so that the onload event of those image objects draws the image according to their own x property so changes to it will actually affect what is drawn. Also add the image object as a property of the Ground object so you can access it later on from outside.
Your approach is really not so good but if you want to do it without going back to 0 do it as follows:
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
var self = this; // Add local reference to this Ground instance
this.img=new Image(); // img is now a property too
this.img.onload=function(){context.drawImage(this, self.x, self.y,self.width,self.height);};
this.img.src='images/'+ImageName;};
Ok so now you can change the property x of the ground tiles and call the draw function of it again (which is the onload event).
Grounds[i].x+=10;
Grounds[i].img.dispatchEvent(new Event("load"));
Please note that you should really make the updates of all the values first and then all the draw calls separately.
Can you not just add a draw method? You usually so something like this:
init -> update -> clear, redraw -> update -> clear, redraw -> ...
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
contect.drawImage(...);
}());

how to retrieve a part of a bitmapdata which is in a particular color

I have a bitmapdata which contains two colors in it say black and white. Now the black area is transparent and the white area is visible. Now the image should be clickable only on the white areas and not on the black transparent area. How can we do this?
PS: The white areas are not in a regular locations I mean they are in random locations.
//add listener for mouse clicks
stage.addEventListener(MouseEvent.CLICK, eventHandler);
function eventHandler(event:MouseEvent):void
{
//1x1 bitmapData to store snapshot
var bmd:BitmapData = new BitmapData(1, 1);
//matrix object to 'move' stage so that pixel under mouse is effectively at 0,0
var matrix:Matrix = new Matrix();
//'move' stage according to mouse x,y values
matrix.translate(-event.stageX, -event.stageY);
//take snapshot of stage
bmd.draw(stage, matrix);
//get colour from snapshot data
var pixel:uint = bmd.getPixel(0, 0);
//trace result
trace("colour = "+pixel.toString(16));
}
Taken from http://blog.leeburrows.com/2011/06/get-pixel-colour-below-mouse-pointer/
Edit because I have time:
So in your case, instead of:
trace("colour = "+pixel.toString(16));
You would use:
if(pixel.toString(16) == "ffffff") // if clicked pixel is white
{
//do something
}
Sam's answer is great, but since you say you know that the pixel in question is part of a bitmapdata you could skip taking a snapshot of the stage and just check the bitmapdata directly instead. I think it could be as simple as:
stage.addEventListener(MouseEvent.CLICK, eventHandler);
function eventHandler(event:MouseEvent):void
{
if (myBitmapData.getPixel(event.stageX, event.stageY) == 0xffffff)
{
// do something
}
}
Also if you want to take alpha into consideration you will want to use getPixel32() instead of getPixel().

action script 3 - is it possible to trigger click event only when mouse is clicked on the image part?

I have a problem and I have potential solution. But I wanted to confirm if there is an easy and simple way to solve my problem.
App type:
Isometric Game
Problem statement:
I am loading images in my flash app and have mouse events attached to them.
The images I load are prop images like vehicles, trees, buildings etc., and all of them are transparent.
Example: Red ball asset (please ignore the yellow background which I applied to describe the problem)
If I click on the actual image area (colored in red), then every thing works perfect
I don't want to trigger mouseevent when I click on empty image part (or transparent area, which I have shown in yellow color)
There is one way I know by creating masks in flash. I don't want to do it unless that is the final option left because I load image assets instead of flash assets and I don't want to create a new mask asset for all the assets
There is another method I was going to adopt by using getPixel method of Bitmap. Which is discussed here.
But there is another problem with this method.
I might be able to ignore the click event when I click on the empty part of the asset but if there is some other asset is behind the image in the same location, then I need to process the click event for the occluded image.
Well, thinking of solution to this problem takes me to the getObjectsUnderPoint where I can scan the occluded assets
Well, what you proposed as a solution is 100% valid. Just move the logic of determining what game object is clicked outside of that object.
Listen for MOUSE_DOWN/MOUSE_UP events at container which contains your game objects.
Catch an event
Check if the game object which is the target of this event is transparent at this point using BitmapData.getPixel32
If it is use getObjectsUnderPoint to find out all other game objects at this point
Find in a loop the first object which is not transparent at this point
Now you got the actual object which is hit.
One interesting solution is to use Sprite objects with the individual non-transparent pixels burnt onto them.
Suppose this is your Loader "complete" handler:
private function loaderCompleteHandler(event:Event):void
{
// Loader is not our child, we use a Sprite instead (below).
var loader:Loader = Loader(event.target);
var sprite:Sprite = new Sprite();
addChild(sprite);
var w:Number = loader.content.width;
var h:Number = loader.content.height;
// Use transparent bitmap.
var bitmapData:BitmapData = new BitmapData(w, h, true, 0);
bitmapData.draw(loader.content);
// Now burn the image onto the Sprite object, ignoring
// the transparent pixels.
for (var xPos:int = 0; xPos < w; xPos++) {
for (var yPos:int = 0; yPos < h; yPos++) {
var pixel32:uint = bitmapData.getPixel32(xPos, yPos);
var alpha:int = pixel32 >>> 24;
if (alpha != 0) {
sprite.graphics.beginFill(pixel32 & 0xFFFFFF, alpha / 0xFF);
sprite.graphics.drawRect(xPos, yPos, 1, 1);
sprite.graphics.endFill();
}
}
}
}
Essentially you want "empty" pixels that aren't clickable, and fully transparent pixels aren't quite the same thing. With this solution you get empty pixels.
Only problem is that this might be slow. Give it a shot.

Transition between two images

I'm developing an ActionScript 3.0 app for Blackberry Playbook.
I'm using Loader Class to show an image.
I want to show another image, at the same place, when the user clicks on this image.
How can I do that? I would like to make a transition between these two images. The second image will go from 0 alpha to 100 alpha.
It all depends on the transition you want to do. For the simplest alpha, you can go through a Tweener engine like irot suggested, or you can do something simple yourself.
Simple:
Basically, when you click on the image, load in the next one (or have it already loaded). Start an enterframe listener to load it up. Something like:
// we're assuming that "image2" is the second image and it has an alpha
// of 0.0 and visible of false. "image1" is the first image and currently
// on stage
// the on click handler for the image
private function _onImageClick( e:MouseEvent ):void
{
// add a enter frame to the stage - I'm going to assume you
// have access through this.stage
this.stage.addEventListener( Event.ENTER_FRAME, this._onEnterFrame );
// make our second image visible so we can fade it up
this.image2.visible = true;
}
// called every frame
private function _onEnterFrame( e:Event ):void
{
// image2 is the second image
this.image2.alpha += 0.05; // slow fade
if( this.image2.alpha >= 1.0 )
{
this.image2.alpha = 1.0;
// hide the first image
this.image1.alpha = 0.0;
this.image1.visible = false;
// remove the enter frame event listener
this.stage.removeEventListener( Event.ENTER_FRAME, this._onEnterFrame );
}
}
Bit more complicated: Check out the BitmapData class and it's merge() or pixelDisolve() functions: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html
Are you familiar with any Tween engines? I would recommend TweenLite if you are not.
What I would usually do is load all the images I plan to use and then stack two or more of them where I want them. Only one of these images will be visible at any time (alpha = 1).
On your click handler, you can do one of two things:
Tween the visible image's alpha down to 0, then have an onComplete handler tween your next image's alpha up to 1
Alternatively, you could just have two tweens running at once. One would tween the visible image's alpha down to 0, the other tweening the next image's alpha up to 1
irot