HTML5 Canvas bitmaps - javascript

I am building a retro styled game, that uses pixelated images. I have not yet created these images, because I wanted to know the best way of doing things.
These images will probably be a 16 or 32 PX square, but I would like to be able to scale the images as big as I like, just without any blur/distortion.
What format should I use? And how should I import them to my canvas. as well?
EDIT#1: Fixed typo & put Q back on topic. (Thank you Spence for pointing it out)

Try "Inkscape", its free
https://inkscape.org/en/
it uses SVG format (scalar vector graphics) so you will be able to scale the images as big as you like, just without any blur/distortion.

The only way to enlarge without any blur or distortion is turn each 1 pixel into a set of 2x2, 3x3, ... pixels.
For example, a single blue pixel in the top-left of the image would become a set of 4 blue pixels at [0,0], [1,0], [0,1] & [1,1]. And the same for every other pixel on the original image. The resulting image would be twice the width & height of the original image.
Since your graphics style is pixelated images, this adjustment would preserve your pixilation while also enlarging the original image.
You can code a function that uses an in-memory html5 canvas to "resize-by-multiplying" your original images as needed. This will use canvas's ability to set the RGBA values every pixel using context.getImageData and context.putImageData.

CanvasContext2d does have an option to disable the image smoothing : imageSmoothingEnabled which is set to true by default.
According to the specs, if set to false,
The image [drawn by drawImage() method] must be rendered using
nearest-neighbor interpolation.
This algorithm is the same as the one proposed by #markE in his answer.
Unfortunately, browsers still use vendor-prefix for this attribute and it wasn't implemented in both IE9 and IE10...
var img = document.querySelector('img'),
canvas = document.querySelector('canvas'),
ctx = canvas.getContext('2d');
// draw the image
img.onload = function(){
canvas.width = img.width*50;
canvas.height = img.height*50;
// disable smoothing after we change canvas' width/height
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
}
//32x32px image taken from https://stackoverflow.com/q/31910043/3702797
img.src="http://i.stack.imgur.com/3Sp5x.png"
canvas{border:.5px solid}
<img/>
<canvas></canvas>
Scroll to see the resized image in canvas

Create large icon images to which you apply a 16x16 or 32x32 tile effect. Then when you write them to the canvas (after loading the images of course) scale them down to the size you want using
context.drawImage(img,x,y,width,height);
File sizes are unlikely to jump greatly since each tile should compress fairly easily.

Related

Having the classic blur issue but not finding a working solution

So we all know the issue with shapes being drawn with a slightly blurred stroke. I was able to fix it without further problems when using plain js and canvas with following methods:
context.sRect=function(x,y,w,h){
x=parseInt(x)+0.50;
y=parseInt(y)+0.50;
this.strokeRect(x,y,w,h);
}
context.fRect=function(x,y,w,h){
x=parseInt(x);
y=parseInt(y);
context.fillRect(x,y,w,h);
}
However, when using easeljs stage, these methods dont have any impact at all and the shapes stay blurred. I also tried offsetting x and y as well as width and height by -.5 when using drawRect(); That didnt work either. The last thing I tried was setting stage.regX and stage.regY to -.5 which did change the output but only to an altered blur (slightly more blurry).
Did I miss something?
PS: I need the canvas to always have the same width and height (200 * 200) but at the same time always fill the screen. I accomplish this by always setting canvas.width/height to 200 and the css of canvas to 100%.
Simple answer: you can't.
When you use CSS to scale a canvas, you can think of it as scaling each pixel on the canvas individually. Because of this, you are going to need to counteract the antialiasing of the browser itself.
To accurately counteract antialiasing in raster graphics, you always need more space than the container, simply because of the quantisation of information in pixels. If you do want your canvas to hold a fixed amount of information (200x200 pixels), this is not possible.
What you can still do is use that canvas as the model, but not the view ;)
If I did not want antialiasing of a fix-sized canvas over a variable space, I'd create another canvas whose size won't depend on the css, but on js events (look into window resize event listener and window.innerWidth, window.innerHeight), then draw the "model" original canvas on it while disabling ctx.imageSmoothingEnabled (= false), so that you'll get the needed result.
Here's a minimal example:
var canvasModel = document.createElement( 'canvas' ), // your current canvas
canvas = document.getElementById( 'view-canvas' ), // displaying on the screen
ctxModel = canvasModel.getContext( '2d' ),
ctx = canvas.getContext( '2d' );
ctx.imageSmoothingEnabled = false;
// all of your stuff that you're currently doing goes here
...
// every time you update the 200x200 canvas, you do this:
ctx.drawImage( canvasModel, 0, 0, w, h );

