Canvas Zoom out Image Quality Loss - html

I written a code for zooming and zoom-out the canvas image but if try to zoom the image more than once the quality of image is changing.
This is my code using canvas2image plugin i am converting canvas to normal image.
Can any body please suggest me is there any thing wrong way i am doing.
function imgZoom(operation) {
var zoomImageObj = document.getElementById("originalImage");
var zoomedCanvas = $("#zoomCanvasPreview")[0];
var zoomedContext = zoomedCanvas.getContext("2d");
var selectedImgWidth = $('#originalImage').width();
var selectedImgHeight = $('#originalImage').height();
$("#zoomCanvasPreview").attr("height", selectedImgHeight);
$("#zoomCanvasPreview").attr("width", selectedImgWidth);
var previewHeight = $("#zoomCanvasPreview").height();
var previewWidth = $("#zoomCanvasPreview").width();
var zoomFactor = $("#zoomFactor option:selected").val();
// Making the zoomfactor string as float point then parsing for addition
// zoomFactor values if user selected 5%(0.05), 10%(0.1), 15(0.15) &etc
var zoomPercent = 1 + parseFloat(zoomFactor);
if(operation == 'zoomIn') {
newZoomWidth = Math.round(previewWidth * zoomPercent) ;
newZoomHeight = Math.round(previewHeight * zoomPercent) ;
} else if(operation == 'zoomOut'){
newZoomWidth = Math.round( previewWidth / zoomPercent );
newZoomHeight = Math.round( previewHeight / zoomPercent );
}
if( (newZoomWidth >= 0) && (newZoomHeight >= 0) )
{
$("#zoomCanvasPreview").attr("width", newZoomWidth);
$("#zoomCanvasPreview").attr("height", newZoomHeight);
// Drawing the image to canvas
zoomedContext.drawImage(zoomImageObj, 0, 0, imgWidth, imgHeight, 0, 0, newZoomWidth, newZoomHeight );
//return canvas.toDataURL("image/png");
var zoomedCanvas = $("#zoomCanvasPreview")[0];
oImgConvertExt = Canvas2Image.saveAsPNG(zoomedCanvas, true);
$("#originalImage").attr("src", oImgConvertExt.src);
$("#originalImage").attr("style", "display: none; visibility: hidden; width: "+newZoomWidth+"px; height: "+newZoomHeight+"px;");
} else {
alert("Reached maximum zoom out limit");
}
}

Image quality will not get changed If you use Images of format .SVG
these image will never get distorted even if you zoom IN or zoom OUT

There is no way around quality loss if you use canvas.drawImage().
Just zoom by changing the CSS width and height values. There won't be any quality loss then.
It is also a lot simpler to code...

Related

image using cordova plugin looks horrible on canvas

i am using cordova for my ios app which captures the image
the code looks
navigator.camera.getPicture(onSuccessCamera, onFailureCamera, {
quality: 25,
destinationType: navigator.camera.DestinationType.DATA_URL,
correctOrientation: true,
allowEdit:false
});
function onSuccessCamera(imageURI) {
var imgData = "data:image/jpeg;base64," + imageURI;
uploadFile(imgData);
}
function uploadFile(file){
var c=document.getElementById("picture");
c.width = window.innerWidth-50;//offset to prevent image flowing out of frame
// window.alert(window.innerWidth+":"+ window.innerHeight);414:736
c.height = "330";//window.innerHeight;//this.height;
var ctx=c.getContext("2d");
var showImg = new Image();
showImg.onload = function(){
var ratio = 1;
if (this.height > c.height) {
ratio = c.height/this.height;
}
else if (this.width>c.width) {
ratio = c.width/this.width;
}
ctx.drawImage(this,0,0, this.width*ratio, this.height*ratio);
// window.alert(c.width + ':' + c.height);
};
showImg.src = file;
}
i dont know why the image looks so horrible
It's because of the retina screen.
Make the height and width of the canvas to be the double, but then style it to be half pixels
c.width = (window.innerWidth-50)*2;//offset to prevent image flowing out of frame
c.height = 330*2;//window.innerHeight;//this.height;
c.style.width = (c.width/2)+"px";
c.style.height = (c.height/2)+"px";
Also, you can consider using a higher value on quality camera option.

Working with canvas to display only the drawn image and not whole canvas

