circular colorTransform - actionscript-3

Is there a way to apply a colorTransform to a BitmapData in a circle rather than in a rectangle?
Instead of erasing rectangular parts of an image by reducing the alpha channel as in the code below, I'd like to do it in circles.
_bitmap.colorTransform(new Rectangle(mouseX-d/2, mouseY-d/2, d, d),
new ColorTransform(1, 1, 1, .5, 0, 0, 0, 1));
I do have some code which loops through the pixels, extracts the alpha value and uses setPixel but it seams significantly slower than the colorTransform function.

Try creating a circle using the drawing API (flash.display.Graphics) and then drawing that onto the bitmap data with BlendMode.ERASE. That might solve your problem, if I understand it correctly.
var circle : Shape = new Shape;
circle.graphics.beginFill(0xffcc00, 1);
circle.graphics.drawEllipse(-50, -50, 100, 100);
// Create a transformation matrix for the draw() operation, with
// a translation matching the mouse position.
var mtx : Matrix = new Matrix();
mtx.translate(mouseX, mouseY);
// Draw circle at mouse position with the ERASE blend mode, to
// set affected pixels to alpha=0.
myBitmap.draw(circle, mtx, null, BlendMode.ERASE);
I'm not 100% sure that the ERASE blend mode works satisfyingly with the draw() command, but I can't see why it shouldn't. Please let me know how it works out!

Related

Merge two images and retain its transparency

It was a while since I programmed AS3. Now I have a problem where I need to merge the two images where the upper image is a png that must retain its transparency. The upper image is an area that must pass through the lower image. A bit like a masked layer.
The result of this merge should result in a a display object. This object will later be sent to a method with the following signature:
public function addImage (
display_object:DisplayObject,
x:Number = 0,
y:Number = 0,
width:Number = 0,
height:Number = 0,
image_format:String = "PNG",
quality:Number = 100,
alpha:Number = 1,
resizeMode:String = "None",
blendMode:String = "Normal",
keep_transformation:Boolean = true,
link:String = ''
):void
Any advice is of the utmost interest. Thanks!
UPDATE;
After some struggling I've come up with this:
var bitmapDataBuffer:BitmapData = new BitmapData ( front.loader.width, front.loader.height, true );
bitmapDataBuffer.draw ( front.loader );
var bitmapOverlay:BitmapData = new BitmapData ( front.loader.width, front.loader.height, true );
bitmapOverlay.draw ( frontBanner.loader );
var rect:Rectangle = new Rectangle(0, 0, front.loader.width, front.loader.height);
var pt:Point = new Point(0, 0);
var mult:uint = 0x00;
bitmapOverlay.merge(bitmapDataBuffer, rect, pt, mult, mult, mult, mult);
var bmp:Bitmap = new Bitmap(bitmapOverlay);
pdf.addImage(bmp,0,0,0,0,ImageFormat.PNG,100,1,ResizeMode.FIT_TO_PAGE);
The problem is that my background image (represented by bitmapDataBuffer) will be totally overwritten by my upper image (the one I call overlay).
The overlay image is a png image. This image has a part of it that is transparent. Through this transparency I want to see my background image.
Any more suggestions?
You should be more specific about what kind of merge you want. You have a few options:
BitmapData.copyPixels - Provides a fast routine to perform pixel manipulation between images with no stretching, rotation, or color effects. This method copies a rectangular area of a source image to a rectangular area of the same size at the destination point of the destination BitmapData object.
BitmapData.merge - Performs per-channel blending from a source image to a destination image. For each channel and each pixel, a new value is computed based on the channel values of the source and destination pixels.
BitmapData.draw - Draws the source display object onto the bitmap image, using the Flash runtime vector renderer. You can specify matrix, colorTransform, blendMode, and a destination clipRect parameter to control how the rendering performs.
Each will work out for a different thing - the first will just copy some image over another (can keep/merge alphas). The second will merge channels data and modify them. The third one is the easiest and can draw one bitmap over another, as well as use blend modes.
Just chose one! :)
In order to make overlay image over the buffer image in your case, you are to use copyPixels() with mergeAlpha set to true.
bitmapDataBuffer.copyPixels(bitmapOverlay, rect, new Point(), null, null, true);
This will place data from bitmapOverlay to those parts of bitmapDataBuffer where overlay's alpha is above 0, blending semitransparent regions with the background.

