I'm trying to apply a filter to a large image in FabricJS.
The image is about 3000 x 4000 which exceeds the default 2048 texture size. I could increase this to 4096 and it would work for this image.
Currently when the filter is applied the image is cut. I have tried reducing the size of the image using the following:
let obj = this.canvas.getActiveObject();
if (Math.max(obj.width, obj.height) > 2048) {
let scale = 2048 / Math.max(obj.width, obj.height);
obj.filters.push(
new fabric.Image.filters.Resize({ scaleX: scale, scaleY: scale })
);
}
obj.filters.push(new fabric.Image.filters.Grayscale());
obj.applyFilters();
this.canvas.renderAll();
This however did not work for me. The image is still cut and rotated.
This is how I am adding my image to the canvas:
//in a parent component
let url = URL.createObjectURL(e.target.files[0]);
fabric.Image.fromURL(url, (image) => {
this.setState({
image,
});
});
//following is in a child component
let canvasWidth = this.canvasWrapperRef.current.clientWidth;
let canvasHeight = this.canvasWrapperRef.current.clientHeight;
//create canvas
this.canvas = new fabric.Canvas("canvas", {
selection: false,
backgroundColor: "black",
hoverCursor: "context-menu",
height: canvasHeight,
width: canvasWidth,
});
let image = this.props.image;
image.set({ selectable: false });
this.canvas.centerObject(image);
this.canvas.setActiveObject(image);
this.canvas.add(image);
this.canvas.renderAll();
Is there a way I can reduce the size/quality of the original image so I can apply filters to it?
It took me a few days but I finally figured out a solution to the issue I was having. It seems that when you resize the image using fabric, it retains the original size. To get around this, I added the image to a canvas of a size that can handle the filters, then exported that canvas as an image and added it to fabric in the usual way.
This seems like a bit of a hacky solution to me, it works but if anyone knows of a better solution please let me know.
//handle the image uploading process
handleImageUpload = async (e) => {
if (e.target.files.length < 1) return;
let objectUrl = URL.createObjectURL(e.target.files[0]);
//resize if needed
let imageUrl = await this.resizeImage(this.textureSize, objectUrl);
fabric.Image.fromURL(imageUrl, (image) => {
this.setState({
image,
});
});
};
//resize the image if one of the sides of the image exceeds the max texture size
//if the image requires resizing, create a canvas element but don't attach to DOM
//size the canvas with the largest size being equal to the max texture size
//then scale the image down to the correct size when adding to the canvas
resizeImage = (maxSize, imageUrl) => {
return new Promise((resolve) => {
let image = new Image();
image.src = imageUrl;
image.onload = (img) => {
//check if resizing is required
if (Math.max(img.target.width, img.target.height) > maxSize) {
//create canvas
let canvas = document.createElement("canvas");
//scale image
if (img.target.height >= img.target.width) {
canvas.height = maxSize;
canvas.width = (maxSize / img.target.height) * img.target.width;
} else {
canvas.width = maxSize;
canvas.height = (maxSize / img.target.width) * img.target.height;
}
//draw to canvas
let context = canvas.getContext("2d");
context.drawImage(img.target, 0, 0, canvas.width, canvas.height);
//assign new image url
resolve(context.canvas.toDataURL());
}
resolve(imageUrl);
};
});
};
I am using 2048 as my max texture size. Anything above this and the filters start to cut and rotate the image. I think this is determined by hardware, I am using 2048 as a relatively safe limit. See here for further information: http://fabricjs.com/fabric-filters
When you are scaling down the image using the above method you can lose quality in your image. A better solution might be to use fabrics built in method of scaling the image down (I have not tested this but I would imagine fabric would handle the rescaling better than the above method), then exporting and re-importing similar to the above. However as this is just a personal project with the purpose of learning react I am happy to take the hit on the image quality and display a warning that large image sizes are resized and may have a loss in quality.