Custom crop method isn't working in iPhone - javascript

Im facing this STRANGEST issue in my website. The method given below takes a base64 string and the crop, zoom and rotation values and returns a modified base64 string. This method works absolutely fine on all the android devices and desktops. But when an iPhone is used, this methods returns a base64 string that when converted is a plain black image. I have no clue why its behaving like this on all the iPhones.
Extra info : This only happens for the HEIF images that are automatically converted to jpeg when a user selects them from the iPhone's gallery. For other formats like jpg and png the method works fine even on iPhone.
Here's the crop method
async function getCroppedImg(imageSrc, pixelCrop, rotation) {
console.log('imagesrc : ', imageSrc)
const image = await createImage(imageSrc);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const maxSize = Math.max(image.width, image.height);
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
canvas.width = safeArea;
canvas.height = safeArea;
ctx.translate(safeArea / 2, safeArea / 2);
ctx.rotate(getRadianAngle(rotation));
ctx.translate(-safeArea / 2, -safeArea / 2);
ctx.drawImage(
image,
safeArea / 2 - image.width * 0.5,
safeArea / 2 - image.height * 0.5
);
const data = ctx.getImageData(0, 0, safeArea, safeArea);
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
ctx.putImageData(
data,
0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x,
0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y
);
// As Base64 string
return canvas.toDataURL("image/jpeg");
}
const createImage = (url) =>
new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener("load", () => resolve(image));
image.addEventListener("error", (error) => reject(error));
image.setAttribute("crossOrigin", "anonymous");
image.src = url;
});
function getRadianAngle(degreeValue) {
return (degreeValue * Math.PI) / 180;
}

Related

Why canvas image size break in Safari?

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.

How to copy image rotation transform on to canvas with drawImage

I've seen this problem online e.g:
HTML5 canvas drawImage with at an angle
http://creativejs.com/2012/01/day-10-drawing-rotated-images-into-canvas/
But I was unsuccessful in applying them to my problem so far.
const TO_RADIANS = Math.PI / 180
export function cropPreview(
image: HTMLImageElement,
canvas: HTMLCanvasElement,
crop: PixelCrop,
scale = 1,
rotate = 0,
) {
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error('No 2d context')
}
const scaleX = image.naturalWidth / image.width
const scaleY = image.naturalHeight / image.height
const pixelRatio = window.devicePixelRatio || 1
canvas.width = Math.floor(crop.width * pixelRatio * scaleX)
canvas.height = Math.floor(crop.height * pixelRatio * scaleY)
ctx.scale(pixelRatio, pixelRatio)
ctx.imageSmoothingQuality = 'high'
const cropX = crop.x * scaleX
const cropY = crop.y * scaleY
const cropWidth = crop.width * scaleX
const cropHeight = crop.height * scaleY
const rotateRads = rotate * TO_RADIANS
const centerX = image.width / 2
const centerY = image.height / 2
ctx.save()
ctx.translate(centerX, centerY)
ctx.rotate(rotateRads)
ctx.drawImage(image, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight)
ctx.restore()
}
Here's a live playground with the problem, just choose an image and apply some rotation: CodeSandbox image rotation
What am I doing wrong here? The bottom image should show exactly what is in the crop area even with rotation applied.
Here is your updated function
export async function cropPreview(
image: HTMLImageElement,
canvas: HTMLCanvasElement,
crop: PixelCrop,
scale = 1,
rotate = 0,
) {
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error('No 2d context')
}
const scaleX = image.naturalWidth / image.width
const scaleY = image.naturalHeight / image.height
const pixelRatio = window.devicePixelRatio || 1
canvas.width = Math.floor(crop.width * pixelRatio * scaleX)
canvas.height = Math.floor(crop.height * pixelRatio * scaleY)
ctx.scale(pixelRatio, pixelRatio)
ctx.imageSmoothingQuality = 'high'
const cropX = crop.x * scaleX
const cropY = crop.y * scaleY
const cropWidth = crop.width * scaleX
const cropHeight = crop.height * scaleY
const rotateRads = rotate * TO_RADIANS
const centerX = image.width / 2
const centerY = image.height / 2
ctx.save()
// 5) Move the crop origin to the canvas origin (0,0)
ctx.translate(-cropX, -cropY)
// 4) Move the origin to the center of the original position
ctx.translate(centerX, centerY)
// 3) Rotate around the origin
ctx.rotate(rotateRads)
// 2) Scaled the image
ctx.scale(scale, scale)
// 1) Move the center of the image to the origin (0,0)
ctx.translate(-centerX, -centerY)
ctx.drawImage(
image,
0,
0,
image.width,
image.height,
0,
0,
image.width,
image.height,
)
ctx.restore()
}
Instead of trying to figure out how to crop you can simply let the canvas to crop it.
The transfromations are applied in the reverse order as they appear, so what I am doing is
Move the center of the image to the origin (0,0)
Scaled the image
Rotate around the origin
Move the origin to the center of the original position
Move the crop origin to the canvas origin (0,0)

iOS resizing image with canvas results in black image

