Scaling a canvas nicely with css - css

I'm trying to draw an image on a canvas, then use css to fit the canvas within a certain size. It turns out that many browsers don't scale the canvas down very nicely. Firefox on OS X seems to be one of the worst, but I haven't tested very many. Here is a minimal example of the problem:
HTML
<img>
<canvas></canvas>
CSS
img, canvas {
width: 125px;
}
JS
var image = document.getElementsByTagName('img')[0],
canvas = document.getElementsByTagName('canvas')[0];
image.onload = function() {
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"
Running in a codepen: http://codepen.io/ford/pen/GgMzJd
Here's the result in Firefox (screenshot from a retina display):
What's happening is that both the <img> and <canvas> start at the same size and are scaled down by the browser with css (the image width is 783px). Apparently, the browser does some nice smoothing/interpolation on the <img>, but not on the <canvas>.
I've tried:
image-rendering, but the defaults seem to already be what I want.
Hacky solutions like scaling the image down in steps, but this didn't help: http://codepen.io/ford/pen/emGxrd.
Context2D.imageSmoothingEnabled, but once again, the defaults describe what I want.
How can I make the image on the right look like the image on the left? Preferably in as little code as possible (I'd rather not implement bicubic interpolation myself, for example).

You can fix the pixelation issue by scaling the canvas's backing store by the window.devicePixelRatio value. Unfortunately, the shoddy image filtering seems to be a browser limitation at this time, and the only reliable fix is to roll your own.
Replace your current onload with:
image.onload = function() {
var dpr = window.devicePixelRatio;
canvas.width = image.width * dpr;
canvas.height = image.height * dpr;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
Results:
Tested on Firefox 35.0.1 on Windows 8.1. Note that your current code doesn't handle browser zoom events, which could reintroduce pixelation. You can fix this by handling the resize event.

Canvas is not quite meant to be css zoomed : Try over-sampling : use twice the required canvas size, and css scaling will do a fine job in down-scaling the canvas.
On hi-dpi devices you should double yet another time the resolution to reach the
same quality.
(even on a standard display, X4 shines a bit more).
(Image, canvas 1X, 2X and 4X)
var $ = document.getElementById.bind(document);
var image = $('fntimg');
image.onload = function() {
drawAllImages();
}
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"
function drawAllImages() {
drawImage(1);
drawImage(2);
drawImage(4);
}
function drawImage(x) {
console.log('cv' + x + 'X');
var canvas = $('cv' + x + 'X');
canvas.width = x * image.width;
canvas.height = x * image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
img,
canvas {
width: 125px;
}
<br>
<img id='fntimg'>
<canvas id='cv1X'></canvas>
<canvas id='cv2X'></canvas>
<canvas id='cv4X'></canvas>
<br>

It's not good idea to scale canvas and think that you solved the image scale problem.you can pass your dynamic value to canvas,and then draw with that size whatever you want.
here is link of canvas doc: http://www.w3docs.com/learn-javascript/canvas.html

Simple answer, you can't do it. The canvas is just like a bitmap, nothing more.
My idea:
You should redraw the whole surface on zooming, and make sure you scale the image you're drawing to the canvas. As it is a vector graphic, this should work. But you're going to have to redraw the canvas for sure.

Related

Microsoft Edge Image Scaling

How do i make an image scale with bicubic for MS Edge? Is there some CSS or similar that can change the behavour.
See this page: http://duttongarage.com/Race-Workshop~317
On the right there are two images that have textured background, you can see the weird artifacts quite clearly
Chrome on the Left, MS Edge on the Right. As you can see there is some weird moire effect from the resize being nearest neighbor or linear, not bicubic.
Another example that is more typical:
Microsoft Edge on Top, Chrome on the Bottom. Notice the pixelation, its like what i would expect from browsers from the last decade.
Sorry for stupidness of answer, but, as I can see, Edge doesn't support any image-rendering options, so, please, try to use jQuery to resize picture.
For example, you can use solution from this answer:
just create <canvas id="canvas"></canvas> under your image and see:
screenshot
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
img = new Image();
img.onload = function () {
canvas.height = canvas.width * (img.height / img.width);
/// step 1
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
/// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
}
img.src = "http://duttongarage.com/img/2167/824";
You can easily adjust oc.width with math. For example, you can use
oc.width = $(".me-wrap-image").width();
oc.height = $(".me-wrap-image").height();
Better, if you adjust your structure by
| .me-wrap-image
| .some-class-to-get-width-and-height
-> img
for img.src you can use
$("div.some-class-to-get img").each(function(){
img.src = $(this).attr('src');
});
But I'm not sure, how to make it work properly.
Hope you fix it :)
Obsolete
This is obsolete solution and does not work on recent versions of MS Edge.
===========
This little css tweak fixed problem for me:
img {
-ms-interpolation-mode: bicubic;
}

HTML5 canvas drawImage or CSS background url

I have a question, wich one have the best performance in webBrowsers?
drawing a canvas with the given url image "context.drawImage(...)" or just using css with "background:url('...')".
(both images filling the entire webPage as a wallpaper)
For big images (wallpapers) who fill the entire page i think it is heavier to load the image with css.
At least for firefox(20+), if you change tabs or minimize the window and go back again to your page (with the background), i can see that the image takes to show again after a half second.
So, i'm asking bc i found a lot of people telling that basic DOM is better.
Code for css:
--html: <body></body>
$('body').css('background',
"url('http://www.iwallscreen.com/stock/city-lights-hd-wallpaper.jpg') repeat fixed 0 0 #000000");
$('body').css('background-size', "100% auto");
Code for canvas:
--html: <canvas id="canvasWall" style="position:fixed;left:0;z-index:-1;"></canvas>
var context = $('#canvasWall')[0].getContext("2d");
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
var img = new Image();
img.src = 'http://www.iwallscreen.com/stock/city-lights-hd-wallpaper.jpg';
img.onload = function() {
context.drawImage(img, 0, 0, window.innerWidth, window.innerHeight);
};

Rotate background-image in a rotated div?

I have a div shape with before: and after: so it looks like a cross shape (Rotated).
But now my problem is, that the background is logically also rotated. I'd like that the background image isn't rotated and the image should be the size of the div.
I already tried to add a transform rotate to the place where I added the background but it didnt rotate back. Also for the size I tried background-size to adjust it, didnt work either.
Here is my jsbin: http://jsbin.com/iYogaCE/29/edit
thanks in advance!
nick
Well, I tried for a while to get a version working with pure CSS and HTML, but I was unable to do so. I believe that double pseudo selectors, aka ::after and ::before, would allow it to be possible, but I don't think that you can do it in pure CSS in one object currently.
With that being said, the way I accomplished it using one element is the much more common way - by using a canvas. With canvas it becomes pretty simple. Hopefully the comments make it easy to understand
Live demo here
// Gets a list of all the canvases to create an X for
var canvases = document.getElementsByClassName('profile');
// Allows the X to be drawn on multiple canvases without being redrawn
var tempCanvas = drawX();
// Gives the canvases a background image (the person's profile)
// If you wanted different images for each you could easily create an array
// and iterate through it for each canvas
var background = new Image();
background.src = "http://asta-design.ch/gameotion/wp-content/uploads/2013/03/placeholder.jpg";
// Once the image has loaded, apply the Xs
background.onload = function() {
// Do it for each canvas
for(var i = 0, j = canvases.length; i < j; i ++)
{
// Gets the current canvas and context
var canvas = canvases[i];
var context = canvas.getContext('2d');
// Allows the portrait only to be shown through the generated X
context.globalCompositeOperation = "destination-atop";
// Draws the profile picture
context.drawImage(background, 0,0, canvas.width, canvas.height)
// Cuts out everything that is not within the X
context.drawImage(tempCanvas, 0, 0);
}
}
// Creates the X to use as the cut out
function drawX() {
// Creates a hidden canvas to draw the X on
var offscreenCanvas = document.createElement('canvas');
var offscreenCtx = offscreenCanvas.getContext('2d');
// The width/height of the original canvas, not sure why "canvas.width" doesn't work here...
var size = 200;
offscreenCanvas.width = size;
offscreenCanvas.height = size;
// Creates the rectangles sloped positively
offscreenCtx.save();
offscreenCtx.translate(3 * size / 4, 3 * size / 4);
offscreenCtx.rotate(Math.PI/4);
offscreenCtx.fillRect(-size/2, -size/2, size * .3, size);
// Loads the state before the first rectangle was created
offscreenCtx.restore();
// Creates the rectangles sloped positively
offscreenCtx.translate(3 * size / 4, 1 * size / 4);
offscreenCtx.rotate(-Math.PI/4);
offscreenCtx.fillRect(-size/2, -size/2, size * .3, size);
// Returns the canvas with the X
return offscreenCanvas;
}
You can't rotate a CSS background independently of the element it is attached to.
The only way you're going to be able to do this is to have the rotated content in an additional element inside your existing one, and only rotate the inner element.
eg:
<div> <-- background applied to this element
<div>....</div> <-- but this one is rotated
</div>
Now your background will remain static while the content inside it rotates.
If you can't have any extra markup, you could still achieve this without changing the HTML, by using CSS the :before selector to create an additional pseudo-element behind the main element. Apply the background to that instead of the main element; after that it's similar to what I described above with the extra markup.
Hope that helps.

CSS Skew only container, not content

I'm having trouble figuring out how to make the following layout work. I'm not restricted to pure CSS - I know JS will be involved to make it cross-browser - but a CSS solution would be awesome. Here's what I am trying to achieve:
I've tried the following code, skewing the container and then skewing the image in the opposite direction, but it just gives me a square image. Chrome inspector shows me that the container is being skewed properly, but skewing the image back makes it square again. Adding an overflow:hidden to the container kind of works but the edges of the angle become jagged. Here's what I have tried:
http://codepen.io/anon/pen/ubrFz
Please help! :)
Need to tweak the positioning and the size of the container so you can crop it, and apply the backface-visibility rule:
.skew {
-webkit-backface-visibility : hidden; /* the magic ingredient */
-webkit-transform : skew(16deg, 0);
overflow : hidden;
width : 300px;
height : 260px;
position : relative;
left : 50px;
border : 1px solid #666
}
.skew img {
-webkit-transform : skew(-16deg, 0);
position : relative;
left : -40px;
}
http://codepen.io/anon/pen/HLtlG <- before (aliased)
http://codepen.io/anon/pen/wnlpt <- after (anti-aliased)
In lieu of a CSS solution, you could also achieve the effect by using a canvas and some JS; and compositing a series of cropped images onto that canvas. The benefit of the canvas method being that you'll potentially get smoother edges on the crops, and it is potentially a bit better supported.
A canvas element in HTML;
<canvas id="mycanvas"></canvas>
And JS;
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.src = '../my/image1.jpg';
img2.src = '../my/image2.jpg';
img3.src = '../my/image3.jpg';
var can = document.getElementById("mycanvas");
var ctx = can.getContext('2d');
var imgs = [img1, img2, img3]; //array of JS image objects that you've set up earlier
can.width = 1000;
can.height = 100;
for (var i=0; i < imgs.length; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(800 - (200 * i), 0);
ctx.lineTo(900 - (200 * i), 100);
ctx.lineTo(0, 100);
ctx.closePath();
ctx.clip();
ctx.drawImage(imgs[i], 0, 0);
}
The code is just off the top of my head - I haven't tested it. But basically - lets say you have a canvas that is a maximum of 1000px wide and 100px high. What happens above is, you set up a clipping area with a diagonal line across the canvas from point (800,0) to (900,100) and then draw the image into that clipping area... Then set up a new clipping path 200 pixels shorter for each image (note the '200 * i' bit).
Obviously the math needs to be adjusted for an arbitrary number of images and so on... But the idea is there.
A bit trickier than pure CSS maybe - but as I said - possibly a bit better supported cross-browser (IE's notwithstanding...).
EDIT
Did a quick test - looks like you need to set the canvas dimensions - and also obviously wait for all images to load properly before you can composite them on the canvas.

CSS background of png with semitransparancies transparant on a gradient shows a white background

if you look at this fiddle( http://jsfiddle.net/5ajYD/ ) with an android browser you see that the PNG that makes up the flowers has a white background.
On all other browsers it shows perfectly normal, except the android browser.
I've googled on this problem but the only thing I can find is a problem with png banding and related to android app programming.
This reminds me of the issues MSIE 6 has with transparant images, and I find it very strange that this happens.
Does anyone know a fix for this issue on android browsers?
I can't use non transparant background because of the gradient differences in different browsers.
What I have tried so far:
I have already tried using "multiple" backgrounds both posistioned at
location 0px 0px, but that doens't work
I've tried adding a gradient to to the div with the flowers, but that
failed too and broke in other browsers.
I find it very mystifying that this kind of issue shows up on a modern browser... even a nokia n95 gets it right....
The android version I’m testing against/found this with is android 2.3.4(Sony Ericsson Xperia Arc S LT18i)
This is what I see with the fiddle in the android browser on the phone
http://t.co/mofPkqjf
I had a big facepalm moment.
I've been battling with this for two months now and I simply couldn't figure out the logic. The problem was in the econtainer element that it had a parameter: width:100%.
What happens is that it only renders the width up to the actual page width of the viewport.
So if you have a browser screen on mobile that's 480px wide, it'll set width to 480px, render a gradient of 480px, and not rerender when you scroll sideways.
The problem was solved by adding a min-width:1200px; and now it renders properly!
Thank you all for looking into this...
Use HTML5 Canvas to create an alphaJPEG, a JPEG layered under an equivalent PNG with an alpha channel.
<head>
<style>img[data-alpha-src]{visibility: hidden;}</style>
</head>
<body>
<img src="image.jpg" data-alpha-src="alpha.png" />
<!--...-->
<script src="ajpeg.js"></script>
</body>
ajpeg.js
(function() {
var create_alpha_jpeg = function(img) {
var alpha_path = img.getAttribute('data-alpha-src')
if(!alpha_path) return
// Hide the original un-alpha'd
img.style.visiblity = 'hidden'
// Preload the un-alpha'd image
var image = document.createElement('img')
image.src = img.src
image.onload = function () {
// Then preload alpha mask
var alpha = document.createElement('img')
alpha.src = alpha_path
alpha.onload = function () {
var canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
img.parentNode.replaceChild(canvas, img)
// For IE7/8
if(typeof FlashCanvas != 'undefined') FlashCanvas.initElement(canvas)
// Canvas compositing code
var context = canvas.getContext('2d')
context.clearRect(0, 0, image.width, image.height)
context.drawImage(image, 0, 0, image.width, image.height)
context.globalCompositeOperation = 'xor'
context.drawImage(alpha, 0, 0, image.width, image.height)
}
}
}
// Apply this technique to every image on the page once DOM is ready
// (I just placed it at the bottom of the page for brevity)
var imgs = document.getElementsByTagName('img')
for(var i = 0; i < imgs.length; i++)
create_alpha_jpeg(imgs[i])
})();
In the head element I linked to FlashCanvas:
<!--[if lte IE 8]><script src="flashcanvas.js"></script><![endif]-->
... and I threw in this to avoid a flicker of the un-alpha’d JPEG:

Resources