CoolFace
Apppublic

nicehero/ManualMask

sourceHugging Faceagpl-3.0updated 2y agoView on Hugging Face
1likes
index.html640 linesDownload Raw Back to root
1<!DOCTYPE html>
2<html>
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
6    <title>蒙版王</title>
7    <style>
8        /* 适用于手机的样式 */
9        @media (max-width: 767px) {
10            body {
11                width: 350;
12                margin: 0 auto; /* 水平居中 */
13            }
14            #canvas {
15              border: 1px solid #000;
16              width: 100%;
17              height: auto;
18            }
19            html, body {
20                overflow-x: hidden;
21            }
22            #uploadButton{
23                width: 80%;
24            }
25            #save{
26                width: 20%;
27            }
28            .myDiv {
29                width: 98%;
30                display: flex;
31                flex-direction: row;
32                justify-content: flex-start;
33                margin: 0px 1%; 
34                flex-wrap: wrap;
35            }
36            .myDiv1 {
37                width: 98%;
38                display: flex;
39                flex-direction: row;
40                justify-content: flex-start;
41                margin: 0px 1%; 
42            }
43            .overlay {
44                left: 20px;
45            }
46        }
47        /* 适用于电脑的样式 */
48        @media (min-width: 768px) {
49            #canvas {
50              border: 1px solid #000;
51              width: auto;
52              height: auto;
53            }
54            .myDiv {
55                display: flex;
56                flex-direction: row;
57                justify-content: center;
58                margin: 0 100px; 
59                width: 512px;
60                flex-wrap: wrap;
61            }
62            .myDiv1 {
63                display: flex;
64                flex-direction: row;
65                justify-content: center;
66                margin: 0 100px; 
67                width: 512px;
68            }
69            .overlay {
70                left: 100px;
71            }
72        }
73        #circle {
74            position: absolute;
75            width: 10px;
76            height: 10px;
77            border-radius: 50%;
78            border: 2px solid red;
79            background-color: transparent;
80            transition: opacity 1s;
81            pointer-events: none;
82        }
83        .show {
84            opacity: 1;
85        }
86        .hide {
87            opacity: 0;
88        }
89        .overlay {
90            position: absolute;
91            top: 82px;
92            z-index: 10;
93            /* 设置悬浮控件的样式和尺寸 */
94            /* 可以使用 z-index 属性来控制层叠顺序 */
95        }
96    </style>
97</head>
98<body>
99    <div class="myDiv1">
100        <button id="uploadButton" style="box-sizing: border-box; padding: 10px; font-size: 16px; height: 50px; line-height: 30px; overflow: hidden; position: relative;">
101            选择图片
102            <input type="file" id="upload" accept="image/*" style="position: absolute; top: 0; left: 0; width: 98%; height: 100%; opacity: 0; cursor: pointer;">
103        </button>
104        <button id="save" style="height: 50px;">保存蒙版</button>
105        <button id="saveToClipboard" style="height: 50px;">保存蒙版到剪贴板</button>
106    </div>
107    <div class="myDiv" id="tools">
108        <input type="range" id="brushSizeSlider" style="width: 100%" value="40" min="1" max="150" step="1" >
109    </div>
110    <br>
111    <br>
112    <div class="myDiv" id="myImg1">
113        <canvas id="canvas"></canvas>
114        <br>
115        <img id="outputImg"></img>
116    </div>
117    <div class="overlay" style="display: flex; flex-direction: row; align-items: flex-start;">
118        <label style="margin-bottom: 10px;">
119            <input type="radio" name="editMode" value="draw" checked> 画笔模式
120        </label>
121        <label style="margin-bottom: 10px;">
122            <input type="radio" name="editMode" value="erase"> 擦除模式
123        </label>
124        <label>
125            <input type="radio" name="editMode" value="select"> 不编辑
126        </label>
127    </div>
128    <div id="circle" style="width: 20px; height: 20px; border-radius: 50%; border: 2px solid red;"></div>
129    <script src="https://telegram.org/js/telegram-web-app.js"></script>
130    <script>
131    // Init TWA
132    Telegram.WebApp.ready();
133    Telegram.WebApp.expand();
134    Telegram.WebApp.enableClosingConfirmation()
135    Telegram.WebApp.HapticFeedback.impactOccurred("medium");
136    // Event occurs whenever theme settings are changed in the user's Telegram app (including switching to night mode).
137    Telegram.WebApp.onEvent('themeChanged', function() {
138        document.documentElement.className = Telegram.WebApp.colorScheme;
139    });
140window.onload = function() {
141    var canvas = document.getElementById('canvas');
142    var context = canvas.getContext('2d');
143    var image = new Image();
144    var imageMask = new Image();
145    var maskData = null;
146    var isDrawing = false;
147    var brushSize = 40;
148    var intervalHandel = null;
149    var brushSizeSlider = document.getElementById('brushSizeSlider');
150    var editModeRadios = document.getElementsByName('editMode');
151    var selectedMode = "draw";
152    var isRotate = false;
153    function isMobile() {
154        return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
155    }
156    for (var i = 0; i < editModeRadios.length; i++) {
157        editModeRadios[i].addEventListener('change', function() {
158            // 获取选中的编辑模式值
159            selectedMode = this.value;
160            drawImagesInterval(100);
161        });
162    }
163    brushSizeSlider.addEventListener('input', function() {
164        brushSize = parseInt(this.value);
165        setCircleSize(brushSize);
166        if (isMobile()){
167            var canvasRect = canvas.getBoundingClientRect();
168            var scaleX = canvas.width / canvasRect.width;
169            showCircle(175 + window.scrollX, 200 + window.scrollY, scaleX);
170        }
171    });
172    function resizeImage(img,s,resizedImage) {
173        var maxWidth = s; // 最大宽度
174        var maxHeight = s; // 最大高度
175        var width = img.width;
176        var height = img.height;
177        if (width/height < 4/3 && width <= maxWidth && height <= maxHeight) {
178            return false;
179        }
180        if (!isMobile() && width <= maxWidth && height <= maxHeight) {
181            return false;
182        }
183        if (width > maxWidth || height > maxHeight || width/height >= 4/3) {
184            var ratio = Math.max(maxWidth / width, maxHeight / height);
185            if (width <= maxWidth && height <= maxHeight){
186                ratio = 1;
187            }
188            width = Math.floor(width * ratio);
189            height = Math.floor(height * ratio);
190            var tempCanvas = document.createElement('canvas');
191            tempCanvas.width = width;
192            tempCanvas.height = height;
193            var ctx = tempCanvas.getContext('2d');
194            isRotate = false;
195            if (width/height >= 4/3 && isMobile()){
196                tempCanvas.height = width;
197                tempCanvas.width = height;
198                ctx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
199                ctx.save();
200                ctx.translate(tempCanvas.width / 2, tempCanvas.height / 2);
201                ctx.rotate(Math.PI / 2);
202                ctx.drawImage(img, -img.width / 2, -img.height / 2);
203                ctx.restore();
204                isRotate = true;
205            }
206            else {
207                ctx.drawImage(img, 0, 0, width, height);
208            }
209            resizedImage.src = tempCanvas.toDataURL();
210            return true;
211        }
212        return false;
213    }
214
215    function drawImages() {
216        context.clearRect(0, 0, canvas.width, canvas.height);
217        context.putImageData(maskData,0,0);
218        if (selectedMode == "select") {
219            return;
220        }
221        context.globalAlpha = 0.75; // 设置透明度为0.5
222        context.drawImage(image, 0, 0);
223        //context.drawImage(imageMask, 0, 0);
224        context.globalAlpha = 1; // 恢复透明度为1
225    }
226    function drawImagesInterval(t) {
227        if (intervalHandel != null){
228            drawImagesTimeOut(100);
229            window.clearInterval(intervalHandel);
230            intervalHandel = null;
231        }
232        intervalHandel = window.setInterval(function() {drawImages();}, t);
233    }
234    window.addEventListener('scroll', function(event) {
235        var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
236        var overlay = document.querySelector(".overlay");
237        overlay.style.top = (scrollTop + 82) + "px";
238    });
239    function stopDrawImagesInterval() {
240        if (intervalHandel != null){
241                drawImagesTimeOut(100);
242                window.clearInterval(intervalHandel);
243                intervalHandel = null;
244        }
245    }
246    function drawImagesTimeOut(t) {
247        window.setTimeout(function() {drawImages();}, t);
248    }
249    function getImageDataB(img) {
250        context.clearRect(0, 0, canvas.width, canvas.height);
251        var width = image.width;
252        var height = image.height;
253        canvas.width = width;
254        canvas.height = height;
255        context.drawImage(img, 0, 0);
256        var imageData = context.getImageData(0, 0, width, height);
257        return imageData;
258    }
259    function getImageData(img) {
260        var tempCanvas = document.createElement('canvas');
261        tempCanvas.width = canvas.width;
262        tempCanvas.height = canvas.height;
263        var tempContext = tempCanvas.getContext('2d');
264        var width = image.width;
265        var height = image.height;
266        tempCanvas.width = width;
267        tempCanvas.height = height;
268        tempContext.drawImage(img, 0, 0);
269        var imageData = tempContext.getImageData(0, 0, width, height);
270        return imageData;
271    }
272    function createMaskImageData(width, height) {
273        // 创建一个新的ImageData对象
274        var imageData = new ImageData(width, height);
275        // 获取像素数据
276        var data = imageData.data;
277        // 将每个像素设置为黑色
278        for (var i = 0; i < data.length; i += 4) {
279                data[i] = 0;         // 设置红色通道
280                data[i + 1] = 0; // 设置绿色通道
281                data[i + 2] = 0; // 设置蓝色通道
282                data[i + 3] = 255; // 设置透明度通道(不透明)
283        }
284        return imageData;
285    }
286    function getImageDataUrl(imageData) {
287        var tempCanvas = document.createElement('canvas');
288        tempCanvas.width = canvas.width;
289        tempCanvas.height = canvas.height;
290        var tempContext = tempCanvas.getContext('2d');
291        tempContext.putImageData(imageData, 0, 0);
292        return tempCanvas.toDataURL();
293    }
294    function handleFile(file) {
295        var reader = new FileReader();
296        reader.onload = function(event) {
297            image.onload = function() {
298                var resizedImage = new Image();
299                resizedImage.onload = _ok;
300                function _ok(event){
301                    image = resizedImage;
302                    canvas.width = resizedImage.width;
303                    canvas.height = resizedImage.height;
304                    maskData = createMaskImageData(image.width,image.height);
305                    imageMask.src = getImageDataUrl(maskData);
306                    drawImagesTimeOut(100);
307                }
308                if (!resizeImage(image,512,resizedImage)) {
309                    canvas.width = image.width;
310                    canvas.height = image.height;
311                    maskData = createMaskImageData(image.width,image.height);
312                    imageMask.src = getImageDataUrl(maskData);
313                    drawImagesTimeOut(100);
314                }
315            }
316            image.src = event.target.result;
317        }
318        reader.readAsDataURL(file);
319    }
320    // 上传图片
321    document.getElementById("upload").addEventListener('change', function(e) {
322        var file = e.target.files[0];
323        handleFile(file);
324    });
325
326    // 添加拖拽文件事件监听
327    document.addEventListener('dragover', function(e) {
328        e.preventDefault();
329    });
330    document.addEventListener('drop', function(e) {
331        e.preventDefault();
332        // 获取拖拽事件中的文件对象
333        var file = e.dataTransfer.files[0];
334        // 执行与文件上传按钮相同的操作
335        handleFile(file);
336    });
337
338    function inRect(x,y,rect){
339        return (
340            x >= rect.left &&
341            x <= rect.right &&
342            y >= rect.top &&
343            y <= rect.bottom
344        );
345    }
346    function calcCanvasOffset(e,canvas) {
347        var canvasRect = canvas.getBoundingClientRect();
348        var scaleX = canvas.width / canvasRect.width;
349        var scaleY = canvas.height / canvasRect.height;
350        var offsetX = (e.clientX - canvasRect.left) * scaleX;
351        var offsetY = (e.clientY - canvasRect.top) * scaleY;
352        return [offsetX,offsetY,scaleX,scaleY];
353    }
354    // 开始绘制
355    function onTouchDown(e) {
356        if (selectedMode == "select") {
357            return;
358        }
359        var canvasRect = canvas.getBoundingClientRect();
360        var [offsetX,offsetY,scaleX,scaleY] = calcCanvasOffset(e,canvas);
361        //console.log([offsetX,offsetY,scaleX,scaleY]);
362        if (!inRect(e.clientX,e.clientY,canvasRect)){
363            return;
364        }
365        if (draw(offsetX, offsetY)) {
366            isDrawing = true;
367            drawImagesInterval(100);
368        }
369    }
370    document.addEventListener('touchstart', function(event) {
371        var touch = event.touches[0];
372        onTouchDown(touch);
373    });
374    document.addEventListener('mousedown',function(event){
375        onTouchDown(event);
376    } );
377    // 结束绘制
378    document.addEventListener('mouseup', function() {
379        isDrawing = false;
380        stopDrawImagesInterval();
381    });
382    // 绘制中
383    function onMove(e) {
384        var [offsetX,offsetY,scaleX,scaleY] = calcCanvasOffset(e,canvas);
385        if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
386            showCircle(e.clientX + window.scrollX, e.clientY + window.scrollY, scaleX);
387        } else {
388            showCircle(e.clientX + window.scrollX, e.clientY + window.scrollY, scaleX);
389        }
390        var canvasRect = canvas.getBoundingClientRect();
391        if (isDrawing) {
392            //console.log('onMove1');
393            if (!inRect(e.clientX,e.clientY,canvasRect)){
394                return;
395            }
396            draw(offsetX, offsetY);
397        }
398    }
399    canvas.addEventListener('touchmove', function(event) {
400        if (selectedMode == "select") {
401            return;
402        }
403        event.preventDefault();
404        var touch = event.touches[0];
405        onMove(touch);
406    }, { passive: false });
407    if (!isMobile()) {
408        document.addEventListener('mousemove', function(event){
409            onMove(event);
410        });
411    }
412
413
414    // 绘制函数
415    function draw(x, y) {
416        if (selectedMode == "selected") {
417                return false;
418        }
419        if (maskData == null) {
420                return false;
421        }
422        var data = maskData.data;
423        var brushRadius = brushSize / 2;
424
425        for (var i = -brushRadius; i <= brushRadius; i++) {
426            for (var j = -brushRadius; j <= brushRadius; j++) {
427                var pixelX = Math.round(x + i);
428                var pixelY = Math.round(y + j);
429
430                if (pixelX < 0 || pixelX >= canvas.width || pixelY < 0 || pixelY >= canvas.height) {
431                    continue;
432                }
433                var distance = Math.sqrt((pixelX - x) * (pixelX - x) + (pixelY - y) * (pixelY - y));
434                if (distance > brushRadius) {
435                    continue;
436                }
437                var index = (pixelY * canvas.width + pixelX) * 4;
438                if (selectedMode == "draw"){
439                    data[index] = 255;    // 设置红色通道为最大值,即白色
440                    data[index + 1] = 255;    // 设置绿色通道为最大值,即白色
441                    data[index + 2] = 255;
442                }
443                else {
444                    data[index] = 0;    // 设置红色通道为最大值,即白色
445                    data[index + 1] = 0;    // 设置绿色通道为最大值,即白色
446                    data[index + 2] = 0;
447                }
448                
449                //data[index + 3] = 128;
450            }
451        }
452        //imageMask.src = getImageDataUrl(maskData);
453        return true;
454    }
455    function generateRandomFileName() {
456        var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
457        var length = 10; // 文件名长度
458        var fileName = '';
459        for (var i = 0; i < length; i++) {
460            var randomIndex = Math.floor(Math.random() * characters.length);
461            fileName += characters.charAt(randomIndex);
462        }
463        return fileName;
464    }
465    function rotateImageData(imageData) {
466      const { width, height } = imageData;
467      const rotatedData = new Uint8ClampedArray(width * height * 4);
468
469      for (let y = 0; y < height; y++) {
470        for (let x = 0; x < width; x++) {
471          const srcIndex = (x + y * width) * 4;
472          const destIndex = ((width - x - 1) * height + y) * 4;
473
474          rotatedData[destIndex] = imageData.data[srcIndex];
475          rotatedData[destIndex + 1] = imageData.data[srcIndex + 1];
476          rotatedData[destIndex + 2] = imageData.data[srcIndex + 2];
477          rotatedData[destIndex + 3] = imageData.data[srcIndex + 3];
478        }
479      }
480
481      return new ImageData(rotatedData, height, width);
482    }
483    
484    function openImageInNewTab(dataUrl) {
485        const newTab = window.open();
486        newTab.document.write('<html><body style="margin: 0;"><img src="' + dataUrl + '"></body></html>');
487        newTab.document.close();
488    }
489    function saveImageAsFile(dataUrl, filename) {
490      // 将 Data URL 转换为 Blob 对象
491      const blob = dataURLToBlob(dataUrl);
492
493      // 创建一个链接元素
494      const link = document.createElement('a');
495      link.href = URL.createObjectURL(blob);
496      link.download = filename;
497
498      // 模拟点击下载链接
499      link.click();
500
501      // 释放 URL 对象
502      URL.revokeObjectURL(link.href);
503    }
504
505    function dataURLToBlob(dataUrl) {
506      console.log(dataUrl);
507      const commaIndex = dataUrl.indexOf(',');
508      const mime = dataUrl.substring(5, commaIndex);
509      const base64Data = dataUrl.substring(commaIndex + 1);
510      const byteCharacters = atob(base64Data);
511      const byteNumbers = new Array(byteCharacters.length);
512
513      for (let i = 0; i < byteCharacters.length; i++) {
514        byteNumbers[i] = byteCharacters.charCodeAt(i);
515      }
516
517      const byteArray = new Uint8Array(byteNumbers);
518
519      return new Blob([byteArray], { type: mime });
520    }
521    // 保存蒙版
522    document.getElementById('saveToClipboard').addEventListener('click',async function() {
523        var data = maskData.data;
524        var tempCanvas = document.createElement('canvas');
525        var ctx = tempCanvas.getContext('2d');
526        if (isRotate == true){
527            var rData = rotateImageData(maskData);
528            tempCanvas.height = maskData.width;
529            tempCanvas.width = maskData.height;
530            ctx.putImageData(rData, 0, 0);
531        }
532        else {
533            tempCanvas.width = canvas.width;
534            tempCanvas.height = canvas.height;
535            ctx.putImageData(maskData, 0, 0);
536        }
537        //await navigator.clipboard.writeText(tempCanvas.toDataURL("image/jpeg", 0.9));
538        const response = await fetch(tempCanvas.toDataURL("image/png"));
539        const blob = await response.blob();
540        const item = new ClipboardItem({ [blob.type]: blob });
541        await navigator.clipboard.write([item]);
542        
543        const img = document.getElementById('outputImg');
544        img.src = tempCanvas.toDataURL("image/jpeg");
545        const base64String = img.src.split(',')[1];
546        const response2 = await fetch('https://api.imgur.com/3/image', {
547            method: 'POST',
548            headers: {
549                'Authorization': 'Client-ID 955c061744537ff',
550                'Content-Type': 'application/json',
551            },
552            body: JSON.stringify({ image: base64String }),
553        });
554        const r = await response2.json()
555        img.src = r.data.link;
556        const script = document.createElement('script');
557        script.src = "https://telegram.org/js/telegram-widget.js?22";
558        script.setAttribute('data-telegram-share-url', r.data.link);
559        script.async = true;
560        const myImg1 = document.getElementById('myImg1');
561        const firstChild = myImg1.firstChild;
562        myImg1.insertBefore(script, firstChild);
563    });
564
565    // 保存蒙版
566    document.getElementById('save').addEventListener('click', function() {
567        var data = maskData.data;
568        var tempCanvas = document.createElement('canvas');
569        var ctx = tempCanvas.getContext('2d');
570        if (isRotate == true){
571            var rData = rotateImageData(maskData);
572            tempCanvas.height = maskData.width;
573            tempCanvas.width = maskData.height;
574            ctx.putImageData(rData, 0, 0);
575        }
576        else {
577            tempCanvas.width = canvas.width;
578            tempCanvas.height = canvas.height;
579            ctx.putImageData(maskData, 0, 0);
580        }
581        var link = document.createElement('a');
582        link.href = tempCanvas.toDataURL("image/jpeg", 0.9);
583        link.download = 'mask_' + generateRandomFileName() + '.jpg';
584        link.click();
585        var oldMode = selectedMode;
586        selectedMode = "select";
587        drawImages();
588        selectedMode = oldMode;
589        //saveImageAsFile(tempCanvas.toDataURL("image/jpeg", 0.9),'mask_' + generateRandomFileName() + '.jpg');
590        //openImageInNewTab(tempCanvas.toDataURL("image/jpeg", 0.9));
591    });
592    // 获取圆圈元素
593    var circle = document.getElementById('circle');
594
595    // 根据 brushSize 设置圆圈大小
596    function setCircleSize(brushSize) {
597    }
598
599    // 在屏幕上显示圆圈
600    function showCircle(x, y, scale) {
601        circle.style.left = (x - brushSize/scale/2) + 'px';
602        circle.style.top = (y - brushSize/scale/2) + 'px';
603        circle.style.width = (brushSize / scale) + 'px';
604        circle.style.height = (brushSize / scale) + 'px';
605        circle.classList.add('show');
606        circle.classList.remove('hide');
607
608        // 过一秒后隐藏圆圈
609        setTimeout(hideCircle, 1000);
610    }
611
612    // 隐藏圆圈
613    function hideCircle() {
614        circle.classList.remove('show');
615        circle.classList.add('hide');
616    }
617
618    // 手机平台点击事件处理函数
619    function handleTouchStart(e) {
620        // 只处理单指触摸事件
621        if (e.touches.length === 1) {
622            var touch = e.touches[0];
623            var x = touch.clientX;
624            var y = touch.clientY;
625            var [offsetX,offsetY,scaleX,scaleY] = calcCanvasOffset(touch,canvas);
626            // 在屏幕上显示圆圈
627            showCircle(x + window.scrollX, y + window.scrollY, scaleX);
628        }
629    }
630
631
632    // 初始化圆圈大小
633    setCircleSize(brushSize);
634    hideCircle();
635    // 设置事件监听
636    //setEventListeners();
637}
638    </script>
639</body>
640</html>