How to crop the canvas to fit a specific area size in Fabric js - html5-canvas

Among the functions of fabric js, there is a method or property that crops objects except canvas, but I did not find a function to crop the canvas itself. I tried changing the canvas to an image in the following way, cropping it, and then inserting it back into the canvas.
fabric.Image.fromURL(canvas.toDataURL(), img => {
currentImage = img; //save current canvas as image
currentImage.set({
scaleX : canvas.width / currentImage.width,
scaleY : canvas.height / currentImage.height,
cropX: selectionRect.left,
cropY: selectionRect.top,
width: selectionRect.getScaledWidth(),
height: selectionRect.getScaledHeight(),
});
canvas.setDimensions({
width: selectionRect.getScaledWidth(),
height: selectionRect.getScaledHeight()
});
canvas.setBackgroundImage(currentImage).renderAll();
but in this case, an image with a transparent background becomes a background.
If you look at the following picture, a picture of a flower without a background is on the canvas, and What I want is to resize the canvas by the dotted rectangle area without making any objects on the canvas an image as follows.
before crop:
after crop:
Just as the starting range and area are determined with attributes such as cropX or cropY when cropping an object, I would like to know how to apply this to the canvas as well.

Related

How to use x2 canvas elements and rendering paper.js on img capture of html5 video to base64

Still very much a newbie to coding, so please be gentle :)
I'm hoping someone might be able to help how to use Paper.js on a second canvas after the first one has been executed?
I'm trying to use x2 canvas elements:
Canvas 1 - to capture a html5 video image still and convert to base64 (tick :-) = done)
Canvas 2 - Use the base64 image and perform the 'Working with Rasters to find the colors of pixels' and convert to circle paths (boo = fail :-( )
Something like this:
The code:
<script src="https://cdn.jsdelivr.net/npm/hls.js#latest"></script>
<video id="video" preload="auto" muted="" playsinline="" width="580" src="blob:https://www.georgefisher.co.uk/78e3a45c-ae07-4ea5-af56-45a5ed9cf1b0"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'https://camsecure.co/HLS/georgefisher.m3u8';
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
}
video.play()
</script>
<br>
<button onclick="capture()">Capture</button>
<br>
<canvas id="canvas" style="overflow:auto">
</canvas>
<canvas id="canvas2" resize>
<img src="" id="myImg"/></canvas>
var resultb64="";
function capture() {
var canvas = document.getElementById('canvas');
var video = document.getElementById('video');
canvas.width = video.videoWidth/4;
canvas.height = video.videoHeight/4;
canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth/4, video.videoHeight/4);
resultb64=canvas.toDataURL();
document.querySelector("#myImg").src = canvas.toDataURL();
}
/*Paper JS Setup for working in CodePen */
/* ====================== *
* 0. Initiate Canvas *
* ====================== */
// expose paperjs classes into global scope
paper.install(window);
// Only executed our code once the DOM is ready.
window.onload = function() {
// bind paper to the canvas
paper.setup('canvas2');
// paper.activate();
// Get a reference to the canvas object
var canvas = document.getElementById('canvas2');
var ctx = canvas.getContext('2d');
// console.log(ctx, image);
// ctx.drawImage(image, 0, 0);
// return;
// }
// Create a raster item using the image id='' tag
var image = document.querySelector('img');
var raster = new Raster(image);
// Hide the raster:
raster.visible = false;
// The size of our grid cells:
var gridSize = 15;
// Space the cells by 120%:
var spacing = 1
;
// As the web is asynchronous, we need to wait for the raster to load before we can perform any operation on its pixels.
raster.onLoad = function() {
// Since the example image we're using is much too large, and therefore has way too many pixels, lets downsize it to 40 pixels wide and 30 pixels high:
raster.size = new Size(40, 30);
for (var y = 0; y < raster.height; y++) {
for(var x = 0; x < raster.width; x++) {
// Get the color of the pixel:
var color = raster.getPixel(x, y);
// Create a circle shaped path:
var path = new Path.Circle({
center: new Point(x, y).multiply(gridSize),
radius: gridSize / 2 / spacing,
});
// Set the fill color of the path to the color
// of the pixel:
path.fillColor = color;
}
}
// Move the active layer to the center of the view, so all the created paths in it appear centered.
project.activeLayer.position = view.center;
}
}
I've tried giving the second canvas a different Id="canvas2" and referencing that, which I can see in the console. However, nothing appears in the second canvas and the paper.js script doesn't seem to execute, can someone help me understand why?
Please see also see link to the fiddle below:
https://jsfiddle.net/jmnes/o4Lpkfs6/1/
Alternatives method.
You don't need to capture the video, you don't need to capture the pixels using paper.js and raster. You don't need to find the color of each circle and draw it.
All these methods are slow, complex, and power hungry.
You can create a mask and mask out the circles, with the colors drawn from a smaller canvas with a res that matches the number off circles.
How to
Add one (main canvas) canvas to the DOM. This will display the result
Create 2 offscreen canvas.
One (color canvas) has the same resolution as the circles you want to display. Eg if you have 30 by 40 circle the canvas res should be 30 by 40
One (mask canvas) is the circle mask. It is the same resolution as the main canvas. Draw the circles all in one color on this canvas.
Then rendering once a frame
Draw the video on the color canvas to fit.
Turn off smoothing on the main canvas eg ctxMain.imageSmoothingEnabled = false
Draw the color canvas onto the main canvas to fit.
This will draw a color square at each circle position. ctx.drawImage(colorCanvas, 0, 0, mainCanvas.width, mainCanvas.height)
Set composite operation "destination-in" eg ctxMain.globalCompositeOperation = "destination-in"
Draw the mask canvas (canvas with circles on it) onto the main canvas. This will remove pixels outside each circle.
Restore default composite operation for the main canvas ctxMain.globalCompositeOperation = "source-over"
All done for a real-time FX on almost any device.
The above methods is the fastest way to render the effect you are after using the 2D API

