How to properly create a Point relative to an Image using FigPanZoom? - actionscript-3

I have an Image that's 5000 pixels by 5000 pixels. It's scaled up and down regularly to fit different parts of the image into the window.
I'm making a function that focuses in on different places on the image (like a map) and then zooms into a certain scale that I specify.
I pass my point into this function (new Point(2000,2500) for example)) however this always breaks because it's not relative to the image specifically.
How do I make the Point relative to the image at the image's given scale at any given time?
Additional Info: I'm using the guide here http://www.adobe.com/devnet/flex/samples/fig_panzoom.html for panning and zooming functionality.
Solved:
One of my pitfalls was that I was using a possible bitmapScaleFactor > 1 which would mess up the scaling. This was the final function that worked for my usage.
protected function testFocus(p:Point):void
{
var content:ContentRectangle = boardViewer._contentRectangle;
var panToPoint:PanToPointCommand = new PanToPointCommand(boardViewer, content);
var scale:Number = boardViewer.bitmapScaleFactor;
var location = new Point((p.x*scale)+content.x, (p.y*scale)+content.y);
var center = new Point(boardViewer.width/2, boardViewer.height/2);
//Move the point to the center
panToPoint.fromPoint = location;
panToPoint.toPoint = center;
panToPoint.execute();
}

I've not tested this but how about using the scale factor that is being applied to the image to alter the point coordinates.
e.g.
var scaler:Number = 0.5;
image.scaleX = image.scaleY = scaler;
new Point(2000*scaler,2500*scaler);

Are you looking for DisplayObject.mouseX/mouseY? These do not take any rotation into account, however.
To take rotation into account, you could do something like (untested code):
var mat:Matrix = image.transform.concatenatedMatrix;
mat.invert();
// now use mat to transform point from 'stage space' to 'image space'
var pImage:Point = mat.transform(new Point(stage.mouseX, stage.mouseY));

Related

AS3 image rotate within / scale to fit sprite

