Multiple Canvas Zooms behaving erratically - html

I'm running into a problem with the HTML5 Canvas tag and adjusting the scale several times. After zooming twice, the canvas only uses a fraction of the available canvas height & width, even though I'm adjusting for the zoom level.
<html>
<head>
<script>
var ctx;
var nScale = 1.00;
function pageLoad() {
ctx=document.getElementById('cnvUni').getContext('2d');
// canvas on page load is 500x500
drawGrid(); // 5 boxes across & 5 down
zoom(0.5); // canvas should be now zoomed out to 1000x1000
drawGrid(); // 10 boxes across & 10 down
zoom(0.5); // effective zoom is now 0.25 = 2000x2000
drawGrid(); // should be 20 boxes across & 20 down
// NOTE: At this point, the grid is drawing boxes # 20x20 but only using 1/4 of the
// canvas size.
}
function zoom(nZoomLevel) {
nScale = nZoomLevel * nScale
ctx.scale(nScale,nScale);
}
function drawGrid() {
var nWidth, nHeight;
nWidth = Math.floor(ctx.canvas.width / nScale);
nHeight = Math.floor(ctx.canvas.height / nScale);
var nGridSize = 100;
var nGridY = 0;
var nGridX = 0;
// sets a random colour each time grid is drawn.
ctx.strokeStyle = 'rgb(' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ')';
for (nGridY=0;nGridY < nHeight;nGridY+=nGridSize) {
for (nGridX=0;nGridX < nWidth;nGridX+=nGridSize) {
// draw the box;
ctx.strokeRect(nGridX, nGridY, nGridSize, nGridSize);
}
}
}
</script>
</head>
<body onload="pageLoad();">
<canvas id="cnvUni" width="500" height="500">
Canvas doesn't work.
</canvas>
</body>
</html>
If I were to multiply the height & width by 2 when drawing the grid for the last time it'll draw out the entire canvas size, but I can't figure out why that would be required.
What I'm wondering is:
Is there a way to query a canvas context to find out what the scale value (or the calculated height/width) is? Or am I approaching this correctly and keeping track of values myself?
If so, then I assume it must be something with my math that's messing this up; I just can't pinpoint it. I'm sure I'm just too close to this problem and not seeing the issue. Another set of eyes would help.
Any suggestions would be appreciated.
Thanks!

I've got a version working on http://jsfiddle.net/sBXTn/5/ without using save/restore. This is the code change:
nScale = nZoomLevel * nScale
ctx.scale(nZoomLevel, nZoomLevel);
Previously using ctx.scale(nScale, nScale) meant that when you zoomed by 0.25 (0.5 twice) you were zooming by 0.25 on a context that was 1000x1000. This meant it increased the size to 4000x4000. Using nZoomLevel means you are zooming in relation to the dimensions of the current context.

I was way on the wrong track with this one and I feel silly for it. My math was correct in calculating the effective scale value (1 * 0.5 = 0.5, then scaling it by 0.5 again does = 0.25), but what I was doing was calculating the effective width & height by the original width & height, not the updated width & height.
So if I've scaled the original down to 0.5, the dimensions of the original 500x500 would be 1000x1000. Scaling it further by 0.5, the effective scale is 0.25, but the new width & height should be 4000x4000 (found by scaling 1000 by 0.25 and NOT 500 by 0.25).
Here's the updated code:
var ctx;
var nScale = 1.00;
var nEffWidth, nEffHeight;
function pageLoad() {
ctx=document.getElementById('cnvUni').getContext('2d');
nEffWidth = ctx.canvas.width;
nEffHeight = ctx.canvas.height;
// canvas on page load is 500x500
drawGrid(); // 5 boxes across & 5 down
zoom(0.5); // canvas should be now zoomed out to 1000x1000
drawGrid(); // 10 boxes across & 10 down
zoom(0.5); // effective zoom is now 0.25 = 4000x4000 based on new scaled width/height
drawGrid(); // should be 40 boxes across & 40 down
}
function zoom(nZoomLevel) {
nScale = nZoomLevel * nScale
nEffHeight = nEffHeight / nScale;
nEffWidth = nEffWidth / nScale;
ctx.scale(nScale,nScale);
}
function drawGrid() {
var nGridSize = 100;
var nGridY = 0;
var nGridX = 0;
// sets a random colour each time grid is drawn.
ctx.strokeStyle = 'rgb(' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ')';
for (nGridY=0;nGridY < nEffHeight;nGridY+=nGridSize) {
for (nGridX=0;nGridX < nEffWidth;nGridX+=nGridSize) {
// draw the box;
ctx.strokeRect(nGridX, nGridY, nGridSize, nGridSize);
}
}
}
</script>
</head>
<body onload="pageLoad();">
<canvas id="cnvUni" width="500" height="500">
Canvas doesn't work.
</canvas>
</body>
</html>
Thanks for everyone who took a peek!

