Spaces:
Running
Running
| class ClothesSelector extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.selectionPoints = []; | |
| this.selectionMode = false; | |
| this.segmentationActive = false; | |
| this.selfieSegmentation = null; | |
| this.attachShadow({ mode: 'open' }); | |
| } | |
| connectedCallback() { | |
| this.render(); | |
| this.setupEventListeners(); | |
| } | |
| render() { | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| .selector-container { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| canvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| pointer-events: none; | |
| } | |
| img { | |
| display: block; | |
| max-width: 100%; | |
| cursor: crosshair; | |
| } | |
| .tools { | |
| margin-top: 1rem; | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| button { | |
| padding: 0.5rem 1rem; | |
| border-radius: 0.25rem; | |
| cursor: pointer; | |
| border: none; | |
| background: #6366f1; | |
| color: white; | |
| font-weight: 500; | |
| } | |
| button:hover { | |
| opacity: 0.9; | |
| } | |
| button.clear { | |
| background: #ef4444; | |
| } | |
| button.download { | |
| background: #10b981; | |
| } | |
| </style> | |
| <div class="selector-container"> | |
| <img id="previewImage" src="#" alt="Clothing Preview"> | |
| <canvas id="selectionCanvas"></canvas> | |
| </div> | |
| <div class="tools"> | |
| <button id="selectBtn">Select Area</button> | |
| <button id="clearBtn" class="clear">Clear</button> | |
| <button id="downloadBtn" class="download">Download</button> | |
| </div> | |
| `; | |
| } | |
| async setupSegmentation() { | |
| this.selfieSegmentation = new SelfieSegmentation({ | |
| locateFile: (file) => { | |
| return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`; | |
| } | |
| }); | |
| this.selfieSegmentation.setOptions({ | |
| modelSelection: 1, | |
| selfieMode: false | |
| }); | |
| await this.selfieSegmentation.initialize(); | |
| } | |
| async processSegmentation() { | |
| if (!this.selfieSegmentation || !this.shadowRoot.getElementById('previewImage').complete) return; | |
| const img = this.shadowRoot.getElementById('previewImage'); | |
| const canvas = this.shadowRoot.getElementById('selectionCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| try { | |
| const result = await this.selfieSegmentation.send({image: img}); | |
| const mask = result.segmentationMask; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.drawImage(mask, 0, 0, canvas.width, canvas.height); | |
| ctx.globalCompositeOperation = 'source-in'; | |
| ctx.fillStyle = 'rgba(59, 130, 246, 0.5)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.globalCompositeOperation = 'source-over'; | |
| } catch (error) { | |
| console.error('Segmentation error:', error); | |
| } | |
| } | |
| setupEventListeners() { | |
| const img = this.shadowRoot.getElementById('previewImage'); | |
| const canvas = this.shadowRoot.getElementById('selectionCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const selectBtn = this.shadowRoot.getElementById('selectBtn'); | |
| const clearBtn = this.shadowRoot.getElementById('clearBtn'); | |
| const downloadBtn = this.shadowRoot.getElementById('downloadBtn'); | |
| selectBtn.addEventListener('click', async () => { | |
| if (!this.selfieSegmentation) { | |
| await this.setupSegmentation(); | |
| } | |
| this.selectionMode = true; | |
| this.segmentationActive = true; | |
| this.selectionPoints = []; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| img.style.cursor = 'crosshair'; | |
| if (img.complete) { | |
| await this.processSegmentation(); | |
| } | |
| }); | |
| clearBtn.addEventListener('click', () => { | |
| this.selectionPoints = []; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| img.style.cursor = 'default'; | |
| this.selectionMode = false; | |
| this.segmentationActive = false; | |
| }); | |
| downloadBtn.addEventListener('click', () => { | |
| if (this.selectionPoints.length > 0) { | |
| const tempCanvas = document.createElement('canvas'); | |
| const tempCtx = tempCanvas.getContext('2d'); | |
| tempCanvas.width = img.naturalWidth; | |
| tempCanvas.height = img.naturalHeight; | |
| tempCtx.drawImage(img, 0, 0); | |
| tempCtx.globalCompositeOperation = 'destination-in'; | |
| tempCtx.fillStyle = 'black'; | |
| tempCtx.beginPath(); | |
| tempCtx.moveTo(this.selectionPoints[0].x, this.selectionPoints[0].y); | |
| for (let i = 1; i < this.selectionPoints.length; i++) { | |
| tempCtx.lineTo(this.selectionPoints[i].x, this.selectionPoints[i].y); | |
| } | |
| tempCtx.closePath(); | |
| tempCtx.fill(); | |
| const link = document.createElement('a'); | |
| link.download = 'selected-clothing.png'; | |
| link.href = tempCanvas.toDataURL('image/png'); | |
| link.click(); | |
| } | |
| }); | |
| img.addEventListener('click', (e) => { | |
| if (!this.selectionMode) return; | |
| const rect = img.getBoundingClientRect(); | |
| const x = e.clientX - rect.left; | |
| const y = e.clientY - rect.top; | |
| this.selectionPoints.push({x, y}); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.strokeStyle = '#3B82F6'; | |
| ctx.lineWidth = 2; | |
| ctx.fillStyle = 'rgba(59, 130, 246, 0.2)'; | |
| if (this.selectionPoints.length > 0) { | |
| ctx.beginPath(); | |
| ctx.moveTo(this.selectionPoints[0].x, this.selectionPoints[0].y); | |
| for (let i = 1; i < this.selectionPoints.length; i++) { | |
| ctx.lineTo(this.selectionPoints[i].x, this.selectionPoints[i].y); | |
| } | |
| if (this.selectionPoints.length > 2) { | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| ctx.stroke(); | |
| } | |
| }); | |
| img.addEventListener('load', async () => { | |
| canvas.width = img.naturalWidth; | |
| canvas.height = img.naturalHeight; | |
| if (this.segmentationActive) { | |
| await this.processSegmentation(); | |
| } | |
| }); | |
| } | |
| setImage(src) { | |
| const img = this.shadowRoot.getElementById('previewImage'); | |
| img.src = src; | |
| } | |
| } | |
| customElements.define('clothes-selector', ClothesSelector); |