With the plugin i found earlier on stackoverflow. Drawing has become smooth and nice. What i want is to only get the image part which i draw cropped from the canvas as an output and not the complete canvas. Can somebody help.
This is the code i am using for my canvas now: http://jsfiddle.net/sVsZL/1/
function canvasDisplay() {
var c=document.getElementById("canvas");
canvasImage=c.toDataURL("image/png");
document.getElementById("SSMySelectedImage").src=canvasImage;
}
Adding another answer because the other one was completely off.
Live Demo
What you need essentially is to keep track of a bounding box. What I do is create an object that holds the min values and max values of where you've drawn. This enables you to keep track of how big the image is and where it begins/ends.
this.dim = {minX : 9999, minY : 9999, maxX : 0, maxY : 0};
Then I created a function that checks the bounds.
this.setDimensions = function(x,y){
if(x < this.dim.minX){
this.dim.minX = x;
}
if(y < this.dim.minY){
this.dim.minY = y;
}
if(x > this.dim.maxX){
this.dim.maxX= x;
}
if(y > this.dim.maxY){
this.dim.maxY = y;
}
}
Make sure to check during clicking or moving.
this.mousedown = function(ev) {
tool.setDimensions(ev._x,ev._y);
};
this.mousemove = function(ev) {
tool.setDimensions(ev._x,ev._y);
};
And this is just a sample function that draws the portion to a new canvas that you could then save with toDataUrl
var button = document.getElementsByTagName("input")[0];
button.addEventListener("click", function(){
var savedCanvas = document.createElement("canvas"),
savedCtx = savedCanvas.getContext("2d"),
minX = PEN.dim.minX,
minY = PEN.dim.minY,
maxX = PEN.dim.maxX,
maxY = PEN.dim.maxY,
width = maxX - minX,
height = maxY - minY;
savedCanvas.width = width;
savedCanvas.height = height;
document.body.appendChild(savedCanvas);
savedCtx.drawImage(canvas,minX,minY,width,height,0,0,width,height);
});

HTML5 canvas: is there a way to resize image with "nearest neighbour" resampling?