How do a cut out one shape from another (XOR) in Canvas when shape is partially transparent?

Normally to cut one shape out from another using Canvas, I've used the globalCompositeOperation option "xor":
var c2 = document.getElementById('canvas').getContext('2d');
c2.globalCompositeOperation = "xor";
c2.fillRect(0, 0, 200, 200);
c2.fillRect(170, 0, 30, 30); // shape 2 is cut out from shape 1
However, when either the fillStyle has a alpha value < 1, or the globalAlpha of the context is < 1, the "cut-out" shape is no longer completely invisible.
Specifically, if the alpha is >0.5 and <1, you see a lighter version of the shape. If the alpha is 0.5, there is no cut-out visible at all. And if alpha is <0.5, we get the inverse: the shape that's supposed to be cut out is in fact darker than the first shape.
This can be seen at http://jsfiddle.net/N7aXY/2/.
You can try changing the alpha value to see the different effects.
Is there any way to completely cut out a shape when the background shape has an alpha < 1?
Ok, this is a bit "hackish", but here we go anyway:
Set compositing to XOR.
Draw to "cut" shape2 from shape1 normally.
Save the canvas.
Set compositing to normal (source-over).
Set globalAlpha to your desired level.
Clear the canvas and redraw the saved image.
Results: globalAlpha and globalCompositing work in harmony!
Here is code and a Fiddle: http://jsfiddle.net/utttk/1/
ctx.fillStyle="red";
ctx.globalCompositeOperation="xor";
ctx.fillRect(0,0,200,200);
ctx.fillRect(170,0,30,30);
var png=canvas.toDataURL();
ctx.globalCompositeOperation="source-over"; // "normal" compositing
ctx.globalAlpha=.2;
var image=new Image();
image.onload=function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(image,0,0);
}
image.src=png;

Tweening Sprite size using GreenSock in AS3