I am trying to resize multiple images in the browser (same results in Chrome and Safari) by using canvas, to then save them to IndexedDB via Dexie.js. It all works fine in Android, but on iOS (tested on iPhone 8 and X) 1 or 2 random pictures result in black images.
What might be the issue? This is the function I am using:
photoHeightMax = 1100;
photoWidthMax = 1100;
resizeBase64Img(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = () => {
const imgWidth = img.width;
const imgHeight = img.height;
const scale = Math.min(this.photoWidthMax / imgWidth, this.photoHeightMax / imgHeight);
const widthScaled = scale > 1 ? imgWidth : imgWidth * scale;
const heightScaled = scale > 1 ? imgHeight : imgHeight * scale;
const elem = document.createElement('canvas');
elem.width = widthScaled;
elem.height = heightScaled;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, widthScaled, heightScaled);
const data = ctx.canvas.toDataURL('image/jpeg', 0.85);
resolve(data);
};
img.onerror = (error) => reject(error);
});
}
Any help would be appreciated!

How can I crop an image on the client side without losing resolution using React

I'm trying to crop an image in a react application. I'm currently using a library called react-image-crop but it seems the cropped image is exactly the same size of crop area on the browser, means if I resize my browser window, the same area of the image comes to be different size. is there any way to retain the resolution of the original image and be still able to crop on the client side? the picture below is the result of cropping of the same area of the picture in different browser sizes.
i'm pretty much using react-image-crop out of the box with following crop parameters
const [crop, setCrop] = useState({unit: '%', width: 100, aspect: 16 / 9 });
Actually i have the same problem, i also use react-image-crop. My current solution is i create a modal with static size in px as a crop windows. so if the window size is smaller, the modal overflow will be activated.
more or less like this:
so scroller will dynamically change based on window size and it doesn't affect the image size.
If there is a real and better solution maybe i'd also like to hear about it :)
in the hook version of the demo, replacing the getResizedCanvas (and moving it inside the react module) fixes the problem
const getResizedCanvas = () => {
const scaleX = imgRef.current.naturalWidth / imgRef.current.width;
const scaleY = imgRef.current.naturalHeight / imgRef.current.height;
const tmpCanvas = document.createElement("canvas");
tmpCanvas.width = Math.ceil(crop.width*scaleX);
tmpCanvas.height = Math.ceil(crop.height*scaleY);
const ctx = tmpCanvas.getContext("2d");
const image = imgRef.current;
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width*scaleX,
crop.height*scaleY,
);
return tmpCanvas;
}
For me, this was what actually fixed (using hook).
Basically i'm calculating canvas size taking into account the window resolution in physical pixels to the resolution in CSS pixels for the current display device.
const canvas = document.createElement('canvas')
const crop = completedCrop
const scaleX = image.naturalWidth / image.width
const scaleY = image.naturalHeight / image.height
const ctx = canvas.getContext('2d')
const pixelRatio = window.devicePixelRatio
canvas.width = scaleX * crop.width * pixelRatio
canvas.height = scaleY * crop.height * pixelRatio
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
ctx.imageSmoothingQuality = 'high'
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width * scaleX,
crop.height * scaleY
)
You can also export the canvas blob as png (lossless) rather than jpeg, which will provide you a better image.
canvas.toBlob(
blob => {
const img = URL.createObjectURL(blob)
// do whatever you want...
},
'image/png',
1
)
I had the same issue with the cropper. But then adding scaled height and width to canvas and drawImage method worked for me.
check below screenshots.
Image while cropping:
Image resolution after crop:
const createCropPreview = async (image, crop, fileName) => {
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
const canvas = document.createElement("canvas");
canvas.width = Math.ceil(crop.width * scaleX);
canvas.height = Math.ceil(crop.height * scaleY);
const ctx = canvas.getContext("2d");
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width * scaleX,
crop.height * scaleY
);
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
blob.name = fileName;
window.URL.revokeObjectURL(previewUrl);
const obj = window.URL.createObjectURL(blob);
setPreviewUrl(obj);
props.setImg(obj, blob);
}
}, "image/jpeg");
});
};

How to resize a centered image on a canvas?

The code I have below will take an uploaded image and center it on a canvas, it is working but I'm unable to change the size of the actual image. I want to be able to upload an image and then scale it down by a percentage or pixel with it centered.
The way it works right now is you upload an image and it automatically gets populated on the canvas. The final product will return a base64 string that I will convert using C#.
Any help would be great, thanks.
Codepen Link
HTML:
<input type="file">
Javascript:
$("input").on("change", function(e) {
var file = this.files[0];
if (!file) return;
var fileReader = new FileReader();
fileReader.onload = () => {
var image = new Image();
image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext("2d");
// Change Background
context.beginPath();
context.rect(0, 0, 1000, 1000);
context.fillStyle = "#7D8491";
context.fill();
// Draw Logo
context.drawImage(image, canvas.width / 2 - image.width / 2,
canvas.height / 2 - image.height / 2);
// Append to body
$("body").append(canvas);
// Open base64 url
//window.open(canvas.toDataURL("image/png"));
};
image.src = fileReader.result;
};
fileReader.readAsDataURL(file);
var canvas = document.createElement("canvas");
});
I simply had to divide both the position and the actual width/height by the percentage I wanted to scale the image down.
// Draw Logo
context.drawImage(
image,
canvas.width / 2 - image.width * .75 / 2,
canvas.height / 2 - image.height * .75 / 2,
image.width * .75, image.height * .75
);

Categories

Resources