We're making bitmap data from a sprite where we want to take an image and rotate within / scale to fit. This is our code, which includes a rotation.
_rotation defines how much the user has input.
The problem is, we're getting an output file that is 100% white.
We think that the image is rotating about 0x0y therefore rotating the image outside the bounds of the sprite.
Furthermore, the image is not scaling to the child, instead is sort of "cropping" as it inherits.
What is the best way of doing this? Basically we want to take an image and rotate within / scale to fit
var sprite1:Sprite = new Sprite();
addChild(sprite1);
var photoBitmap:Bitmap = new Bitmap(_bitmapData);
sprite1.addChild(photoBitmap);
sprite1.rotation = _rotation;
var sprite2:Sprite = new Sprite();
addChild(sprite2);
sprite2.addChild(sprite1);
var bitmapData:BitmapData = new BitmapData(sprite2.width,sprite2.height,false,0xFFFFFF);
bitmapData.draw(sprite2);
The simple way to draw sprite with scaling/rotating is using Matrix in method bitmapData.draw().
Example:
var sourceImage:SomeSprite = new SomeSprite();
var matrix:Matrix = new Matrix();
matrix.scale(0.5, 0.5);
matrix.rotate(0.5 * Math.PI);
var newImage:BitmapData = new BitmapData(sourceImage.width/2, sourceImage.height/2);
newImage.draw(sourceImage, matrix);
Your issue is likely a cause of rotating around the top left corner (which can make the entire object left of or above the registration point (0 x and 0 y) and not get drawn.
An easy way you can account for this, is to move sprite1 after the rotation to account for the new size and position caused by rotating:
...
sprite2.addChild(sprite1);
var actualPosition:Rectangle = sprite2.getBounds(sprite2); //this gets the new position/dimensions of the object
sprite1.x = -actualPosition.x;
sprite1.y = -actualPosition.y;
var bitmapData:BitmapData = new BitmapData(sprite2.width,sprite2.height,false,0xFFFFFF);
...

Scaling points to match background. Actionscript 3

Using some code I found online has helped me create a zoom function for a program I am attempting to make. It is to make a map that allows a user to mark points. Currently the code scales in on the map image alone but I cant get the point icons to realign to where they originally where. I cant workout the maths of it.
Code to zoom in and out
if (mev.shiftKey) {
image.scaleX = Math.max(scaleFactor*image.scaleX, minScale);
image.scaleY = Math.max(scaleFactor*image.scaleY, minScale);
}
if (mev.ctrlKey) {
image.scaleX = Math.min(1/scaleFactor*image.scaleX, maxScale);
image.scaleY = Math.min(1/scaleFactor*image.scaleY, maxScale);
mat = image.transform.matrix.clone();
MatrixTransformer.matchInternalPointWithExternal(mat,internalCenter,externalCenter);
image.transform.matrix=mat;
This allows the image to scale up with the following factors
public var scaleFactor:Number = 0.8;
public var minScale:Number = 0.25;
public var maxScale:Number = 2.0;
The problem occurs when I try to move the pointer icons that are overlaid on this image. They are not to grow or shrink at the moment but they I cant get the maths to get them to move the correct number of pixels away from the mouse location so that they are still in line. Currently I am using the following formulas
//decrease zoom
stage.getChildAt(i).x = stage.getChildAt(i).x * scaleFactor;
//increase zoom
stage.getChildAt(i2).x = stage.getChildAt(i2).x / scaleFactor;
Any thoughts ? Code I am using came from
http://www.flashandmath.com/howtos/zoom/
Quite a few elements missing from the question like the moving map underneath. Anyway now that it's sorted out ...
If you are not a math genius and can't tackle 2 math formulas at the same time then don't and tackle them one by one then combine them. Once again don't use the x,y property of point for calculation but create specific property (like in a custom class for example). I will name them here origin for convenience.
Given a point with origin property of x:100, y:200, its position on the map is (assuming map is top left coordinate, if not adapt accordingly):
point.x = map.x + point.origin.x;
point.y = map.y + point.origin.y;
the positioning is solved now you need to solve for scale which is easy:
point.x = point.origin.x * scaleFactor;
point.y = point.origin.y * scaleFactor;
Both systems are solved now you can combine the two:
point.x = map.x + (point.origin.x * scaleFactor);
point.y = map.y + (point.origin.y * scaleFactor);

What is the proper way to add MANY images to WP8 map?

In building a program that displays a map, I have two sliders:
that changes the map's pitch
that rotates the map's heading
And, two Buttons that change the Zoom_Level of the map
I have close to 200 images that need to be displayed on the map at any given time based on the zoom level. (each one is click-able)
With so many Images, I'm getting an:
"A first chance exception of type 'System.OutOfMemoryException' occurred in System.Windows.ni.dll"
thrown.
Many of these Images are the same - just displayed in different places. I find it very inefficient that I need to initialize each different Image separately and place it inside it's own MapOverlay instead of referencing a globally constructed Image and referencing it when I need it. (there is also a problem with Clearing the Map.Layers when the Images are defined globally).
Is there a better way to add many Images to a map?
After I hit about 50 Images I get the OutOfMemoryException (the Images need to be defined separately so that they are square in dimension for the rotating to work properly).
Does it matter how the Images are added to the Project/App ("Build Action" or the "Copy to Output Directory")? Would it be better to have them in Isolated Storage? Would this make a difference?
Here is the code I'm currently using to add Images:
MapLayer pinLayerZoom12 = new MapLayer();
MapOverlay pinOverlay = new MapOverlay();
// Add the location of the Pushpin using latitude and longitude.
pinOverlay.GeoCoordinate = new GeoCoordinate(49.33783000, -0.45215600);
Image pinOverlayImage = new Image();
pinOverlayImage.Source = new BitmapImage(new Uri("images/Hedgehog.png", UriKind.Relative));
pinOverlay.Content = pinOverlayImage;
pinOverlay.PositionOrigin = new Point(0.0, 0.0);
pinOverlayImage.Opacity = 0.8;
pinOverlayImage.Height = 10;
pinOverlayImage.Width = 10;
pinOverlayImage.Tap += pinOverlayImage_Tap;
pinLayerZoom12.Add(pinOverlay);
MapOverlay pinOverlay2 = new MapOverlay();
// Add the location of the Pushpin using latitude and longitude.,
pinOverlay2.GeoCoordinate = new GeoCoordinate(49.33783000, -0.44547083);
Image pinOverlay2Image = new Image();
pinOverlay2Image.Source = new BitmapImage(new Uri("images/Hedgehog.png", UriKind.Relative));
pinOverlay2.Content = pinOverlay2Image;
pinOverlay2.PositionOrigin = new Point(0.0, 0.0);
pinOverlay2Image.Opacity = 0.8;
pinOverlay2Image.Height = 10;
pinOverlay2Image.Width = 10;
pinOverlayImage.Tap += pinOverlayImage2_Tap;
pinLayerZoom12.Add(pinOverlay2);
// Add the layer to the map
map1.Layers.Add(pinLayerZoom12);
Thank you for any help you can give me!

Loop an image in Flash

I want to have a scene where an image which is 5000 pixels high is moving up 5 pixels each frame-refresh. When the image is all up, I'd like to see the top of the image connected to the bottom of the image. This should be done untill the level is 'done'. How can I 'loop' such an Image?
You can create a copy of that image which you keep hidden/above and the trick is to update the position and loop accordingly so when one image goes bellow the screen it goes back on top and repeats.
Here's a basic snippet to illustrate the idea using the DisplayObject class and the scrollRect property:
//ignore this, you have your content already
var dummyContent:BitmapData = new BitmapData(100,100,false);
dummyContent.perlinNoise(10,10,8,12,true,true);
//important stuff starts here
var container:Sprite = addChild(new Sprite()) as Sprite;//make a container
container.scrollRect = new Rectangle(0,0,dummyContent.width,dummyContent.height);//set a scrollRect/'mask'
var part1:DisplayObject = container.addChild(new Bitmap(dummyContent));//add two copies
var part2:DisplayObject = container.addChild(new Bitmap(dummyContent));//of the same content
part2.y -= part2.height;//set the 2nd at the top of the 1st
addEventListener(Event.ENTER_FRAME,update);
function update(e:Event):void{
//move both
part1.y += 5;
part2.y += 5;
//check if any reach the bottom so they can be moved back up
if(part1.y >= part1.height) part1.y = -part1.height;
if(part2.y >= part2.height) part2.y = -part2.height;
//the above can also be nicely placed in a loop if you plan on using more seamless looping clips/images
}
Obviously you will have different content, but the principle is the same.
If you're working with images, you can simply use BitmapData's copyPixels method:
var s:int = 5;//scroll speed
//make some content
var w:int = 100;
var h:int = 100;
var dummyContent:BitmapData = new BitmapData(w,h,false);
dummyContent.perlinNoise(10,10,8,12,true,true);
//prepare for stiching
var renderPos:Point = new Point();//position to render the current image to
var prenderPos:Point = new Point();//position to render the previous image (the 'hidden' copy above)
var render:BitmapData = new BitmapData(w,h,false);//create a bitmap data instance to render updated pixels int
addChild(new Bitmap(render));//and add it to the stage
addEventListener(Event.ENTER_FRAME,update);
function update(e:Event):void{
renderPos.y = (renderPos.y+s)%h;//update the scroll position for the 1st part, % is used to loop back to 0 when the position gets to the content height
prenderPos.y = renderPos.y - h;//update the scroll position for the 2nd part (above)
render.lock();//freeze pixel updates
render.copyPixels(dummyContent,dummyContent.rect,renderPos);//copy pixels from the scroll position to the bottom
render.copyPixels(dummyContent,dummyContent.rect,prenderPos);//copy pixels from the top to the scroll position
render.unlock();//unfreeze/update ALL THE PIXELS
}
You can try to use a Rectangle object which changes height (height-scrollPosition) so you potentially access less pixels each time or you can manually work out single for loops using BitmapData's getVector method, but that's something to look into if performance is actually an issue for such a simple task and it's worth checking what's faster ( copy full bitmap rect vs copy partial bitmap rect vs manually copy values using vector )
Be warned, Flash cannot load an image greater than 16,769,025 pixels (or 4095x4095). The height of 5000 pixels will work as long as the width is not greater than 3353.
That said, I'd loop the image by keeping two copies of the image onstage, move both at the same time with a parent object, and reset to origin once your loop point is met.
Consider the following stage setup:
Stage ¬
0: MainTimeline:MovieClip ¬
0: Container:MovieClip ¬
0: img1:Bitmap
1: img2:Bitmap
Now moving container up, you'd just need to check that the looping second image reaches the origin point of the first image.
function onEnterFrame(e:Event):void {
Container.y = Container.y - 5;
if (Container.y < -5000) {
Container.y = -5;
}
}

make visual clone of displayObject that's nested within other displayObjects, and add the clone to the stage layer in the same location, rotation, etc

I want to be able to grab a copy of a DisplayObject that is nested within other transformed DisplayObjects (rotated, scaled, stretched objects), and be able to stamp it back into the same visual location, but on the stage layer. Essentially, being able to make a clone of a nested DisplayObject, but be able to add the clone to the stage layer, yet have it perfectly align (visually) with the original (same position, scale, rotation)
I have been working with something along the lines of:
// draw the pixels of a displayobject into a new bitmap object
var bitmapData:BitmapData = new BitmapData(nestedSprite.width, nestedSprite.height, true, 0xFFFFFF);
var bitmap:Bitmap = new Bitmap(bitmapData);
bitmapData.draw(nestedSprite);
// put the copy on the top most layer
stage.addChild(bitmap);
// position the copy to perfectly overlay the original, but on the top stage layer
var point:Point = nestedSprite.localToGlobal(new Point(0, 0));
bitmap.x = point.x;
bitmap.y = point.y;
But this only works well for displayObjects whose parents are not transformed; and for displayObjetcs that are perectly at the (0,0) origin. It falls apart for centered aligned objects or scaled parents, etc.
I am aware that I can add a matrix param to the .draw() method, as well as a clipping rectngle, and scale my bitmap afterwards, or setting the transform of one object to another, or use .transform.concatenatedMatrix, or use nestedObject.getBounds(null), or nestedSprite.getBounds(nestedSprite), etc. But I have unfortunately fallen into doing trial and error programming on this one, and with some many variables, this is never a good way to solve a programming problem.
I believe this function should work, the only extra step was offsetting the concatenated matrix so that the target would draw with its top left at (0, 0) on the Bitmap even if its origin was somewhere else. Hopefully the rest is self explanatory, but I can add more comments if anything doesn't make sense.
function createBitmapClone(target:DisplayObject):Bitmap {
var targetTransform:Matrix = target.transform.concatenatedMatrix;
var targetGlobalBounds:Rectangle = target.getBounds(target.stage);
var targetGlobalPos:Point = target.localToGlobal(new Point());
// Calculate difference between target origin and top left.
var targetOriginOffset:Point = new Point(targetGlobalPos.x - targetGlobalBounds.left, targetGlobalPos.y - targetGlobalBounds.top);
// Move transform matrix so that top left of target will be at (0, 0).
targetTransform.tx = targetOriginOffset.x;
targetTransform.ty = targetOriginOffset.y;
var cloneData:BitmapData = new BitmapData(targetGlobalBounds.width, targetGlobalBounds.height, true, 0x00000000);
cloneData.draw(target, targetTransform);
var clone:Bitmap = new Bitmap(cloneData);
// Move clone to target's global position, minus the origin offset.
clone.x = targetGlobalPos.x - targetOriginOffset.x;
clone.y = targetGlobalPos.y - targetOriginOffset.y;
return clone;
}
Unfortunately, pixelBounds seems to return an origin of (0, 0) if there are any filters on the DisplayObjects, which obviously breaks things.
Edit: Replaced target.transform.pixelBounds with target.getBounds(target.stage) as a slight improvement. This keeps the position correct if there are filters, but filters on parent DisplayObjects still won't be included, and filters on the target can overlap the edges of the Bitmap. I'm not sure if there's a simple way to work around that.
Update: Jimmi Heiserman spotted that this function is broken if the swf is scaled. Without stage.scaleMode = StageScaleMode.NO_SCALE; though, the stageWidth and stageHeight parameters seem to stay unchanged, so the only (rather hacky) workaround I've found is to add an "unscaled" test Sprite and use its concatenatedMatrix to adjust the clone's position and scale:
function createScaledBitmapClone(target:DisplayObject):Bitmap {
var targetTransform:Matrix = target.transform.concatenatedMatrix;
var targetGlobalBounds:Rectangle = target.getBounds(target.stage);
var targetGlobalPos:Point = target.localToGlobal(new Point());
// Calculate difference between target origin and top left.
var targetOriginOffset:Point = new Point(targetGlobalPos.x - targetGlobalBounds.left, targetGlobalPos.y - targetGlobalBounds.top);
// Create a test Sprite to check if the stage is scaled.
var testSprite:Sprite = new Sprite();
target.stage.addChild(testSprite);
var testMatrix:Matrix = testSprite.transform.concatenatedMatrix;
target.stage.removeChild(testSprite);
// Move transform matrix so that top left of target will be at (0, 0).
targetTransform.tx = targetOriginOffset.x * testMatrix.a;
targetTransform.ty = targetOriginOffset.y * testMatrix.d;
var cloneData:BitmapData = new BitmapData(targetGlobalBounds.width * testMatrix.a, targetGlobalBounds.height * testMatrix.d, true, 0x00000000);
cloneData.draw(target, targetTransform);
var clone:Bitmap = new Bitmap(cloneData);
// Move clone to target's global position, minus the origin offset, and cancel out stage scaling.
clone.x = targetGlobalPos.x - targetOriginOffset.x;
clone.y = targetGlobalPos.y - targetOriginOffset.y;
clone.scaleX = 1 / testMatrix.a;
clone.scaleY = 1 / testMatrix.d;
return clone;
}
Have you tried passing the parents transform into draw? draw takes a transform matrix as the second param.
If you have a handle on the parent you can use something like this
bitmapData.draw(nestedSprite, parent.transform.matrix);