I have some JS that makes some manipulations with images. I want to have pixelart-like graphics, so I had to enlarge original images in graphics editor.
But I think it'd be good idea to make all the manipulations with the small image and then enlarge it with html5 functionality. This will save bunch of processing time (because now my demo (warning: domain-name may cause some issues at work etc) loads extremely long in Firefox, for example).
But when I try to resize the image, it gets resampled bicubically. How to make it resize image without resampling? Is there any crossbrowser solution?
image-rendering: -webkit-optimize-contrast; /* webkit */
image-rendering: -moz-crisp-edges /* Firefox */
http://phrogz.net/tmp/canvas_image_zoom.html can provide a fallback case using canvas and getImageData. In short:
// Create an offscreen canvas, draw an image to it, and fetch the pixels
var offtx = document.createElement('canvas').getContext('2d');
offtx.drawImage(img1,0,0);
var imgData = offtx.getImageData(0,0,img1.width,img1.height).data;
// Draw the zoomed-up pixels to a different canvas context
for (var x=0;x<img1.width;++x){
for (var y=0;y<img1.height;++y){
// Find the starting index in the one-dimensional image data
var i = (y*img1.width + x)*4;
var r = imgData[i ];
var g = imgData[i+1];
var b = imgData[i+2];
var a = imgData[i+3];
ctx2.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
ctx2.fillRect(x*zoom,y*zoom,zoom,zoom);
}
}
More: MDN docs on image-rendering
I wrote a NN resizing script a while ago using ImageData (around line 1794)
https://github.com/arahaya/ImageFilters.js/blob/master/imagefilters.js
You can see a demo here
http://www.arahaya.com/imagefilters/
unfortunately the builtin resizing should be slightly faster.
This CSS on the canvas element works:
image-rendering: pixelated;
This works in Chrome 93, as of September 2021.
You can simply set context.imageSmoothingEnabled to false. This will make everything drawn with context.drawImage() resize using nearest neighbor.
// the canvas to resize
const canvas = document.createElement("canvas");
// the canvas to output to
const canvas2 = document.createElement("canvas");
const context2 = canvas2.getContext("2d");
// disable image smoothing
context2.imageSmoothingEnabled = false;
// draw image from the canvas
context2.drawImage(canvas, 0, 0, canvas2.width, canvas2.height);
This has better support than using image-rendering: pixelated.
I'll echo what others have said and tell you it's not a built-in function. After running into the same issue, I've made one below.
It uses fillRect() instead of looping through each pixel and painting it. Everything is commented to help you better understand how it works.
//img is the original image, scale is a multiplier. It returns the resized image.
function Resize_Nearest_Neighbour( img, scale ){
//make shortcuts for image width and height
var w = img.width;
var h = img.height;
//---------------------------------------------------------------
//draw the original image to a new canvas
//---------------------------------------------------------------
//set up the canvas
var c = document.createElement("CANVAS");
var ctx = c.getContext("2d");
//disable antialiasing on the canvas
ctx.imageSmoothingEnabled = false;
//size the canvas to match the input image
c.width = w;
c.height = h;
//draw the input image
ctx.drawImage( img, 0, 0 );
//get the input image as image data
var inputImg = ctx.getImageData(0,0,w,h);
//get the data array from the canvas image data
var data = inputImg.data;
//---------------------------------------------------------------
//resize the canvas to our bigger output image
//---------------------------------------------------------------
c.width = w * scale;
c.height = h * scale;
//---------------------------------------------------------------
//loop through all the data, painting each pixel larger
//---------------------------------------------------------------
for ( var i = 0; i < data.length; i+=4 ){
//find the colour of this particular pixel
var colour = "#";
//---------------------------------------------------------------
//convert the RGB numbers into a hex string. i.e. [255, 10, 100]
//into "FF0A64"
//---------------------------------------------------------------
function _Dex_To_Hex( number ){
var out = number.toString(16);
if ( out.length < 2 ){
out = "0" + out;
}
return out;
}
for ( var colourIndex = 0; colourIndex < 3; colourIndex++ ){
colour += _Dex_To_Hex( data[ i+colourIndex ] );
}
//set the fill colour
ctx.fillStyle = colour;
//---------------------------------------------------------------
//convert the index in the data array to x and y coordinates
//---------------------------------------------------------------
var index = i/4;
var x = index % w;
//~~ is a faster way to do 'Math.floor'
var y = ~~( index / w );
//---------------------------------------------------------------
//draw an enlarged rectangle on the enlarged canvas
//---------------------------------------------------------------
ctx.fillRect( x*scale, y*scale, scale, scale );
}
//get the output image from the canvas
var output = c.toDataURL("image/png");
//returns image data that can be plugged into an img tag's src
return output;
}
Below is an example of it in use.
Your image would appear in the HTML like this:
<img id="pixel-image" src="" data-src="pixel-image.png"/>
The data-src tag contains the URL for the image you want to enlarge. This is a custom data tag. The code below will take the image URL from the data tag and put it through the resizing function, returning a larger image (30x the original size) which then gets injected into the src attribute of the img tag.
Remember to put the function Resize_Nearest_Neighbour (above) into the <script> tag before you include the following.
function Load_Image( element ){
var source = element.getAttribute("data-src");
var img = new Image();
img.addEventListener("load", function(){
var bigImage = Resize_Nearest_Neighbour( this, 30 );
element.src = bigImage;
});
img.src = source;
}
Load_Image( document.getElementById("pixel-image") );
There is no built-in way. You have to do it yourself with getImageData.
Based on Paul Irish's comment:
function resizeBase64(base64, zoom) {
return new Promise(function(resolve, reject) {
var img = document.createElement("img");
// once image loaded, resize it
img.onload = function() {
// get image size
var imageWidth = img.width;
var imageHeight = img.height;
// create and draw image to our first offscreen canvas
var canvas1 = document.createElement("canvas");
canvas1.width = imageWidth;
canvas1.height = imageHeight;
var ctx1 = canvas1.getContext("2d");
ctx1.drawImage(this, 0, 0, imageWidth, imageHeight);
// get pixel data from first canvas
var imgData = ctx1.getImageData(0, 0, imageWidth, imageHeight).data;
// create second offscreen canvas at the zoomed size
var canvas2 = document.createElement("canvas");
canvas2.width = imageWidth * zoom;
canvas2.height = imageHeight * zoom;
var ctx2 = canvas2.getContext("2d");
// draw the zoomed-up pixels to a the second canvas
for (var x = 0; x < imageWidth; ++x) {
for (var y = 0; y < imageHeight; ++y) {
// find the starting index in the one-dimensional image data
var i = (y * imageWidth + x) * 4;
var r = imgData[i];
var g = imgData[i + 1];
var b = imgData[i + 2];
var a = imgData[i + 3];
ctx2.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a / 255 + ")";
ctx2.fillRect(x * zoom, y * zoom, zoom, zoom);
}
}
// resolve promise with the zoomed base64 image data
var dataURI = canvas2.toDataURL();
resolve(dataURI);
};
img.onerror = function(error) {
reject(error);
};
// set the img soruce
img.src = base64;
});
}
resizeBase64(src, 4).then(function(zoomedSrc) {
console.log(zoomedSrc);
});
https://jsfiddle.net/djhyquon/69/

Poor anti-aliasing of text drawn on Canvas

