Why canvas image size break in Safari? - javascript

First, the website width and height is based on the user screen.
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = window.innerWidth;
c.height = window.innerHeight;
window.addEventListener("resize", function () {
c.width = window.innerWidth;
c.height = window.innerHeight;
})
Then, I am using drawImage
class castle {
constructor() {
const castle = new Image()
castle.src = './Img/castle.png'
castle.onload = () => {
this.scale = 0.5
this.image = castle
this.width = this.image.width * this.scale
this.height = this.image.height * this.scale
this.position = {
x: c.width / 2 - this.width / 2,
y: c.height / 2 - this.height / 2
}
this.center = {
x: c.width / 2,
y: c.height / 2
}
}
}
draw() {
ctx.drawImage(
this.image,
this.position.x,
this.position.y,
this.width,
this.height
)
}
}
Same monitor:
Why the image shown different size between Google Chrome and Safari?
The performance of my project with Google Chrome is fine.
However, the image size with Safari is too too big
I googled:
image to canvas on chrome but not safari
Em...
Should I upload all image to imgur? It's better than open a folder Img in my project?

Safari has issues handling html canvas size. Probably this is why your images are rendering differently.
Canvas size must be: width * height < 16777216.
This happened to me a while ago while debugging one of my projects on Safari Mobile.

Related

How to handle window resize for displaying canvas for mobile devices with devicepixelratio

I am trying to handle window resize event for a javascript canvas application.
When I change the width on browser it seems to work fine, but if I open device toolbar and change the width, the width doesn't change sticks to 980, though the height changes. And the rendered objects get much smaller.
According to this How to stop chrome responsive inspector from changing the zoom?, now width change when I resize.
My new problem is the rendered objects still looks smaller on resize. It is related to window.devicePixelRatio.
For example If use this code to set the dimensions used to draw objects:
this.width = displayWidth;
this.height = displayHeight;
things render normally initially, and much smaller on resize.
If I change this code to this:
this.width = displayWidth * devicePixelRatio;
this.height = displayHeight * devicePixelRatio;
Things render much larger initially, but if I have the same code on resize, things render normal.
I also have this somewhere in:
canvasContext.scale(pixelRatio, pixelRatio);
Canvas javascript:
export default function Canvas(element) {
const canvas = document.createElement('canvas');
element.append(canvas);
const displayWidth = canvas.clientWidth,
displayHeight = canvas.clientHeight,
devicePixelRatio = window.devicePixelRatio || 1;
this.pixelRatio = devicePixelRatio;
this.width = displayWidth;
this.height = displayHeight;
canvas.width = displayWidth * devicePixelRatio;
canvas.height = displayHeight * devicePixelRatio;
this.aspect = this.width / this.height;
this.canvas = canvas;
this.bounds = canvas.getBoundingClientRect();
// called on `window.onresize` event
this.resize = () => {
const displayWidth = canvas.clientWidth,
displayHeight = canvas.clientHeight,
pixelRatio = window.devicePixelRatio || 1;
this.pixelRatio = pixelRatio;
this.width = displayWidth;
this.height = displayHeight;
canvas.width = displayWidth * pixelRatio;
canvas.height = displayHeight * pixelRatio;
this.aspect = this.width / this.height;
this.bounds = canvas.getBoundingClientRect();
};
};
css:
#app canvas {
position: absolute;
top: 50%;
bottom: 0;
left: 50%;
right: 0;
width: 100vw;
height: 100vh;
transform: translate(-50%, -25%);
image-rendering: optimizeSpeed;
cursor: pointer;
margin: auto;
}
The problem was when it's resized, the canvas context lost it's scale properties, so I had to call scale again.
ctx.scale(pixelRatio, pixelRatio);
_canvas.addResizeListener(() => {
ctx.scale(pixelRatio, pixelRatio);
});

Pixelated resize using canvas with transparent PNG

