UNPKG

aico-image-editor

Version:

Combine multiple image into and create single combined image

1,242 lines (1,055 loc) 94.1 kB
// 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})