I'm a bit new to canvas and such so forgive if it's a trivial question.
I'd like to be able to animate an object following a path (defined as bezier path) but I'm not sure how to do it.
I've looked at Raphael but I can't work out how to follow the path over time.
Cake JS looked promising in the demo, but I'm really struggling the documentation, or lack thereof in this case.
Has anyone got some working example of this?
Use the code on my website from this related question, but instead of changing the .style.left and such in the callback, erase and re-draw your canvas with the item at the new location (and optionally rotation).
Note that this uses SVG internally to easily interpolate points along a bézier curve, but you can use the points it gives you for whatever you want (including drawing on a Canvas).
In case my site is down, here's a current snapshot of the library:
function CurveAnimator(from,to,c1,c2){
this.path = document.createElementNS('http://www.w3.org/2000/svg','path');
if (!c1) c1 = from;
if (!c2) c2 = to;
this.path.setAttribute('d','M'+from.join(',')+'C'+c1.join(',')+' '+c2.join(',')+' '+to.join(','));
this.updatePath();
CurveAnimator.lastCreated = this;
}
CurveAnimator.prototype.animate = function(duration,callback,delay){
var curveAnim = this;
// TODO: Use requestAnimationFrame if a delay isn't passed
if (!delay) delay = 1/40;
clearInterval(curveAnim.animTimer);
var startTime = new Date;
curveAnim.animTimer = setInterval(function(){
var now = new Date;
var elapsed = (now-startTime)/1000;
var percent = elapsed/duration;
if (percent>=1){
percent = 1;
clearInterval(curveAnim.animTimer);
}
var p1 = curveAnim.pointAt(percent-0.01),
p2 = curveAnim.pointAt(percent+0.01);
callback(curveAnim.pointAt(percent),Math.atan2(p2.y-p1.y,p2.x-p1.x)*180/Math.PI);
},delay*1000);
};
CurveAnimator.prototype.stop = function(){
clearInterval(this.animTimer);
};
CurveAnimator.prototype.pointAt = function(percent){
return this.path.getPointAtLength(this.len*percent);
};
CurveAnimator.prototype.updatePath = function(){
this.len = this.path.getTotalLength();
};
CurveAnimator.prototype.setStart = function(x,y){
var M = this.path.pathSegList.getItem(0);
M.x = x; M.y = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setEnd = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x = x; C.y = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setStartDirection = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x1 = x; C.y1 = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setEndDirection = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x2 = x; C.y2 = y;
this.updatePath();
return this;
};
…and here's how you might use it:
var ctx = document.querySelector('canvas').getContext('2d');
ctx.fillStyle = 'red';
var curve = new CurveAnimator([50, 300], [350, 300], [445, 39], [1, 106]);
curve.animate(5, function(point, angle) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillRect(point.x-10, point.y-10, 20, 20);
});
In action: http://jsfiddle.net/Z2YSt/
So, here is the verbose version:
t being any number between 0 and 1 representing time; the p0, p1, p2, p3 objects are the start point, the 1st control point, the 2nd control point an the end point respectively:
var at = 1 - t;
var green1x = p0.x * t + p1.x * at;
var green1y = p0.y * t + p1.y * at;
var green2x = p1.x * t + p2.x * at;
var green2y = p1.y * t + p2.y * at;
var green3x = p2.x * t + p3.x * at;
var green3y = p2.y * t + p3.y * at;
var blue1x = green1x * t + green2x * at;
var blue1y = green1y * t + green2y * at;
var blue2x = green2x * t + green3x * at;
var blue2y = green2y * t + green3y * at;
var finalx = blue1x * t + blue2x * at;
var finaly = blue1y * t + blue2y * at;
Here is a ball using <canvas> following a path in JSfiddle
The names of the variables come from this gif wich is the best explication for bezier curves: http://en.wikipedia.org/wiki/File:Bezier_3_big.gif
A short version of the code, inside a function ready to copy/paste:
var calcBezierPoint = function (t, p0, p1, p2, p3) {
var data = [p0, p1, p2, p3];
var at = 1 - t;
for (var i = 1; i < data.length; i++) {
for (var k = 0; k < data.length - i; k++) {
data[k] = {
x: data[k].x * at + data[k + 1].x * t,
y: data[k].y * at + data[k + 1].y * t
};
}
}
return data[0];
};
Related stuff:
http://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html
http://13thparallel.com/archive/bezier-curves/
http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js
http://www.youtube.com/watch?v=hUCT4b4wa-8
I wouldn't use Canvas for this unless you really have to. SVG has animation along a path built in. Canvas requires quite a bit of math to get it working.
Here's one example of SVG animating along a path.
Here's some discussion about it for raphael: SVG animation along path with Raphael
Please note that Raphael uses SVG and not HTML5 Canvas.
One way to animate along a bezier path in Canvas is to continuously bisect the bezier curve, recoring the midpoints until you have a lot of points (say, 50 points per curve) that you can animate the object along that list of points. Search for bisecting beziers and similar queries for the related math on that.
Related
I need to check if an image exists in another image using JavaScript, I need to know what are the best approaches (algorithm) and solutions (ex: librarie) to do this operations
I explained what I need to do in this image:
Using the GPU to help in image processing.
Using the 2D API and some simple tricks you can exploit the GPUs power to speed up Javascript.
Difference
To find an image you need to compare the pixels you are looking for (A) against the pixels in the image (B). If the difference between the Math.abs(A-B) === 0 then the pixels are the same.
A function to do this may look like the following
function findDif(imageDataSource, imageDataDest, xx,yy)
const ds = imageDataSource.data;
const dd = imageDataDest.data;
const w = imageDataSource.width;
const h = imageDataSource.height;
var x,y;
var dif = 0;
for(y = 0; y < h; y += 1){
for(x = 0; x < w; x += 1){
var indexS = (x + y * w) * 4;
var indexD = (x + xx + (y + yy) * imageDataDest.width) * 4;
dif += Math.abs(ds[indexS]-dd[indexD]);
dif += Math.abs(ds[indexS + 1]-dd[indexD + 1]);
dif += Math.abs(ds[indexS + 2]-dd[indexD + 2]);
}
}
return dif;
}
var source = sourceCanvas.getContext("2d").getImageData(0,0,sourceCanvas.width,sourceCanvas.height);
var dest = destinationCanvas.getContext("2d").getImageData(0,0,destinationCanvas.width,destinationCanvas.height);
if(findDif(source,dest,100,100)){ // is the image at 100,100?
// Yes image is very similar
}
Where the source is the image we are looking for and the dest is the image we want to find it in. We run the function for every location that the image may be and if the result is under a level then its a good chance we have found it.
But this is very very slow in JS. This is where the GPU can help.
Using the ctx.globalCompositeOperation = "difference"; operation we can speed up the process as it will do the difference calculation for us
When you render with the comp operation "difference" the resulting pixels are the difference between the pixels you are drawing and those that are already on the canvas. Thus if you draw on something that is the same the result is all pixels are black (no difference)
To find a similar image in the image you render the image you are testing for at each location on the canvas that you want to test for. Then you get the sum of all the pixels you just rendered on, if the result is under a threshold that you have set then the image under that area is very similar to the image you are testing for.
But we still need to count all the pixels one by one.
A GPU mean function
The comp op "difference" already does the pixel difference calculation for you, but to get the sum you can use the inbuilt image smoothing.
After you have rendered to find the difference you take that area and render it at a smaller scale with ctx.imageSmoothingEnabled = true the default setting. The GPU will do something similar to an average and can reduce the amount of work JS has to do by several orders of magnitude.
Now instead of 100s or 1000s of pixels you can reduce it down to as little at 4 or 16 depending on the accuracy you need.
An example.
Using these methods you can get a near realtime image in image search with just the basic numerical analysis.
Click to start a test. Results are shown plus the time it took. The image that is being searched for is in the top right.
//------------------------------------------------------------------------
// Some helper functions
var imageTools = (function () {
var tools = {
canvas(width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var i = this.canvas(width, height);
i.ctx = i.getContext("2d");
return i;
},
image2Canvas(img) {
var i = this.canvas(img.width, img.height);
i.ctx = i.getContext("2d");
i.ctx.drawImage(img, 0, 0);
return i;
},
copyImage(img){ // just a named stub
return this.image2Canvas(img);
},
};
return tools;
})();
const U = undefined;
const doFor = (count, callback) => {var i = 0; while (i < count && callback(i ++) !== true ); };
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randA = (array) => array[(Math.random() * array.length) | 0];
const randG = (min, max = min + (min = 0)) => Math.random() * Math.random() * Math.random() * Math.random() * (max - min) + min;
// end of helper functions
//------------------------------------------------------------------------
function doit(){
document.body.innerHTML = ""; // clear the page;
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
// a grid of 36 images
canvas.width = 6 * 64;
canvas.height = 6 * 64;
console.log("test");
// get a random character to look for
const digit = String.fromCharCode("A".charCodeAt(0) + randI(26));
// get some characters we dont want
const randomDigits = setOf(6,i=>{
return String.fromCharCode("A".charCodeAt(0) + randI(26));
})
randomDigits.push(digit); // add the image we are looking for
var w = canvas.width;
var h = canvas.height;
// create a canvas for the image we are looking for
const imageToFind = imageTools.createImage(64,64);
// and a larger one to cover pixels on the sides
const imageToFindExtend = imageTools.createImage(128,128);
// Draw the character onto the image with a white background and scaled to fit
imageToFindExtend.ctx.fillStyle = imageToFind.ctx.fillStyle = "White";
imageToFind.ctx.fillRect(0,0,64,64);
imageToFindExtend.ctx.fillRect(0,0,128,128);
ctx.font = imageToFind.ctx.font = "64px arial black";
ctx.textAlign = imageToFind.ctx.textAlign = "center";
ctx.textBaseline = imageToFind.ctx.textBaseline = "middle";
const digWidth = imageToFind.ctx.measureText(digit).width+8;
const scale = Math.min(1,64/digWidth);
imageToFind.ctx.fillStyle = "black";
imageToFind.ctx.setTransform(scale,0,0,scale,32,32);
imageToFind.ctx.fillText(digit,0,0);
imageToFind.ctx.setTransform(1,0,0,1,0,0);
imageToFindExtend.ctx.drawImage(imageToFind,32,32);
imageToFind.extendedImage = imageToFindExtend;
// Now fill the canvas with images of other characters
ctx.fillStyle = "white";
ctx.setTransform(1,0,0,1,0,0);
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "black";
ctx.strokeStyle = "white";
ctx.lineJoin = "round";
ctx.lineWidth = 12;
// some characters will be rotated 90,180,-90 deg
const dirs = [
[1,0,0,1,0,0],
[0,1,-1,0,1,0],
[-1,0,0,-1,1,1],
[0,-1,1,0,0,1],
]
// draw random characters at random directions
doFor(h / 64, y => {
doFor(w / 64, x => {
const dir = randA(dirs)
ctx.setTransform(dir[0] * scale,dir[1] * scale,dir[2] * scale,dir[3] * scale,x * 64 + 32, y * 64 + 32);
const d = randA(randomDigits);
ctx.strokeText(d,0,0);
ctx.fillText(d,0,0);
});
});
ctx.setTransform(1,0,0,1,0,0);
// get a copy of the canvas
const saveCan = imageTools.copyImage(ctx.canvas);
// function that finds the images
// image is the image to find
// dir is the matrix direction to find
// smapleSize is the mean sampling size samller numbers are quicker
function checkFor(image,dir,sampleSize){
const can = imageTools.copyImage(saveCan);
const c = can.ctx;
const stepx = 64;
const stepy = 64;
// the image that will contain the reduced means of the differences
const results = imageTools.createImage(Math.ceil(w / stepx) * sampleSize,Math.ceil(h / stepy) * sampleSize);
const e = image.extendedImage;
// for each potencial image location
// set a clip area and draw the source image on it with
// comp mode "difference";
for(var y = 0 ; y < h; y += stepy ){
for(var x = 0 ; x < w; x += stepx ){
c.save();
c.beginPath();
c.rect(x,y,stepx,stepy);
c.clip();
c.globalCompositeOperation = "difference";
c.setTransform(dir[0],dir[1],dir[2],dir[3],x +32 ,y +32 );
c.drawImage(e,-64,-64);
c.restore();
}
}
// Apply the mean (reducing nnumber of pixels to check
results.ctx.drawImage(can,0,0,results.width,results.height);
// get the pixel data
var dat = new Uint32Array(results.ctx.getImageData(0,0,results.width,results.height).data.buffer);
// for each area get the sum of the difference
for(var y = 0; y < results.height; y += sampleSize){
for(var x = 0; x < results.width; x += sampleSize){
var val = 0;
for(var yy = 0; yy < sampleSize && y+yy < results.height; yy += 1){
var i = x + (y+yy)*results.width;
for(var xx = 0; xx < sampleSize && x + xx < results.width ; xx += 1){
val += dat[i++] & 0xFF;
}
}
// if the sum is under the threshold we have found an image
// and we mark it
if(val < sampleSize * sampleSize * 5){
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.lineWidth = 2;
ctx.strokeRect(x * (64/sampleSize),y * (64/sampleSize),64,64);
ctx.fillRect(x * (64/sampleSize),y * (64/sampleSize),64,64);
foundCount += 1;
}
}
}
}
var foundCount = 0;
// find the images at different orientations
var now = performance.now();
checkFor(imageToFind,dirs[0],4);
checkFor(imageToFind,dirs[1],6); // rotated images need larger sample size
checkFor(imageToFind,dirs[2],6);
checkFor(imageToFind,dirs[3],6);
var time = performance.now() - now;
var result = document.createElement("div");
result.textContent = "Found "+foundCount +" matching images in "+time.toFixed(3)+"ms. Click to try again.";
document.body.appendChild(result);
// show the image we are looking for
imageToFind.style.left = (64*6 + 16) + "px";
imageToFind.id = "lookingFor";
document.body.appendChild(imageToFind);
}
document.addEventListener("click",doit);
canvas {
border : 2px solid black;
position : absolute;
top : 28px;
left : 2px;
}
#lookingFor {
border : 4px solid red;
}
div {
border : 2px solid black;
position : absolute;
top : 2px;
left : 2px;
}
Click to start test.
Not perfect
The example is not perfect and will sometimes make mistakes. There is a huge amount of room for improving both the accuracy and the speed. This is just something I threw together as an example to show how to use the GPU via the 2D API. Some further maths will be needed to find the statistically good results.
This method can also work for different scales, and rotations, you can even use some of the other comp modes to remove colour and normalize contrast. I have used a very similar approch to stabilize webcam by tracking points from one frame to the next, and a veriaty of other image tracking uses.
I am trying to get middle point of a line (a line in a rectangle polygon), but following code does not give me correct answer. The number is too small. Can anyone help?
The code is modified from here: http://www.yourhomenow.com/house/haversine.html
var p1 = myRectPolygon.getPath().getAt(0);
var p2 = myRectPolygon.getPath().getAt(3);
var lat1 = p1.lat().toRad();
var lat2 = p2.lat().toRad();
var lon1 = p1.lng().toRad();
var dLon = (p2.lng() - p1.lng()).toRad();
var Bx = Math.cos(lat2) * Math.cos(dLon);
var By = Math.cos(lat2) * Math.sin(dLon);
lat3 = Math.atan2(Math.sin(lat1)+Math.sin(lat2),
Math.sqrt((Math.cos(lat1)+Bx)*(Math.cos(lat1)+Bx) + By*By ) );
lon3 = lon1.toRad() + Math.atan2(By, Math.cos(lat1) + Bx);
var mid_latLng = new google.maps.LatLng(lat3, lon3);
You are converting lon1 to rad twice:
You declared:
var lon1 = p1.lng().toRad();
And later you do:
lon3 = lon1.toRad() + Math.atan2(By, Math.cos(lat1) + Bx);
try changing it to
lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx);
I also found this solution (Using the Haversine Formula in Javascript) which indicates that doing something like the following may not work correctly
(lon2-lon1).toRad();
and to instead do something like
var dLonDeg = lon2-lon1
var dlon = dLonDeg.toRad()
You can use the geometry library to compute the midpoint:
var p1 = myRectPolygon.getPath().getAt(0);
var p2 = myRectPolygon.getPath().getAt(3);
var mid_latLng = google.maps.geometry.spherical.computeOffset(p1,
google.maps.geometry.spherical.computeDistanceBetween(p1, p2) / 2,
google.maps.geometry.spherical.computeHeading(p1, p2));
proof of concept fiddle
I have Image component inside some container with clipAndEnableScrolling property set to true. I need a static method which gets this Image, rotation angle and rotates Image around center point of container without loosing any previous transformations. The best method I've created adds error after few rotations.
I thing it must work like this
public static function rotateImageAroundCenterOfViewPort(image:Image, value:int):void
{
// Calculate rotation and shifts
var bounds:Rectangle = image.getBounds(image.parent);
var angle:Number = value - image.rotation;
var radians:Number = angle * (Math.PI / 180.0);
var shiftByX:Number = image.parent.width / 2 - bounds.x;
var shiftByY:Number = image.parent.height / 2 - bounds.y;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-shiftByX, -shiftByY);
matrix.rotate(radians);
matrix.translate(+shiftByX, +shiftByY);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
}
but it doesn't. Looks like I can't understand how transformation works(
If you are trying to rotate the object around it's center, I think you'll want some more like this:
var matrix:Matrix = image.transform.matrix;
var rect:Rectangle = image.getBounds( insertParentObject );
//translate matrix to center
matrix.translate(- ( rect.left + ( rect.width/2 ) ), - ( rect.top + ( rect.height/2 ) ) );
matrix.rotate(radians);
//translate back
matrix.translate(rect.left + ( rect.width/2 ), rect.top + ( rect.height/2 ) );
image.transform.matrix = matrix;
Also here is a link to the same SO question with varying answers including the one I provided:
Flex/ActionScript - rotate Sprite around its center
As discussed in the comments if you are looking to rotate an object around a point (that is the center of your container), here's a function that I think would work:
//pass rotateAmount as the angle you want to rotate in degrees
private function rotateAround( rotateAmount:Number, obj:DisplayObject, origin:Point, distance:Number = 100 ):void {
var radians:Number = rotateAmount * Math.PI / 180;
obj.x = origin.x + distance * Math.cos( radians );
obj.y = origin.y + distance * Math.sin( radians );
}
Then you just call it:
rotateAround( rotateAmount, image, new Point( container.width/2, container.height/2 ) );
The last parameter distance you can pass whatever you like, so for example if I wanted a distance of the image vector length:
var dx:Number = spr.x - stage.stageWidth/2;
var dy:Number = spr.y - stage.stageHeight/2;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
rotateAround( rotateAmount, image, new Point( container.width/2, container.height/2 ), dist );
Here's the solution I've found:
public static function rotateImageAroundCenterOfViewPort(image:Image, value:int):void
{
// Calculate rotation and shifts
var center:Point = new Point(image.parent.width / 2, image.parent.height / 2);
center = image.parent.localToGlobal(center);
center = image.globalToLocal(center);
var angle:Number = value - image.rotation;
var radians:Number = angle * (Math.PI / 180.0);
var shiftByX:Number = center.x;
var shiftByY:Number = center.y;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-shiftByX, -shiftByY);
matrix.rotate(radians);
matrix.translate(+shiftByX, +shiftByY);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
image.rotation = Math.round(image.rotation);
}
Teste only with the angles like 90, 180 etc. (I don't need any else).
UPDATE: Once I got this demo working... holy smokes, it's SLOW, like 12-16 seconds for only a level 2 render (when image is around 1000x2000 pixels). This is not even worth bothering with.
I found this really awesome and hopeful looking code in the top answer here: Resizing an image in an HTML5 canvas
//returns a function that calculates lanczos weight
function lanczosCreate(lobes){
return function(x){
if (x > lobes)
return 0;
x *= Math.PI;
if (Math.abs(x) < 1e-16)
return 1
var xx = x / lobes;
return Math.sin(x) * Math.sin(xx) / x / xx;
}
}
//elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes){
this.canvas = elem;
elem.width = img.width;
elem.height = img.height;
elem.style.display = "none";
this.ctx = elem.getContext("2d");
this.ctx.drawImage(img, 0, 0);
this.img = img;
this.src = this.ctx.getImageData(0, 0, img.width, img.height);
this.dest = {
width: sx,
height: Math.round(img.height * sx / img.width),
};
this.dest.data = new Array(this.dest.width * this.dest.height * 3);
this.lanczos = lanczosCreate(lobes);
this.ratio = img.width / sx;
this.rcp_ratio = 2 / this.ratio;
this.range2 = Math.ceil(this.ratio * lobes / 2);
this.cacheLanc = {};
this.center = {};
this.icenter = {};
setTimeout(this.process1, 0, this, 0);
}
thumbnailer.prototype.process1 = function(self, u){
self.center.x = (u + 0.5) * self.ratio;
self.icenter.x = Math.floor(self.center.x);
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio;
self.icenter.y = Math.floor(self.center.y);
var a, r, g, b;
a = r = g = b = 0;
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width)
continue;
var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
if (!self.cacheLanc[f_x])
self.cacheLanc[f_x] = {};
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height)
continue;
var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
if (self.cacheLanc[f_x][f_y] == undefined)
self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
weight = self.cacheLanc[f_x][f_y];
if (weight > 0) {
var idx = (j * self.src.width + i) * 4;
a += weight;
r += weight * self.src.data[idx];
g += weight * self.src.data[idx + 1];
b += weight * self.src.data[idx + 2];
}
}
}
var idx = (v * self.dest.width + u) * 3;
self.dest.data[idx] = r / a;
self.dest.data[idx + 1] = g / a;
self.dest.data[idx + 2] = b / a;
}
if (++u < self.dest.width)
setTimeout(self.process1, 0, self, u);
else
setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self){
self.canvas.width = self.dest.width;
self.canvas.height = self.dest.height;
self.ctx.drawImage(self.img, 0, 0);
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
var idx, idx2;
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 3;
idx2 = (j * self.dest.width + i) * 4;
self.src.data[idx2] = self.dest.data[idx];
self.src.data[idx2 + 1] = self.dest.data[idx + 1];
self.src.data[idx2 + 2] = self.dest.data[idx + 2];
}
}
self.ctx.putImageData(self.src, 0, 0);
self.canvas.style.display = "block";
}
...
img.onload = function() {
var canvas = document.createElement("canvas");
new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
//but feel free to raise it up to 8. Your client will appreciate
//that the program makes full use of his machine.
document.body.appendChild(canvas);
}
However, this implementation loads an image and renders it, end of story.
I have been trying to re-implement this code so that it does the filtering every time an existing canvas is scaled (think, zooming in and out of an image or document) without having to load a new image or create a new canvas.
How can I adapt it to work this way? Or is that even possible?
What you want to do is something like a singleton to reuse your canvas object. This will let you save the cost of create a new canvas object each time and you will reuse the same object
function getCanvas(){
var canvas;
if (typeof canvas === "undefined"){ canvas = document.createElement("canvas");}
return canvas;
}
img.onload = function() {
var canvas = getCanvas("canvas");
.... THE REST OF YOUR CODE .......
}
.
However this is not what slows your code, image scaling Algorithms are really heavy algorithms with intensive cpu use "usually make use of gpu acceleration at a really low level", and use advanced techniques like multiple bufferr and so others. here is a interesting tutorial in java.net on how image scaling works, it is in java but you can interpolate to any language.
Javascript is not ready for this techniques, so I recommend you to use the transformations available in the canvas api, as in the tutorial you read the efficient way is using the canvas2Dcontext.
var ctx = canvas.getContext("2d");
ctx.scale(2,2);
I've read a number of similar questions to this on here, but unfortunately none of them seem to give the exact answer I'm after, or they might but the maths is beyond me!
I'm creating a game where you have a cannon at the left edge of the screen. I want to be able to fire a cannonball from the cannon in an arc so that it intersects where the mouse pointer is on the screen.
I've seen a few examples that move a projectile in an arc from point a to point b, but what I need is for the cannonball to first move along the axis of the cannon itself, it's no good if the ball leaves the end of the cannon at a different angle to which the cannon is pointing.
The only force acting on the ball will be gravity and it's starting velocity.
Also to complicate matters, I need the cannons angle to change according to how far away the mouse pointer is from the end of the cannon, so if the pointer is far away than the cannon will point upwards say at an angle of 45 degrees, but if the pointer is very close to the end of the cannon then the cannon will point directly at the pointer, this I've more or less already got working by just getting the distance between them and then dividing it by a number and subtracting it from the rotation value of the cannon, but it's a bit of a rough way of doing it.
EDIT
Using the code below I've managed to the line in the screen shot below. But as you can see it's not the trajectory I need, I need something more like the red line I've put in.
And here's how I've implemented the code (probably wrongly)
public class GameTurretLine2
{
var rt:Object = null;
var lineMc:MovieClip = new MovieClip();
var myTurret:GameMainGun = null;
var pta:Point = new Point(0,0);
var ptb:Point = new Point(0,0);
var ptc:Point = new Point(0,0);
var ptd:Point = new Point(0,0);
public function GameTurretLine2(rt2,turret)
{
rt = rt2;
myTurret = turret;
lineMc.graphics.lineStyle(2, 0x55aa00);
mainLoop();
rt.rt.GameLayers.turretLineMc.addChild(lineMc);
}
function mainLoop()
{
lineMc.graphics.clear();
//get points
var turretEnd:Object = myTurret.rt.Useful.localToGlobalXY(myTurret.mC.turret.firePoint);
var turretStart:Object = myTurret.rt.Useful.localToGlobalXY(myTurret.mC.turret);
var mousePos:Point = new Point(myTurret.rt.rt.mouseX,myTurret.rt.rt.mouseY);
var inbetween:Point = new Point(0,0);
//start
pta.x = turretStart.newX;
pta.y = turretStart.newY;
//mouse end
ptd.x = mousePos.x;
ptd.y = mousePos.y;
// The cannon's angle:
// make the cannon's angle some inverse factor
// of the distance between the mouse and cannon tip
var dist:Number = myTurret.rt.Useful.getDistance(turretEnd.newX, turretEnd.newY, mousePos.x, mousePos.y);
var cAng:Number = dist * (180/Math.PI);
var ptbc:Point = new Point((ptd.x - pta.x) *.5,0);
ptbc.y = Math.tan(cAng) * ptbc.x;
//ptb = new Point(ptbc.x - ptbc.x * .15, ptbc.y);
ptb = new Point(turretEnd.newX, turretEnd.newY);
ptc = new Point(ptbc.x + ptbc.x * .5, ptbc.y);
// create the Bezier:
var bz:BezierSegment = new BezierSegment(pta,ptb,ptc,ptd);
trace(bz);
// define the distance between points that you want to draw
// has to be between 0 and 1.
var stepVal:Number = .1;
var curPt:Point = pta;
//draw circles
lineMc.graphics.drawCircle(pta.x, pta.y, 4);
lineMc.graphics.drawCircle(ptb.x, ptb.y, 4);
lineMc.graphics.drawCircle(ptc.x, ptc.y, 4);
lineMc.graphics.drawCircle(ptd.x, ptd.y, 4);
lineMc.graphics.lineStyle(2, 0x0000ff);
//step along the curve to draw it
for(var t:Number = 0;t < 1;t+=stepVal){
lineMc.graphics.moveTo(curPt.x, curPt.y);
curPt = bz.getValue(t);
trace("curPt = " + curPt.x + "," + curPt.y);
lineMc.graphics.lineTo(curPt.x, curPt.y);
}
trace("pta = " + pta.x + "," + pta.y);
trace("ptb = " + ptb.x + "," + ptb.y);
trace("ptc = " + ptc.x + "," + ptc.y);
trace("ptd = " + ptd.x + "," + ptd.y);
}
}
Also for some strange reason, the line created by the code flips, from how it is in the screen shot to an indented code (y flipped) just by moving the mouse a tiny amount, so as you move the mouse the line jumps everywhere.
One method is to create a Bezier curve.
This sounds like a workable solution because you essentially want the curve to always fit under some triangle. If this triangle defines the control points for a Bezier curve, you can make that match pretty closely the arc of a cannonball under gravity (it's not a perfect representation of gravity). One side-effect of this method is that the (inversed) height can define the force of the cannonball.
You can use the fl.motion.BezierSegment to create a curve and step along it. Paste this code into an FLA:
import fl.motion.BezierSegment;
var mySprite:Sprite = new Sprite();
addChild(mySprite);
mySprite.graphics.lineStyle(2, 0x55aa00);
// End point of the cannon:
var pta:Point = new Point(0, 100);
mySprite.graphics.drawCircle(pta.x, pta.y, 4);
trace("pta = " + pta.x + "," + pta.y);
// mouse point
// var ptd:Point = new Point(mouseX, mouseY);
// for testing:
var ptd:Point = new Point(200,100);
mySprite.graphics.drawCircle(ptd.x, ptd.y, 4);
trace("ptd = " + ptd.x + "," + ptd.y);
// The cannon's angle:
// make the cannon's angle some inverse factor
// of the distance between the mouse and cannon tip
// var dx:Number = ptd.x-pta.x;
// var dy:Number = ptd.y-pta.y;
// var dist:Number = Math.sqrt(dx * dx + dy * dy);
var cAng:Number = 30 * /(180/Math.PI);
// point the cannon in the correct direction here, however you are intending to do that.
// triangulate the cannon pt and mouse pt assuming the cannon's angle for both:
// *** NOTE: for simplicity, this assumes a straight line on the x-plane. ***
var ptbc:Point = new Point((ptd.x - pta.x) *.5,0);
ptbc.y = Math.tan(cAng) * ptbc.x;
trace("ptbc = " + ptbc.x + "," + ptbc.y);
// to adjust the curve:
var ptb:Point = new Point(ptbc.x - ptbc.x * .15, ptbc.y);
var ptc:Point = new Point(ptbc.x + ptbc.x * .5, ptbc.y);
mySprite.graphics.drawCircle(ptb.x, ptb.y, 4);
mySprite.graphics.drawCircle(ptc.x, ptc.y, 4);
// create the Bezier:
var bz:BezierSegment = new BezierSegment(pta,ptb,ptc,ptd);
trace(bz);
// define the distance between points that you want to draw
// has to be between 0 and 1.
var stepVal:Number = .1;
var curPt:Point = pta;
mySprite.graphics.lineStyle(2, 0x0000ff);
//step along the curve to draw it
for(var t:Number = 0;t < 1;t+=stepVal){
mySprite.graphics.moveTo(curPt.x, curPt.y);
curPt = bz.getValue(t);
trace("curPt = " + curPt.x + "," + curPt.y);
mySprite.graphics.lineTo(curPt.x, curPt.y);
}
mySprite.x = stage.stageWidth/2-mySprite.width/2;
mySprite.y = stage.stageHeight/2-mySprite.height/2;
As is, this code is not attached directly to the mouse, so you will have to use your own MouseEvent and AdjustCannonEvent to run this code. (Also, make sure to see the note in the code.)