Clip an Image and Show in Another Canvas - javascript

I have two canvas on one page. I need to clip an image in first one and transfer it to second one as clipped.
I have a code like below but when i click to send i just see a black clipped image in second canvas. Plus, selectable area is big as before clipping.
public clip() {
var canvas = new fabric.Canvas('cLeft');
let src = this.srcLeft;
loadImage(src, canvas);
// If there is enough width -> clipTo
if (Math.abs(this.firstX - this.lastX) > 20 || Math.abs(this.firstY - this.lastY) > 20) {
canvas.clipTo = (ctx) => {
console.log("-->> clipTo: " + this.firstX + " " + this.firstY);
var shp = new fabric.Rect({
top: this.firstY,
left: this.firstX,
width: Math.abs(this.lastX - this.firstX),
height: Math.abs(this.lastY - this.firstY),
hasControls: false,
selectable: false,
evented: false
});
shp.render(ctx);
};
this.data = canvas.toDataURL();
}
}
public send() {
var canvas = new fabric.Canvas('cRight');
fabric.Image.fromURL(this.data, function (img) {
img.left = 10;
img.top = 10;
canvas.add(img);
canvas.renderAll();
})
}

Related

Fabricjs - Rendering multiple canvases in one document

I'm having a problem dynamically creating and setting canvas backgrounds.
I want the user to be able to annotate the images with text and shapes, thats why I am doing stuff this way.
I would like to know why is this code producing such output
The idea
Send a get request to an endpoint which returns json data containing
image urls.
Convert that data into a javascript array.
Dynamically create fabric js canvases based on the length of above
array.
Set the canvas backgrounds to the images using their urls. (i.e each canvas will have a different background taken from the url)
The problem
Only the last canvas has a background image.
code
<!DOCTYPE html>
<html>
<head>
<style>
body {
}
</style>
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"
></script>
</head>
<body>
<script src="fabric.js"></script>
<!--new start-->
<script>
//procedurally load images from a given source
//endpoint with image links
var end_point = "http://localhost:8080/endpoint.php";
var settings = {
url: "http://localhost:8080/endpoint.php",
method: "GET",
};
//get the images array from end point
$.ajax(settings).done(function (images_json) {
var images = JSON.parse(images_json);
//procedurally create farbric.js canvas for each image element in the html document and set their background as the corresponding image.
//for each item in the images array, create a fabric.js canvas with its background set as the image itself
var canvas_array = [];
for (var i = 0, len = images.length; i < len; i++) {
//console.log(images[i]);
//create canvas and then make it a fabric canvas
document.body.innerHTML += `<canvas
id=${[i]}
width="1000"
height="200"
style="border-style: solid;">
</canvas>`;
//canvases stored in canvas_array
canvas_array[i] = new fabric.Canvas(`${[i]}`);
console.log(canvas_array[i]);
//set canvas background as the image
canvas_array[i].setBackgroundImage(
`${images[i]}`,
canvas_array[i].renderAll.bind(canvas_array[i]),
{
backgroundImageOpacity: 1,
backgroundImageStretch: false,
}
);
}
});
</script>
<!--new end-->
</body>
</html>
result
result
note
The code is generating correct number of canvases. However the background image does not seem to work
Desired result would have 10 canvases with different backgrounds corresponding to the image urls.
I'm new to fabric.js, this may be a dumb mistake.
Using remote source was a strict requirement. I tried several stuff and this finally worked.
code
//c global variable
var n_can_arr = [];
var f_can_arr = [];
var img_arr = [];
//c procedurally load images from a given source
$.ajax({
url: "http://localhost:8080/endpoint.php",
type: "GET",
dataType: "json", //c added data type
success: function (res) {
for (var index = 0; index < res.length; index++) {
//c create canvas
var setHtml = `<canvas
id=${index}
width="1000"
height="800"
style="border-style: solid;">
</canvas>`;
document.body.innerHTML += setHtml;
//c update canvas and image arrays
n_can_arr.push(index);
img_arr.push(res[index]);
}
//c call image set after the loop is over
$(document).trigger("images_set");
},
});
//c on image_set called
$(document).bind("images_set", () => {
//c for each element of normal canvas array, create a fabric js canvas and set its background
for (var i = 0; i < n_can_arr.length; i++) {
create_canvas(i);
}
//c for each element of fabric canvas array, apply the extend canvas function
extend_canvas();
});
function create_canvas(i) {
//c create fabric js canvases with normal canvas id from canvas arrray
var canvas = new fabric.Canvas(`${i}`);
f_can_arr.push(canvas);
//c set canvas background using image array
canvas.setBackgroundImage(img_arr[i], canvas.renderAll.bind(canvas), {
backgroundImageOpacity: 1,
backgroundImageStretch: false,
});
}
function extend_canvas() {
f_can_arr.forEach((canvas) => {
var origX, origY, isDown, mode_rect, mode_uline, mode_free, pointer;
//c setting keypress listener
$(window).on("keypress", (e) => {
console.log(e.key);
//c drawing rectangles
if (e.key == 1) {
var rect;
console.log("Box");
isDown = true;
mode_free = false;
mode_uline = false;
mode_rect = true;
//c canvas event listners
canvas.on("mouse:down", function (o) {
isDown = true;
if (mode_rect) {
pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
console.log(origX + "," + origY);
rect = new fabric.Rect({
left: origX,
top: origY,
originX: "left",
originY: "top",
width: pointer.x - origX,
height: pointer.y - origY,
fill: "red",
angle: 0,
fill: "rgba(255,0,0,0.0)",
stroke: "black",
strokeWidth: 1,
});
canvas.add(rect);
}
});
canvas.on("mouse:move", function (o) {
if (mode_rect) {
if (isDown) {
var pointer = canvas.getPointer(o.e);
if (origX > pointer.x) {
rect.set({ left: Math.abs(pointer.x) });
}
if (origY > pointer.y) {
rect.set({ top: Math.abs(pointer.y) });
}
rect.set({ width: Math.abs(origX - pointer.x) });
rect.set({ height: Math.abs(origY - pointer.y) });
canvas.renderAll();
}
}
});
}
//c freehand drawing/Highlighter
if (e.key == 2) {
console.log("freehand");
isDown = true;
mode_free = true;
mode_uline = false;
mode_rect = false;
canvas.isDrawingMode = 1;
canvas.freeDrawingBrush.color = "rgba(255,0,0,0.2)";
canvas.freeDrawingBrush.width = 20;
//c canvas event listners
canvas.on("mouse:down", function (o) {
isDown = true;
if (mode_free) {
canvas.renderAll();
}
});
}
//c line mode
if (e.key == 3) {
var line;
console.log("line");
isDown = true;
mode_free = false;
mode_uline = true;
mode_rect = false;
//c canvas event listners
canvas.on("mouse:down", function (o) {
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [pointer.x, pointer.y, pointer.x, pointer.y];
if (mode_uline) {
line = new fabric.Line(points, {
strokeWidth: 3,
fill: "red",
stroke: "red",
originX: "center",
originY: "center",
targetFindTolerance: true,
});
canvas.add(line);
}
});
canvas.on("mouse:move", function (o) {
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
if (mode_uline) {
line.set({ x2: pointer.x, y2: pointer.y });
canvas.renderAll();
}
});
}
//c deleting a selected shape
if (e.key == 4) {
var activeObject = canvas.getActiveObject();
if (activeObject) {
canvas.remove(activeObject);
}
}
//c cancling freehand drawing mode
if (e.key == "x") {
console.log("freehand mode cancled");
canvas.isDrawingMode = 0;
}
});
//c removing previous event listeners and resetting some global variables
canvas.on("mouse:up", function (o) {
isDown = false;
mode_free = false;
mode_uline = false;
mode_rect = false;
canvas.off("mouse:down");
canvas.off("mouse:move");
});
});
}
function save_canvas() {}
function load_canvas() {}
solution
I setup a custom trigger event after the ajax call is made. When the trigger event is fired, only then I create fabricjs canvases.
(apparently even with promises, the fabric code was not running in correct order. Which was probably due to bad syntax. Custom triggers solve this issue.)
results
-Each image appear separately in its own fabric.js canvas as background.
-Each canvas is independent.
Have create for you just copy code and then use your own images in local to get actual full result in output div( images will generated) .
let CANVAS_LIST = [];
let imageList = [
'https://homepages.cae.wisc.edu/~ece533/images/airplane.png',
'https://homepages.cae.wisc.edu/~ece533/images/arctichare.png',
'https://homepages.cae.wisc.edu/~ece533/images/baboon.png',
]
imageList.forEach(element => {
let canvasElement = document.createElement("canvas");
canvasElement.width = '300';
canvasElement.height = '300';
$("#canvas_container").append(canvasElement);
let canvas = new fabric.Canvas(canvasElement);
// Adding Example Text here.
canvas.add(new fabric.Text('This text is added', {
fontFamily: 'Delicious_500',
color: 'red',
left: 10,
top: 100
}));
// Setting up Background to dynamic generated Canvas
canvas.setBackgroundImage(
`${element}`,
canvas.renderAll.bind(canvas),
{
backgroundImageOpacity: 1,
backgroundImageStretch: false,
}
);
CANVAS_LIST.push(canvas);
});
setTimeout(() => {
generateOutput();
}, 3000);
function generateOutput() {
$("#output").empty();
CANVAS_LIST.forEach(canvas => {
let image = $("<img>").attr({
width: 200,
height: 200,
src: canvas.toDataURL()
});
image.css({ marginLeft: '20px' });
$("#output").append(image);
})
}
<!DOCTYPE html>
<html>
<head>
<style>
body {}
</style>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.1.0/fabric.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div id="canvas_container">
</div>
<button onclick="generateOutput()"> Show output</button>
<div id="output">
</div>
</div>
</body>
</html>
Thanks

