aico-image-editor
Version:
Combine multiple image into and create single combined image
1,242 lines (1,055 loc) • 94.1 kB
JavaScript
// import Alpine from 'alpinejs'
//import persist from '@alpinejs/persist';
let persist;
if (typeof window.Alpine.$persist === 'undefined') {
persist = require('@alpinejs/persist')
Alpine.plugin(persist.default)
}
// note- as fabric is now in externals,
//externals script directly gives fabric and not as {fabric} under object
// which modular system does so it will be like this
// and not like this - import {fabric} from 'fabric';
import fabric from 'fabric';
import canvasSize from 'canvas-size';
require('./roundedBackgroundTextbox');
import chroma from 'chroma-js';
//history
import {canvasHistory} from './history/history';
import { AddCommand } from './history/addCommand';
import { RemoveCommand } from './history/removeCommand';
import { TransformCommand } from './history/transformCommand';
window.canvasHistory = canvasHistory;
// Alpine.plugin(persist)
//
import {getObjectControlImages, getProductImages} from './getImages';
import { addCropRectangleControls } from './utilities/cropControls';
import { productPicturesSubTab } from './components/pictureTab/pictureTab';
let webglBackend, canvas2dBackend;
Alpine.store('canvas', {
products: [
{
id: 2,
name: 'gr-5',
productImageUrl: 'images/gr-5/product-gr5.svg',
overlapImageUrl: 'images/gr-5/product-gr5-overlap.svg',
innerImageUrl: 'images/gr-5/product-gr5-inner.svg',
innerImageStrokeOnlyUrl: 'images/gr-5/product-gr5-inner-strokeonly.svg',
tagImageUrl: 'images/gr-5/gr5-tag.svg',
tagCloneImageUrl: 'images/gr-5/gr5-tag-cloned.svg',
tagImageLeft: 4.12,
tagImageTop: 37.25,
tagImageWidth: 32.06,
tagImageHeight: 45.50
},
{ id: 0,
name: 'gr-6',
productImageUrl: 'images/gr-6/product-gr6.svg',
overlapImageUrl: 'images/gr-6/product-gr6-overlap.svg',
overlapStickerImageUrl: 'images/gr-6/product-gr6-overlap-sticker.svg',
innerImageUrl: getProductImages().gr6InnerImage,
innerImageStrokeOnlyUrl: 'images/gr-6/product-gr6-inner-strokeonly.svg',
tagImageUrl: 'images/gr-6/gr6-tag.svg',
tagCloneImageUrl: 'images/gr-6/gr6-tag-cloned.svg',
leftUpperTextImage: 'images/gr-6/pdf-left-text.png',
rightUpperTextImage: 'images/gr-6/pdf-right-text.png',
bottomTextImageUrl: 'images/gr-6/pdf-bottom-text.svg',
bottomTextImage: 'images/gr-6/pdf-bottom-text.png',
plusicon: 'images/gr-6/pluspdf.png',
tagImageLeft: 6.20,
tagImageTop: 38.96,
tagImageWidth: 24.48,
tagImageHeight: 50.37
},
{ id: 1,
name: 'gr-7',
productImageUrl: 'images/gr-7/product-gr7.svg',
overlapImageUrl: 'images/gr-7/product-gr7-overlap.svg',
overlapStickerImageUrl: 'images/gr-7/product-gr7-overlap-sticker.svg',
innerImageUrl: 'images/gr-7/product-gr7-inner.svg',
innerImageStrokeOnlyUrl: 'images/gr-7/product-gr7-inner-strokeonly.svg',
tagImageUrl: 'images/gr-7/gr7-tag.svg',
tagCloneImageUrl: 'images/gr-7/gr7-tag-cloned.svg',
leftUpperTextImage:'images/gr-7/pdf-left-text.png',
rightUpperTextImage: 'images/gr-7/pdf-right-text.png',
bottomTextImageUrl: 'images/gr-7/pdf-bottom-text.svg',
bottomTextImage: 'images/gr-7/pdf-bottom-text.png',
plusicon: 'images/gr-7/pluspdf.png',
tagImageLeft: 5.24,
tagImageTop: 50.32,
tagImageWidth: 24.27,
tagImageHeight: 39.61
}
],
apiConfig: {
apiUrl: '',
apiToken: '',
},
currentLocale: 'de',
canvasData: Alpine.$persist(''),
isConfiguratorIdChanged: false,
productId: Alpine.$persist(null),
configuratorId: Alpine.$persist(null),
selectedProduct: (function() {
const urlParams = new URLSearchParams(window.location.search);
const myProduct = urlParams.get('productKey');
return myProduct || 'gr-6';
})(),
init() {
fabric.perfLimitSizeTotal = 225000000;
},
isServerLoaderCanvasVisible: false,
isServerLoaderTabVisible: false,
isColorBlockLoaderVisible: false,
isColorPickerBlockDisabled: true,
isObjectFilterControlsDisabled: true,
isCurveBlockDisabled: true,
isObjectDeselected: true,
isCanvasResetBtnDisabled:true,
isFontStyleSaveBtnDisabled: true,
isTextObjectSelected: false,
isTextBackgroundButtonDisabled: true,
isInside3dView: false,
textErrorMessage:false,
fontSuccessMessage:false,
isSaveButtonDisabled: false,
enable3d(formData) {
const self = this, canvas = window.__canvas;
const product = this.getSelectedProductObj();
this.isInside3dView = true;
const clippedPathImageHeight = canvas.clipPath.getScaledHeight();
const differenceInHeight = (canvas.height - clippedPathImageHeight)/2;
window.__canvas.clone(function(clonedCanvas) {
clonedCanvas.clipPath = null;
window.clonedCanvas = clonedCanvas;
const clonedCanvasObjects = clonedCanvas.getObjects();
clonedCanvasObjects.forEach(function(clonedCanvasObject,i) {
clonedCanvasObject.set({
clipPath: null,
})
if(clonedCanvasObject.excludeFromExportIn3D) {
clonedCanvas.remove(clonedCanvasObject)
}
})
// clonedCanvasObjects.forEach(function(clonedCanvasObject,i) {
// console.log(clonedCanvasObject.top);
// const percentageFromClippedPath = (clonedCanvasObject.top/clippedPathImageHeight)*100,
// topFromInnerShape = clonedCanvasObject.top - differenceInHeight,
// percentageFromInnerShape = (topFromInnerShape/clippedPathImageHeight)*100;
// console.log(percentageFromInnerShape);
// newObjTop = (percentageFromInnerShape*canvas.height)/100;
// console.log(newObjTop)
// clonedCanvasObject.set({
// clipPath: null,
// top: newObjTop,
// })
// clonedCanvas.renderAll()
// })
fabric.Image.fromURL(product.tagCloneImageUrl,function(img) {
//img.scaleToWidth(clonedCanvas.width * (product.tagImageWidth/100));
img.scaleToHeight(clonedCanvas.height/clonedCanvas.viewportTransform[0] * (product.tagImageHeight/100));
img.set({
left: clonedCanvas.width * product.tagImageLeft/100,
top: clonedCanvas.height/clonedCanvas.viewportTransform[0] - img.getScaledHeight() - 20
})
img.set({zIndex: 1000})
clonedCanvas.add(img)
const url = clonedCanvas.toDataURL({
format: 'jpeg',
enableRetinaScaling: true,
multiplier: 3
});
if(self.isInside3dView) {
window.dispatchEvent(new CustomEvent('toggle-3d', { detail: {
url: url,
topBottomTextureUrl: clonedCanvas.backgroundImage?.src,
formData: formData
}, bubbles: true }));
}
}, {crossOrigin: 'anonymous'})
},['excludeFromExportIn3D','name', 'filters', 'viewportTransform'])
},
toPDF(formData) {
const self = this, canvas = window.__canvas;
const product = this.getSelectedProductObj();
window.__canvas.clone(function(clonedPDFCanvas) {
fabric.Image.fromURL(product.overlapStickerImageUrl,function(overlapStickerImg) {
self.fitImageInCanvas(overlapStickerImg, clonedPDFCanvas);
self.setCenter(overlapStickerImg);
self.preventInteraction(overlapStickerImg)
overlapStickerImg.set({zIndex: 1002})
clonedPDFCanvas.add(overlapStickerImg)
clonedPDFCanvas.renderAll()
const url = clonedPDFCanvas.toDataURL({
format: 'png',
enableRetinaScaling: true,
multiplier: 3
});
const { jsPDF } = window.jspdf;
var pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [935, 875],
compress: true,
});
pdf.setFillColor(241,241,242);
pdf.rect(0, 0, 935, 935, 'F');
pdf.addImage(product.plusicon, 'PNG', 15, 15, 30, 30,'','FAST');
pdf.addImage(product.plusicon, 'PNG', 890, 15, 30, 30,'','FAST');
pdf.addImage(product.plusicon, 'PNG', 15, 830, 30, 30,'','FAST');
pdf.addImage(product.plusicon, 'PNG', 890, 830, 30, 30,'','FAST');
pdf.addImage(product.rightUpperTextImage, 'PNG', 740, 105, 124, 67,'','FAST');
pdf.addImage(product.leftUpperTextImage, 'PNG', 50, 80, 138, 105,'','FAST');
//draw black lines
pdf.setLineWidth(0.1);
pdf.setDrawColor(0,0,0); // draw black lines
pdf.line(0, 437.5, 29, 437.5); // horizontal line
pdf.line(906, 437.5, 935, 437.5);
pdf.line(467, 0, 467, 29); // vertical line
pdf.line(467, 846, 467, 877);
//console.log(pdf.internal.pageSize.getWidth());
pdf.addImage(url, 'JPEG', 23, 0, 888, 888,'','FAST');
pdf.addImage(product.bottomTextImage, 'PNG', 0, 0, 895, 895,'','FAST');
//window.open(pdf.output('bloburl'), '_blank');
const blob = pdf.output('blob');
const fileName = 'productPDF-' + self.configuratorId; // Specify your file name here
const file = new File([blob], fileName, {type: 'application/pdf'});
formData.append('pdf', file);
self.enable3d(formData);
//pdf.save("download.pdf");
//document.getElementById('canvasConvertedPDFImage').src= url
}, {crossOrigin: 'anonymous'})
}, ['excludeFromExportIn3D','name', 'filters', 'viewportTransform'])
},
disable3d() {
this.isInside3dView = false;
},
getSelectedProductObj() {
return this.products.find(product => product.name === this.selectedProduct);
},
fitImageInCanvas(img,canvas) {
//same logic as fitImageAndCanvasToContainer method except that here,
// parent will be canvas itself and canvas will not be resized anymore
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const imgWidth = img.width;
const imgHeight = img.height;
// find the minimum of scale factor to which image should be scaled
// scaleFactor can be also lesser than 1, in which case image would be scaled down
const scaleFactor = Math.min(canvasWidth / imgWidth, canvasHeight / imgHeight);
// and then scale image to that factor and hence the other dimension will be automatically scaled in propotion
img.scaleToWidth(imgWidth * scaleFactor);
// this is for filling image into the canvas instead of fitting.
// img.scaleToWidth(canvasWidth);
// if(img.getScaledHeight() < canvasHeight) {
// img.scaleToHeight(canvasHeight);
// }
img.set({
left: (canvasWidth - img.getScaledWidth()) / 2,
top: (canvasHeight - img.getScaledHeight()) / 2,
});
canvas.renderAll();
},
setObjectFullSize() {
if(__canvas.getActiveObject()) {
this.fitImageInCanvas(__canvas.getActiveObject(), __canvas)
}
this.setVerticalCenter();
this.setHorizontalCenter();
},
setCenter(obj) {
//obj.center();
window.__canvas.viewportCenterObject(obj)
},
preventInteraction(obj) {
obj.set({
// hasControls: false,
// hasBorders: false,
// lockMovementX: true,
// lockMovementY: true,
// lockScalingX: true,
// lockScalingY: true,
// lockRotation: true,
selectable: false,
evented: false,
})
},
setCanvasClipPath(canvas) {
const self = this, product = this.getSelectedProductObj();
fabric.Image.fromURL(product.productImageUrl,function(img) {
self.fitImageInCanvas(img,canvas);
canvas.set({
clipPath: img
})
//canvas.add(img).renderAll()
self.setCenter(img);
}, {crossOrigin: 'anonymous'});
//this is for showing inner path
fabric.Image.fromURL(product.innerImageStrokeOnlyUrl,function(img) {
self.fitImageInCanvas(img, canvas);
self.setCenter(img);
self.preventInteraction(img)
img.set({
name: 'initOperationObj',
excludeFromExportIn3D: true,
//excludeFromExport: true,
zIndex: 1001
})
canvas.add(img)
}, {crossOrigin: 'anonymous'})
},
addProductTagImage(canvas) {
const self = this, product = this.getSelectedProductObj();
fabric.Image.fromURL(product.tagImageUrl,function(img) {
self.fitImageInCanvas(img,canvas);
self.preventInteraction(img)
img.set({
name: 'initOperationObj',
excludeFromExportIn3D: true,
//excludeFromExport: true,
zIndex: 1000
})
canvas.add(img)
}, {crossOrigin: 'anonymous'})
},
addOverlapImage(canvas) {
const self = this, product = this.getSelectedProductObj();
fabric.Image.fromURL(product.overlapImageUrl,function(img) {
self.fitImageInCanvas(img, canvas);
self.setCenter(img);
self.preventInteraction(img)
img.set({
name: 'initOperationObj',
excludeFromExportIn3D: true,
//excludeFromExport: true,
zIndex: 1002
})
canvas.add(img)
}, {crossOrigin: 'anonymous'})
},
removeObjectControls(obj) {
obj.setControlsVisibility({
ml: false,
mt: false,
mr: false,
mb: false,
// bl: false,
// br: false,
// tl: false,
// tr: false
})
},
getObjectsClipPathImg(canvas) {
const self = this, product = this.getSelectedProductObj();
return new Promise((resolve,reject) => {
try {
fabric.Image.fromURL(product.innerImageUrl,function(img, isError) {
if(isError) {
reject('image could not be loaded');
return;
}
self.fitImageInCanvas(img,canvas);
img.set({
absolutePositioned: true
})
self.setCenter(img);
self.preventInteraction(img)
resolve(img);
}, {crossOrigin: 'anonymous'})
} catch {
reject('image could not be loaded')
}
})
},
layers: [],
currentLayerId: 0,
selectedObjectLayerIndex: 0,
activeObjectLayerId: null,
updateActiveObjectLayerId(layerId) {
this.activeObjectLayerId = layerId;
},
setActiveObjectFromLayerId(layerId) {
//first update activeObjectLayerId
this.updateActiveObjectLayerId(layerId)
/// and then set active object in fabric from that layerId
const activeObject = __canvas.getObjects().find(obj => obj.id === layerId);
__canvas.setActiveObject(activeObject).renderAll();
},
objectAddEvents(canvas) {
const self = this;
canvas.on('object:added',function(options) {
const currentObj = options.target;
if(currentObj.name !== 'initOperationObj') {
if(!currentObj.addedViaHistory) {
if(!(currentObj.id)) {
currentObj.set({
id: 'layer-' + self.currentLayerId,
originX:'right',
originY: 'top',
idNumeric: self.currentLayerId,
objid: self.selectedObjectLayerIndex
});
self.removeObjectControls(currentObj)
if(currentObj.name === 'motive' || currentObj.name === 'symbol') {
currentObj.scaleToWidth(canvas.width/10 <= 50 ? 50 : canvas.width/10);
self.layers.unshift({
id: currentObj.id,
idNumeric: self.currentLayerId,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
objid: self.selectedObjectLayerIndex,
visible: currentObj.get('visible')
})
self.currentLayerId++;
}
if(currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
self.fitImageInCanvas(currentObj,canvas);
self.layers.unshift({
id: currentObj.id,
idNumeric: self.currentLayerId,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
objid: self.selectedObjectLayerIndex,
visible: currentObj.get('visible')
})
self.currentLayerId++;
}
if(currentObj.name === 'label') {
if(currentObj.labelType !== 'template') {
const size = Math.max(parseInt(canvas.width/25), 22);
currentObj.set("fontSize", size)
currentObj.fontFamily = 'Times New Roman';
}
currentObj.fill = 'rgb(0,0,0)';
self.layers.unshift({
id: currentObj.id,
idNumeric: self.currentLayerId,
type: currentObj.name,
text: currentObj.text,
objid: self.selectedObjectLayerIndex,
visible: currentObj.get('visible')
})
self.currentLayerId++;
if(!currentObj.stateProperties.includes('backgroundMode')) {
currentObj.stateProperties.push('backgroundMode')
}
}
self.setCenter(currentObj)
currentObj.set({
zIndex: self.currentLayerId
});
self.sortObjects(canvas);
canvasHistory.add(new AddCommand(currentObj,canvas));
if(!(currentObj.name === 'symbol' || currentObj.name === 'label')) {
if(!currentObj.stateProperties.includes('filters')) {
currentObj.stateProperties.push('filters');
}
}
if(!currentObj.stateProperties.includes('selectable')) {
currentObj.stateProperties.push('selectable')
}
self.selectedObjectLayerIndex = self.layers.length;
self.isSaveButtonDisabled = false;
} else {
if(currentObj.name === 'motive' || currentObj.name === 'symbol' ||
currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
// if it is symbol object which is being restored now from json, check whether
// it was already saved with svgPathColor in json and if not only, then add that property
// to it's stateProperties array so it can be saved again to server with that prop
if(currentObj.name === 'symbol' && !currentObj.stateProperties.includes('svgPathColor')) {
currentObj.stateProperties.push('svgPathColor')
}
if(!(currentObj.name === 'symbol' || currentObj.name === 'label') && !currentObj.stateProperties.includes('filters')) {
currentObj.stateProperties.push('filters')
}
if(currentObj.name === 'label') {
if(!currentObj.stateProperties.includes('backgroundMode')) {
currentObj.stateProperties.push('backgroundMode')
}
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
text: currentObj.text,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
if(!currentObj.stateProperties.includes('selectable')) {
currentObj.stateProperties.push('selectable')
}
}
currentObj.setCoords().saveState();
if(currentObj.filters) {
self.saveStateForFilters(currentObj)
}
} else {
//console.log(currentObj.zIndex)
if(currentObj.name === 'motive' || currentObj.name === 'symbol' ||
currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
if(currentObj.name === 'label') {
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
text: currentObj.text,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
Alpine.nextTick(() => {
if(currentObj.lastLayersArrayBeforeRemoval && currentObj.commandName !== 'AddCommand') {
//if there is lastLayersArrayBeforeRemoval associated when adding object from history,
// then sort objects as per that array and sort layers in reverse of that
sortable.sort(currentObj.lastLayersArrayBeforeRemoval);
sortableCanvas.sort(currentObj.lastLayersArrayBeforeRemoval);
self.sortFromLayers(currentObj.lastLayersArrayBeforeRemoval.slice().reverse());
}
})
}
}
//canvas.bringToFront(currentObj).renderAll();
})
},
objectRemoveEvents(canvas) {
const self = this;
canvas.on('object:removed',function(options) {
const currentObj = options.target;
const indexOfLayerToRemove = self.layers.findIndex(layer => layer.id === currentObj.id);
(indexOfLayerToRemove >= 0) && self.layers.splice(indexOfLayerToRemove,1)
if(!currentObj.removedViaHistory && currentObj.name !== 'initOperationObj') {
// this condition is here to check if object was removed due to removing manually
// or removed in event of reloading external data via restoreCanvasData
// if it was removed via restoreCanvasData operation i.e. if isCanvasDataLoading is set
// then we don;t need to add remove Caommand in history for that
if(!(self.isCanvasDataLoading)) {
canvasHistory.add(new RemoveCommand(currentObj,canvas));
}
self.selectedObjectLayerIndex = self.layers.length;
self.isSaveButtonDisabled = false;
}
})
},
saveStateForFilters(currentObj) {
currentObj.myOwnStateProps = {};
currentObj.myOwnStateProps.filters = []
currentObj.filters.forEach(function(filter, filterIndex) {
currentObj.myOwnStateProps.filters.push(filter)
})
},
objectUpdateEvents(canvas) {
canvas.on('object:modified', function (options) {
this.isSaveButtonDisabled = false;
const currentObj = options.target;
if(currentObj.name !== 'initOperationObj') {
// remove duplicate entries in stateProperties
currentObj.stateProperties = currentObj.stateProperties.filter((value, index, array) => {
return array.indexOf(value) === index;
})
canvasHistory.add(new TransformCommand(currentObj,canvas));
currentObj.saveState()
// specifically define myOwnStateProps and update filters over there
if(currentObj.filters) {
this.saveStateForFilters(currentObj)
}
}
}.bind(this))
},
syncLayerToObject(object) {
//console.log(object)
this.layers.find(layer => layer.id === object.id).visible = object.visible;
},
objectScaleEvents(canvas) {
const self = this;
canvas.on('object:scaling', function (options) {
const currentObj = options.target;
self.getSizeData(currentObj);
})
},
clearSelectionHandler() {
this.isObjectDeselected = true;
this.isFontStyleSaveBtnDisabled = true;
this.isCurveBlockDisabled = true;
this.isColorPickerBlockDisabled = true;
this.isObjectFilterControlsDisabled = true;
this.isTextBackgroundButtonDisabled = true;
// also update active layer to none by passing empty string so nothing is selected
this.updateActiveObjectLayerId('')
this.updateTextarea('');
this.isTextObjectSelected = false;
},
updateSelectionHandler(selectedObject) {
window.dispatchEvent(new CustomEvent('object-added-to-ai-canvas', {
detail: {
object: selectedObject
}
}))
this.updateActiveObjectLayerId(selectedObject.id)
this.isObjectDeselected = selectedObject.name === 'initOperationObj';
this.isColorPickerBlockDisabled = !(selectedObject.name === 'symbol' || selectedObject.name === 'label');
this.isObjectFilterControlsDisabled = (selectedObject.name === 'symbol' || selectedObject.name === 'label');
this.isTextObjectSelected = selectedObject.name === 'label';
this.isMarkObjectOptionDisabled = selectedObject.id === this.markedObject?.id;
this.isFontStyleSaveBtnDisabled = selectedObject.name !== 'label';
this.isCurveBlockDisabled = (selectedObject.name !== 'label') || (selectedObject._textLines?.length > 1) || selectedObject.textBackgroundColor;
this.isTextBackgroundButtonDisabled = (selectedObject.name !== 'label') || selectedObject.path;
if (selectedObject.name === 'label') {
this.updateTextarea(selectedObject.text);
window.dispatchEvent(new CustomEvent('object-selected-on-canvas', {
detail: {
objectColor: selectedObject.backgroundMode !== 'foreground' ? chroma(selectedObject.fill).hex() : chroma(selectedObject.textBackgroundColor).hex(),
backgroundMode: selectedObject.backgroundMode || 'none',
}
}))
}
if (selectedObject.name === 'symbol') {
window.dispatchEvent(new CustomEvent('object-selected-on-canvas', {
detail: {
objectColor: chroma(selectedObject.svgPathColor).hex(),
backgroundMode: selectedObject.backgroundMode || 'none'
}
}))
}
if (selectedObject) {
const selectedObjectLayer = sortable.toArray().find(layer => layer.includes(selectedObject.idNumeric));
this.selectedObjectLayerIndex = sortable.toArray().reverse().indexOf(selectedObjectLayer) + 1;
this.getSizeData(selectedObject);
}
if(!this.isObjectFilterControlsDisabled && selectedObject.filters) {
window.dispatchEvent(new CustomEvent('update-brightness-slider-value', {
detail: {
brightness: selectedObject.filters.find(filter => filter.brightness)?.brightness || 0
}
}))
window.dispatchEvent(new CustomEvent('update-contrast-slider-value', {
detail: {
contrast: selectedObject.filters.find(filter => filter.contrast)?.contrast || 0
}
}))
window.dispatchEvent(new CustomEvent('update-grayscale-switch-value', {
detail: {
grayscale: selectedObject.filters.find(filter => filter.type === 'Grayscale')
}
}))
}
},
objectSelectionEvents(canvas) {
const self = this;
canvas.on('selection:created', function(options) {
self.updateSelectionHandler(options.selected[0]);
}).on('selection:updated', function(options) {
self.updateSelectionHandler(options.selected[0]);
}).on('selection:cleared', function(options) {
self.clearSelectionHandler();
});
},
objectModifyEvents(canvas,img) {
const tempCanvasEl = fabric.util.createCanvasElement(),
tempCanvasCtx = tempCanvasEl.getContext('2d',{ willReadFrequently: true }),
tempCanvas = new fabric.Canvas(tempCanvasEl, {
preserveObjectStacking: true,
uniformScaling: true,
enableRetinaScaling: false,
width: canvas.width,
height: canvas.height,
});
tempCanvas.clipPath = img;
tempCanvasCtx.fillStyle = 'red';
tempCanvas.add(img).renderAll()
tempCanvasEl.style.zIndex = -1;
tempCanvasEl.style.display = 'none';
//appending in dom is not even necessary to getimagedata from canvas
document.getElementById('product-canvas-holder').append(tempCanvasEl);
// using event system from fabric itself we can claim that this code will
// be only executed once
canvas.on('object:modified', function (options) {
const currentObj = options.target;
//console.log(currentObj.left, currentObj.top)
//check if any of corners of object are not outside the inner image
//i.e by checking if it has pixel data below it
const allowMove = Object.values(currentObj.aCoords).every((corner) => {
//console.log(corner.x,corner.y)
const pxColor = tempCanvasCtx.getImageData(corner.x, corner.y, 1, 1).data;
return pxColor[3]
})
if(!allowMove && currentObj.name !== 'bgimage') {
//console.log( currentObj._stateProperties)
currentObj.set( {
top: currentObj._stateProperties.top,
left: currentObj._stateProperties.left,
angle: currentObj._stateProperties.angle,
scaleX: currentObj._stateProperties.scaleX,
scaleY: currentObj._stateProperties.scaleY
})
}
currentObj.setCoords();
currentObj.saveState();
})
},
renderIcon(icon) {
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
var size = this.cornerSize;
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
ctx.drawImage(icon, -size/2, -size/2);
ctx.restore();
}
},
addCustomRotationControl() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().rotationImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const rotationIconDataUrl = img.toDataURL();
const rotationIcon = document.createElement('img');
rotationIcon.src = rotationIconDataUrl;
const mtr = new fabric.Control({
x: -0.5,
y: 0.5,
//offsetY: -16,
actionHandler: fabric.controlsUtils.rotationWithSnapping,
cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
withConnection: true,
actionName: 'rotate',
render: self.renderIcon(rotationIcon),
cornerSize: 20
});
fabric.Object.prototype.controls.mtr = mtr;
resolve();
}, {crossOrigin: 'anonymous'});
})
},
addCustomDeleteControl() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().removeImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const deleteIconDataUrl = img.toDataURL();
const deleteIcon = document.createElement('img');
deleteIcon.src = deleteIconDataUrl;
fabric.Object.prototype.controls.deleteControl = new fabric.Control({
x: -0.5,
y: -0.5,
cursorStyle: 'pointer',
mouseUpHandler: self.openDeleteModal,
render: self.renderIcon(deleteIcon),
cornerSize: 20
})
resolve();
}, {crossOrigin: 'anonymous'})
})
},
addCustomSizeUpControls() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().sizeDownImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const sizeDownIconDataUrl = img.toDataURL();
const sizeDownIcon = document.createElement('img');
sizeDownIcon.src = sizeDownIconDataUrl;
fabric.Object.prototype.controls.sizeDownControl = new fabric.Control({
x: 0.5,
y: -0.5,
offsetY: 24,
cursorStyle: 'pointer',
render: self.renderIcon(sizeDownIcon),
mouseUpHandler: () => self.decreaseObjectSize(),
cornerSize: 20
})
resolve()
}, {crossOrigin: 'anonymous'});
})
},
addCustomSizeDownControls() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().sizeUpImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const sizeUpIconDataUrl = img.toDataURL();
const sizeUpIcon = document.createElement('img');
sizeUpIcon.src = sizeUpIconDataUrl;
fabric.Object.prototype.controls.sizeUpControl = new fabric.Control({
x: 0.5,
y: -0.5,
cursorStyle: 'pointer',
render: self.renderIcon(sizeUpIcon),
mouseUpHandler: () => self.increaseObjectSize(),
cornerSize: 20
})
resolve()
}, {crossOrigin: 'anonymous'});
})
},
get isWebglSupported() {
return fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize);
},
manualInitFabricFilterBackend() {
// this will determine fabric.maxTextureSize and so we can only assign this
if (this.isWebglSupported) {
fabric.textureSize = fabric.maxTextureSize > 8192 ? 4096 : 2048;
webglBackend = new fabric.WebglFilterBackend({ tileSize: fabric.textureSize });
}
canvas2dBackend = new fabric.Canvas2dFilterBackend()
},
swapFilterBackend(isWebglBackendEnabled) {
if (isWebglBackendEnabled && webglBackend) {
fabric.filterBackend = webglBackend;
} else {
fabric.filterBackend = canvas2dBackend;
}
},
maxCanvasWidth: 4096,
maxCanvasHeight: 4096,
async setMaxCanvasSize() {
const { success, width, height} = await canvasSize.maxArea();
if(success) {
this.maxCanvasHeight = height;
this.maxCanvasWidth = width;
}
},
initFabricCanvas(el, $watch, $store) {
// manually init fabric filter backend
this.manualInitFabricFilterBackend();
const canvas = window.__canvas = new fabric.Canvas(el, {
preserveObjectStacking: true,
uniformScaling: true,
//stateful: true,
uniScaleTransform: true,
});
//this.enablePanning(canvas)
//const ratio = canvas.getWidth() / canvas.getHeight();
const canvasResizeEL = Alpine.store('elements').canvasResizeEL;
const offsetWidth = canvasResizeEL.offsetWidth - 2;
const offsetHeight = canvasResizeEL.offsetWidth - 2;
this.setCanvasDimensions({
width: offsetWidth,
height: offsetHeight
});
$watch('$store.canvas.configuratorId', (value, oldValue) => {
if(value !== oldValue) {
this.isConfiguratorIdChanged = true;
}
})
//until eventId is guaranteed to be unique, i can not uncomment this code
// $watch('$store.canvas.apiConfig.eventId', (value, oldValue) => {
// debugger;
// if(value !== oldValue) {
// this.isConfiguratorIdChanged = true;
// }
// })
this.initCanvasObjectsOperations(canvas);
this.setMaxCanvasSize();
},
disposeCanvas() {
window.__canvas.dispose();
},
async initCanvasObjectsOperations(canvas) {
const self = this;
//this.getObjectsClipPathImg(canvas).then(function(img){
self.objectAddEvents(canvas);
self.objectRemoveEvents(canvas);
self.objectUpdateEvents(canvas);
self.objectSelectionEvents(canvas);
//adding custom controls via custom control api
await this.addControls();
//self.objectModifyEvents(canvas,img);
self.objectScaleEvents(canvas);
if(self.configuratorId) {
self.restoreCanvasData(canvas).then(() => {
// all data has been loaded so now it's time to add mainPicture above it
this.canvasIsReady();
}).catch((error) => {
console.error(error)
// in case of unsuccessful restoration also, add mainPicture
this.canvasIsReady();
})
} else {
// when configurator id was empty, reset canvas data
this.resetCanvasData(canvas);
}
// }, function(error) {
// alert(error);
// });
},
async addControls() {
await this.addCustomRotationControl();
await this.addCustomDeleteControl();
await this.addCustomSizeUpControls();
await this.addCustomSizeDownControls();
},
async getDataUrlFromURL(imageURL) {
let response = await fetch(imageURL);
let blob = await response.blob();
let reader = new FileReader();
let dataURL = await new Promise(resolve => {
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
return dataURL;
},
showCropSelectionRect: false,
canvasCropWidth: '',
canvasCropHeight: '',
finalImageWidth: 2000,
finalImageHeight: 2000,
canvasWidth: '',
canvasHeight: '',
sizeTemplates: [],
sizeTemplatesForWorkspace: [],
scalingStretegy: 'fit',
toggleCropSelectionRect() {
const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
if(cropSelectionRect) {
cropSelectionRect.set({
visible: this.showCropSelectionRect,
})
if(!this.showCropSelectionRect) {
__canvas.discardActiveObject()
}
__canvas.set({
clipPath: this.showCropSelectionRect ? cropSelectionRect : null
})
}
__canvas.renderAll()
},
updateCropSelectionRect() {
const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
if(cropSelectionRect) {
if( !(isNaN(this.canvasCropWidth)) && !(isNaN(this.canvasCropHeight)) ) {
this.canvasCropWidth = parseInt(this.canvasCropWidth)
this.canvasCropHeight = parseInt(this.canvasCropHeight)
// height width with viewporttransform offset
const actualCanvasWidth = __canvas.width / __canvas.viewportTransform[0];
const actualCanvasHeight = __canvas.height / __canvas.viewportTransform[3]
if(this.canvasCropWidth > actualCanvasWidth || this.canvasCropHeight > actualCanvasHeight) {
//if(false) {
if(this.canvasCropWidth > this.canvasCropHeight) {
cropSelectionRect.set({
width: actualCanvasWidth,
height: actualCanvasWidth * this.canvasCropHeight / this.canvasCropWidth
})
} else if (this.canvasCropHeight > this.canvasCropWidth) {
cropSelectionRect.set({
height: actualCanvasHeight,
width: actualCanvasWidth * this.canvasCropWidth / this.canvasCropHeight
})
} else {
cropSelectionRect.set({
height: actualCanvasWidth,
width: actualCanvasHeight
})
}
} else {
cropSelectionRect.set({
width: this.canvasCropWidth,
height: this.canvasCropHeight
})
}
}
// always reset scale of crop rectangle back to 1
// to have the actual size and not scaled
cropSelectionRect.scale(1)
this.setCenter(cropSelectionRect)
}
},
async setCanvasBackground(url) {
const canvas = window.__canvas;
//const dataURL = await this.getDataUrlFromURL(url);
if(!url) {
canvas.set({
backgroundImage: null
})
canvas.renderAll();
return;
}
fabric.Image.fromURL(url, (img) => {
//img.set({ crossOrigin: 'Anonymous' });
img.scaleToWidth(canvas.width/canvas.viewportTransform[0]);
if(img.getScaledHeight() < (canvas.height/canvas.viewportTransform[3])) {
img.scaleToHeight(canvas.height/canvas.viewportTransform[3]);
}
const leftOffset = ((canvas.width - img.getScaledWidth()).toFixed(2))/2;
const topOffset = ((canvas.height - img.getScaledHeight()).toFixed(2))/2;
let centerPoint = {
x: (canvas.width/canvas.viewportTransform[0])/2,
y: (canvas.height/canvas.viewportTransform[3])/2
}
//console.log(centerPoint)
///console.log(fabric.util.transformPoint(centerPoint, canvas.viewportTransform))
img.set({
left: centerPoint.x,
top: centerPoint.y,
name: 'bgimage',
originX: 'center',
originY: 'center',
});
canvas.backgroundImage = img;
//canvas.add(img);
this.isSaveButtonDisabled = false;
canvas.requestRenderAll();
}, { crossOrigin: 'anonymous' })
},
sortFromLayers(layers) {
layers.forEach(function(layer,layerIndex,layers) {
window.__canvas.getObjects().forEach(function(obj) {
if(obj.id === layer) {
obj.zIndex = layerIndex;
}
})
})
this.sortObjects(window.__canvas);
const selectedObject = window.__canvas.getActiveObject();
if (selectedObject) {
const selectedObjectLayer = layers.find(layer => layer.includes(selectedObject.id));
this.selectedObjectLayerIndex = layers.indexOf(selectedObjectLayer) + 1;
}
},
sortObjects(canvas) {
canvas._objects.sort((a, b) => (a.zIndex > b.zIndex) ? 1 : -1);
canvas.renderAll();
},
setCanvasDimensions(containerSize) {
const canvas = window.__canvas;
canvas.setDimensions(containerSize);
const {width, height} = containerSize;
// this.canvasWidth = width;
// this.canvasHeight = height;
this.canvasCropWidth = Math.round((width / canvas.viewportTransform[0]) * 90 / 100);
this.canvasCropHeight = Math.round((height / canvas.viewportTransform[3]) * 90 / 100);
this.updateCropSelectionRect();
this.oldContainerWidth = Alpine.store('elements').canvasResizeEL.offsetWidth - 2;
canvas.renderAll();
},
addCropRectangle(canvas) {
this.showCropSelectionRect = false;
__canvas.clipPath = null;
__canvas.renderAll();
const cropRectangle = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
if(cropRectangle) {
cropRectangle.set({visible: false})