I've seen examples where it's possible to tween a rectangle using scaleX, but I can't find anything that tweens a circle. (The "circle" that I'm drawing is actually a donut shape and I want the outside circle to be the one that is tweened).
var resizeVar:Number = 75;
myCircle.graphics.drawCircle((myCircle.width/2), (myCircle.height/2), resizeVar);
myCircle.graphics.drawCircle((myCircle.width/2), (myCircle.height/2), 75);
I tried doing it this way, but this throws lots of errors. I don't think it's possible this way:
TweenMax.to(myCircle, 2, {resizeVar:150, ease:SlowMo.ease.config(1, 0)});
Normally with display objects, it is done this way. It doesn't work with this "donut" though:
TweenMax.to(myRectangle, 2, {scaleX:1.5, scaleY:1.5 ease:SlowMo.ease.config(1, 0)});
So my question is, how can I tween the radius size of my outside circle?
EDIT: This is how the donut is being drawn, so the resizeVar needs to change from 75 to 150.
var myCircle:Sprite = new Sprite();
myCircle.graphics.beginFill(0xbbbbbb);
myCircle.graphics.drawCircle(0, 0, 150); // this is what should be tweening/scaling
myCircle.graphics.drawCircle(0, 0, 75); // this should stay the same
myCircle.graphics.endFill();
addChild(myCircle);
You should be able to tween the scaleX and scaleY properties of ANY displayObject:
var radius:Number = 75;
var myCircle:Sprite = new Sprite();
myCircle.graphics.beginFill(0);
myCircle.graphics.drawCircle(radius/2, radius/2, radius);
myCircle.graphics.endFill();
addChild(myCircle);
TweenMax.to(myCircle, 2, {scaleX:2, scaleY:2, ease:SlowMo.ease.config(1,0)});
EDIT
This is how you would scale just the outside of the donut:
var resizeObject:Object = { innerRadius:75, outerRadius:150 };
myCircle = new Sprite();
myCircle.graphics.beginFill(0xbbbbbb);
myCircle.graphics.drawCircle(0, 0, resizeObject.outerRadius);
myCircle.graphics.drawCircle(0, 0, resizeObject.innerRadius);
myCircle.graphics.endFill();
addChild(myCircle);
TweenMax.to(resizeObject, 2, {outerRadius:300, ease:SlowMo.ease.config(1,0), onUpdate:updateCircle, onUpdateParams:[resizeObject]});
function updateCircle(resizeObject:Object):void
{
myCircle.graphics.clear();
myCircle.graphics.beginFill(0xbbbbbb);
myCircle.graphics.drawCircle(0, 0, resizeObject.outerRadius);
myCircle.graphics.drawCircle(0, 0, resizeObject.innerRadius);
myCircle.graphics.endFill();
}
The reason it works with the rectangle is that you are changing the scale of the rectangle. When you change the scale Flash Player adjusts the scale of the display object containing your graphics.
However, with the circle, you are trying to change the radius of the circle. The radius is only used when you draw the circle with the drawCircle() method. One way to tween the radius is to use your tween to re-draw the circle many times (not that ideal).
To re-draw the circle with a new radius, you can use the onUpdate callback that TweenMax offers:
TweenMax.to(myCircle, 2, {resizeVar:150, onUpdate: onUpdateCallback, onUpdateParams: [resizeVar] });
function onUpdateCallback(radius):void
{
myCircle.graphics.drawCircle(myCircle.graphics.drawCircle((myCircle.width/2), (myCircle.height/2), radius);
}
[Edit]
Note, I've added some params that you need to pass to the onUpdateCallback() function. I've also modified the function to add a radius parameter, and then use the radius when drawing the circle.
In regards to "trying to change the outside circle of this donut", this may be more complex. You might need to draw both circles of the donut. You might need to also call graphics.clear() before you draw the circle.
However, perhaps the answer from #Marcela is better, just change the scaleX and scaleY of the object you've already drawn. But if you need to get to a specified radius, the only way to do that is by re-drawing the circle(s) on each interval of the tween.

Drawing a dot on HTML5 canvas [duplicate]

This question already has answers here:
What's the best way to set a single pixel in an HTML5 canvas?
(14 answers)
Closed 7 years ago.
Drawing a line on the HTML5 canvas is quite straightforward using the context.moveTo() and context.lineTo() functions.
I'm not quite sure if it's possible to draw a dot i.e. color a single pixel. The lineTo function wont draw a single pixel line (obviously).
Is there a method to do this?
For performance reasons, don't draw a circle if you can avoid it. Just draw a rectangle with a width and height of one:
ctx.fillRect(10,10,1,1); // fill in the pixel at (10,10)
If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing.
var canvas = document.getElementById("myCanvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// That's how you define the value of a pixel
function drawPixel (x, y, r, g, b, a) {
var index = (x + y * canvasWidth) * 4;
canvasData.data[index + 0] = r;
canvasData.data[index + 1] = g;
canvasData.data[index + 2] = b;
canvasData.data[index + 3] = a;
}
// That's how you update the canvas, so that your
// modification are taken in consideration
function updateCanvas() {
ctx.putImageData(canvasData, 0, 0);
}
Then, you can use it in this way :
drawPixel(1, 1, 255, 0, 0, 255);
drawPixel(1, 2, 255, 0, 0, 255);
drawPixel(1, 3, 255, 0, 0, 255);
updateCanvas();
For more information, you can take a look at this Mozilla blog post : http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
It seems strange, but nonetheless HTML5 supports drawing lines, circles, rectangles and many other basic shapes, it does not have anything suitable for drawing the basic point. The only way to do so is to simulate a point with whatever you have.
So basically there are 3 possible solutions:
draw point as a line
draw point as a polygon
draw point as a circle
Each of them has their drawbacks.
Line
function point(x, y, canvas){
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+1, y+1);
canvas.stroke();
}
Keep in mind that we are drawing to South-East direction, and if this is the edge, there can be a problem. But you can also draw in any other direction.
Rectangle
function point(x, y, canvas){
canvas.strokeRect(x,y,1,1);
}
or in a faster way using fillRect because render engine will just fill one pixel.
function point(x, y, canvas){
canvas.fillRect(x,y,1,1);
}
Circle
One of the problems with circles is that it is harder for an engine to render them
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.stroke();
}
the same idea as with rectangle you can achieve with fill.
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.fill();
}
Problems with all these solutions:
it is hard to keep track of all the points you are going to draw.
when you zoom in, it looks ugly
If you are wondering, what is the best way to draw a point, I would go with filled rectangle. You can see my jsperf here with comparison tests
In my Firefox this trick works:
function SetPixel(canvas, x, y)
{
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+0.4, y+0.4);
canvas.stroke();
}
Small offset is not visible on screen, but forces rendering engine to actually draw a point.
The above claim that "If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing" seems to be quite wrong - at least with Chrome 31.0.1650.57 m or depending on your definition of "lot of pixel". I would have preferred to comment directly to the respective post - but unfortunately I don't have enough stackoverflow points yet:
I think that I am drawing "a lot of pixels" and therefore I first followed the respective advice for good measure I later changed my implementation to a simple ctx.fillRect(..) for each drawn point, see http://www.wothke.ch/webgl_orbittrap/Orbittrap.htm
Interestingly it turns out the silly ctx.fillRect() implementation in my example is actually at least twice as fast as the ImageData based double buffering approach.
At least for my scenario it seems that the built-in ctx.getImageData/ctx.putImageData is in fact unbelievably SLOW. (It would be interesting to know the percentage of pixels that need to be touched before an ImageData based approach might take the lead..)
Conclusion: If you need to optimize performance you have to profile YOUR code and act on YOUR findings..
This should do the job
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
//draw a dot
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

