Simple Vary Region (inpaint) mask editor
November 22, 2024
Midjourney inpainting Vary Region and Vary Region + Remix button require a base64-encoded black and white mask image. The generated base64 mask value is used by the POST jobs/button button Vary (Region)
param mask
.
Source code
Copy the code provided below into a <select desired file name>.html
and open it in your browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple Vary Region (inpaint) mask editor</title>
<style>
#canvasContainer {
position: relative;
max-width: 100vw;
max-height: 100vh;
/* overflow: hidden; */
margin: 0 auto;
}
#imageCanvas, #maskCanvas {
display: block;
}
#imageCanvas {
border: 1px solid #ccc;
}
#maskCanvas {
position: absolute;
left: 0;
top: 0;
cursor: crosshair;
}
#maskBase64 {
width: 100%;
height: 100px;
margin-top: 10px;
}
#copyMaskButton {
margin-top: 10px;
}
</style>
<input type="file" id="imageLoader" name="imageLoader"/>
<div id="canvasContainer">
<canvas id="imageCanvas"></canvas>
<canvas id="maskCanvas"></canvas>
</div>
<button id="copyMaskButton">Copy Base64 to Clipboard</button>
<textarea id="maskBase64" readonly></textarea>
<script>
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
var scaleX = canvas.width / rect.width;
var scaleY = canvas.height / rect.height;
return {
x: (evt.clientX - rect.left) * scaleX,
y: (evt.clientY - rect.top) * scaleY
}
}
document.addEventListener('DOMContentLoaded', () => {
var isDrawing = false;
var rect = {};
var img = new Image();
var base64Mask = '';
var imageLoader = document.getElementById('imageLoader');
var canvasContainer = document.getElementById('canvasContainer');
var copyMaskButton = document.getElementById('copyMaskButton');
var maskBase64 = document.getElementById('maskBase64');
var imageCanvas = document.getElementById('imageCanvas');
var maskCanvas = document.getElementById('maskCanvas');
var imageCtx = imageCanvas.getContext('2d');
var maskCtx = maskCanvas.getContext('2d');
var viewportWidth = window.innerWidth;
var rectCanvasContainer = canvasContainer.getBoundingClientRect();
var viewportHeight = window.innerHeight - rectCanvasContainer.top + window.scrollY;
imageLoader.addEventListener('change', function(e) {
var reader = new FileReader();
reader.onload = function(event) {
img.onload = function() {
var widthScale = viewportWidth / img.width;
var heightScale = viewportHeight / img.height;
var scale = Math.min(widthScale, heightScale, 1);
var scaledWidth = img.width * scale;
var scaledHeight = img.height * scale;
imageCanvas.width = img.width;
imageCanvas.height = img.height;
maskCanvas.width = img.width;
maskCanvas.height = img.height;
imageCanvas.style.width = scaledWidth + 'px';
imageCanvas.style.height = scaledHeight + 'px';
maskCanvas.style.width = scaledWidth + 'px';
maskCanvas.style.height = scaledHeight + 'px';
imageCtx.drawImage(img, 0, 0);
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
maskBase64.value = '';
base64Mask = '';
isDrawing = false;
rect = {};
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}, false);
maskCanvas.addEventListener('mousedown', function(e) {
isDrawing = true;
rect = {};
var pos = getMousePos(maskCanvas, e);
rect.startX = pos.x;
rect.startY = pos.y;
rect.w = 0;
rect.h = 0;
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
});
maskCanvas.addEventListener('mousemove', function(e) {
if (isDrawing) {
var pos = getMousePos(maskCanvas, e);
rect.w = pos.x - rect.startX;
rect.h = pos.y - rect.startY;
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
maskCtx.strokeStyle = 'red';
maskCtx.lineWidth = 2;
maskCtx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
}
});
maskCanvas.addEventListener('mouseup', function(e) {
if (isDrawing) {
isDrawing = false;
var rectWidth = Math.abs(rect.w);
var rectHeight = Math.abs(rect.h);
if (rectWidth > 1 && rectHeight > 1) {
var maskImage = document.createElement('canvas');
maskImage.width = maskCanvas.width;
maskImage.height = maskCanvas.height;
var maskImageCtx = maskImage.getContext('2d');
maskImageCtx.fillStyle = '#000';
maskImageCtx.fillRect(0, 0, maskImage.width, maskImage.height);
var drawX = rect.w >= 0 ? rect.startX : rect.startX + rect.w;
var drawY = rect.h >= 0 ? rect.startY : rect.startY + rect.h;
maskImageCtx.fillStyle = '#fff';
maskImageCtx.fillRect(drawX, drawY, rectWidth, rectHeight);
var base64String = maskImage.toDataURL('image/png');
base64Mask = base64String.replace(/^data:image\/png;base64,/, '');
maskBase64.value = base64Mask;
} else {
maskBase64.value = '';
base64Mask = '';
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
}
}
});
copyMaskButton.addEventListener('click', function() {
if (base64Mask) {
maskBase64.select();
maskBase64.setSelectionRange(0, maskBase64.value.length);
try {
var successful = document.execCommand('copy');
if (successful) {
alert('✅ Base64 mask copied to clipboard!');
} else {
alert('🛑 Failed to copy base64 mask.');
}
} catch (err) {
alert('🛑 Oops, unable to copy');
}
} else {
alert('🛑 No base64 mask to copy. Please select a valid rectangle.');
}
});
});
</script>
</body>
</html>