Here's a noodle scratcher.
Bearing in mind we have HTML5 local storage and xhr v2 and what not. I was wondering if anyone could find a working example or even just give me a yes or no for this question:
Is it possible to Pre-size an image using the new local storage (or whatever), so that a user who does not have a clue about resizing an image can drag their 10mb image into my website, it resize it using the new localstorage and THEN upload it at the smaller size.
I know full well you can do it with Flash, Java applets, active X... The question is if you can do with Javascript + Html5.
Looking forward to the response on this one.
Ta for now.
Yes, use the File API, then you can process the images with the canvas element.
This Mozilla Hacks blog post walks you through most of the process. For reference here's the assembled source code from the blog post:
// from an input element
var filesToUpload = input.files;
var file = filesToUpload[0];
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
var dataurl = canvas.toDataURL("image/png");
//Post dataurl to the server with AJAX
I tackled this problem a few years ago and uploaded my solution to github as https://github.com/rossturner/HTML5-ImageUploader
robertc's answer uses the solution proposed in the Mozilla Hacks blog post, however I found this gave really poor image quality when resizing to a scale that was not 2:1 (or a multiple thereof). I started experimenting with different image resizing algorithms, although most ended up being quite slow or else were not great in quality either.
Finally I came up with a solution which I believe executes quickly and has pretty good performance too - as the Mozilla solution of copying from 1 canvas to another works quickly and without loss of image quality at a 2:1 ratio, given a target of x pixels wide and y pixels tall, I use this canvas resizing method until the image is between x and 2 x, and y and 2 y. At this point I then turn to algorithmic image resizing for the final "step" of resizing down to the target size. After trying several different algorithms I settled on bilinear interpolation taken from a blog which is not online anymore but accessible via the Internet Archive, which gives good results, here's the applicable code:
ImageUploader.prototype.scaleImage = function(img, completionCallback) {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
while (canvas.width >= (2 * this.config.maxWidth)) {
canvas = this.getHalfScaleCanvas(canvas);
}
if (canvas.width > this.config.maxWidth) {
canvas = this.scaleCanvasWithAlgorithm(canvas);
}
var imageData = canvas.toDataURL('image/jpeg', this.config.quality);
this.performUpload(imageData, completionCallback);
};
ImageUploader.prototype.scaleCanvasWithAlgorithm = function(canvas) {
var scaledCanvas = document.createElement('canvas');
var scale = this.config.maxWidth / canvas.width;
scaledCanvas.width = canvas.width * scale;
scaledCanvas.height = canvas.height * scale;
var srcImgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
var destImgData = scaledCanvas.getContext('2d').createImageData(scaledCanvas.width, scaledCanvas.height);
this.applyBilinearInterpolation(srcImgData, destImgData, scale);
scaledCanvas.getContext('2d').putImageData(destImgData, 0, 0);
return scaledCanvas;
};
ImageUploader.prototype.getHalfScaleCanvas = function(canvas) {
var halfCanvas = document.createElement('canvas');
halfCanvas.width = canvas.width / 2;
halfCanvas.height = canvas.height / 2;
halfCanvas.getContext('2d').drawImage(canvas, 0, 0, halfCanvas.width, halfCanvas.height);
return halfCanvas;
};
ImageUploader.prototype.applyBilinearInterpolation = function(srcCanvasData, destCanvasData, scale) {
function inner(f00, f10, f01, f11, x, y) {
var un_x = 1.0 - x;
var un_y = 1.0 - y;
return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y);
}
var i, j;
var iyv, iy0, iy1, ixv, ix0, ix1;
var idxD, idxS00, idxS10, idxS01, idxS11;
var dx, dy;
var r, g, b, a;
for (i = 0; i < destCanvasData.height; ++i) {
iyv = i / scale;
iy0 = Math.floor(iyv);
// Math.ceil can go over bounds
iy1 = (Math.ceil(iyv) > (srcCanvasData.height - 1) ? (srcCanvasData.height - 1) : Math.ceil(iyv));
for (j = 0; j < destCanvasData.width; ++j) {
ixv = j / scale;
ix0 = Math.floor(ixv);
// Math.ceil can go over bounds
ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv));
idxD = (j + destCanvasData.width * i) * 4;
// matrix to vector indices
idxS00 = (ix0 + srcCanvasData.width * iy0) * 4;
idxS10 = (ix1 + srcCanvasData.width * iy0) * 4;
idxS01 = (ix0 + srcCanvasData.width * iy1) * 4;
idxS11 = (ix1 + srcCanvasData.width * iy1) * 4;
// overall coordinates to unit square
dx = ixv - ix0;
dy = iyv - iy0;
// I let the r, g, b, a on purpose for debugging
r = inner(srcCanvasData.data[idxS00], srcCanvasData.data[idxS10], srcCanvasData.data[idxS01], srcCanvasData.data[idxS11], dx, dy);
destCanvasData.data[idxD] = r;
g = inner(srcCanvasData.data[idxS00 + 1], srcCanvasData.data[idxS10 + 1], srcCanvasData.data[idxS01 + 1], srcCanvasData.data[idxS11 + 1], dx, dy);
destCanvasData.data[idxD + 1] = g;
b = inner(srcCanvasData.data[idxS00 + 2], srcCanvasData.data[idxS10 + 2], srcCanvasData.data[idxS01 + 2], srcCanvasData.data[idxS11 + 2], dx, dy);
destCanvasData.data[idxD + 2] = b;
a = inner(srcCanvasData.data[idxS00 + 3], srcCanvasData.data[idxS10 + 3], srcCanvasData.data[idxS01 + 3], srcCanvasData.data[idxS11 + 3], dx, dy);
destCanvasData.data[idxD + 3] = a;
}
}
};
This scales an image down to a width of config.maxWidth, maintaining the original aspect ratio. At the time of development this worked on iPad/iPhone Safari in addition to major desktop browsers (IE9+, Firefox, Chrome) so I expect it will still be compatible given the broader uptake of HTML5 today. Note that the canvas.toDataURL() call takes a mime type and image quality which will allow you to control the quality and output file format (potentially different to input if you wish).
The only point this doesn't cover is maintaining the orientation information, without knowledge of this metadata the image is resized and saved as-is, losing any metadata within the image for orientation meaning that images taken on a tablet device "upside down" were rendered as such, although they would have been flipped in the device's camera viewfinder. If this is a concern, this blog post has a good guide and code examples on how to accomplish this, which I'm sure could be integrated to the above code.
Correction to above:
<img src="" id="image">
<input id="input" type="file" onchange="handleFiles()">
<script>
function handleFiles()
{
var filesToUpload = document.getElementById('input').files;
var file = filesToUpload[0];
// Create an image
var img = document.createElement("img");
// Create a file reader
var reader = new FileReader();
// Set the image once loaded into file reader
reader.onload = function(e)
{
img.src = e.target.result;
var canvas = document.createElement("canvas");
//var canvas = $("<canvas>", {"id":"testing"})[0];
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 400;
var MAX_HEIGHT = 300;
var width = img.width;
var height = img.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
var dataurl = canvas.toDataURL("image/png");
document.getElementById('image').src = dataurl;
}
// Load files into file reader
reader.readAsDataURL(file);
// Post the data
/*
var fd = new FormData();
fd.append("name", "some_filename.jpg");
fd.append("image", dataurl);
fd.append("info", "lah_de_dah");
*/
}</script>
Modification to the answer by Justin that works for me:
Added img.onload
Expand the POST request with a real example
function handleFiles()
{
var dataurl = null;
var filesToUpload = document.getElementById('photo').files;
var file = filesToUpload[0];
// Create an image
var img = document.createElement("img");
// Create a file reader
var reader = new FileReader();
// Set the image once loaded into file reader
reader.onload = function(e)
{
img.src = e.target.result;
img.onload = function () {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
dataurl = canvas.toDataURL("image/jpeg");
// Post the data
var fd = new FormData();
fd.append("name", "some_filename.jpg");
fd.append("image", dataurl);
fd.append("info", "lah_de_dah");
$.ajax({
url: '/ajax_photo',
data: fd,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
$('#form_photo')[0].reset();
location.reload();
}
});
} // img.onload
}
// Load files into file reader
reader.readAsDataURL(file);
}
If you don't want to reinvent the wheel you may try plupload.com
Typescript
async resizeImg(file: Blob): Promise<Blob> {
let img = document.createElement("img");
img.src = await new Promise<any>(resolve => {
let reader = new FileReader();
reader.onload = (e: any) => resolve(e.target.result);
reader.readAsDataURL(file);
});
await new Promise(resolve => img.onload = resolve)
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
let MAX_WIDTH = 1000;
let MAX_HEIGHT = 1000;
let width = img.naturalWidth;
let height = img.naturalHeight;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
let result = await new Promise<Blob>(resolve => { canvas.toBlob(resolve, 'image/jpeg', 0.95); });
return result;
}
The accepted answer works great, but the resize logic ignores the case in which the image is larger than the maximum in only one of the axes (for example, height > maxHeight but width <= maxWidth).
I think the following code takes care of all cases in a more straight-forward and functional way (ignore the typescript type annotations if using plain javascript):
private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} {
if (width <= maxWidth && height <= maxHeight)
return { width, height };
else if (width / maxWidth > height / maxHeight)
return { width: maxWidth, height: height * maxWidth / width};
else
return { width: width * maxHeight / height, height: maxHeight };
}
fd.append("image", dataurl);
This will not work. On PHP side you can not save file with this.
Use this code instead:
var blobBin = atob(dataurl.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"});
fd.append("image", file); // blob file
Resizing images in a canvas element is generally bad idea since it uses the cheapest box interpolation. The resulting image noticeable degrades in quality. I'd recommend using http://nodeca.github.io/pica/demo/ which can perform Lanczos transformation instead. The demo page above shows difference between canvas and Lanczos approaches.
It also uses web workers for resizing images in parallel. There is also WEBGL implementation.
There are some online image resizers that use pica for doing the job, like https://myimageresizer.com
You can use dropzone.js if you want to use simple and easy upload manager with resizing before upload functions.
It has builtin resize functions, but you can provide your own if you want.
Related
There's been a-lot of questions around this but none of those have fixed my problem. Any image that I upload onto the object becomes pixelated regardless of the minFilter or magFilter that I use - and I've used all of them:
THREE.NearestFilter
THREE.NearestMipMapNearestFilter
THREE.NearestMipMapLinearFilter
THREE.LinearFilter
THREE.LinearMipMapNearestFilter
THREE.LinearMipMapLinearFilter
Here's the object with a pixelated image:
And here's a snapshot of how I'm loading the image on:
// Build a canvas object and add the image to it
var imageCanvas = this.getCanvas(imageLayer.guid, 'image');
var imageLoader = new THREE.ImageLoader();
imageLoader.load(imageUrl, img => {
// this.drawImage(img, gr, imageCanvas.canvas, imageCanvas.ctx);
var canvas = imageCanvas.canvas;
var ctx = imageCanvas.ctx;
canvas.width = 1024;
canvas.height = 1024;
var imgAspectRatioAdjustedWidth, imgAspectRatioAdjustedHeight;
var pushDownValueOnDy = 0;
var grWidth = canvas.width / 1.618;
if(img.width > img.height) {
grWidth = canvas.width - grWidth;
}
var subtractFromDx = (canvas.width - grWidth) / 2;
var grHeight = canvas.height / 1.618;
if(img.height > img.height) {
grHeight = canvas.height - grHeight;
}
var subtractFromDy = (canvas.height - grHeight) / 2;
var dx = (canvas.width / 2);
dx -= subtractFromDx;
var dy = (canvas.height / 2);
dy -= (subtractFromDy + pushDownValueOnDy);
imgAspectRatioAdjustedWidth = (canvas.width - grWidth) + 50;
imgAspectRatioAdjustedHeight = (canvas.height - grHeight) + 50;
ctx.globalAlpha = 0.5;
ctx.fillStyle = 'blue;'
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1.0;
ctx.drawImage(img, dx, dy, imgAspectRatioAdjustedWidth, imgAspectRatioAdjustedHeight);
});
After this the canvas data is added to an array to be painted onto the object - it is at this point that the CanvasTexture gets the mapped canvas:
var canvasTexture = new THREE.CanvasTexture(mainCanvas.canvas);
canvasTexture.magFilter = THREE.LinearFilter;
canvasTexture.minFilter = THREE.LinearMipMapLinearFilter;
// Flip the canvas
if(this.currentSide === 'front' || this.currentSide === 'back'){
canvasTexture.wrapS = THREE.RepeatWrapping;
canvasTexture.repeat.x = -1;
}
canvasTexture.needsUpdate = true;
// { ...overdraw: true... } seems to allow the other sides to be transparent so we can see inside
var material = new THREE.MeshBasicMaterial({map: canvasTexture, side: THREE.FrontSide, transparent: false});
for(var i = 0; i < this.layers[this.currentSide].length; i++) {
mainCanvas.ctx.drawImage( this.layers[this.currentSide][i].canvas, 0, 0, this.canvasWidth, this.canvasHeight);
}
Thanks to #2pha for the help as his suggestions lead me to the correct answer and, it turns out, that the pixelated effect was caused by different dimensions of the canvases.
For example the main canvas itself was 1024x1024 whereas the text & image canvases were only 512x512 pixels meaning that it would have to be stretched to cover the size of the main canvas.
What does google play music use to create the "particles" visualiser on their website?
What 3D graphic software was used? My guess was originally unity 3D exported via webGL, or perhaps three.js or UE4?
I don't understand how they get the audio web player to stream the audio while the 3D visualiser reacts to the audio frequencies.
I wanted to re-create the same thing. Not sure where to start. I lack the knowledge on how it is done.
Couldn't find any answers on the web.
Most importantly are there different methods doing what google has done. What are the main differences?
Link of Visualiser demonstration: https://www.youtube.com/watch?v=mjfKCSPFdGI
Thanks.
It would be nice if you'd post a gif or something to show what you're referring to.
Making something audio reactive is pretty simple though. Here's an open source site with lots audio reactive examples.
As for how to do it you basically use the Web Audio API to stream the music and use it's AnalyserNode to get audio data out.
"use strict";
const ctx = document.querySelector("canvas").getContext("2d");
ctx.fillText("click to start", 100, 75);
ctx.canvas.addEventListener('click', start);
function start() {
ctx.canvas.removeEventListener('click', start);
// make a Web Audio Context
const context = new AudioContext();
const analyser = context.createAnalyser();
// Make a buffer to receive the audio data
const numPoints = analyser.frequencyBinCount;
const audioDataArray = new Uint8Array(numPoints);
function render() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// get the current audio data
analyser.getByteFrequencyData(audioDataArray);
const width = ctx.canvas.width;
const height = ctx.canvas.height;
const size = 5;
// draw a point every size pixels
for (let x = 0; x < width; x += size) {
// compute the audio data for this point
const ndx = x * numPoints / width | 0;
// get the audio data and make it go from 0 to 1
const audioValue = audioDataArray[ndx] / 255;
// draw a rect size by size big
const y = audioValue * height;
ctx.fillRect(x, y, size, size);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// Make a audio node
var audio = new Audio();
audio.loop = true;
audio.autoplay = true;
// this line is only needed if the music you are trying to play is on a
// different server than the page trying to play it.
// It asks the server for permission to use the music. If the server says "no"
// then you will not be able to play the music
audio.crossOrigin = "anonymous";
// call `handleCanplay` when it music can be played
audio.addEventListener('canplay', handleCanplay);
audio.src = "https://twgljs.org/examples/sounds/DOCTOR%20VOX%20-%20Level%20Up.mp3";
audio.load();
function handleCanplay() {
// connect the audio element to the analyser node and the analyser node
// to the main Web Audio context
const source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
}
}
canvas { border: 1px solid black; display: block; }
<canvas></canvas>
Then it's just up to you to do something creative. For example instead of drawing a bunch of black dots across the screen like the first example we could scale random colored circles and adjust their color and velocity something like this
"use strict";
var context = new AudioContext();
var analyser = context.createAnalyser();
var numPoints = analyser.frequencyBinCount;
var audioDataArray = new Uint8Array(numPoints);
var ctx = document.querySelector("canvas").getContext("2d");
var ctx2 = document.createElement("canvas").getContext("2d");
var numSpots = 5;
var spots = [];
for (var ii = 0; ii < numSpots; ++ii) {
spots.push({
x: Math.random(),
y: Math.random(),
velocity: 0.01,
direction: Math.random(),
hue: Math.random() * 360 | 0,
});
}
function rnd(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
function render() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.globalAlpha = .97;
ctx.globalCompositeOperation = "source-out";
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(1.001, 1.001);
ctx.rotate(0.003);
ctx.translate(-ctx.canvas.width / 2, -ctx.canvas.height / 2);
ctx.drawImage(ctx2.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore();
analyser.getByteFrequencyData(audioDataArray);
const width = ctx.canvas.width;
const height = ctx.canvas.height;
spots.forEach((spot, n) => {
const ndx = n * numPoints / numSpots | 0;
const audioValue = audioDataArray[ndx] / 255;
const sat = Math.pow(audioValue, 2) * 100;
spot.velocity = audioValue * 0.02;
spot.direction = (spot.direction + 1 + rnd(-.01, 0.01)) % 1;
const angle = spot.direction * Math.PI * 2;
spot.x = (spot.x + Math.cos(angle) * spot.velocity + 1) % 1;
spot.y = (spot.y + Math.sin(angle) * spot.velocity + 1) % 1;
ctx.fillStyle = "hsl(" + spot.hue + "," + sat + "%,50%)";
ctx.beginPath();
ctx.arc(spot.x * width, spot.y * height, 50 * audioValue, 0, Math.PI * 2, false);
ctx.fill();
});
var temp = ctx;
ctx = ctx2;
ctx2 = temp;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
var audio = new Audio();
audio.loop = true;
audio.autoplay = true;
// this line is only needed if the music you are trying to play is on a
// different server than the page trying to play it.
// It asks the server for permission to use the music. If the server says "no"
// then you will not be able to play the music
audio.crossOrigin = "anonymous";
audio.addEventListener('canplay', handleCanplay);
audio.loop = true;
audio.src = "https://twgljs.org/examples/sounds/DOCTOR%20VOX%20-%20Level%20Up.mp3";
audio.load();
function handleCanplay() {
const source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
}
canvas { border: 1px solid black; display: block; }
<canvas></canvas>
music: DOCTOR VOX - Level Up
I'm trying to animate a circle that will draw itself similar to a progress bar. I'm intending to use it on a carousel to track when the next slide is coming up. The problem I'm having is I don't know how to change the duration of the animation. I tried adjusting the framerate, and it works but the animation gets really choppy. setInterval kind of works but it displays the entire circle rather than just a portion of it like I'm intending, so I can't time things properly. I need to be able to control the speed of the animation, slowing it down without it being stuttery. The code I'm working on is below.
<script>
(function() {
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 90;
var endPercent = 85;
var curPerc = 0;
var circ = -Math.PI;
var quart = -(Math.PI * 2) + 1;
function animate(current) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(centerX, centerY, radius, -(quart), ((circ) * current) - quart, true);
context.lineWidth = 3;
context.strokeStyle = '#000';
context.stroke();
curPerc++;
if (curPerc < endPercent) {
requestAnimationFrame(function () {
animate(curPerc / 100)
});
}
}
animate();
</script>
requestAnimationFrame does pass an high resolution timestamp in the callback argument. So you could use it to determine where you are in your current animation, and use this delta time to set your positions variables instead of curPerc++.
Here is a naive implementation.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 90;
var endPercent = 85;
var quart = -(Math.PI * 2) + 1;
var startTime = null;
var duration = null;
function animate(time) {
if (!startTime) {
startTime = time;
}
var delta = Math.min(1, (time - startTime) / duration);
var curPerc = ((-2 * Math.PI) / 100) * (endPercent * delta);
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(centerX, centerY, radius, -quart, curPerc - quart, true);
context.stroke();
if (delta < 1) {
requestAnimationFrame(animate);
} else {
startTime = null;
slider.disabled = false;
}
}
var startAnim = function() {
context.lineWidth = 3;
context.strokeStyle = '#000';
slider.disabled = true;
duration = +slider.value;
l.textContent = duration + 'ms';
requestAnimationFrame(animate);
};
slider.onchange = startAnim;
startAnim();
<p>use the slider to update the animation's duration</p>
<input type="range" min="250" max="9000" value="2000"id="slider" />
<label id="l"></label><br>
<canvas id="myCanvas" height="300"></canvas>
I need these images rendered in html5 canvas to be centered, rotated and resized depending on the size of the bounding box. I managed to rotate and resize the images but they are not centered anymore.
Can someone help me to fit these images in bounds and keep them in the center of the boxes? (fiddle updated)
http://jsfiddle.net/owtwv1a5/6/
var renderSprite = function(img, x, y, width,height, degree, scale){
var rads = degree * Math.PI/180;
var heightRatio = height/img.height;
var widthRatio = width/img.width;
var isRotated = (degree==90 || degree==270);
if (isRotated) {
var scale_ratio = height/img.width;
} else {
var scale_ratio = heightRatio;
}
var scaledImgHeight = img.height*scale_ratio;
var scaledImgWidth = img.width*scale_ratio;
var offsetX = width - scaledImgWidth;
if ((scaledImgHeight) < height) {
y += parseInt((height-scaledImgHeight)/2);
if (isRotated) {
x -= (scaledImgWidth - scaledImgHeight) / 2;
}
}
if ((scaledImgWidth) < width) {
x += parseInt((width-scaledImgWidth)/2);
if (isRotated) {
x -= (scaledImgWidth - scaledImgHeight) / 2;
}
}
ctx.save();
var centerX = x + scaledImgWidth * 0.5;
var centerY = y + scaledImgHeight * 0.5;
ctx.translate(centerX, centerY);
ctx.rotate(rads);
//ctx.scale(scale,scale);
ctx.translate(-centerX, -centerY);
ctx.drawImage(img, x,y, scaledImgWidth ,scaledImgHeight);
ctx.restore();
};
I have seen 3 tickets on bitbucket asking about this over the last year but have never seen a definitive answer.
One of those tickets gave some code, but I'm at a loss as to where that code belongs.
var devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
ratio = devicePixelRatio / backingStoreRatio;
if (devicePixelRatio !== backingStoreRatio) {
var oldWidth = canvas.width;
var oldHeight = canvas.height;
this.canvasOrigWidth = oldWidth;
this.canvasOrigHeight = oldHeight;
canvas.width = oldWidth * ratio;
canvas.height = oldHeight * ratio;
canvas.style.width = oldWidth + 'px';
canvas.style.height = oldHeight + 'px';
// now scale the context to counter
// the fact that we've manually scaled
// our canvas element
ctx.scale(ratio, ratio);
}
How do you get JQPlot to output high resolution graphs?
Edit 1
The above code seems to have come from this website.
I figured it out based on the examples linked to in the question.
Replace
this.initCanvas = function(canvas) {
if ($.jqplot.use_excanvas) {
return window.G_vmlCanvasManager.initElement(canvas);
}
return canvas;
}
With
this.initCanvas = function(canvas) {
if ($.jqplot.use_excanvas) {
return window.G_vmlCanvasManager.initElement(canvas);
}
var cctx = canvas.getContext('2d');
var canvasBackingScale = 1;
if (window.devicePixelRatio > 1 && (cctx.webkitBackingStorePixelRatio === undefined ||
cctx.webkitBackingStorePixelRatio < 2)) {
canvasBackingScale = window.devicePixelRatio;
}
var oldWidth = canvas.width;
var oldHeight = canvas.height;
canvas.width = canvasBackingScale * canvas.width;
canvas.height = canvasBackingScale * canvas.height;
canvas.style.width = oldWidth + 'px';
canvas.style.height = oldHeight + 'px';
cctx.save();
cctx.scale(canvasBackingScale, canvasBackingScale);
return canvas;
};
That method can be found around line 290 in jquery.jqplot.js.
Then if you do not have a HIDPI or Retina display but do have a Mac you can use Quartz Debug and System Pref/Displays to simulate a HIDPI resolution for testing. Here is a composite screenshot showing normal graphing and the same graph with the replacement code.