Related

Canvas content disappering on resize [duplicate]

This question already has answers here:
Resize HTML5 canvas element
(3 answers)
Closed 8 years ago.
Probably I'm missing something fundamental.
I'm filling a canvas with objects. On user input I need to resize the canvas (thus scaling the content).
JS (simplified):
var c = document.getElementById('c');
var cc = c.getContext('2d');
cc.beginPath();
cc.rect(20, 20, 20, 12);
cc.fill();
function resizeCanvas(size){
var c = document.getElementById('c');
var cc = c.getContext('2d');
// This will make the content disappear
c.width = c.style.width = c.height = c.style.height = size;
}
HTML
<canvas id="c" width="200" height="200"></canvas>
<br/>
<button onclick='resizeCanvas(100);return false;'>Resize</button>
CSS
canvas {border: 1px solid black}
The canvas is resized but the content is blanked. Any idea?
jsfddle here
By changing either width or height -and obviously by changing both-, you completely redefine the canvas, so in fact it is quite logical that the canvas content is cleared : how should it change ?
Should it scale ? , re-use existing pixels values, but how ? centered, ... ??
That was even a 'trick' at some time : to clear a canvas, just do canvas.width = 0 then canvas.width = oldWidth , and that will clear things up. Since clearRect or fillRect are now faster that this trick, it has no reason to be used any more.
So when your canvas get resized, it will get cleared. It's up to you to decide of the policy you want when such resize occur : will you scale old canvas and copy it on the new one, or copy at same scale centered, or will that be an up/left most copy ??
you decide.
If you have a 'scene graph', meaning : if you are able to redraw every objects of your canvas, there's no real issue.
If you don't, you have to do some efforts to get the old content on the new canvas.
Something like (untested) :
function resizeCanvas(size){
var c = document.getElementById('c');
var canvasCopy = document.createElement('canvas');
canvasCopy.width = c.width; canvasCopy.height = c.height;
canvasCopy.getContext('2d').drawImage(c, 0,0 ); // copy the 'old' canvas
// This will make the content disappear
c.width = c.style.width = c.height = c.style.height = size;
var cc = c.getContext('2d');
cc.drawImage(canvasCopy, 0, 0) ; // or you might scale, or center, or...
}

Pixels twice its size

I just created little pixel art for my project http://i.imgur.com/9FLj6Uk.png. And I want to use it on my site. As you can see, it's small pixel art. I want one pixel to be drawn as 2*2 pixels instead of 1*1 pixel. I would redraw the picture with using 2*2 pixels instead of one but that seems to be bad solution.
I tried to use CSS on it
img.pixel {
width: 32px;
height: 32px
}
but that doesn't work, it shows weird shades in my browser. I want to see hard pixels. Does anyone know any solution for this problem?
This is the problem when I use the CSS above
Pixel art is tough in browsers, mainly due to lack of universal browser support of "pixelated" or "crisp-edges" image rendering. It should be supported in CSS4.
Currently the CSS stack looks like this, although it looks as if Chrome 30 and Opera 16 have broken support for CSS solutions
image-rendering:optimizeSpeed;
image-rendering:-moz-crisp-edges;
image-rendering:-o-crisp-edges;
image-rendering:optimize-contrast;
image-rendering:-webkit-optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
See this answer by #Phrogz, with a test case. Also see mozilla's documentation on the subject. For universal support now, a JS solution may have to work for the time being such as seen here on the great article drawing pixels is hard:
var resize = function( img, scale ) {
// Takes an image and a scaling factor and returns the scaled image
// The original image is drawn into an offscreen canvas of the same size
// and copied, pixel by pixel into another offscreen canvas with the
// new size.
var widthScaled = img.width * scale;
var heightScaled = img.height * scale;
var orig = document.createElement('canvas');
orig.width = img.width;
orig.height = img.height;
var origCtx = orig.getContext('2d');
origCtx.drawImage(img, 0, 0);
var origPixels = origCtx.getImageData(0, 0, img.width, img.height);
var scaled = document.createElement('canvas');
scaled.width = widthScaled;
scaled.height = heightScaled;
var scaledCtx = scaled.getContext('2d');
var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled );
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
var indexScaled = (y * widthScaled + x) * 4;
scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];
scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];
scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];
}
}
scaledCtx.putImageData( scaledPixels, 0, 0 );
return scaled;
}
Read the article through, the presence of retina displays and mobile safari may add additional complexity to rendering the correct size pixel art. Although with iOS7's mobile safari this may be rectified.
I suggest you to use an svg image, as that will be scalable and get you what you are looking for.
You can read more about the same from the below link.
https://developer.mozilla.org/en/docs/SVG_In_HTML_Introduction
Hope this helps.
Are you looking for this? <img src="http://i.imgur.com/9FLj6Uk.png width="32" height="32">
check fiddle