Basic Fabric.js Image Selection with Mask

I have a unique (but hopefully simple) issue to fix with Fabric.js to fix.
I have this very simple example below:
I have
2 Images
Both have an absolutePositioned mask via clipPath property on the fabric.Image instance
My issue is:
I want the image to only be selectable (and hoverable) whenever the selection happens within the bounds of the mask, not anywhere on image (even outside of the bounds of its mask).
This image shows the mouse hovering over the red door picture (even though the mouse is outside of the mask bounds, but not outside the image bounds:
Here's a code snippet of the door image snippet:
fabric.Image.fromURL(url1, function(img){
canvas.add(img.set({
left: 0,
top: 0,
clipPath: rect1,
hasControls: false,
}));
img.on('mouseover', () => {
const filter = new fabric.Image.filters.BlendColor({
color: 'white',
alpha: 0.7,
mode: 'tint'
})
img.filters.push(filter)
img.applyFilters()
canvas.renderAll()
})
img.on('mouseout', () => {
img.filters.pop()
img.applyFilters()
canvas.renderAll()
})
}, {crossOrigin: "Anonymous"});
JS Fiddle Example showing the current behavior that I'm trying to change.
A possible solution is to listen to the mouse event in the canvas and toggle the activeObject based on the mouse position:
canvas.observe('mouse:move', function(options) {
const pos = canvas.getPointer(options.e);
if (!imageRight || !imageLeft) return
if (pos.x > 200) {
activeImage = imageRight
} else {
activeImage = imageLeft
}
const activeObj = canvas.getActiveObject();
if (activeImage !== activeObj) {
canvas.setActiveObject(activeImage);
canvas.renderAll()
}
});
This is a very simplified way to detect if the mouse is over the image. It checks if the x > 200 since that is where the line between both images is positioned. This could be improved to be more accurate, but it works just to illustrate the idea.
var canvas = window._canvas = new fabric.Canvas('c');
const url1 = 'https://picsum.photos/300/200'
const url2 = 'https://picsum.photos/320'
let imageRight
let imageLeft
let activeImage
canvas.observe('mouse:move', function(options) {
const pos = canvas.getPointer(options.e);
if (!imageRight || !imageLeft) return
if (pos.x > 200) {
activeImage = imageRight
} else {
activeImage = imageLeft
}
const activeObj = canvas.getActiveObject();
if (activeImage !== activeObj) {
canvas.setActiveObject(activeImage);
canvas.renderAll()
}
});
const baseProps = {
width: 200,
height: 200,
top: 0,
fill: '#000',
absolutePositioned: true,
hasControls: false,
evented: false,
selectable: false
}
const rect1 = new fabric.Rect({
...baseProps,
left: 0,
})
const rect2 = new fabric.Rect({
...baseProps,
left: 200,
})
canvas.add(rect1)
canvas.add(rect2)
fabric.Image.fromURL(url2, function(img) {
imageRight = img
canvas.add(img.set({
left: 200,
top: 0,
clipPath: rect2,
hasControls: false
}))
}, {
crossOrigin: "Anonymouse"
})
fabric.Image.fromURL(url1, function(img) {
imageLeft = img
canvas.add(img.set({
left: 0,
top: 0,
clipPath: rect1,
hasControls: false,
}));
}, {
crossOrigin: "Anonymous"
});
canvas {
border: 2px red solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.6.0/fabric.min.js"></script>
<canvas id="c" width="420" height="220"></canvas>

Edited: Switch between viewable ares of the HTML <canvas> preferably without page load

EDIT (original post at bottom below code)**
Fiddle: https://jsfiddle.net/Tron4949/oezuz1g9/9/
(fiddle example will not have a background, it's unnecessary to demonstrate the issue, the console log shows more detail about which view you are on, what was just saved, etc.)
Here is my attempt based on the suggestions from below. It has changed forms many times - currently when the user loads an image, and positions it where they want, then toggles to another view, the uploaded image carries over to that other view (which is undesired - each view should have their own specific layout).
I added 'front', 'side', and 'back' buttons to correspond to different canvas views. When you toggle back and forth - I track the canvas view ("front", "side", or "back") in session storage, defaulting to "front" at the very beginning. Depending on which button is pressed (for which view), it redefines the string in sessionStorage so that we know what the previous view was supposed to be.
It then saves the content of the canvas to storage as a dataURL according to the view it just came from. (*It then clears the canvas and accesses the dataURL for the correct position of the background picture that the user chose earlier on - this is not displayed in the fiddle).
From this point is where I keep having to re-configure things, and I can not find a solution to make it run properly, that is, to make it run without images carrying over.
As is, the image that the user uploaded not only carries over to the other views when you toggle between them, but anything I do with that image, or a newly uploaded one, continues to carry over as you toggle back and forth between views, to the point where each of the three uploaded images and positions are all the same(unexpected/undesired).
A stripped down version - The three click handlers for 'views' are basically the same:
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<canvas id="c" width="740" height="500" style="border-left: 3px solid black; border-top: 3px solid black; border-radius: 10px;"></canvas>
<button class="btn" id="customer-chosen-background-image">Set A Background Image</button>
<input class="btn" type="file" id="files" name="files[]"/>
<button class="btn" id="sessionInfoClear">Clear My Session</button>
<hr>
<button class="btn view-btn" id="front-hat-view-button">Front</button>
<button class="btn view-btn" id="side-hat-view-button">Side</button>
<button class="btn view-btn" id="back-hat-view-button">Back</button>
<canvas id="d" width="740" height="250" style="border-left: 3px solid black; border-top: 3px solid black; border-radius: 10px;"></canvas>
JS
$(document).ready(function() {
var canvas = new fabric.Canvas('c');
var canvas2 = new fabric.Canvas('d');
var scopeTesterOne = "testing scope";//SOMETHING I DO OCCASIONALLY
document.getElementById('files').addEventListener("change", function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function (f) {
var data = f.target.result;
fabric.Image.fromURL(data, function (img) {
var oImg = img.set({left: 350, top: 300, angle: 00,width:100, height:100}).scale(0.9);
canvas.add(oImg).renderAll();
var a = canvas.setActiveObject(oImg);
});
};
reader.readAsDataURL(file);
$('#files').css({'background-color': 'white', 'color': 'black', 'border': '2px solid black'});
});
$('#customer-chosen-background-image').click(function() {
var backgroundImageFilePath = 'http://store-1p9yfoynqe.mybigcommerce.com/product_images/Viewsforfiddle.png';
// var backgroundURL = "url(" + backgroundImageFilePath + ")"
canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
top: 0,
left: 50,
scaleY: .8,
scaleX: .8
});
});
var whatIsTheHatViewOnCanvas = function() {//INITIALLY THIS IS HOW WE TELL/TRACK/SET WHAT THE PREVIOUS VIEW WAS
var doesTrackingDataExist = sessionStorage.getItem('viewForHatCanvas');
if (doesTrackingDataExist) {
console.log("traker has previous view data");
} else {
console.log("traker has no previous view data");
sessionStorage.setItem('viewForHatCanvas', "front");
var consoleTestttt = sessionStorage.getItem('viewForHatCanvas');
console.log(consoleTestttt);
}
};
whatIsTheHatViewOnCanvas();
function loadCanvas(dataURL) { //THIS IS FOR THE MAIN CANVAS
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 0, 0);
};
imageObj.src = dataURL;
}
function loadPreviewCanvasFront(dataURL) {//IMAGES FOR THE 'PREVIEW' CANVAS
var canvas2 = document.getElementById('d');
var context = canvas2.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 0, 0, 185, 125);
};
imageObj.src = dataURL;
}
function loadPreviewCanvasSide(dataURL) {//IMAGES FOR THE 'PREVIEW' CANVAS
var canvas2 = document.getElementById('d');
var context = canvas2.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 185, 0, 185, 125);
};
imageObj.src = dataURL;
}
function loadPreviewCanvasBack(dataURL) {//IMAGES FOR THE 'PREVIEW' CANVAS
var canvas2 = document.getElementById('d');
var context = canvas2.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 370, 0, 185, 125);
};
imageObj.src = dataURL;
}
$('#front-hat-view-button').click(function() {
console.log(scopeTesterOne);
var context = canvas.getContext('2d');
var previousHatView = sessionStorage.getItem('viewForHatCanvas');
sessionStorage.setItem('viewForHatCanvas', "front");
console.log("from " + previousHatView + " to front");
canvas.deactivateAll().renderAll();
var savingCanvasContentF = canvas.toDataURL("image");
if (previousHatView === "side") {
sessionStorage.setItem('storedSideHatCreation', savingCanvasContentF);
console.log("from " + previousHatView + " to front - STORED AS SIDE VIEW");
}
if (previousHatView === "back") {
sessionStorage.setItem('storedBackHatCreation', savingCanvasContentF);
console.log("from " + previousHatView + " to back - STORED AS BACK VIEW");
}
// canvas.clear();
context.clearRect(0, 0, canvas.width, canvas.height);
var imgToLoadOnFrontView = sessionStorage.getItem('storedFrontHatCreation');
//var backgroundImageFilePath =
//var backgroundURL = "url(" + backgroundImageFilePath + ")"
//canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
//top: 0,
//left: 50,
//scaleY: .8,
//scaleX: .8
//});
if (imgToLoadOnFrontView != null) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("says there is an image to load on front");
loadCanvas(imgToLoadOnFrontView);
loadPreviewCanvasFront(imgToLoadOnFrontView);
}
});
$('#side-hat-view-button').click(function() {
var context = canvas.getContext('2d');
var previousHatView = sessionStorage.getItem('viewForHatCanvas');
var currentHatView = "side";
sessionStorage.setItem('viewForHatCanvas', "side");
canvas.deactivateAll().renderAll();
var savingCanvasContentS = canvas.toDataURL("image");
if (previousHatView === "front") {
sessionStorage.setItem('storedFrontHatCreation', savingCanvasContentS);
console.log("from " + previousHatView + " to side - STORED AS FRONT VIEW");
}
if (previousHatView === "back") {
sessionStorage.setItem('storedBackHatCreation', savingCanvasContentS);
console.log("from " + previousHatView + " to side - STORED AS BACK VIEW");
}
// canvas.clear();
context.clearRect(0, 0, canvas.width, canvas.height);
var imgToLoadOnSideView = sessionStorage.getItem('storedSideHatCreation');
// var backgroundImageFilePath = sessionStorage.getItem('hatImageLocation');
// var backgroundURL = "url(" + backgroundImageFilePath + ")"
// canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
// top: -560,
// left: 50,
// scaleY: .8,
// scaleX: .8
// });
if (imgToLoadOnSideView != null) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("says there is an image to load on side");
loadCanvas(imgToLoadOnSideView);
loadPreviewCanvasSide(imgToLoadOnSideView);
}
});
$('#back-hat-view-button').click(function() {
var context = canvas.getContext('2d');
var previousHatView = sessionStorage.getItem('viewForHatCanvas');
sessionStorage.setItem('viewForHatCanvas', "back");
console.log("from " + previousHatView + " to back");
canvas.deactivateAll().renderAll();
var savingCanvasContentB = canvas.toDataURL("image");
if (previousHatView === "front") {
sessionStorage.setItem('storedFrontHatCreation', savingCanvasContentB);
console.log("from " + previousHatView + " to back - STORED AS FRONT VIEW");
}
if (previousHatView === "side") {
sessionStorage.setItem('storedSideHatCreation', savingCanvasContentB);
console.log("from " + previousHatView + " to back - STORED AS SIDE VIEW");
}
// canvas.clear();
context.clearRect(0, 0, canvas.width, canvas.height);
var imgToLoadOnBackView = sessionStorage.getItem('storedBackHatCreation');
//var backgroundImageFilePath = sessionStorage.getItem('hatImageLocation');
//var backgroundURL = "url(" + backgroundImageFilePath + ")"
//canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
// top: -1065,
// left: 50,
// scaleY: .8,
// scaleX: .8
//});
if (imgToLoadOnBackView != null) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("says there is an image to load on back");
loadCanvas(imgToLoadOnBackView);
loadPreviewCanvasBack(imgToLoadOnBackView);
}
});
$('#sessionInfoClear').click(function() {
sessionStorage.clear();
location.reload();
});
});
ORIGINAL POST (below):
I have a canvas that users can interact with (to create a hat). It has one background image that has three 'sections' on it for front side and back. As of now, a user is able to upload their own images and manipulate them on the canvas to make their own designs - I am using Fabric.js for image manipulation.
My Goal: Although the background image is one large image with three 'sections' on it, I only want the user to be able to see one of those sections at a time, and then be able to switch back and forth between any of the three of them while they make their picture/creation (without a page load - page load removes the uploaded images).
My thoughts: Change the canvas's viewable area on a click - I can not figure out how to make a specific viewable area but I feel like this is the best option I can think of. I just need to be able to figure out how to have a 'viewing window' that is smaller than the canvas, and then I could use thumbnails or buttons to change whatever parameters I need to change so that it appears to be a different canvas (without a reload - it will remove the images).
Is this realistic and or possible? Is there a particular/better way that I have not considered? I really would like to keep the app (mostly) in-tact, but if I absolutely have to change everything, then I will, but I'm hoping someone knows how to do what I am proposing. Thanks so much for your time.
I think there is an easier solution then what you are trying to do. if you have the reference to the fabric instance you can get the whole object state out of it with: var currentCanvasData = canvas.toObject(). You will get a nice javascript object that you can pass in later to recreate the canvas. canvas.clear() will remove everything from the canvas and canvas.loadFromJSON(currentCanvasData) will load it all back up. Make sure to call canvas.renderAll() after loadFromJSON.
this will make it easy to switch bewteen 3 different work spaces for you and the user.
var canvas = new fabric.Canvas('c');
var currentView = 1;
var view1Save = {};
var view2Save = {};
var view1 = document.querySelector('.one');
var view2 = document.querySelector('.two');
view1.onclick = (function() {
if (currentView === 2) {
view2Save = canvas.toObject();
canvas.clear();
canvas.loadFromJSON(view1Save, canvas.renderAll.bind(canvas));
currentView = 1;
view1.disabled = true
view2.disabled = false
}
});
view2.onclick = (function() {
if (currentView === 1) {
view1Save = canvas.toObject();
canvas.clear();
canvas.loadFromJSON(view2Save, canvas.renderAll.bind(canvas));
currentView = 2;
view1.disabled = false
view2.disabled = true
}
});
var addRect = document.querySelector('.rect');
addRect.onclick = (function() {
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: 'red'
}));
});
var addImg = document.querySelector('.image');
addImg.onclick = (function() {
var imgElement = new Image();
imgElement.src = 'http://cdn.sstatic.net/Sites/stackoverflow/img/sprites.png';
imgElement.onload = (function() {
canvas.add(new fabric.Image(imgElement, {
left: 100,
top: 100,
angle: 30,
opacity: 0.85
}));
});
});
canvas { border: 1px solid #ccc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
different views:
<button class="one" disabled>view 1</button>
<button class="two" >view 2</button>
<br />
<button class="rect">add rect</button>
<button class="image">add image</button>
<canvas id="c" width="600" height="600"></canvas>

fabric js Polygon setCoords

I've got a question about fabric.js and the polygon-object.
I have an example of my problem in this fiddle:
Click me
First 4 fabric.Circle subobjects called linePoint are drawn.
The linePoint objects have an extra x(same as left) and y(same as top) coordinate and a reference to which polygon they belong to:
fabric.LinePoint = fabric.util.createClass(fabric.Circle,
{
initialize: function (options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('type', 'line_point'),
this.set('x', this.left),
this.set('y', this.top),
this.set('polygon', options.polygon)
},
setPointCoordinates: function(new_left, new_top) {
this.set('x', new_left);
this.set('y', new_top);
this.set('left', new_left);
this.set('top', new_top);
}
With the now given x and y coordinates there is a Polygon drawn between the Points.
The problem is now, when you move the Circles, the Polygon is moved correctly, but its border (or I don't know how to exactly call it) will stay the same small rectangle as it was.
I want to update the polygon Coords too, I tried .setCoords(), but nothing happened.
Maybe you can help me. :) Thanks!
In reply also to:
https://groups.google.com/forum/#!topic/fabricjs/XN1u8E0EBiM
This is your modified fiddle:
https://jsfiddle.net/wum5zvwk/2/
fabric.LinePoint = fabric.util.createClass(fabric.Circle,
{
initialize: function (options) {
options || (options = {});
this.callSuper('initialize', options);
this.set('type', 'line_point'),
this.set('x', this.left),
this.set('y', this.top),
this.set('polygon', options.polygon)
},
setPointCoordinates: function(new_left, new_top) {
this.set('x', new_left);
this.set('y', new_top);
this.set('left', new_left);
this.set('top', new_top);
}
});
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
drawPolygonToCanvas();
canvas.on('object:moving', function(event) {
var object = event.target;
switch(object.type) {
case 'line_point':
//move polygon
object.setPointCoordinates(object.left, object.top);
object.polygon.setCoords();
object.polygon.initialize(object.polygon.points);
object.polygon.top += object.polygon.height / 2;
object.polygon.left += object.polygon.width / 2;
canvas.renderAll();
break;
}
});
function drawPolygonToCanvas()
{
//creting end_points and set them
old_position = canvas.getPointer(event.e);
var end_point_1 = createLinePoint(100, 100);
var end_point_2 = createLinePoint(100, 150);
var end_point_3 = createLinePoint(150, 150);
var end_point_4 = createLinePoint(150, 100);
end_points_in_use = [end_point_1, end_point_2, end_point_3, end_point_4];
canvas.add(end_point_1, end_point_2, end_point_3, end_point_4);
drawPoly(end_points_in_use);
canvas.deactivateAll();
canvas.renderAll();
}
function drawPoly(point_array)
{
var poly = new fabric.Polygon(point_array, {
left: (100 + ((150 - 100) /2)),
top: (100 + ((150 - 100) /2)),
fill: 'lightblue',
lockScalingX: true,
lockScalingY: true,
lockMovementX: true,
lockMovementY: true,
perPixelTargetFind: true,
opacity: 0.5,
type: 'polygon'
});
for (var i = 0; i < point_array.length; i++) {
point_array[i].polygon = poly;
}
canvas.add(poly);
poly.sendToBack();
}
function createLinePoint(left, top) {
return new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: '#fff',
stroke: '#666',
related_poly_point: 0,
lockScalingX: true,
lockScalingY: true
});
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<div id="canvas-wrapper" style="position:relative;width:704px;float:left;">
<canvas id="canvas" width="700" height="600" style="border:1px solid #000000;"></canvas>
</div>
Modifying the polygon points is not enough to have the bounding box adjusted. The easies thing i can think of is to re initialize the polygon with the new points coordinates.

canvas rectangles get drawn then disappear javascript

I want to have a canvas with a background image, and draw rectangles over the top. The rectangles are called hotSpots in my application. They get drawn to the canvas then quickly disappear. How can I make them stay?
appendSection() first appends a picture with appendPicture() which just appends a canvas to a div, then after that function has run, the canvas and context is made, then for every hotSpot, which there are 3 in this case, it will draw a rect, which it does, then they disappear.
function appendSection(theSection, list) {
list.append('<label class="heading">' + theSection.description + '</label><br/><hr><br/>');
if (theSection.picture) {
appendPicture(list, theSection);
var canvas = document.getElementById('assessmentImage');
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0,canvas.width,canvas.height);
}
img.src = "data:image/jpeg;base64,"+ theSection.picture;
if(theSection.allHotSpots.length > 0) {
for( var x = 0; x < theSection.allHotSpots.length; x++) {
appendHotSpot(list, theSection.allHotSpots[x], theSection.thePicture, ctx);
}
}
}
appendSectionQuestions(theSection, list);
if (theSection.allSubSections) {
for (var x = 0; x < theSection.allSubSections.length; x++) {
var theSectionA = theSection.allSubSections[x];
appendSection(theSectionA, list);
}
}
}
function appendHotSpot(list, HotSpot, picture, ctx) {
var imageWidth = document.getElementById('assessmentImage' + platform).clientWidth;
var imageHeight = document.getElementById('assessmentImage' + platform).clientHeight;
var xScale = imageWidth / picture.xSize;
var yScale = imageHeight / picture.ySize;
HotSpot.topLeft = [Math.round(HotSpot.topLeft[0] * xScale), Math.round(HotSpot.topLeft[1] * yScale)];
HotSpot.bottomRight = [Math.round(HotSpot.bottomRight[0] * xScale), Math.round(HotSpot.bottomRight[1] * yScale)];
var rect = {x1: HotSpot.topLeft[0], y1: HotSpot.topLeft[1], x2: HotSpot.bottomRight[0], y2: HotSpot.bottomRight[1]};
ctx.fillStyle = "#FF0000";
ctx.fillRect(rect.x1, rect.y1, rect.x2, rect.y2);
//addRect("blue", rect);
}
function appendPicture(list, theSection) {
list.append('<div id="wrapper' + platform + '" style="width:100%; text-align:center">\
<canvas class="assessmentImageSmall" style="width:100%;" id="assessmentImage' + platform + '" align="middle" ></canvas>\
<!--<p style="color:#666;" id="imageInstruction">Tap image to enlarge.</p>-->\
</div>');
$("#wrapper").kendoTouch({
tap: function (e) {
switchImage();
}
});
}
It looks like your rects are being drawn and then the image (which loads asynchronously) is eventually loaded and then drawn on top of the rects--making them disappear.
Put the if(theSection.allHotSpots.length > 0 inside the onload so the image doesn't later clobber the rects.

Categories