I want to accomplish a pixelated effect using the canvas option imageSmoothingEnabled=false; so the image "unblurs" on scroll.
Everything works fine until using transparent images namely PNGs. The scaled image is projected, which stays in the background.
Also the image does not get loaded until the user has scrolled a few pixels.
I've found out that the canvas.drawImage() function owns parameters to set the offset. However I haven't found a solution to this.
Demo https://jsfiddle.net/aLjfemru/
var ctx = canvas.getContext('2d'),
img = new Image(),
play = false;
/// turn off image smoothing - this will give the pixelated effect
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
/// wait until image is actually available
img.onload = function(){
image1.src="nf.png";
context.drawImage(image1, 50, 50, 10, 10);
};
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// MAIN function
function pixelate(v) {
document.getElementById("v").innerHTML = "(v): " + v;
/// if in play mode use that value, else use slider value
var size = v * 0.01;
var w = canvas.width * size;
var h = canvas.height * size;
/// draw original image to the scaled size
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, w, h);
ctx.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}
function onScroll() {
$(window).on('scroll', function() {
var y = window.pageYOffset;
if (y > 10) {
y = Math.pow(y, 0.8);
if (y >= 60) {
y = 100;
}
pixelate(y);
}
});
}
onScroll();
Some quick changes to get it happening
Use a second canvas to do the pixelation
Wait for the images to load before doing the rendering.
The onscroll will not fire until you scroll, so when image has loaded call the rendering function to display the image.
canvas.width = innerWidth-20;
ctx = canvas.getContext("2d");
var ctxImage;
const img = new Image;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// wait until image is actually available
img.onload = function(){
// I dont knwo what this is for so removed the following two lines
//image1.src="nf.png";
//context.drawImage(image1, 50, 50, 10, 10);
// Create a canvas to match the image
var c = document.createElement("canvas");
canvas.width = Math.min(canvas.width,(c.width = this.naturalWidth));
canvas.height = c.height = this.naturalHeight;
ctxImage = c.getContext("2d");
// changing canvas size resets the state so need to set this again.
ctx.imageSmoothingEnabled = false;
onScroll();
pixelate(100); // call first time
};
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.fillText("Loading please wait.",ctx.canvas.width /2, ctx.canvas.height / 4);
/// MAIN function
function pixelate(v) {
document.getElementById("v").innerHTML = "(v): " + v;
/// if in play mode use that value, else use slider value
var size = Number(v) * 0.01;
var w = img.width * size;
var h = img.height * size;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctxImage.clearRect(0, 0, ctxImage.canvas.width, ctxImage.canvas.height);
ctxImage.drawImage(img, 0, 0, w, h);
ctx.drawImage(ctxImage.canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}
function onScroll() {
addEventListener("scroll", function() {
var y = window.pageYOffset;
if (y > 10) {
y = Math.pow(y, 0.65);
if (y >= 100) {
y = 100;
}
pixelate(y);
}
});
}
#fix {
position: fixed;
}
html {
height: 2000px;
}
<div id="fix">
<p id="v" value="Animate">1</p><br />
<canvas id="canvas"></canvas>
</div>
This has since been made into an extremely minimalist library, and my PR for PNG support can be found here.
Once it has been merged I will come back and update this answer.
The full code, generalized and simplified from #Blindman67's answer:
/**
* 8bit
*
* A module that converts an image into a pixelated version (just like
* 8bit artwork).
*
* #author rogeriopvl <https://github.com/rogeriopvl>
* #license MIT
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.eightBit = factory();
}
} (this, function () {
// Necessary to hide the original image with PNG transparency
const invisibleCanvas = document.createElement("canvas");
const invisibleCtx = invisibleCanvas.getContext("2d");
/**
* Draws a pixelated version of an image in a given canvas.
* #param {object} canvas - a canvas object
* #param {object} image - an image HTMLElement object
* #param {number} quality - the new quality: between 0 and 100
*/
const eightBit = function (canvas, image, quality) {
quality /= 100;
canvas.width = invisibleCanvas.width = image.width;
canvas.height = invisibleCanvas.height = image.height;
const scaledW = canvas.width * quality;
const scaledH = canvas.height * quality;
const ctx = canvas.getContext("2d");
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
// Draws image scaled to desired quality on the invisible canvas, then
// draws that scaled image on the visible canvas.
ctx.clearRect(0, 0, canvas.width, canvas.height);
invisibleCtx.clearRect(0, 0, invisibleCtx.canvas.width, invisibleCtx.canvas.height);
invisibleCtx.drawImage(image, 0, 0, scaledW, scaledH);
ctx.drawImage(invisibleCtx.canvas, 0, 0, scaledW, scaledH, 0, 0, canvas.width, canvas.height);
};
return eightBit;
}));

Canvas disappear with soft keyboard on android