Generating a random image with specific content at each page reload/refresh

So I have a banner on a site, but I want to make it so that each time the page loads, a different image appears. More precisely, I want (say 50) squares (say having a black border, white fill) of random size (say from 5 pixels to 20 pixels in size) in random positions of a 750x63 px frame, with a white background.
What would be the best way to do this? I know a little JavaScript and HTML (and am very willing to learn more), but I really have no idea where to start. This is for my personal webpage, which I wish to spruce up a bit. Right now the fanciest code I have is some JavaScript for a simple Lightbox interface.
Wow, that was easier and more fun than I expected. Here's the code, that goes in the <body> section of my HTML code, optimized for a 750x80px frame. The random integer generator I got from this other question.
<canvas id="canvas" width="750" height="80"></canvas>
<script type="text/javascript">
//Random integer generator
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
//Loop to make 80 squares
for (var i=0;i<80;i++) {
//The x-position of the square, with 5px padding in frame
var sx = getRandomInt(5,705);
//The y-position of the square, with 5px padding in frame
var sy = getRandomInt(5,35);
//The height of the square, smallest 8x8px, largest 40x40px
var sh = getRandomInt(8,40);
//First, create a black square
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect (sx, sy, sh, sh);
//Second, create a white square that's 4px shorter and thinner,
//leaving a boundary of 2px
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect (sx+2, sy+2, sh-4, sh-4);
}
}
draw();
</script>
The approach I used is from a Mozilla Developers page. The result is something like this:
Hooray!

Canvas getImageData() bizarre issue

I have faced quite bizarre issue lately, please take a look at the code snippet below
<canvas id="cancan" width="320", height="480">One color image</canvas>
<script type="text/javascript">
function imageLoaded(ev) {
element = document.getElementById("cancan");
c = element.getContext("2d");
im = ev.target; // the image, assumed to be 200x200
// read the width and height of the canvas
width = element.width;
height = element.height;
// stamp the image on the left of the canvas:
c.drawImage(im, 0, 0);
// get all canvas pixel data
imageData = c.getImageData(0, 0, width, height);
console.log(imageData.data[0] + " " + imageData.data[1] + " " + imageData.data[2]);
// output is "243 52 47"
// matlab and c# output is: "237 36 27"
}
im = new Image();
im.onload = imageLoaded;
im.src = "imgtest1.jpg"; // image is 320x480
</script>
imgtest1.jpg used in this example is constant - each pixel is (237,36,27). Pixel color returned by getImageData() differs - it is brighter then what is returned from - for example - matlab - any ideas what could be the reason ?
Lightness or Brightness or Intensity can be computed as (R+G+B)/3 (see HSI color code). After your sample code result, it is obvious that your output-image is a little bit bright than the original one because your R-G-B values are higher than the original ones (from Matlab or C++).
The question must be "Why your code computes higher values?". I don't know, but you could re-scale the values in order to have the same brightness.

How to rotate an image on the canvas and expand the parent so that the image is not cut away?

How to rotate an image on an HTML5 Canvas, without loosing any image data? I mean if rotation causes the image dimensions to increase, I want to expand the Canvas container as well, so that the image is not cut off. The following image might say better:
The brown colored box is actually the container that wraps the Canvas. I want to expand it (and the Canvas to fit the image) when the Canvas is rotated, so that the image is not cut off.
Update:
The image could be larger than the Canvas hence I'm using a bounding box method to calculate proportional sizes with the parent container to fit the image. So the Canvas's style dimensions will be the calculated ones whereas it's height and width attributes will be the image dimensions.
Any help is appreciated!
This function will resize your canvas to exactly fit the rotated image. You must supply the width and height of the image and it's current rotation angle in degrees.
[Edited: OOPS! I should have converted the angle to radians ... And ... the canvas width/height should be changed, not the css width/height]
function resizeCanvasContainer(w,h,a){
var newWidth,newHeight;
var rads=a*Math.PI/180;
var c = Math.cos(rads);
var s = Math.sin(rads);
if (s < 0) { s = -s; }
if (c < 0) { c = -c; }
newWidth = h * s + w * c;
newHeight = h * c + w * s ;
var canvas = document.getElementById('canvas');
canvas.width = newWidth + 'px';
canvas.height = newHeight + 'px';
}