I'm doing an animated transform in Raphael (and Snap.svg, which does the same).
If I apply a rotation to a basic element, it rotates normally as I would expect. However, if I already have a previous transform applied (even if its t0,0 or r0), the element seems to scale down and back up, as though it always has to fit in its previous bounding box or something.
Here is an example fiddle
var r1 = s.rect(0,0,100,100,20,20).attr({ fill: "red", opacity: "0.8", stroke: "black", strokeWidth: "2" });
r1.transform('t0,0'); // any transform leads to shrink on rotate...
r1.animate({ transform: 'r90,50,50' }, 2000);
var r2 = s.rect(150,0,100,100,20,20).attr({ fill: "blue", opacity: "0.8", stroke: "black", strokeWidth: "2" });
r2.animate({ transform: 'r90,200,50' }, 2000);
Is there something obvious I'm missing on animated transforms as to what is happening ?
There are a couple different things you need to understand to figure out what's going on here.
The first is that your animating transform is replacing your original transform, not adding on to it. If you include the original transform instruction in the animation, you avoid the shrinking effect:
var r1 = s.rect(0,0,100,100,20,20)
.attr({ fill: "red", opacity: "0.8", stroke: "black", strokeWidth: "2" });
r1.transform('t0,0');
// any transform leads to shrink on rotate...
r1.animate({ transform: 't0,0r90,50,50' }, 5000);
//unless you repeat that transform in the animation instructions
http://jsfiddle.net/96D8t/3/
You can also avoid the shrinking effect if your original transformation is a rotation around the same center:
var r1 = s.rect(0,0,100,100,20,20)
.attr({ fill: "red", opacity: "0.8", stroke: "black", strokeWidth: "2" });
r1.transform('r0,50,50'); // no shrinking this time...
r1.animate({ transform: 'r90,50,50' }, 2000);
http://jsfiddle.net/96D8t/4/
But why should it make a difference, seeing as a translation of 0,0 or a rotation of 0 doesn't actually change the graphic? It's a side effect of the way the program calculates in-between values when you ask it to convert between two different types of transformations.
Snap/Raphael are converting your two different transformations into matrix transformations, and then interpolating (calculating intermediate values) between each value in the matrix.
A 2D graphical transformation can be represented by a matrix of the form
a c e
b d f
(that's the standard lettering)
You can think of the two rows of the matrix as two algebra formulas for determining the final x and y value, where the first number in the row is multiplied by the original x value, the second number is multiplied by the original y value, and the third number is multiplied by a constant 1:
newX = a*oldX + c*oldY + e;
newY = b*oldX + d*oldY + f;
The matrix for a do-nothing transformation like t0,0 is
1 0 0
0 1 0
Which is actually represented internally as an object with named values, like
{a:1, c:0, e:0,
b:0, d:1, f:0}
Either way, it just says that the newX is 1 times the oldX, and the newY is 1 times the oldY.
The matrix for your r90,50,50 command is:
0 -1 100
1 0 0
I.e., if your old point is (50,100), the formulas are
newX = 0*50 -1*100 + 100*1 = 0
newY = 1*50 + 0*100 + 0 = 50
The point (50,100) gets rotated 90degrees around the point (50,50) to become (0,50), just as expected.
Where it starts getting unexpected is when you try to transform
1 0 0
0 1 0
to
0 -1 100
1 0 0
If you transform each number in the matrix from the start value to the end value, the half-way point would be
0.5 -0.5 50
0.5 0.5 0
Which works out as the matrix for scaling the rectangle down by half and rotating it 45degrees around (50,50).
All of that might be more math than you needed to know, but I hope it helps make sense of what's going on.
Regardless, the easy solution is to make sure that you always match up the types of transforms before and after the animation, so that the program can interpolate the original values, instead of the matrix values.
Related
I'd like to implement something like the powerpoint image below. A gradient that goes between three values.
It starts at A (-1), the mid point is B (0), and the end is C (1).
I have realised that I can save some effort by calculating the 'start' as a-to-b, and the 'end' as b-to-c. I can do as 2 sets of 2 gradients, instead of 1 gradient with three values.
But I'm stumped (despite googling) on how to get from one colour to another - ideally in the RGB colour space.
I'd like to be able to have something like this -
const colourSpace = (value, startColor, endColor) => {...}
colorSpace(-0.25, red, yellow) // some sort of orangey color
colorSpace(1, yellow, green) // fully green
colorSpace(0.8, yellow, green) // mostly green
This isn't a front-end application, so no CSS gradients - which is what google was mostly referencing.
Thanks all,
Ollie
If you aren't too worried about being perceptually consistent across the color space (you would need to work in something like LAB mode to do that), you can just take the linear interpolation in RGB space. Basically you take a distance (between 0 and 1), multiply it by the different in the coordinates, and add it to the first one. This will allow you to find arbitrary points (i.e colors) along the line between any two colors.
For example between red and yellow:
let canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
let rgb1 = [255, 0, 0] // red
let rgb2 = [255, 255, 0] // yellow
function getPoint(d, a1, a2) {
// find a color d% between a1 and a2
return a1.map((p, i) => Math.floor(a1[i] + d * (a2[i] - a1[i])))
}
// for demo purposes fill a canvas
for (let i = 0, j = 0; i < 1; i += .002, j++) {
let rgb = getPoint(i, rgb1, rgb2)
ctx.fillStyle = `rgba(${rgb.join(",")}, 1)`
ctx.fillRect(j, 0, 1, 200);
}
<canvas id="canvas" width="500"></canvas>
You can repeat this to get multiple 'stops' in the gradient.
I ended up using Chroma for converting between colour spaces.
I am using ILNumerics to generate a surface plot.
I want to use a flat shaded color map (i.e. color ranges) instead of a smooth shaded color map (i.e. each pixel has it's own color).
Is this possible with ILNumerics?
Example of Flat-Shaded surface plot and color bar legend:
Example of Smooth-Shaded surface plot and color bar legend:
You can create a colormap which exposes a flat shading behavior. Just duplicate the keypoints existing in a common colormap in order to model a range of color data getting the same color assigned.
How flat shaded colormaps are working
According to the documentation the keypoints for colormaps consist out of 5 columns: a "position" and 4 color values (RGBA). In order to model a 'flat' shaded colormap, place two keypoints 'almost' exactly on top of each other, giving the first one the color of the next lower range and the second one the color of the next higher range. A color range therefore is modeled by two keypoints having the same color assigned.
I wrote 'almost' in the above paragraph, because I thought you have to leave at least a tiny gap between the edges of two ranges - hoping no actual color data value will ever hit that gap. But it seems, there is no gap needed at all and one can give both keypoints exactly the same value. But you will have to be careful while sorting: don't mix the colors up (the quicksort in ILMath.sort() is not stable!)
In the following example a flat shaded colormap is created from Colormaps.Jet:
Keypoints for Colormaps.Jet (original, interpolating)
<Single> [6,5]
[0]: 0 0 0 0,5625 1
[1]: 0,1094 0 0 0,9375 1
[2]: 0,3594 0 0,9375 1 1
[3]: 0,6094 0,9375 1 0,0625 1
[4]: 0,8594 1 0,0625 0 1
[5]: 1 0,5000 0 0 1
The flat shading version derived from it:
Colormaps.Jet - flat shading version
<Single> [11,5]
[0]: 0 0 0 0,5625 1
[1]: 0,1094 0 0 0,5625 1
[2]: 0,1094 0 0 0,9375 1
[3]: 0,3594 0 0 0,9375 1
[4]: 0,3594 0 0,9375 1 1
[5]: 0,6094 0 0,9375 1 1
[6]: 0,6094 0,9375 1 0,0625 1
[7]: 0,8594 0,9375 1 0,0625 1
[8]: 0,8594 1 0,0625 0 1
[9]: 1,0000 1 0,0625 0 1
[10]: 1 0,5000 0 0 1
As you can easily see, I made a mistake in CreateFlatShadedColormap(): The last keypoint with (0.5,0,0,1) will never be used. I'll leave it as an exercise to fix that... ;)
Full Flat Shaded Example
private void ilPanel1_Load(object sender, EventArgs e) {
ILArray<float> A = ILMath.tosingle(ILSpecialData.terrain["0:400;0:400"]);
// derive a 'flat shaded' colormap from Jet colormap
var cm = new ILColormap(Colormaps.Jet);
ILArray<float> cmData = cm.Data;
cmData.a = Computation.CreateFlatShadedColormap(cmData);
cm.SetData(cmData);
// display interpolating colormap
ilPanel1.Scene.Add(new ILPlotCube() {
Plots = {
new ILSurface(A, colormap: Colormaps.Jet) {
Children = { new ILColorbar() },
Wireframe = { Visible = false }
}
},
ScreenRect = new RectangleF(0,-0.05f,1,0.6f)
});
// display flat shading colormap
ilPanel1.Scene.Add(new ILPlotCube() {
Plots = {
new ILSurface(A, colormap: cm) {
Children = { new ILColorbar() },
Wireframe = { Visible = false }
}
},
ScreenRect = new RectangleF(0, 0.40f, 1, 0.6f)
});
}
private class Computation : ILMath {
public static ILRetArray<float> CreateFlatShadedColormap(ILInArray<float> cm) {
using (ILScope.Enter(cm)) {
// create array large enough to hold new colormap
ILArray<float> ret = zeros<float>(cm.S[0] * 2 - 1, cm.S[1]);
// copy the original
ret[r(0, cm.S[0] - 1), full] = cm;
// double original keypoints, give small offset (may not even be needed?)
ret[r(cm.S[0], end), 0] = cm[r(1, end), 0] - epsf;
ret[r(cm.S[0], end), r(1, end)] = cm[r(0, end - 1), r(1, end)];
// reorder to sort keypoints in ascending order
ILArray<int> I = 1;
sort(ret[full, 0], Indices: I);
return ret[I, full];
}
}
Result
This is not possible. Surface plots in ILNumerics always interpolate colors between grid points. For other shading models you must create your own surface class.
So, I'm using snap.svg and I'd like to dynamically rotate an object over time. Something like this:
function rotateObject()
{
myObject.rotation += value;
}
The problem is I don't know how to access the rotation values for my display objects (or if they even exist!) So given something simple, let's say a circle declared like this:
snap = Snap(800,600);
circle = snap.circle(400,300,50);
I know I can access the x and y values like so:
circle.attr("cx");
circle.attr("cy");
What I need help with is:
Is there a rotation property of some sort that I can use to rotate this object?
If not, how do I rotate an object with snap.svg?
Better rotate objects using Snap.Matrix()
The way suggested by Ian, works for me perfectly while I used Chrome version < 36.0
When I updated Chrome to 36.0.1985.125 I saw bug with text rotation.
So, the soulution was using
var matrix = new Snap.Matrix();
matrix.rotate(-90, x, y);
Paper.text(x, y, 'Text').attr({
fontWeight: 'bold',
fill: '#434343',
transform: matrix
});
instead of
Paper.text(x, y, 'Text').attr({
fontWeight: 'bold',
fill: '#434343',
transform: 'r-90'
});
Maybe it will be useful for somebody.
Ideally you will control the rotation yourself, (rather than figuring it out from the attributes which is possible, but fiddlier). Animation can be easier, depending on what you need. Here is an example showing some basic animation with a rect (as circle rotation is just itself if around the centre)...
s = Snap(400, 620);
var myRect = s.rect(100, 100, 100, 200).attr({
fill : 'white',
stroke : 'black'
});
var myRotate = 45;
// if you wanted to rotate manually using your own adjust variable then this
// myRect.transform("r" + myRotate);
// but simpler in most cases to use the animate method
myRect.animate( { transform: "r" + myRotate + ",150,200" }, 1000 ); //rotate around centre of 150,200
Fiddle at http://jsfiddle.net/XG7ks/6/
Really it would probably be best to get a basic grounding on transformations with SVG (and translate, rotate, scale) just for it to make a bit more sense. You can 'see' the resultant transform with myRect.attr('transform') but I would probably leave that just at first.
How can I find the scale ratio a rotated Rect element in order fit it in a bounding rectangle (unrotated) of a specific size?
Basically, I want the opposite of getBoundingClientRect, setBoundingClientRect.
First you need to get the transform applied to the element, with <svg>.getTransformToElement, together with the result of rect.getBBox() you can calculate the actual size. Width this you can calculate the scale factor to the desired size and add it to the transform of the rect. With this I mean that you should multiply actual transform matrix with a new scale-matrix.
BUT: This is a description for a case where you are interested in the AABB, means axis aligned bounding box, what the result of getBoundingClientRect delivers, for the real, rotated bounding box, so the rectangle itself in this case, you need to calculate (and apply) the scale factor from the width and/or height.
Good luck…
EDIT::
function getSVGPoint( x, y, matrix ){
var p = this._dom.createSVGPoint();
p.x = x;
p.y = y;
if( matrix ){
p = p.matrixTransform( matrix );
}
return p;
}
function getGlobalBBox( el ){
var mtr = el.getTransformToElement( this._dom );
var bbox = el.getBBox();
var points = [
getSVGPoint.call( this, bbox.x + bbox.width, bbox.y, mtr ),
getSVGPoint.call( this, bbox.x, bbox.y, mtr ),
getSVGPoint.call( this, bbox.x, bbox.y + bbox.height, mtr ),
getSVGPoint.call( this, bbox.x + bbox.width, bbox.y + bbox.height, mtr ) ];
return points;
};
with this code i one time did a similar trick... this._dom refers to a <svg> and el to an element. The second function returns an array of points, beginning at the top-right edge, going on counter clockwise arround the bbox.
EDIT:
the result of <element>.getBBox() does not include the transform that is applied to the element and I guess that the new desired size is in absolute coordinates. So the first thing you need to is to make the »BBox« global.
Than you can calculate the scaling factor for sx and sy by:
var sx = desiredWidth / globalBBoxWidth;
var sy = desiredHeight / globalBBoxHeight;
var mtrx = <svg>.createSVGMatrix();
mtrx.a = sx;
mtrx.d = sy;
Than you have to append this matrix to the transform list of your element, or concatenate it with the actual and replace it, that depends on you implementation. The most confusion part of this trick is to make sure that you calculate the scaling factors with coordinates in the same transformation (where absolute ones are convenient). After this you apply the scaling to the transform of the <element>, do not replace the whole matrix, concatenate it with the actually applied one, or append it to the transform list as new item, but make sure that you do not insert it before existing item. In case of matrix concatenation make sure to preserve the order of multiplication.
The last steps depend on your Implementation, how you handle the transforms, if you do not know which possibilities you have, take a look here and take special care for the DOMInterfaces you need to implement this.
I have a system that requires moving an image on the screen. I am currently using a png and just placing it at the desired screen coordinates.
Because of a combination of the screen resolution and the required frame rate, some frames are identical because the image has not yet moved a full pixel. Unfortunately, the resolution of the screen is not negotiable.
I have a general understanding of how sub-pixel rendering works to smooth out edges but I have been unable to find a resource (if it exists) as to how I can use shading to translate an image by less than a single pixel.
Ideally, this would be usable with any image but if it was only possible with a simple shape like a circle or a ring, that would also be acceptable.
Sub-pixel interpolation is relatively simple. Typically you apply what amounts to an all-pass filter with a constant phase shift, where the phase shift corresponds to the required sub-pixel image shift. Depending on the required image quality you might use e.g. a 5 point Lanczos or other windowed sinc function and then apply this in one or both axes depending on whether you want an X shift or a Y shift or both.
E.g. for a 0.5 pixel shift the coefficients might be [ 0.06645, 0.18965, 0.27713, 0.27713, 0.18965 ]. (Note that the coefficients are normalised, i.e. their sum is equal to 1.0.)
To generate a horizontal shift you would convolve these coefficients with the pixels from x - 2 to x + 2, e.g.
const float kCoeffs[5] = { 0.06645f, 0.18965f, 0.27713f, 0.27713f, 0.18965f };
for (y = 0; y < height; ++y) // for each row
for (x = 2; x < width - 2; ++x) // for each col (apart from 2 pixel border)
{
float p = 0.0f; // convolve pixel with Lanczos coeffs
for (dx = -2; dx <= 2; ++dx)
p += in[y][x + dx] * kCoeffs[dx + 2];
out[y][x] = p; // store interpolated pixel
}
Conceptually, the operation is very simple. First you scale up the image (using any method of interpolation, as you like), then you translate the result, and finally you subsample down to the original image size.
The scale factor depends on the precision of sub-pixel translation you want to do. If you want to translate by 0.5 degrees, you need scale up the original image by a factor of 2 then you translate the resulting image by 1 pixel; if you want to translate by 0.25 degrees, you need to scale up by a factor of 4, and so on.
Note that this implementation is not efficient because when you scale up you end up calculating pixel values that you won't actually use because they're just dropped when you subsample back to the original image size. The implementation in Paul's answer is more efficient.