i've made a little script where a canvas is drawn dynamically according to user input(type:number), it needs work but the issue is that on android devices, when the user hide the soft keyboard, the canvas is erased. is it a way to prevent that ? Thx for any help
here is the code
var form = document.querySelector("form"),
canvas = document.getElementById("canvas"),
positions = [];
if (!canvas) {
alert("Impossible de récupérer le canvas");
}
window.addEventListener("resize", function () {
if (window.innerWidth < 500) {
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerWidth * 0.8;
} else {
canvas.width = 600;
canvas.height = 600;
}
});
var context = canvas.getContext("2d");
if (!context) {
alert("Impossible de récupérer le contexte");
}
form.addEventListener("input", function () {
var table = form.table.value,
modulo = form.modulo.value,
centerX = canvas.width / 2,
centerY = canvas.height / 2,
rayon = (canvas.width - 5) / 2;
context.lineWidth = 2;
context.strokeStyle = "#21A8A3";
context.clearRect(0, 0, canvas.width, canvas.width);
for (var i = modulo; i >= 0; i -= 1) {
var angle = (2 * Math.PI / modulo) * i - (Math.PI / 2);
positions[i] = {
x: centerX + rayon * Math.cos(angle),
y: centerY + rayon * Math.sin(angle)
};
}
context.beginPath();
context.arc(centerX, centerY, rayon, 0, Math.PI * 2);
var j = positions.length - 1;
for (j; j >= 0; j -= 1) {
var next = j * table;
context.moveTo(positions[j].x, positions[j].y);
context.lineTo(positions[next % modulo].x, positions[next % modulo].y);
}
context.stroke();
});
and the url :http://multiplier.hyperion-web.com/
I didn't tried on an Android device, but your problem is more likely due to your bad resize event listener.
This handler do resize your canvas at each resize event, but it doesn't call again your drawing functions, which leaves you with an empty canvas, since modifying the width and height of a canvas resets its context (it does clear it, but also resets any properties like fillStyle, transformation matrix, clipping area etc).
Calling and hiding the soft keyboard will fire a resize event.
So the solution for you is to fix your event handler so that it does redraw everything after it has set the new dimensions of your canvas.
For this you'll have to store your drawing function in a separate variable, then attach it to your input event, and make a call to it in your resize event listener.
Ps : even setting the width or height attributes to the same value they were will reset the whole context

Image scaling works locally but not when ran from server

I am trying to scale images when loaded into the browser. Here is my code that I am working with -
function importImage(e) {
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var reader = new FileReader;
reader.onload = function(event) {
event.preventDefault();
var img = new Image;
img.src = event.target.result;
img.onload = function() {
width = img.width;
height = img.height;
var scaleX, scaleY, scale;
var scaledWidth, scaledHeight;
scaleX = width / canvas.width;
scaleY = height / canvas.height;
scale = scaleX > scaleY ? scaleX : scaleY;
scaledWidth = width / scale;
scaledHeight = height / scale;
ctx.clearRect(0,0, canvas.width, canvas.height);
ctx.drawImage(img, (canvas.width - scaledWidth) / 2, (canvas.height - scaledHeight) / 2, scaledWidth, scaledHeight);
}
}
reader.readAsDataURL(e.target.files[0]);
}
It seems to works ok when used from a local machine but when it is ran from the server the scaling doesn't work. I assume it is to do with how I am referring to the actual image but I am unsure.

HTML5 Canvas drawImage ratio bug iOS