How to make sure image alway include entire clipping Rect inside while it's transforming (move, scale) in fabric.js?

I have a Image with a simple clipping Rect ( rotate 30 degree with size 200x200 to make a small view part of image ). My problem is how to prevent Image move or scale outside clipping rect ( that mean Image need alway include entire clipping Rect )?
Here is my code:
var canvasObject = document.getElementById("editorCanvas");
// set canvas equal size with div
$(canvasObject).width($("#canvasContainer").width());
$(canvasObject).height($("#canvasContainer").height());
var canvas = new fabric.Canvas('editorCanvas', {
backgroundColor: 'white',
selectionLineWidth: 2,
width: $("#canvasContainer").width(),
height: $("#canvasContainer").height()
});
// create a clipping path
var rectClippingPath = new fabric.Rect({
top: 100,
left: 200,
width: 200,
height: 200,
fill: 'red',
angle: 30,
absolutePositioned: true
});
// create image
var newImage = new fabric.Image();
newImage.clipPath = rectClippingPath;
canvas.add(newImage);
newImage.setSrc('http://fabricjs.com/assets/pug_small.jpg', (imageObject) => {
newImage.set({ left: 50, top: 50});
canvas.setActiveObject(newImage);
newImage.setCoords();
canvas.requestRenderAll();
})
#canvasContainer {
width: 100%;
height: 100vh;
background-color: gray;
}
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.6/fabric.min.js" integrity="sha512-XcwgBTqf2CXR/nsswCV1e0j9CjXo87APyBsATK2/l7MvTpcIG0QYKA87v5KIJ4RS6ytArv2pWD6UcRorKhYp1A==" crossorigin="anonymous"></script>
<div id="canvasContainer">
<canvas id="editorCanvas"></canvas>
</div>
And here are some example cases:
Here is expected result:
Thank you!
I can't see a way to achieve it based on your example. I think it needs a structural change. You can do it by having a parent element with all controls and then a child element (image object).
Whenever you move the parent frame you should force the photo to move by the same amount, whenever you scale the frame you should also proportionally scale the child photo, same with rotation.
Let's say you rotate the parent frame, now in order to manipulate the photo inside you will need some kind of activating trigger: double click or a button. That will make sure you are not moving the parent frame, but the child.
While you move the child photo you will need to force it to stay contained within the parent frame borders. This is not hard if both elements have a rotation of 0, 90, 180, 270, 360 degrees. It is much harder to make it nice and smooth for all other rotation angles. You can find a working, but not perfect solution here: Contain a rotated object inside another rotated object FabricJS. I also started working on a smoother solution for that, but it is not completed https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-v3-j38sz.
Implementing this solution you will most probably encounter also an issue having with rotating. It's easy to rotate an child image and the parent if they have the same origin point, but if you move the child and try to rotate the parent, then you will need to rotate them considering a different origin point. Here is a solution to this: https://codesandbox.io/s/rotation-of-an-object-with-different-rotation-origin-fabric-js-362-8m4cb?file=/src/index.js
You can also visit this platform and have look at similar ideas https://diamondphoto.co.nz/. It is build in Fabric JS version 3.

