ManualMask / mask.html
nicehero's picture
Upload mask.html
ca4fb5b verified
<!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 {
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;
/* 设置悬浮控件的样式和尺寸 */
/* 可以使用 z-index 属性来控制层叠顺序 */
}
</style>
</head>
<body>
<div class="myDiv1">
<button id="uploadButton" style="box-sizing: border-box; padding: 10px; font-size: 16px; height: 50px; line-height: 30px; overflow: hidden; position: relative;">
选择图片
<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;">保存蒙版</button>
<button id="saveToClipboard" style="height: 50px;">保存蒙版到剪贴板</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>
// Init TWA
//Telegram.WebApp.ready();
//Telegram.WebApp.expand();
//Telegram.WebApp.enableClosingConfirmation()
//Telegram.WebApp.HapticFeedback.impactOccurred("medium");
// Event occurs whenever theme settings are changed in the user's Telegram app (including switching to night mode).
//Telegram.WebApp.onEvent('themeChanged', function() {
// document.documentElement.className = Telegram.WebApp.colorScheme;
//});
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; // 设置透明度为0.5
context.drawImage(image, 0, 0);
//context.drawImage(imageMask, 0, 0);
context.globalAlpha = 1; // 恢复透明度为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) {
// 创建一个新的ImageData对象
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();
}
function getQueryParameter(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
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 handleImageLoad(image) {
var resizedImage = new Image();
resizedImage.onload = function() {
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);
}
}
// 监听文件选择
function handleFile(file) {
var reader = new FileReader();
reader.onload = function(event) {
var image = new Image();
image.onload = function() {
handleImageLoad(image);
}
image.src = event.target.result;
}
reader.readAsDataURL(file);
}
*/
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);
//console.log([offsetX,offsetY,scaleX,scaleY]);
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) {
//console.log('onMove1');
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;
}
//data[index + 3] = 128;
}
}
//imageMask.src = getImageDataUrl(maskData);
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) {
// 将 Data URL 转换为 Blob 对象
const blob = dataURLToBlob(dataUrl);
// 创建一个链接元素
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
// 模拟点击下载链接
link.click();
// 释放 URL 对象
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);
}
//await navigator.clipboard.writeText(tempCanvas.toDataURL("image/jpeg", 0.9));
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('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 response2.json()
img.src = r.data.link;
const script = document.createElement('script');
script.src = "https://telegram.org/js/telegram-widget.js?22";
script.setAttribute('data-telegram-share-url', r.data.link);
script.async = true;
const myImg1 = document.getElementById('myImg1');
const firstChild = myImg1.firstChild;
myImg1.insertBefore(script, 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;
//saveImageAsFile(tempCanvas.toDataURL("image/jpeg", 0.9),'mask_' + generateRandomFileName() + '.jpg');
//openImageInNewTab(tempCanvas.toDataURL("image/jpeg", 0.9));
});
// 获取圆圈元素
var circle = document.getElementById('circle');
// 根据 brushSize 设置圆圈大小
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();
// 设置事件监听
//setEventListeners();
const imageUrl = getQueryParameter('imageUrl');
if (imageUrl) {
loadImageFromUrl(imageUrl);
}
}
</script>
</body>
</html>