I want to resize the image taken from the iOS camera on the client side with HTML5 Canvas but I keep running in this weird bug where the image has a wrong ratio if bigger than ~1.5mb
It works on the desktop but not in the latest iOS version with the media upload API.
You can see an example here: http://jsbin.com/ekuros/1
Any idea how to fix this please? Is this a memory issue?
$('#file').on('change', function (e) {
var file = e.currentTarget.files[0];
var reader = new FileReader();
reader.onload = function (e) {
var image = $('<img/>');
image.on('load', function () {
var square = 320;
var canvas = document.createElement('canvas');
canvas.width = square;
canvas.height = square;
var context = canvas.getContext('2d');
context.clearRect(0, 0, square, square);
var imageWidth;
var imageHeight;
var offsetX = 0;
var offsetY = 0;
if (this.width > this.height) {
imageWidth = Math.round(square * this.width / this.height);
imageHeight = square;
offsetX = - Math.round((imageWidth - square) / 2);
} else {
imageHeight = Math.round(square * this.height / this.width);
imageWidth = square;
offsetY = - Math.round((imageHeight - square) / 2);
}
context.drawImage(this, offsetX, offsetY, imageWidth, imageHeight);
var data = canvas.toDataURL('image/jpeg');
var thumb = $('<img/>');
thumb.attr('src', data);
$('body').append(thumb);
});
image.attr('src', e.target.result);
};
reader.readAsDataURL(file);
});
If you still need to use the long version of the drawImage function you can change this:
context.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
to this:
drawImageIOSFix(context, img, sx, sy, sw, sh, dx, dy, dw, dh);
You just need to include these two functions somewhere:
/**
* Detecting vertical squash in loaded image.
* Fixes a bug which squash image vertically while drawing into canvas for some images.
* This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel
*
*/
function detectVerticalSquash(img) {
var iw = img.naturalWidth, ih = img.naturalHeight;
var canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = ih;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var data = ctx.getImageData(0, 0, 1, ih).data;
// search image edge pixel position in case it is squashed vertically.
var sy = 0;
var ey = ih;
var py = ih;
while (py > sy) {
var alpha = data[(py - 1) * 4 + 3];
if (alpha === 0) {
ey = py;
} else {
sy = py;
}
py = (ey + sy) >> 1;
}
var ratio = (py / ih);
return (ratio===0)?1:ratio;
}
/**
* A replacement for context.drawImage
* (args are for source and destination).
*/
function drawImageIOSFix(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
var vertSquashRatio = detectVerticalSquash(img);
// Works only if whole image is displayed:
// ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
// The following works correct also when only a part of the image is displayed:
ctx.drawImage(img, sx * vertSquashRatio, sy * vertSquashRatio,
sw * vertSquashRatio, sh * vertSquashRatio,
dx, dy, dw, dh );
}
This will work fine whether it is run on iOS or other platforms.
This is based on the great work by stomita and you should credit him in your work.
There is a JavaScript canvas resize library which works around the subsampling and vertical squash issues encountered when drawing scaled images on canvas on iOS devices:
http://github.com/stomita/ios-imagefile-megapixel
There are side issues when scaling images with alpha channel (as it uses the alpha channel for the issues detection) and when trying to resize existing canvas elements, however it's the first solution I've found that actually works with the issue at hand.
stomita is also a StackOverflow user and posted his solution here:
https://stackoverflow.com/a/12615436/644048
It looks like this is an iOS 6 bug. There is no reason for the aspect to get out of whack from your code. I have the same problem which was only introduced in iOS 6. It seems that their sub-sampling routine gives the wrong height. I submitted a bug report to Apple, and you should do the same. The more bug reports they get for this the better.
I've experienced the same problem. It seems that this is an iOS limitation, jpg over 2 megapixel are subsampled.
See Creating Compatible Web Content for Safari on IPhone
A modified version of the above code.
Edit: saw L0LN1NJ4's code at http://jsfiddle.net/gWY2a/24/ .. guess that one's a bit better...
function drawImageIOSFix (ctx, img) {
var vertSquashRatio = detectVerticalSquash (img)
var arg_count = arguments.length
switch (arg_count) {
case 4 : ctx.drawImage (img, arguments[2], arguments[3] / vertSquashRatio); break
case 6 : ctx.drawImage (img, arguments[2], arguments[3], arguments[4], arguments[5] / vertSquashRatio); break
case 8 : ctx.drawImage (img, arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7] / vertSquashRatio); break
case 10 : ctx.drawImage (img, arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9] / vertSquashRatio); break
}
// Detects vertical squash in loaded image.
// Fixes a bug which squash image vertically while drawing into canvas for some images.
// This is a bug in iOS6 (and IOS7) devices. This function from https://github.com/stomita/ios-imagefile-megapixel
function detectVerticalSquash (img) {
var iw = img.naturalWidth, ih = img.naturalHeight
var canvas = document.createElement ("canvas")
canvas.width = 1
canvas.height = ih
var ctx = canvas.getContext('2d')
ctx.drawImage (img, 0, 0)
var data = ctx.getImageData(0, 0, 1, ih).data
// search image edge pixel position in case it is squashed vertically.
var sy = 0, ey = ih, py = ih
while (py > sy) {
var alpha = data[(py - 1) * 4 + 3]
if (alpha === 0) {ey = py} else {sy = py}
py = (ey + sy) >> 1
}
var ratio = (py / ih)
return (ratio === 0) ? 1 : ratio
}
}

Categories

Resources