| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
| <title>蒙版王</title> |
| <style> |
| |
| @media (max-width: 767px) { |
| body { |
| --bg-color: var(--tg-theme-bg-color); |
| font: 12px/18px "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, Verdana, sans-serif; |
| background-color: var(--bg-color); |
| color: var(--tg-theme-text-color); |
| |
| width: 350; |
| margin: 0 auto; |
| } |
| #canvas { |
| border: 1px solid #000; |
| width: 100%; |
| height: auto; |
| } |
| html, body { |
| overflow-x: hidden; |
| } |
| #uploadButton{ |
| width: 80%; |
| } |
| #save{ |
| width: 20%; |
| } |
| .myDiv { |
| width: 98%; |
| display: flex; |
| flex-direction: row; |
| justify-content: flex-start; |
| margin: 0px 1%; |
| flex-wrap: wrap; |
| } |
| .myDiv1 { |
| width: 98%; |
| display: flex; |
| flex-direction: row; |
| justify-content: flex-start; |
| margin: 0px 1%; |
| } |
| .overlay { |
| left: 20px; |
| } |
| } |
| |
| @media (min-width: 768px) { |
| #canvas { |
| border: 1px solid #000; |
| width: auto; |
| height: auto; |
| } |
| .myDiv { |
| display: flex; |
| flex-direction: row; |
| justify-content: center; |
| margin: 0 100px; |
| width: 512px; |
| flex-wrap: wrap; |
| } |
| .myDiv1 { |
| display: flex; |
| flex-direction: row; |
| justify-content: center; |
| margin: 0 100px; |
| width: 512px; |
| } |
| .overlay { |
| left: 100px; |
| } |
| } |
| #circle { |
| position: absolute; |
| width: 10px; |
| height: 10px; |
| border-radius: 50%; |
| border: 2px solid red; |
| background-color: transparent; |
| transition: opacity 1s; |
| pointer-events: none; |
| } |
| .show { |
| opacity: 1; |
| } |
| .hide { |
| opacity: 0; |
| } |
| .overlay { |
| position: absolute; |
| top: 82px; |
| z-index: 10; |
| |
| |
| } |
| </style> |
| </head> |
| <body> |
| <div class="myDiv1"> |
| <button id="uploadButton" style="display: None;"> |
| 选择图片 |
| <input type="file" id="upload" accept="image/*" style="position: absolute; top: 0; left: 0; width: 98%; height: 100%; opacity: 0; cursor: pointer;"> |
| </button> |
| <button id="save" style="height: 50px;display: None">保存蒙版</button> |
| <button id="saveToClipboard" style="height: 50px;box-sizing: border-box; padding: 10px; width: 98%; font-size: 16px; height: 50px; |
| line-height: 30px; overflow: hidden; position: relative;">上传蒙版 Upload mask</button> |
| </div> |
| <div class="myDiv" id="tools"> |
| <input type="range" id="brushSizeSlider" style="width: 100%" value="40" min="1" max="150" step="1" > |
| </div> |
| <br> |
| <br> |
| <div class="myDiv" id="myImg1"> |
| <canvas id="canvas"></canvas> |
| <br> |
| <img id="outputImg" ></img> |
| </div> |
| <div class="overlay" style="display: flex; flex-direction: row; align-items: flex-start;"> |
| <label style="margin-bottom: 10px;"> |
| <input type="radio" name="editMode" value="draw" checked> 画笔模式 |
| </label> |
| <label style="margin-bottom: 10px;"> |
| <input type="radio" name="editMode" value="erase"> 擦除模式 |
| </label> |
| <label> |
| <input type="radio" name="editMode" value="select"> 不编辑 |
| </label> |
| </div> |
| <div id="circle" style="width: 20px; height: 20px; border-radius: 50%; border: 2px solid red;"></div> |
| <script src="https://telegram.org/js/telegram-web-app.js"></script> |
| <script> |
| |
| Telegram.WebApp.ready(); |
| Telegram.WebApp.expand(); |
| Telegram.WebApp.enableClosingConfirmation() |
| Telegram.WebApp.HapticFeedback.impactOccurred("medium"); |
| |
| Telegram.WebApp.onEvent('themeChanged', function() { |
| document.documentElement.className = Telegram.WebApp.colorScheme; |
| }); |
| function getQueryParameter(name) { |
| const urlParams = new URLSearchParams(window.location.search); |
| return urlParams.get(name); |
| } |
| async function uploadMask(base64String) { |
| const response = await fetch('https://api.imgur.com/3/image', { |
| method: 'POST', |
| headers: { |
| 'Authorization': 'Client-ID 955c061744537ff', |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ image: base64String }), |
| }); |
| const r = await response.json() |
| return r.data.link; |
| } |
| window.onerror = function (message, source, lineno, colno, error) { |
| const errorData = { |
| message: message, |
| source: source, |
| lineno: lineno, |
| colno: colno, |
| stack: error ? error.stack : null |
| }; |
| console.error("JavaScript Error:", errorData); |
| |
| fetch('/log-error', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify(errorData) |
| }); |
| }; |
| window.onload = function() { |
| var canvas = document.getElementById('canvas'); |
| var context = canvas.getContext('2d'); |
| var image = document.getElementById('outputImg'); |
| var imageMask = new Image(); |
| var maskData = null; |
| var isDrawing = false; |
| var brushSize = 40; |
| var intervalHandel = null; |
| var brushSizeSlider = document.getElementById('brushSizeSlider'); |
| var editModeRadios = document.getElementsByName('editMode'); |
| var selectedMode = "draw"; |
| var isRotate = false; |
| function isMobile() { |
| return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)); |
| } |
| for (var i = 0; i < editModeRadios.length; i++) { |
| editModeRadios[i].addEventListener('change', function() { |
| |
| selectedMode = this.value; |
| drawImagesInterval(100); |
| }); |
| } |
| brushSizeSlider.addEventListener('input', function() { |
| brushSize = parseInt(this.value); |
| setCircleSize(brushSize); |
| if (isMobile()){ |
| var canvasRect = canvas.getBoundingClientRect(); |
| var scaleX = canvas.width / canvasRect.width; |
| showCircle(175 + window.scrollX, 200 + window.scrollY, scaleX); |
| } |
| }); |
| function resizeImage(img,s,resizedImage) { |
| var maxWidth = s; |
| var maxHeight = s; |
| var width = img.width; |
| var height = img.height; |
| if (width/height < 4/3 && width <= maxWidth && height <= maxHeight) { |
| return false; |
| } |
| if (!isMobile() && width <= maxWidth && height <= maxHeight) { |
| return false; |
| } |
| if (width > maxWidth || height > maxHeight || width/height >= 4/3) { |
| var ratio = Math.max(maxWidth / width, maxHeight / height); |
| if (width <= maxWidth && height <= maxHeight){ |
| ratio = 1; |
| } |
| width = Math.floor(width * ratio); |
| height = Math.floor(height * ratio); |
| var tempCanvas = document.createElement('canvas'); |
| tempCanvas.width = width; |
| tempCanvas.height = height; |
| var ctx = tempCanvas.getContext('2d'); |
| isRotate = false; |
| if (width/height >= 4/3 && isMobile()){ |
| tempCanvas.height = width; |
| tempCanvas.width = height; |
| ctx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); |
| ctx.save(); |
| ctx.translate(tempCanvas.width / 2, tempCanvas.height / 2); |
| ctx.rotate(Math.PI / 2); |
| ctx.drawImage(img, -img.width / 2, -img.height / 2); |
| ctx.restore(); |
| isRotate = true; |
| } |
| else { |
| ctx.drawImage(img, 0, 0, width, height); |
| } |
| resizedImage.src = tempCanvas.toDataURL(); |
| return true; |
| } |
| return false; |
| } |
| |
| function drawImages() { |
| context.clearRect(0, 0, canvas.width, canvas.height); |
| context.putImageData(maskData,0,0); |
| if (selectedMode == "select") { |
| return; |
| } |
| context.globalAlpha = 0.75; |
| context.drawImage(image, 0, 0); |
| |
| context.globalAlpha = 1; |
| } |
| function drawImagesInterval(t) { |
| if (intervalHandel != null){ |
| drawImagesTimeOut(100); |
| window.clearInterval(intervalHandel); |
| intervalHandel = null; |
| } |
| intervalHandel = window.setInterval(function() {drawImages();}, t); |
| } |
| window.addEventListener('scroll', function(event) { |
| var scrollTop = window.pageYOffset || document.documentElement.scrollTop; |
| var overlay = document.querySelector(".overlay"); |
| overlay.style.top = (scrollTop + 82) + "px"; |
| }); |
| function stopDrawImagesInterval() { |
| if (intervalHandel != null){ |
| drawImagesTimeOut(100); |
| window.clearInterval(intervalHandel); |
| intervalHandel = null; |
| } |
| } |
| function drawImagesTimeOut(t) { |
| window.setTimeout(function() {drawImages();}, t); |
| } |
| function getImageDataB(img) { |
| context.clearRect(0, 0, canvas.width, canvas.height); |
| var width = image.width; |
| var height = image.height; |
| canvas.width = width; |
| canvas.height = height; |
| context.drawImage(img, 0, 0); |
| var imageData = context.getImageData(0, 0, width, height); |
| return imageData; |
| } |
| function getImageData(img) { |
| var tempCanvas = document.createElement('canvas'); |
| tempCanvas.width = canvas.width; |
| tempCanvas.height = canvas.height; |
| var tempContext = tempCanvas.getContext('2d'); |
| var width = image.width; |
| var height = image.height; |
| tempCanvas.width = width; |
| tempCanvas.height = height; |
| tempContext.drawImage(img, 0, 0); |
| var imageData = tempContext.getImageData(0, 0, width, height); |
| return imageData; |
| } |
| function createMaskImageData(width, height) { |
| |
| var imageData = new ImageData(width, height); |
| |
| var data = imageData.data; |
| |
| for (var i = 0; i < data.length; i += 4) { |
| data[i] = 0; |
| data[i + 1] = 0; |
| data[i + 2] = 0; |
| data[i + 3] = 255; |
| } |
| return imageData; |
| } |
| function getImageDataUrl(imageData) { |
| var tempCanvas = document.createElement('canvas'); |
| tempCanvas.width = canvas.width; |
| tempCanvas.height = canvas.height; |
| var tempContext = tempCanvas.getContext('2d'); |
| tempContext.putImageData(imageData, 0, 0); |
| return tempCanvas.toDataURL(); |
| } |
| async function loadImageFromUrl0(imageUrl) { |
| const dataUrl = await convertImageUrlToDataUrl(imageUrl); |
| await loadImageFromUrl(dataUrl); |
| } |
| async function loadImageFromUrl(dataUrl) { |
| image.crossOrigin='anonymous'; |
| image.onload = function() { |
| var resizedImage = new Image(); |
| resizedImage.onload = _ok; |
| function _ok(event){ |
| image = resizedImage; |
| canvas.width = resizedImage.width; |
| canvas.height = resizedImage.height; |
| maskData = createMaskImageData(image.width,image.height); |
| imageMask.src = getImageDataUrl(maskData); |
| drawImagesTimeOut(100); |
| } |
| if (!resizeImage(image,512,resizedImage)) { |
| canvas.width = image.width; |
| canvas.height = image.height; |
| maskData = createMaskImageData(image.width,image.height); |
| imageMask.src = getImageDataUrl(maskData); |
| drawImagesTimeOut(100); |
| } |
| } |
| image.src = dataUrl; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function handleFile(file) { |
| var reader = new FileReader(); |
| reader.onload = function(event) { |
| image.onload = function() { |
| var resizedImage = new Image(); |
| resizedImage.onload = _ok; |
| function _ok(event){ |
| image = resizedImage; |
| canvas.width = resizedImage.width; |
| canvas.height = resizedImage.height; |
| maskData = createMaskImageData(image.width,image.height); |
| imageMask.src = getImageDataUrl(maskData); |
| drawImagesTimeOut(100); |
| } |
| if (!resizeImage(image,512,resizedImage)) { |
| canvas.width = image.width; |
| canvas.height = image.height; |
| maskData = createMaskImageData(image.width,image.height); |
| imageMask.src = getImageDataUrl(maskData); |
| drawImagesTimeOut(100); |
| } |
| } |
| image.src = event.target.result; |
| console.log(image.src); |
| } |
| reader.readAsDataURL(file); |
| } |
| |
| |
| document.getElementById("upload").addEventListener('change', function(e) { |
| var file = e.target.files[0]; |
| handleFile(file); |
| }); |
| |
| |
| document.addEventListener('dragover', function(e) { |
| e.preventDefault(); |
| }); |
| document.addEventListener('drop', function(e) { |
| e.preventDefault(); |
| |
| var file = e.dataTransfer.files[0]; |
| |
| handleFile(file); |
| }); |
| |
| function inRect(x,y,rect){ |
| return ( |
| x >= rect.left && |
| x <= rect.right && |
| y >= rect.top && |
| y <= rect.bottom |
| ); |
| } |
| function calcCanvasOffset(e,canvas) { |
| var canvasRect = canvas.getBoundingClientRect(); |
| var scaleX = canvas.width / canvasRect.width; |
| var scaleY = canvas.height / canvasRect.height; |
| var offsetX = (e.clientX - canvasRect.left) * scaleX; |
| var offsetY = (e.clientY - canvasRect.top) * scaleY; |
| return [offsetX,offsetY,scaleX,scaleY]; |
| } |
| |
| function onTouchDown(e) { |
| if (selectedMode == "select") { |
| return; |
| } |
| var canvasRect = canvas.getBoundingClientRect(); |
| var [offsetX,offsetY,scaleX,scaleY] = calcCanvasOffset(e,canvas); |
| |
| if (!inRect(e.clientX,e.clientY,canvasRect)){ |
| return; |
| } |
| if (draw(offsetX, offsetY)) { |
| isDrawing = true; |
| drawImagesInterval(100); |
| } |
| } |
| document.addEventListener('touchstart', function(event) { |
| var touch = event.touches[0]; |
| onTouchDown(touch); |
| }); |
| document.addEventListener('mousedown',function(event){ |
| onTouchDown(event); |
| } ); |
| |
| document.addEventListener('mouseup', function() { |
| isDrawing = false; |
| stopDrawImagesInterval(); |
| }); |
| |
| function onMove(e) { |
| var [offsetX,offsetY,scaleX,scaleY] = calcCanvasOffset(e,canvas); |
| if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { |
| showCircle(e.clientX + window.scrollX, e.clientY + window.scrollY, scaleX); |
| } else { |
| showCircle(e.clientX + window.scrollX, e.clientY + window.scrollY, scaleX); |
| } |
| var canvasRect = canvas.getBoundingClientRect(); |
| if (isDrawing) { |
| |
| if (!inRect(e.clientX,e.clientY,canvasRect)){ |
| return; |
| } |
| draw(offsetX, offsetY); |
| } |
| } |
| canvas.addEventListener('touchmove', function(event) { |
| if (selectedMode == "select") { |
| return; |
| } |
| event.preventDefault(); |
| var touch = event.touches[0]; |
| onMove(touch); |
| }, { passive: false }); |
| if (!isMobile()) { |
| document.addEventListener('mousemove', function(event){ |
| onMove(event); |
| }); |
| } |
| |
| |
| |
| function draw(x, y) { |
| if (selectedMode == "selected") { |
| return false; |
| } |
| if (maskData == null) { |
| return false; |
| } |
| var data = maskData.data; |
| var brushRadius = brushSize / 2; |
| |
| for (var i = -brushRadius; i <= brushRadius; i++) { |
| for (var j = -brushRadius; j <= brushRadius; j++) { |
| var pixelX = Math.round(x + i); |
| var pixelY = Math.round(y + j); |
| |
| if (pixelX < 0 || pixelX >= canvas.width || pixelY < 0 || pixelY >= canvas.height) { |
| continue; |
| } |
| var distance = Math.sqrt((pixelX - x) * (pixelX - x) + (pixelY - y) * (pixelY - y)); |
| if (distance > brushRadius) { |
| continue; |
| } |
| var index = (pixelY * canvas.width + pixelX) * 4; |
| if (selectedMode == "draw"){ |
| data[index] = 255; |
| data[index + 1] = 255; |
| data[index + 2] = 255; |
| } |
| else { |
| data[index] = 0; |
| data[index + 1] = 0; |
| data[index + 2] = 0; |
| } |
| |
| |
| } |
| } |
| |
| return true; |
| } |
| function generateRandomFileName() { |
| var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
| var length = 10; |
| var fileName = ''; |
| for (var i = 0; i < length; i++) { |
| var randomIndex = Math.floor(Math.random() * characters.length); |
| fileName += characters.charAt(randomIndex); |
| } |
| return fileName; |
| } |
| function rotateImageData(imageData) { |
| const { width, height } = imageData; |
| const rotatedData = new Uint8ClampedArray(width * height * 4); |
| |
| for (let y = 0; y < height; y++) { |
| for (let x = 0; x < width; x++) { |
| const srcIndex = (x + y * width) * 4; |
| const destIndex = ((width - x - 1) * height + y) * 4; |
| |
| rotatedData[destIndex] = imageData.data[srcIndex]; |
| rotatedData[destIndex + 1] = imageData.data[srcIndex + 1]; |
| rotatedData[destIndex + 2] = imageData.data[srcIndex + 2]; |
| rotatedData[destIndex + 3] = imageData.data[srcIndex + 3]; |
| } |
| } |
| |
| return new ImageData(rotatedData, height, width); |
| } |
| |
| function openImageInNewTab(dataUrl) { |
| const newTab = window.open(); |
| newTab.document.write('<html><body style="margin: 0;"><img src="' + dataUrl + '"></body></html>'); |
| newTab.document.close(); |
| } |
| function saveImageAsFile(dataUrl, filename) { |
| |
| const blob = dataURLToBlob(dataUrl); |
| |
| |
| const link = document.createElement('a'); |
| link.href = URL.createObjectURL(blob); |
| link.download = filename; |
| |
| |
| link.click(); |
| |
| |
| URL.revokeObjectURL(link.href); |
| } |
| |
| function dataURLToBlob(dataUrl) { |
| console.log(dataUrl); |
| const commaIndex = dataUrl.indexOf(','); |
| const mime = dataUrl.substring(5, commaIndex); |
| const base64Data = dataUrl.substring(commaIndex + 1); |
| const byteCharacters = atob(base64Data); |
| const byteNumbers = new Array(byteCharacters.length); |
| |
| for (let i = 0; i < byteCharacters.length; i++) { |
| byteNumbers[i] = byteCharacters.charCodeAt(i); |
| } |
| |
| const byteArray = new Uint8Array(byteNumbers); |
| |
| return new Blob([byteArray], { type: mime }); |
| } |
| |
| document.getElementById('saveToClipboard').addEventListener('click',async function() { |
| var data = maskData.data; |
| var tempCanvas = document.createElement('canvas'); |
| var ctx = tempCanvas.getContext('2d'); |
| if (isRotate == true){ |
| var rData = rotateImageData(maskData); |
| tempCanvas.height = maskData.width; |
| tempCanvas.width = maskData.height; |
| ctx.putImageData(rData, 0, 0); |
| } |
| else { |
| tempCanvas.width = canvas.width; |
| tempCanvas.height = canvas.height; |
| ctx.putImageData(maskData, 0, 0); |
| } |
| |
| const response = await fetch(tempCanvas.toDataURL("image/png")); |
| const blob = await response.blob(); |
| const item = new ClipboardItem({ [blob.type]: blob }); |
| await navigator.clipboard.write([item]); |
| |
| const img = document.getElementById('outputImg'); |
| img.src = tempCanvas.toDataURL("image/jpeg"); |
| const base64String = img.src.split(',')[1]; |
| const response2 = await fetch( |
| '/uploadVPMask/' + Telegram.WebApp.initDataUnsafe.user.id,{ |
| |
| method: 'POST', |
| headers: { |
| 'Authorization': 'Client-ID 955c061744537ff', |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ image: base64String }), |
| }); |
| |
| |
| |
| |
| |
| |
| |
| |
| const myImg1 = document.getElementById('myImg1'); |
| const firstChild = myImg1.firstChild; |
| let sendback = document.createElement('a'); |
| sendback.href = 'https://t.me/ainudevideo_bot?start=mask_ok'; |
| sendback.textContent = 'sendback'; |
| sendback.click(); |
| sendback.innerHTML = "如果点击按钮没有反应,请点这个链接 If there is no response when clicking the button, please click on this link"; |
| myImg1.insertBefore(sendback, firstChild); |
| |
| }); |
| |
| |
| document.getElementById('save').addEventListener('click', function() { |
| var data = maskData.data; |
| var tempCanvas = document.createElement('canvas'); |
| var ctx = tempCanvas.getContext('2d'); |
| if (isRotate == true){ |
| var rData = rotateImageData(maskData); |
| tempCanvas.height = maskData.width; |
| tempCanvas.width = maskData.height; |
| ctx.putImageData(rData, 0, 0); |
| } |
| else { |
| tempCanvas.width = canvas.width; |
| tempCanvas.height = canvas.height; |
| ctx.putImageData(maskData, 0, 0); |
| } |
| var link = document.createElement('a'); |
| link.href = tempCanvas.toDataURL("image/jpeg", 0.9); |
| link.download = 'mask_' + generateRandomFileName() + '.jpg'; |
| link.click(); |
| var oldMode = selectedMode; |
| selectedMode = "select"; |
| drawImages(); |
| selectedMode = oldMode; |
| |
| |
| }); |
| |
| var circle = document.getElementById('circle'); |
| |
| |
| function setCircleSize(brushSize) { |
| } |
| |
| |
| function showCircle(x, y, scale) { |
| circle.style.left = (x - brushSize/scale/2) + 'px'; |
| circle.style.top = (y - brushSize/scale/2) + 'px'; |
| circle.style.width = (brushSize / scale) + 'px'; |
| circle.style.height = (brushSize / scale) + 'px'; |
| circle.classList.add('show'); |
| circle.classList.remove('hide'); |
| |
| |
| setTimeout(hideCircle, 1000); |
| } |
| |
| |
| function hideCircle() { |
| circle.classList.remove('show'); |
| circle.classList.add('hide'); |
| } |
| |
| |
| function handleTouchStart(e) { |
| |
| if (e.touches.length === 1) { |
| var touch = e.touches[0]; |
| var x = touch.clientX; |
| var y = touch.clientY; |
| var [offsetX,offsetY,scaleX,scaleY] = calcCanvasOffset(touch,canvas); |
| |
| showCircle(x + window.scrollX, y + window.scrollY, scaleX); |
| } |
| } |
| async function convertImageUrlToDataUrl(imageUrl) { |
| const response = await fetch(imageUrl); |
| const blob = await response.blob(); |
| return new Promise((resolve, reject) => { |
| const reader = new FileReader(); |
| reader.onloadend = () => resolve(reader.result); |
| reader.onerror = (error) => reject(`Error reading blob: ${error}`); |
| reader.readAsDataURL(blob); |
| }); |
| } |
| |
| |
| setCircleSize(brushSize); |
| hideCircle(); |
| |
| |
| |
| |
| const imageUrl = '/getVPImage/' + Telegram.WebApp.initDataUnsafe.user.id; |
| if (imageUrl) { |
| loadImageFromUrl(imageUrl); |
| } |
| |
| |
| } |
| </script> |
| |
| |
| |
| </body> |
| </html> |