d3js: Use view port center for zooming focal point - dictionary

We are trying to implement zoom buttons on top of a map created in D3 - essentially as it works on Google maps. The zoom event can be dispatched programmatically using
d3ZoomBehavior.scale(myNewScale);
d3ZoomBehavior.event(myContainer);
and the map will zoom using the current translation for the view. If using zoom buttons the focal point (zoom center) is no longer the translation but the center of the view port. For zoom using the scroll wheel we have the option of using zoom.center - but this apparently have no effect when dispatching your own event.
I'm confused as to how a calculate the next translation taking the new scaling factor and the view port center into account.
Given that I know the current scale, the next scale, the current translation and the dimensions of the map view port how do I calculate the next translation, so that the center of the view port do not change?

I've recently had to do the same thing, and I've got a working example up here http://bl.ocks.org/linssen/7352810. Essentially it uses a tween to smoothly zoom to the desired target scale as well as translating across by calculating the required difference after zooming to centre.
I've included the gist of it below, but it's probably worth looking at the working example to get the full effect.
html
<button id="zoom_in">+</button>
<button id="zoom_out">-</button>
js
var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoomed);
function zoomed() {
svg.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
function interpolateZoom (translate, scale) {
var self = this;
return d3.transition().duration(350).tween("zoom", function () {
var iTranslate = d3.interpolate(zoom.translate(), translate),
iScale = d3.interpolate(zoom.scale(), scale);
return function (t) {
zoom
.scale(iScale(t))
.translate(iTranslate(t));
zoomed();
};
});
}
function zoomClick() {
var clicked = d3.event.target,
direction = 1,
factor = 0.2,
target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
view = {x: translate[0], y: translate[1], k: zoom.scale()};
d3.event.preventDefault();
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() * (1 + factor * direction);
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
d3.selectAll('button').on('click', zoomClick);

A more succinct version of Wil's solution:
var vis = d3.select('.vis');
var zoom = d3.behavior.zoom()...
var width = .., height = ..;
function zoomByFactor(factor) {
var scale = zoom.scale();
var extent = zoom.scaleExtent();
var newScale = scale * factor;
if (extent[0] <= newScale && newScale <= extent[1]) {
var t = zoom.translate();
var c = [width / 2, height / 2];
zoom
.scale(newScale)
.translate(
[c[0] + (t[0] - c[0]) / scale * newScale,
c[1] + (t[1] - c[1]) / scale * newScale])
.event(vis.transition().duration(350));
}
};
function zoomIn() { zoomByFactor(1.2); }
function zoomOut() { zoomByFactor(0.8); }

I've found this to be quite difficult to do in practice. The approach I've taken here is to simply create a mouse event that triggers the zoom when the zoom buttons are used. This event is created at the center of the map.
Here's the relevant code:
.on("click", function() {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent(
'dblclick', // in DOMString typeArg,
true, // in boolean canBubbleArg,
true, // in boolean cancelableArg,
window,// in views::AbstractView viewArg,
120, // in long detailArg,
width/2, // in long screenXArg,
height/2, // in long screenYArg,
width/2, // in long clientXArg,
height/2, // in long clientYArg,
0, // in boolean ctrlKeyArg,
0, // in boolean altKeyArg,
(by > 0 ? 0 : 1), // in boolean shiftKeyArg,
0, // in boolean metaKeyArg,
0, // in unsigned short buttonArg,
null // in EventTarget relatedTargetArg
);
this.dispatchEvent(evt);
});
The whole thing is a bit of a hack, but it works in practice and I've found this much easier than to calculate the correct center for every offset/zoom.

Related

HTML5 drag and drop: prevent dropping off the edge of the document window?

I am working on a simple HTML5 drag and drop element. Here is my JSFiddle: http://jsfiddle.net/e4ogxcum/3/
I would like to edit this so that it's impossible to drop the toolbar half way off the page. Is this possible?
In other words, I would like to prevent the toolbar being dropped half way off the page, like below:
Here is my code in full:
function drag_start(event) {
console.log('drag_start', event);
var style = window.getComputedStyle(event.target, null);
event.dataTransfer.setData("text/plain",
(parseInt(style.getPropertyValue("left"),10) - event.clientX) + ',' + (parseInt(style.getPropertyValue("top"),10) - event.clientY));
}
function drag_over(event) {
event.preventDefault();
return false;
}
function drop(event) {
event.preventDefault();
var offset = event.dataTransfer.getData("text/plain").split(',');
dm.style.left = (event.clientX + parseInt(offset[0],10)) + 'px';
dm.style.top = (event.clientY + parseInt(offset[1],10)) + 'px';
return false;
}
var dm = document.getElementById('taskbar');
dm.addEventListener('dragstart',drag_start,false);
document.body.addEventListener('dragover',drag_over,false);
document.body.addEventListener('drop',drop,false)
;
You can limit the toolbar's drag extent by changing your drop event as follows:
function drop(event) {
event.preventDefault();
var offset = event.dataTransfer.getData("text/plain").split(',');
var x= event.clientX + parseInt(offset[0],10);
var y= event.clientY + parseInt(offset[1],10);
var w= dm.offsetWidth;
var h= dm.offsetHeight;
dm.style.left= Math.min(window.innerWidth -w/2,Math.max(-w/2,x))+'px';
dm.style.top = Math.min(window.innerHeight-h/2,Math.max(-h/2,y))+'px';
return false;
}
x and y are based on your calculations, but they are then constrained so at least half the toolbar is always on-screen.
Fiddle
An alternative solution is to simulate the drag-drop behavior by using mousedown, mouseup, and mousemove.
In mousedown, grab the toolbar's left and top coordinates (variables x and y), its width and height (variables w and h), and the mouse's pageX and pageY coordinates (variables px and py).
In mousemove, calculate the new left and top coordinates, then constrain them so at least half the element is always visible on screen:
newx= x+(ev.pageX-px);
newy= y+(ev.pageY-py);
this.style.left= Math.min(window.innerWidth -w/2,Math.max(-w/2,newx))+'px';
this.style.top = Math.min(window.innerHeight-h/2,Math.max(-h/2,newy))+'px';
Fiddle
Code:
(function() {
var tb= document.querySelector('aside'),
moving,
w, h,
x, y,
newx, newy,
px,py;
tb.addEventListener('mousedown',function(ev) {
this.style.cursor= 'move';
x= tb.offsetLeft;
y= tb.offsetTop;
w= tb.offsetWidth;
h= tb.offsetHeight;
px= ev.pageX;
py= ev.pageY;
moving= true;
});
document.addEventListener('mouseup',function() {
tb.style.cursor= '';
moving= false;
});
tb.addEventListener('mousemove',function(ev) {
if(moving) {
newx= x+(ev.pageX-px);
newy= y+(ev.pageY-py);
this.style.left= Math.min(window.innerWidth -w/2,Math.max(-w/2,newx))+'px';
this.style.top = Math.min(window.innerHeight-h/2,Math.max(-h/2,newy))+'px';
}
});
document.addEventListener('mousemove', function(ev) {
if(moving) {
ev.preventDefault();
}
});
})();

Custom map overlay heremaps js api v3

I am trying to switch a project from here maps 2.5.4 to 3.0.5.
I have a map with a custom animated image overlay. In 2.5.4 it is realized via ImageProvider:
var imageProvider = new nokia.maps.map.provider.ImageProvider({
opacity: 0.8,
getBoundingBox: function() {
return new nokia.maps.geo.BoundingBox(
new nokia.maps.geo.Coordinate(55.599073, 3.550307),
new nokia.maps.geo.Coordinate(47.27036232672, 15.434621365086)
)},
getUrl: function() {
return images[index]; // return the current image
},
updateCycle: 86400,
cache: new nokia.maps.util.Cache(100)
});
//add listener to show next image
imageProvider.addListener("update", function(evt) {
index++;
}
In v3.0.5 there is no ImageProvider and I didn't find another solution in the api documentation. Does anyone know how to realize it in v3 ?
Yup, there seems to be no ImageProvider (yet?).
You can hack it in, though:
// assuming you have a H.Map instance call "map"
// that's where we want the image
var rect = new H.geo.Rect(51, 12, 54, 15),
// that's the images we want
images = [
'http://newnation.sg/wp-content/uploads/random-pic-internet-06.jpg',
'http://newnation.sg/wp-content/uploads/random-pic-internet-04.jpg',
'http://newnation.sg/wp-content/uploads/random-pic-internet-06.jpg'
],
current = 0,
// that the image node we'll use
image = document.createElement('img');
// you could probably use CSS3 matrix transforms to improve performance
// but for demo purposes, I'lluse position:absolute and top/left for the image
image.style.position = "absolute";
image.style.opacity = "0.8";
// this function updates the image whenever something changes
var update = function() {
// project the rectangle's geo-coords to screen space
var topLeft = map.geoToScreen(rect.getTopLeft());
var bottomRight = map.geoToScreen(rect.getBottomRight());
// calculate top/left and width/height
var offsetX = topLeft.x;
var offsetY = topLeft.y;
var width = bottomRight.x - topLeft.x;
var height = bottomRight.y - topLeft.y;
// set image source (update is also called, when we choose another image)
image.src = images[current];
// set image position and size
image.style.top = offsetY + "px";
image.style.left = offsetX + "px";
image.style.width = width + "px";
image.style.height = height + "px";
};
// append the image
map.getViewPort().element.appendChild(image);
// set initial values
update();
// update whenever viewport or viewmodel changes
map.getViewPort().addEventListener('sync', function() {
update();
});
map.getViewModel().addEventListener('sync', function() {
update();
});
// zoom to rectangle (just to get the images nicely in view)
map.setViewBounds(rect);
// start the image change interval
setInterval(function() {
current = (current + 1) % 3;
update();
}, 3000);

Subpixel issues with an animated sprite in odd zoom levels of Safari and FF

I have the following fiddle which distills an issue I am having with a larger project
http://jsfiddle.net/zhaocnus/6N3v8/
in Firefox and Safari, this animation will start having a jittering effect left and right on odd zoom levels (zoom in/out using Ctrl+/- or Cmd+/- on Mac). I believe this is do to sub-pixel rendering issues and the differences between the various browsers round up or down pixels during the zoom calculations, but I have no idea how to fix it and am looking for any suggestions.
I can't use more modern CSS3 animation features as I need to support legacy browsers like IE7.
(code from fiddle below, can't seem to post without it, although not sure it makes sense without CSS and HTML)
// js spritemap animation
// constants
var COUNTER_MAX = 9,
OFFSET = -50,
FRAMERATE = 100;
// variables
var _counter = 0,
_animElm = document.getElementById('animation'),
_supportBgPosX = false;
// functions
function play() {
// update counter
_counter++;
if (_counter > COUNTER_MAX) {
_counter = 0;
}
// show current frame
if (_supportBgPosX) {
_animElm.style.backgroundPositionX = (_counter * OFFSET) + 'px';
} else {
_animElm.style.backgroundPosition = (_counter * OFFSET) + 'px 0';
}
// next frame
setTimeout(play, FRAMERATE);
}
// check if browser support backgroundPositionX
if (_animElm.style.backgroundPositionX != undefined) {
_supportBgPosX = true;
}
// start animation
play();
Instead of moving the background to the new frame, have a canvas tag re-draw the frame. The canvas tag handles sub-pixel interpretation independent of the browser and because of this you not only control the render (browser agnostic) but also solve the jitter issue as it's being re-drawn frame-by-frame into the canvas' dimensions in realtime.
Zooming is specifically tough because there's no reliable way to detect the zoom level from the browser using jQuery or plain-ole javascript.
Check out the demo here: http://jsfiddle.net/zhaocnus/Gr9TF/
*Credit goes to my coworker zhaocnus for the solution. I'm simply answering this question on his behalf.
// js spritemap animation
// constants
var COUNTER_MAX = 14,
OFFSET = -200,
FRAMERATE = 100;
// variables
var _counter = 0,
_sprite = document.getElementById("sprite"),
_canvas = document.getElementById("anim-canvas"),
_ctx = _canvas.getContext("2d"),
_img = null;
// functions
function play() {
// update counter
_counter++;
if (_counter > COUNTER_MAX) {
_counter = 0;
}
// show current frame
_ctx.clearRect(0, 0, _canvas.width, _canvas.height);
_ctx.drawImage(_img, _counter * OFFSET, 0);
// next frame
setTimeout(play, FRAMERATE);
}
function getStyle(oElm, strCssRule) {
var strValue = '';
if (document.defaultView && document.defaultView.getComputedStyle) {
strValue = document.defaultView.getComputedStyle(oElm, null).getPropertyValue(strCssRule);
} else if (oElm.currentStyle) {
var strCssRule = strCssRule.replace(_styleRegExp, function (strMatch, p1) {
return p1.toUpperCase();
});
strValue = oElm.currentStyle[strCssRule];
}
return String(strValue);
}
function initCanvas(callback) {
var url = getStyle(_sprite, 'background-image');
url = url.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
_img = new Image();
_img.onload = function(){
_ctx.drawImage(_img, 0, 0);
callback();
};
_img.src = url;
}
// start animation
initCanvas(play);

Can I Implement Slide Bar Mark

Hi I am fairly new to HTML5 development. I am currently doing a school project using video-js. The project asks for dynamic marks in the video player slide bar to indicate specific position so that video viewer would know where to jump at. I am not sure about the following questions:
Is there a way to implement this?
Could I achieve that via changing skins simply of the videojs player (video-js.css)?
If I must modify source files to have this function, where should I start from?
Is it possible of adding additional elements (say buttons, images) on to the videoJS player or to its slide bar?
Thanks for the help.
Yes - video js has a great plugin framework that can modify the player's look and functionality.
You can't simply change skins to get what you're asking for, since the those marks don't exist without you creating them explicitly.
Yes - take a look at the example below (which uses the latest 4.1 videojs api)
Definitely! See an example in an answer posted here: VideoJS 4.0 Plugin: How to properly add items to the controlBar?
Example plugin for creating marks in the control bar:
/**
* Utility function to find the percent a given time represents in
* the overall duration.
*/
var getPercent = function(value, total) {
var raw = value * 1.0 / total;
var bounded = Math.min(1, Math.max(0, raw));
return Math.round(bounded * 100, 2);
};
/**
* Draws a single highlighted section on the control bar. Assumes all
* sections require a start value, but not an end value (defaults to a
* 1 second duration).
*/
var drawSection = function(player, section, duration) {
if (!section.start) return;
if (!section.end) section.end = section.start + 1;
var seekBar = vid.controlBar.progressControl.seekBar;
var sectionDiv = seekBar.createEl('div');
// You'd probably want to have a css class for these styles.
sectionDiv.style.backgroundColor = 'yellow';
sectionDiv.style.position = 'absolute';
sectionDiv.style.height = '100%';
var startPercent = getPercent(section.start, duration);
var endPercent = getPercent(section.end, duration);
sectionDiv.style.left = startPercent + '%';
sectionDiv.style.width = (endPercent - startPercent) + '%';
var seekHandle = seekBar.seekHandle;
seekBar.el().insertBefore(sectionDiv, seekHandle.el());
};
var drawSections = function(player, sections) {
var duration = player.duration();
if (duration === undefined || isNaN(duration)) return;
for (var i = 0, l = sections.length; i < l; i++) {
drawSection(player, sections[i], duration);
}
};
var highlightedSectionsPlugin = function(options) {
var player = this;
player.on('durationchange',
function() { drawSections(player, options.sections); });
};
videojs.plugin('highlightedSectionsPlugin', highlightedSectionsPlugin);
var vid = videojs("video", {
plugins : {
highlightedSectionsPlugin : {
sections : [ {start:10, end:20}, {start:25, end:30}, {start: 40} ]
}
}
});

Get bounds of filters applied to Flash Sprite within Sprite

I have a Flash library with Sprite symbols composed of other sprites with design-time applied filters. I'm embedding those symbols into a Flex application like so:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
[Bindable]
[Embed(source="Resources.swf", symbol="SquareContainer")]
private var squareContainer_class:Class;
private function log(msg:String):void {
output.text = output.text + "\n" + msg;
}
]]>
</mx:Script>
<mx:VBox horizontalAlign="center" width="100%" height="100%" >
<mx:Image id="squareContainer" source="{squareContainer_class}"/>
<mx:Button click="log(squareContainer.width + ', ' + squareContainer.height);"/>
<mx:TextArea id="output" width="100%" height="100%" />
</mx:VBox>
</mx:Application>
In this example, the SquareContainer symbol is 100px wide by 100px height; however it contains a child sprite with a glow and blur filter, that cause the sprite to appear to be significantly larger than 100x100. Since I cannot know for certain the composition of the container, I cannot use BitmapData.generateFilterRect() to get at the filters applied to nested sprites.
How can I get the size of the sprite plus its filters?
Oh sweet success! (and thanks for the tips) A friend helped solve the problem with a nice recursive function to handle the filters which may exist on nested sprites:
private function getDisplayObjectRectangle(container:DisplayObjectContainer, processFilters:Boolean):Rectangle {
var final_rectangle:Rectangle = processDisplayObjectContainer(container, processFilters);
// translate to local
var local_point:Point = container.globalToLocal(new Point(final_rectangle.x, final_rectangle.y));
final_rectangle = new Rectangle(local_point.x, local_point.y, final_rectangle.width, final_rectangle.height);
return final_rectangle;
}
private function processDisplayObjectContainer(container:DisplayObjectContainer, processFilters:Boolean):Rectangle {
var result_rectangle:Rectangle = null;
// Process if container exists
if (container != null) {
var index:int = 0;
var displayObject:DisplayObject;
// Process each child DisplayObject
for(var childIndex:int = 0; childIndex < container.numChildren; childIndex++){
displayObject = container.getChildAt(childIndex);
//If we are recursing all children, we also get the rectangle of children within these children.
if (displayObject is DisplayObjectContainer) {
// Let's drill into the structure till we find the deepest DisplayObject
var displayObject_rectangle:Rectangle = processDisplayObjectContainer(displayObject as DisplayObjectContainer, processFilters);
// Now, stepping out, uniting the result creates a rectangle that surrounds siblings
if (result_rectangle == null) {
result_rectangle = displayObject_rectangle.clone();
} else {
result_rectangle = result_rectangle.union(displayObject_rectangle);
}
}
}
// Get bounds of current container, at this point we're stepping out of the nested DisplayObjects
var container_rectangle:Rectangle = container.getBounds(container.stage);
if (result_rectangle == null) {
result_rectangle = container_rectangle.clone();
} else {
result_rectangle = result_rectangle.union(container_rectangle);
}
// Include all filters if requested and they exist
if ((processFilters == true) && (container.filters.length > 0)) {
var filterGenerater_rectangle:Rectangle = new Rectangle(0,0,result_rectangle.width, result_rectangle.height);
var bmd:BitmapData = new BitmapData(result_rectangle.width, result_rectangle.height, true, 0x00000000);
var filter_minimumX:Number = 0;
var filter_minimumY:Number = 0;
var filtersLength:int = container.filters.length;
for (var filtersIndex:int = 0; filtersIndex < filtersLength; filtersIndex++) {
var filter:BitmapFilter = container.filters[filtersIndex];
var filter_rectangle:Rectangle = bmd.generateFilterRect(filterGenerater_rectangle, filter);
filter_minimumX = filter_minimumX + filter_rectangle.x;
filter_minimumY = filter_minimumY + filter_rectangle.y;
filterGenerater_rectangle = filter_rectangle.clone();
filterGenerater_rectangle.x = 0;
filterGenerater_rectangle.y = 0;
bmd = new BitmapData(filterGenerater_rectangle.width, filterGenerater_rectangle.height, true, 0x00000000);
}
// Reposition filter_rectangle back to global coordinates
filter_rectangle.x = result_rectangle.x + filter_minimumX;
filter_rectangle.y = result_rectangle.y + filter_minimumY;
result_rectangle = filter_rectangle.clone();
}
} else {
throw new Error("No displayobject was passed as an argument");
}
return result_rectangle;
}
Here is a slightly different approach: just draw the entire object into BitmapData and then calculate the bounds of the non-transparent area of the bitmap. This approach might be more performant, especially on complex objects.
package lup.utils
{
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
public class BoundsHelper
{
private var _hBmd:BitmapData;
private var _hBmdRect:Rectangle;
private var _hMtr:Matrix;
private var _hPoint:Point;
private var _xMin:Number;
private var _xMax:Number;
private var _yMin:Number;
private var _yMax:Number;
/**
* Specify maxFilteredObjWidth and maxFilteredObjHeight to match the maximum possible size
* of a filtered object. Performance of the helper is inversely proportional to the product
* of these values.
*
* #param maxFilteredObjWidth Maximum width of a filtered object.
* #param maxFilteredObjHeight Maximum height of a filtered object.
*/
public function BoundsHelper(maxFilteredObjWidth:Number = 500, maxFilteredObjHeight:Number = 500) {
_hMtr = new Matrix();
_hPoint = new Point();
_hBmd = new BitmapData(maxFilteredObjWidth, maxFilteredObjHeight, true, 0);
_hBmdRect = new Rectangle(0, 0, maxFilteredObjWidth, maxFilteredObjHeight);
}
/**
* Calculates the boundary rectangle of an object relative to the given coordinate space.
*
* #param obj The object which bounds are to be determined.
*
* #param space The coordinate space relative to which the bounds should be represented.
* If you pass null or the object itself, then the bounds will be represented
* relative to the (untransformed) object.
*
* #param dst Destination rectangle to store the result in. If you pass null,
* new rectangle will be created and returned. Otherwise, the passed
* rectangle will be updated and returned.
*/
public function getRealBounds(obj:DisplayObject, space:DisplayObject = null, dst:Rectangle = null):Rectangle {
var tx:Number = (_hBmdRect.width - obj.width ) / 2,
ty:Number = (_hBmdRect.height - obj.height) / 2;
// get transformation matrix that translates the object to the center of the bitmap
_hMtr.identity();
_hMtr.translate(tx, ty);
// clear the bitmap so it will contain only pixels with zero alpha channel
_hBmd.fillRect(_hBmdRect, 0);
// draw the object; it will be drawn untransformed, except for translation
// caused by _hMtr matrix
_hBmd.draw(obj, _hMtr);
// get the area which encloses all pixels with nonzero alpha channel (i.e. our object)
var bnd:Rectangle = dst ? dst : new Rectangle(),
selfBnd:Rectangle = _hBmd.getColorBoundsRect(0xFF000000, 0x00000000, false);
// transform the area to eliminate the effect of _hMtr transformation; now we've obtained
// the bounds of the object in its own coord. system (self bounds)
selfBnd.offset(-tx, -ty);
if (space && space !== obj) { // the dst coord space is different from the object's one
// so we need to obtain transformation matrix from the object's coord space to the dst one
var mObjToSpace:Matrix;
if (space === obj.parent) {
// optimization
mObjToSpace = obj.transform.matrix;
} else if (space == obj.stage) {
// optimization
mObjToSpace = obj.transform.concatenatedMatrix;
} else {
// general case
var mStageToSpace:Matrix = space.transform.concatenatedMatrix; // space -> stage
mStageToSpace.invert(); // stage -> space
mObjToSpace = obj.transform.concatenatedMatrix; // obj -> stage
mObjToSpace.concat(mStageToSpace); // obj -> space
}
// now we transform all four vertices of the boundary rectangle to the target space
// and determine the bounds of this transformed shape
_xMin = Number.MAX_VALUE;
_xMax = -Number.MAX_VALUE;
_yMin = Number.MAX_VALUE;
_yMax = -Number.MAX_VALUE;
expandBounds(mObjToSpace.transformPoint(getPoint(selfBnd.x, selfBnd.y)));
expandBounds(mObjToSpace.transformPoint(getPoint(selfBnd.right, selfBnd.y)));
expandBounds(mObjToSpace.transformPoint(getPoint(selfBnd.x, selfBnd.bottom)));
expandBounds(mObjToSpace.transformPoint(getPoint(selfBnd.right, selfBnd.bottom)));
bnd.x = _xMin;
bnd.y = _yMin;
bnd.width = _xMax - _xMin;
bnd.height = _yMax - _yMin;
} else {
// the dst coord space is the object's one, so we simply return the self bounds
bnd.x = selfBnd.x;
bnd.y = selfBnd.y;
bnd.width = selfBnd.width;
bnd.height = selfBnd.height;
}
return bnd;
}
private function getPoint(x:Number, y:Number):Point {
_hPoint.x = x;
_hPoint.y = y;
return _hPoint;
}
private function expandBounds(p:Point):void {
if (p.x < _xMin) {
_xMin = p.x;
}
if (p.x > _xMax) {
_xMax = p.x;
}
if (p.y < _yMin) {
_yMin = p.y;
}
if (p.y > _yMax) {
_yMax = p.y;
}
}
}
}
I'm not sure this is possible using the normal methods of getBounds or getRect as these methods will just return the normal 100x100 square.
I have a few of suggestions for you.
Firstly you could apply the filters dynamically. That way you will have the numbers for the filters and you could do the maths programatically to work out what the actual size is.
You could add a second layer to the movieclip in the fla, which has the dimensions of your original square plus all the filters. Then set the alpha for this square to zero
Thirdly you could just have a png in the fla that contained the square plus, say, the glow within it.
Personally i'd go with the middle option as it would require the least amount of maintenance if the filters or the original square were to change.
Well, there is good news and bad news. The bad news is that there really isn't an effective way to do this "the right way". The good news is that there is an adequate way to approximate it.The general rule is that the size of the width is that it is approximately ( filter.blurX * 1.5 ) + sprite.width where "filter" is the Filter in the sprite.filters array. The same is true regarding blurY and height. The other general rule is that the minimum x becomes sprite.x - ( filter.blurX * 1.5 ) / 2;None of these numbers are "Adobe official", but the work within a reasonable enough margin of error to allow you to create a BitmapData based on that.

Resources