I'm drawing text on Canvas, and am disappointed with the quality of antialiasing. As far as I've been able to determine, browsers don't do subpixel antialising of text on Canvas.
Is this accurate?
This is particularly noticeable on iPhone and Android, where the resulting text isn't as crisp as text rendered by other DOM elements.
Any suggestions for high quality text out put on Canvas?
Joubert
My answer came from this link, maybe it will help someone else.
http://www.html5rocks.com/en/tutorials/canvas/hidpi/
The important code is as follows.
// finally query the various pixel ratios
devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1,
ratio = devicePixelRatio / backingStoreRatio;
// upscale the canvas if the two ratios don't match
if (devicePixelRatio !== backingStoreRatio) {
var oldWidth = canvas.width;
var oldHeight = canvas.height;
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
context.scale(ratio, ratio);
}
Try adding the following META tag to your page. This seems to fix anti-aliasing issues I've had on iPhone Safari:
<meta name="viewport" content="user-scalable=no, width=device-width,
initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5" />
I realise this is an old question, but I worked on this problem today and got it working nicely. I used Alix Axel's answer above and stripped down the code I found there (on the web.archive.org link) to the bare essentials.
I modified the solution a bit, using two canvases, one hidden canvas for the original text and a second canvas to actually show the anti-aliaised text.
Here's what I came up with... http://jsfiddle.net/X2cKa/
The code looks like this;
function alphaBlend(gamma, c1, c2, alpha) {
c1 = c1/255.0;
c2 = c2/255.0;
var c3 = Math.pow(
Math.pow(c1, gamma) * (1 - alpha)
+ Math.pow(c2, gamma) * alpha,
1/gamma
);
return Math.round(c3 * 255);
}
function process(textPixels, destPixels, fg, bg) {
var gamma = 2.2;
for (var y = 0; y < textPixels.height; y ++) {
var history = [255, 255, 255];
var pixel_number = y * textPixels.width;
var component = 0;
for (var x = 0; x < textPixels.width; x ++) {
var alpha = textPixels.data[(y * textPixels.width + x) * 4 + 1] / 255.0;
alpha = Math.pow(alpha, gamma);
history[component] = alpha;
alpha = (history[0] + history[1] + history[2]) / 3;
out = alphaBlend(gamma, bg[component], fg[component], alpha);
destPixels.data[pixel_number * 4 + component] = out;
/* advance to next component/pixel */
component ++;
if (component == 3) {
pixel_number ++;
component = 0;
}
}
}
}
function toColor(colorString) {
return [parseInt(colorString.substr(1, 2), 16),
parseInt(colorString.substr(3, 2), 16),
parseInt(colorString.substr(5, 2), 16)];
}
function renderOnce() {
var phrase = "Corporate GOVERNANCE"
var c1 = document.getElementById("c1"); //the hidden canvas
var c2 = document.getElementById("c2"); //the canvas
var textSize=40;
var font = textSize+"px Arial"
var fg = "#ff0000";
var bg = "#fff9e1";
var ctx1 = c1.getContext("2d");
var ctx2 = c2.getContext("2d");
ctx1.fillStyle = "rgb(255, 255, 255)";
ctx1.fillRect(0, 0, c1.width, c1.height);
ctx1.save();
ctx1.scale(3, 1);
ctx1.font = font;
ctx1.fillStyle = "rgb(255, 0, 0)";
ctx1.fillText(phrase, 0, textSize);
ctx1.restore();
var textPixels = ctx1.getImageData(0, 0, c1.width, c1.height);
var colorFg = toColor(fg);
var colorBg = toColor(bg);
var destPixels3 = ctx1.getImageData(0, 0, c1.width, c1.height);
process(textPixels, destPixels3, colorBg, colorFg);
ctx2.putImageData(destPixels3, 0, 0);
//for comparison, show Comparison Text without anti aliaising
ctx2.font = font;
ctx2.fillStyle = "rgb(255, 0, 0)";
ctx2.fillText(phrase, 0, textSize*2);
};
renderOnce();
I also added a comparison text object so that you can see the anti-aliasing working.
Hope this helps someone!
There is some subpixel antialiasing done, but it is up to the browser/OS.
There was a bit of an earlier discussion on this that may be of help to you.
I don't have an android or iOS device but just for kicks, try translating the context by (.5, 0) pixels before you draw and see if that makes a difference in how your text is rendered.

Insane Graphics.lineStyle behavior

