You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, statusText = "PLAY \u25BA", timecode = "01:23:45", osdColor = "#33FF33", combingIntensity = "2", videoNoise = "12", addScanlines = "1", addLetterbox = "1", showDVDLogo = "1") {
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw the original base image
ctx.drawImage(originalImg, 0, 0, width, height);
// Parse strength parameters
const applyCombingValue = parseFloat(combingIntensity) || 0;
const noiseVal = parseFloat(videoNoise) || 0;
// Apply Interlacing Combing effect and/or video noise to simulate paused DVD video
if (applyCombingValue > 0 || noiseVal > 0) {
// Pixel level shift for field combing (horizontal misalignments on alternate rows)
const shift = Math.floor(applyCombingValue * Math.max(1, width / 400));
// Standard video has 480 interlaced lines, we map that to the image height
const fieldHeight = Math.max(1, Math.floor(height / 480));
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
const originalData = new Uint8ClampedArray(data); // clone for reading
for (let y = 0; y < height; y++) {
const fieldIndex = Math.floor(y / fieldHeight);
const isEvenField = fieldIndex % 2 === 0;
const currentShift = isEvenField ? shift : -shift;
for (let x = 0; x < width; x++) {
let srcX = x - currentShift;
// Clamp edges to cause realistic video field smearing
if (srcX < 0) srcX = 0;
else if (srcX >= width) srcX = width - 1;
const srcIdx = (y * width + srcX) * 4;
const destIdx = (y * width + x) * 4;
if (noiseVal > 0) {
const noise = (Math.random() - 0.5) * noiseVal;
// Float implicit conversion handles rounding and clamping on Uint8ClampedArray naturally
data[destIdx] = originalData[srcIdx] + noise;
data[destIdx+1] = originalData[srcIdx+1] + noise;
data[destIdx+2] = originalData[srcIdx+2] + noise;
data[destIdx+3] = originalData[srcIdx+3];
} else {
data[destIdx] = originalData[srcIdx];
data[destIdx+1] = originalData[srcIdx+1];
data[destIdx+2] = originalData[srcIdx+2];
data[destIdx+3] = originalData[srcIdx+3];
}
}
}
ctx.putImageData(imgData, 0, 0);
}
// Apply Screen Vignette / Dimmed corners
const gradient = ctx.createRadialGradient(width/2, height/2, height/3, width/2, height/2, Math.max(width, height));
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0.5)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Apply Cinematic Letterboxing
const isLetterbox = String(addLetterbox).toLowerCase() === "1" || String(addLetterbox).toLowerCase() === "true";
if (isLetterbox) {
const barHeight = Math.floor(height * 0.12);
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, width, barHeight);
ctx.fillRect(0, height - barHeight, width, barHeight);
}
// Apply CRT Over-layer Scanlines
const isScanlines = String(addScanlines).toLowerCase() === "1" || String(addScanlines).toLowerCase() === "true";
if (isScanlines) {
const lineStep = Math.max(2, Math.floor(height / 360));
const lineThickness = Math.max(1, Math.floor(lineStep / 2.5));
ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
for (let y = 0; y < height; y += lineStep) {
ctx.fillRect(0, y, width, lineThickness);
}
}
// OSD / TEXT RENDERING
const fontSize = Math.max(16, Math.floor(height * 0.05));
const paddingY = Math.max(15, Math.floor(height * 0.05));
const paddingX = Math.max(15, Math.floor(width * 0.05));
function drawText(text, x, y, align = "left", baseline = "top") {
ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
ctx.textAlign = align;
ctx.textBaseline = baseline;
// Draw heavy black stroke for retro visibility
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.lineWidth = Math.max(2, Math.floor(fontSize * 0.15));
ctx.strokeStyle = 'black';
ctx.lineJoin = 'round';
ctx.strokeText(text, x, y);
// Draw inner colored text with native glow
ctx.fillStyle = osdColor;
ctx.shadowColor = osdColor;
ctx.shadowBlur = Math.max(4, fontSize * 0.25);
ctx.fillText(text, x, y);
// Reset Shadow for subsequent drawings
ctx.shadowBlur = 0;
}
if (statusText && statusText.trim() !== "") {
drawText(statusText, paddingX, paddingY, "left", "top");
}
if (timecode && timecode.trim() !== "") {
drawText(timecode, width - paddingX, paddingY, "right", "top");
}
// Custom DVD Logo Render
const isShowLogo = String(showDVDLogo).toLowerCase() === "1" || String(showDVDLogo).toLowerCase() === "true";
if (isShowLogo) {
function drawDVDLogo(x, y, h) {
ctx.save();
ctx.translate(x, y);
const logoW = h * 2.5;
const logoH = h;
// Multiple render passes for intense black stroke logic
ctx.shadowColor = 'black';
ctx.shadowBlur = Math.max(2, h * 0.1);
ctx.lineJoin = 'round';
for(let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.ellipse(logoW/2, logoH*0.65, logoW/2, logoH*0.25, 0, 0, 2 * Math.PI);
ctx.strokeStyle = 'black';
ctx.lineWidth = Math.max(2, h*0.12);
ctx.stroke();
ctx.font = `italic bold ${h * 0.8}px "Arial Black", Arial, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.lineWidth = Math.max(2, h*0.15);
ctx.strokeText("DVD", logoW/2, logoH*0.35);
ctx.font = `bold ${h * 0.22}px "Arial", sans-serif`;
ctx.strokeText("V I D E O", logoW/2, logoH*0.95);
}
// Fill color and apply neon glow
ctx.shadowColor = osdColor;
ctx.shadowBlur = Math.max(3, h * 0.2);
ctx.strokeStyle = osdColor;
ctx.fillStyle = osdColor;
ctx.beginPath();
ctx.ellipse(logoW/2, logoH*0.65, logoW/2, logoH*0.25, 0, 0, 2 * Math.PI);
ctx.lineWidth = Math.max(1, h*0.04);
ctx.stroke();
ctx.font = `italic bold ${h * 0.8}px "Arial Black", Arial, sans-serif`;
ctx.fillText("DVD", logoW/2, logoH*0.35);
ctx.font = `bold ${h * 0.22}px "Arial", sans-serif`;
ctx.fillText("V I D E O", logoW/2, logoH*0.95);
ctx.restore();
}
const logoHeight = Math.max(20, Math.floor(height * 0.08));
drawDVDLogo(paddingX, height - paddingY - (logoHeight * 1.1), logoHeight);
}
return canvas;
}
Apply Changes