operations with BitmapData

we have a square movie clip with some clild movie clips on it. Consider a diagonal of a squere. I want to make the following transformation with bimap data of a movie clip: the triangle located upper then diagonal should remain unchanged, the lower triangle should be replaced with simetric reflection of upper triangle.
Is there a simple way to do that?
Exactly, the way is to take a snapshot of the movie, then mirror it, take another snapshot and combine these two snapshots.
Note, that the content you will get will be "static", so this way you can't copy the movieclip's animated parts.
//store the movie's graphical content in a bitmapdata
var bmd:BitmapData = new BitmapData(movie.width, movie.height);
bmd.draw(movie);
//then create a temporary movie in which you will do the mirroring
var temp:MovieClip = new MovieClip();
temp.addChild(new Bitmap(bmd));
//create the diagonal mask
var _mask:MovieClip = new MovieClip();
with(_mask.graphics) beginFill(0xff0000), lineTo(movie.width, 0), lineTo(movie.width, movie.height), lineTo(0, 0), endFill();
temp.addChild(_mask);
temp.mask = _mask;
//this is the mirroring part
temp.scaleX = temp.scaleY = -1;
addChild(temp);
//then create another bitmapdata for storing the so called "upper-triangle"
//important, that this bitmapdata should be transparent, "true" sets this
var bmd1:BitmapData = new BitmapData(temp.width, temp.height, true, 0x00);
//then do another mirroring transformation
var matrix:Matrix = new Matrix(-1, 0, 0, -1);
matrix.tx = temp.width;
matrix.ty = temp.height;
//draw the visual content on the bitmapdata
bmd1.draw(temp, matrix);
//and finally, on the original bitmapdata, draw the mirrored part
bmd.draw(bmd1);
//and add it to the top layer of the original movie, or whatever you want to do with it
movie.addChild(new Bitmap(bmd));
The simplest way I can think of is rotating the movieclip 45º and draw the upper rectangle into the lower rectangle inversed, and then rotate the bitmapdata back 45º. You can use a Matrix (2nd argument in the BitmapData.draw method) to draw it rotated and not actually rotate anything.