I'd like some help with a little project of mine.
Background:
i have a little hierarchy of Sprite derived classes (5 levels starting from the one, that is the root application class in Flex Builder). Width and Height properties are overriden so that my class always remembers it's requested size (not just bounding size around content) and also those properties explicitly set scaleX and scaleY to 1, so that no scaling would ever be involved. After storing those values, draw() method is called to redraw content.
Drawing:
Drawing is very straight forward. Only the deepest object (at 1-indexed level 5) draws something into this.graphics object like this:
var gr:Graphics = this.graphics;
gr.clear();
gr.lineStyle(0, this.borderColor, 1, true, LineScaleMode.NONE);
gr.beginFill(0x0000CC);
gr.drawRoundRectComplex(0, 0, this.width, this.height, 10, 10, 0, 0);
gr.endFill();
Further on:
There is also MouseEvent.MOUSE_WHEEL event attached to the parent of the object that draws. What handler does is simply resizes that drawing object.
Problem:
Screenshot
When resizing sometimes that hairline border line with LineScaleMode.NONE set gains thickness (quite often even >10 px) + it quite often leaves a trail of itself (as seen in the picture above and below blue box (notice that box itself has one px black border)). When i set lineStile thickness to NaN or alpha to 0, that trail is no more happening.
I've been coming back to this problem and dropping it for some other stuff for over a week now.
Any ideas anyone?
P.S. Grey background is that of Flash Player itself, not my own choise.. :D
As requested, a bit more:
Application is supposed to be a calendar-timeline with a zooming "feature" (project for a course at university). Thus i have these functions that have something to do with resizing:
public function performZoom():void
{
// Calculate new width:
var newDayWidth:Number = view.width / 7 * this.calModel.zoom;
if (newDayWidth < 1)
{
newDayWidth = 1;
}
var newWidth:int = int(newDayWidth * timeline.totalDays);
// Calculate day element Height/Width ratio:
var headerHeight:Number = this.timeline.headerAllDay;
var proportion:Number = 0;
if (this.view.width != 0 && this.view.height != 0)
{
proportion = 1 / (this.view.width / 7) * (this.view.height - this.timeline.headerAllDay);
}
// Calculate new height:
var newHeight:int = int(newDayWidth * proportion + this.timeline.headerAllDay);
// Calculate mouse position scale on X axis:
var xScale:Number = 0;
if (this.timeline.width != 0)
{
xScale = newWidth / this.timeline.width;
}
// Calculate mouse position scale on Y axis:
var yScale:Number = 0;
if (this.timeline.height - this.timeline.headerAllDay != 0)
{
yScale = (newHeight - this.timeline.headerAllDay) / (this.timeline.height - this.timeline.headerAllDay);
}
this.timeline.beginUpdate();
// Resize the timeline
this.timeline.resizeElement(newWidth, newHeight);
this.timeline.endUpdate();
// Move timeline:
this.centerElement(xScale, yScale);
// Reset timeline local mouse position:
this.centerMouse();
}
public function resizeElement(widthValue:Number, heightValue:Number):void
{
var prevWidth:Number = this.myWidth;
var prevHeight:Number = this.myHeight;
if (widthValue != prevWidth || heightValue != prevHeight)
{
super.width = widthValue;
super.height = heightValue;
this.scaleX = 1.0;
this.scaleY = 1.0;
this.myHeight = heightValue;
this.myWidth = widthValue;
if (!this.docking)
{
this.origHeight = heightValue;
this.origWidth = widthValue;
}
this.updateAnchorMargins();
onResizeInternal(prevWidth, prevHeight, widthValue, heightValue);
}
}
Yes. I know.. a lot of core, and a lot of properties, but in fact most of the stuff has been disabled at the end and the situation is as described at the top.
this didn't work:
gr.lineStyle(); // Reset line style
Can we see your resizing code?
Also try clearing your line style as well as your fill:
gr.lineStyle(0, this.borderColor, 1, true, LineScaleMode.NONE);
gr.beginFill(0x0000CC);
gr.drawRoundRectComplex(0, 0, this.width, this.height, 10, 10, 0, 0);
gr.endFill();
gr.lineStyle();//<---- add this line
I don't know whether it's flash bug or what it is, but i finally found the solution.
The thing is that in my case when resizing in a nutshell you get like this:
this.width = this.stage.stageWidth / 7 * 365;
When i switch to maximized window this.width gains value 86k+. When i added this piece of code to draw horizontal line, it fixed itself:
var recWidth:Number = this.width;
var i:int;
var newEnd:Number = 0;
for (i = 1; newEnd < recWidth; i++)
{
newEnd = 100 * i;
if (newEnd > recWidth)
{
newEnd = recWidth;
}
gr.lineTo(newEnd, 0);
}
I don't know what is going on.. This is inconvenient...