Low Quality Images in spot motion canvas animation

I am using a jquery plugin that utilities canvas to draw up spot motion animations.(http://arena.palamago.com.ar/spotMotion/)
I know that in this instance i can use animated GIF images, but the image types i use in future will be requiring higher quality and transparency.
If you look at the jsfiddle below you will see the images are not sharp, i am on a retina display and they look even worse, the original image is 800px. Canvas is not scaling the images high enough fo some unknown reason. I am fairly new to canvas and have seen a few methods for up scaling but have had no luck in getting a better result.
I looked at canvas width and canvas style width
canvas.width = "200";
canvas.height = "200"; // allow 40 pixels for status bar on iOS
canvas.style.width = "100px";
canvas.style.height = "100px";
I also looked at css image rendering techniques
canvas { image-rendering:optimizeQuality;}
http://jsfiddle.net/TsAzP/1/
Another attempt but i just cant seem to intergrate it with this plugin.
function enhanceContext(canvas, context) {
var ratio = window.devicePixelRatio || 1,
width = canvas.width,
height = canvas.height;
if (ratio > 1) {
canvas.width = width * ratio;
canvas.height = height * ratio;
canvas.style.width = width + "px";
canvas.style.height = height + "px";
context.scale(ratio, ratio);
}
}
I have seen some very complicated methods with people writing up-scaling algorithms, i just dont understand how to put it together. If anyone knows how to improve image quality please spare me some time.
Thank you
Problem
The cause is that the source image is too large to reduce in size in a single down-scale.
The browser typically uses bi-linear interpolation over bi-cubic interpolation when it comes to the canvas element.
Bi-linear interpolation analyses 2x2 pixels while bi-cubic uses 4x4 (in down-sampling functioning as a low-pass filter to create an average pixel). If the image size reduction is too steep there is simply not enough pixels to consider for averaging and the result will be in part "choppy" or pixelated.
Solution
To solve you can do one of the following steps:
Prepare the image(s) at a smaller size in an image editor (for example Photoshop) and scale the image to the destination size you want to use (ref. retina display).
Process the image on client before drawing it by creating an off-screen canvas and scale down the image in 2-3 steps.
The first step could be a better solution for a variety of reasons such as:
Processing of the image (for size) does not happen on client
Quality in resulting image (easier to post-process)
Bandwidth reduction (less data to transfer)
Faster processing of the image (in use) on client
Saves on battery (less processing involved)
As for the second step: There's too much code in the fiddle (TL; TR), but the principle is as follows (and it's not so complicated):
/// create two temporary canvas elements
var ocanvas = document.createElement('canvas'), /// off-screen canvas
tcanvas = document.createElement('canvas'), /// temp canvas
octx = ocanvas.getContext('2d'),
tctx = ocanvas.getContext('2d');
Then we do a first step-down scaling of the image - for this example we will do it twice which is the minimum needed. You might need a third step if the size difference is huge (you can normally calculate this by using a function of log etc., but I'll leave that out of the um, "equation" here):
/// use temp canvas (tcanvas) to scale for the first step
tcanvas.width = img.width * 0.5; /// 50% allow good result with bi-linear
tcanvas.height = img.height * 0.5;
/// draw image into canvas
tctx.drawImage(img, 0, 0, tcanvas.width, tcanvas.height);
The next step is just as simple as the above but with an absolute size:
/// set destination size
ocanvas.width = 200;
ocanvas.height = 200;
/// draw temp canvas into canvas
octx.drawImage(tcanvas, 0, 0, ocanvas.width, ocanvas.height);
You can now use ocanvas in your solution instead of img.
We use two canvases as we want to use the final ocanvas to replace img directly later at the proper size. If we used one canvas we would have to resize it in the final step which mean the canvas would be cleared.
If you do need a third step then you can reuse one of the canvases.
The added advantage here is that the browser won't need to scale anything when animating which reduces the load on the CPU/GPU.
I suggest also doing this down-scaling inside a function so that the temporary canvas references (except the one you need to use pf course, which you need to return) can be easily discarded by the browser after use (GC/memory wise).
Hope this helps!

Image to base64 in fixed resolution

In my webapplication the user can take a photo with the camera on his mobile device. The photo is then displayed in a canvas.
<canvas id="photo" ></canvas>
Now I want to send the photo to a backend server. The best way to do this seems to be encoding the image to a base64 string.
However, I want the photo to always be the same resolution, for example 100 x 100 pixels. Otherwise the base64 string is too big.
I am currently using the second parameter of toDataURL to determine the export quality of the picture. (here 2%).
var base64 = document.getElementById("photo").toDataURL("image/jpeg", 0.02);
This does not seem to be a good way because I don't know the initial resolution of the photo. So I would like to always get the photo in the same resolution. How to achieve this?
You could use an invisible background canvas with a size of 100x100 and copy the image from the photo-canvas to it. The drawImage function allows you to specify a size for the destination rectangle. When it's not the same as the source rectangle, the content will be scaled:
// Create a temporary, invisible 100x100 canvas
var tmpCanvas = document.createElement('canvas');
tmpCanvas.width = 100;
tmpCanvas.height = 100;
var tmpContext = canvas.getContext('2d');
// copy the content of the photo-canvas to the temp canvas rescaled
var photoCanvas = document.getElementById("photo");
tmpContext.drawImage(photoCanvas, // source
0, 0, photoCanvas.width, photoCanvas.height, // source rect
0, 0, 100, 100 // destination rect
);
// get data-url of temporary canvas
var base64 = tmpCanvas.toDataURL("image/jpeg");
But keep in mind that when the source-canvas isn't rectangular, the aspect ratio won't be preserved. When you don't want this, there are different ways to deal with this issue, like cropping or adding a border. Most of these would be implemented by choosing different source- or destination rectangles in the drawImage call.

How redraw on canvas resize without blurring?

When the canvas element is resized (via the style changing) I also want to scale the canvas' drawn image as well. I cannot just change the height/width as this causes the canvas to clear itself, so I do:
Create a temporary canvas element
Draw the current canvas' image onto that temporary canvas
Resize the current canvas
Draw the temp canvas' image back to the current canvas but scaled to the new size
This results in some blurring - very noticeable after many resizes (example: when dragging to resize). How would I do this without any blurring?
EDIT: Turning off image smoothing (context.webkitImageSmoothingEnabled = false;) does not fix the problem, it simply makes it redraw it more and more jagged until the image looks nothing like the original after a number of resizes.
Called on resize event:
var tmpCanvas = null;
//Make a temporary canvas
tmpCanvas = document.createElement( "canvas" );
//Set its size to be equal
tmpCanvas.height = originalCanvas.height;
tmpCanvas.width = originalCanvas.width;
//Draw our current canvas onto it
tmpCanvas.getContext( "2d" ).drawImage( originalCanvas, 0, 0 );
//Set new dimensions
originalCanvas.width = originalCanvas.offsetWidth;
originalCanvas.height = originalCanvas.offsetHeight;
var originalContext = originalCanvas.getContext( "2d" );
//Set background and colors
originalContext.fillStyle = "#ffffff";
originalContext.strokeStyle = "#000000";
//Set paintbrush
originalContext.lineWidth = 4;
originalContext.lineCap = "round";
//Fill background as white
originalContext.fillRect( 0, 0, originalCanvas.width, originalCanvas.height );
//We have a saved signature
if ( SignatureCanvas.hasSignature === true )
{
//Draw it back but scaled (results in blurred image)
originalContext.drawImage( tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height, 0, 0, originalCanvas.width, originalCanvas.height );
/**
* This results in a blurred image as well
//Draw it back but scaled
originalContext.scale( originalCanvas.width / tmpCanvas.width, originalCanvas.height / tmpCanvas.height );
originalContext.drawImage( tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height, 0, 0, tmpCanvas.width, tmpCanvas.height );
*/
}
Is there a way to get the strokes and "scale" all those points and redraw?
Instead of taking the rendered image from the original canvas, actually redraw the image. By that, I mean execute the same logic you executed against the original canvas, but with the points involved scaled to the new size.
If you can, think about using SVG instead. It scales well by its nature.
Edit: Another option I've thought of is to simply use a gigantic canvas to start with. Sizing down tends to look better than sizing up, especially with smoothing on.
Edit II: The original answer was irrelevant, though the comment I had made is relevant, and am now promoting it and editing it to be an answer...and the answer I had given was not all that great anyway **.
Of course if you scale up raster graphics, that is, from an image with a smaller pixel dimensions of pixels, create an image with higher pixel dimensions, you are going to get blurred images. By scaling up, you're making a low resolution picture high resolution, but without the high resolution details.
There's absolutely no way around that blurriness unless you make multiple additional assumptions about your raster image like the only gray you'd see is at an image edge, or corners can only occur at apparent inflection points where the angle between the tangents of the joined curves must be 100 degrees or less. Essentially, you'd have to give additional information so that your higher resolution image can have detail "filled in". It's not all that terribly different from reverse engineering an SVG from a raster.
So, you appear to want to emulate is scaling vector graphics, in which the only solution is to save the commands, draw a SVG, or draw to a bigger canvas like Stuart Branham suggested.
** I had originally proposed that invoking drawImage would distort the pixels even if it were not scaled, and that it would be better to work with the actual pixel data. If that's true, I can't find proof, but that's irrelevant, as he wanted his image scaled up, without blurring...which is impossible, as I just mentioned.

Drawing a Canvas into a smaller Canvas not working

For a Project I want to take the content of a canvas (Called SAVE_CV) and display it in another, smaller canvas.
Some things that I am aware of so far that could be causing me problems: resizing a canvas clears its content, the JS-size of a canvas is different from the CSS-size.
I want the smaller canvas to be 500px wide and appropriately high.
function restoreTaggingCV() {
var cv = document.getElementById( 'taggingCV' );
var ctx = cv.getContext( "2d" );
var styleHeight = SAVE_CV.height * 500 / SAVE_CV.width;
ctx.drawImage(SAVE_CV, 0, 0, cv.width, cv.height);
}
This is my Code so far. Whenever I try to resize the smaller canvas appropriately it only gives me a blank canvas with nothing in it. I tried to set the size with "cv.height = X" and "cv.style.height = styleHeight + 'px'" but neither worked. Also I would love to set the width of the canvas using CSS.
Appreciate any help.
EDIT
I want the image in a picture because later I want the user to mark areas in the smaller version which I then want to use to create individual imaged from the big version. I want to visualise thise area to the user. I probably could do all this by using an image and putting divs over it or something but I just fell more comfident using a canvas since I am pritty new to HTML and CSS.
Try using the CanvasRenderingContext2d.prototype.scale method. It sets the scale factor of the canvas and renders anything in the current state with it's dimensions multiplied by the factor.
So before you use the drawImage function, you scale the context appropriately (in this case, down). For example:
context.save();
context.scale(0.5, 0.5);
context.drawImage(canvas, 0, 0);
context.restore();
This would render the canvas on the context at 0.5 times it's current size. See in this fiddle how I used it to mirror a larger canvas onto a smaller, separate one.
Canvas objects don't like to be resised. After drawing Your image simply convert it toDataURL() and set as image source. They You may resize image as you want.
$img.attr('src',canvas.toDataURL());

Categories