On p5JS canvas changing background image showing already drawn objects behind the background image

I'm drawing some objects on canvas but if later I change the background image on demand, then already drawn objects are behind the background image. How to bring already drawn objects in front. Below is the sample code i'm using to change the background image.
function draw() { if(chnBg){ //if change background is clicked loadImage("images/Grid_Image.png",function(bg){
background(bg);
}); } }
You will need to set globalCompositeOperation to destination-over before drawing / changing the background image ...
let canvas;
// setup
function setup() {
canvas = createCanvas(width, height);
}
// draw
function draw() {
if (chnBg) { //if change background is clicked
loadImage("images/Grid_Image.png", function(bg) {
canvas.drawingContext.globalCompositeOperation = 'destination-over'; //<-- set this
background(bg);
});
}
}
You could just store your state in a data structure of some kind and redraw everything each frame.
Another approach would be to draw your objects to a buffer graphics instead of directly to the canvas. Then draw the background to the canvas, and then draw the buffer to the canvas.
More info is available in the reference.

HTML5 canvas conversion - monitor pixel to canvas point

As I haven't found anything about this I wonder if I missed a simple thing:
canvas#cvs1 {
width: 32px;
height: 32px;
}
var context = cvs1.getContext("2d");
context.beginPath();
context.moveTo(0, 0);
context.lineTo(16, 32);
context.lineTo(32, 32);
context.closePath();
If I draw a triangle onto the canvas the triangle is about 3x3 pixels (and not 32x32 like I thougth) so what do I miss that the coordinates from context.lineTo(32, 32); don't reflect the pixels from width/heigth of the canvas?
Don't use CSS to resize the canvas. Resizing with CSS "shrinks" each pixel on the canvas and results in your mini-triangle.
// the canvas is 300px by 150px size by default
// This CSS causes each pixel to be squished in size to
// 32/300 wide and 32/150 high
canvas#cvs1 { width: 32px; height: 32px; }
Instead, resize the canvas element itself:
cvs1.width=32;
cvs1.height=32;

HTML5 canvas, scale image after drawing it

I'm trying to scale an image that has already been draw into canvas.
This is the code:
var canvas = document.getElementById('splash-container');
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
// draw image at its original size
context.drawImage(imageObj, 0, 0);
};
imageObj.src = 'images/mine.jpeg';
// Now let's scale the image.
// something like...
imageObj.scale(0.3, 0.3);
How should I do?
You're thinking about it wrong. Once you've drawn the image onto the canvas it has no relationship to the imageObj object. Nothing you do to imageObj will affect what's already drawn. If you want to scale the image, do in the drawImage function:
drawImage(imgObj, 0, 0, imgObj.width * 0.3, imgObj.height * 0.3)
If you want to animate the scaling or are looking to achieve some other effect which requires you to draw the image at full size initially you'll have to first clear it before drawing the scaled down image.
What robertc says is correct, but if you really wanted to scale an image on a canvas after drawing it for some reason, you could just scale the whole canvas using the CSS width/height properties and that would scale the image without having to redraw it.

Resources