DEV Community

tage cc
tage cc

Posted on

Built a Browser Tool to Stop Using Photoshop for Theme Comparisons

How I used Canvas to create diagonal split screenshots in one hour


The Problem

While working on my project docs, I wanted to show light/dark theme comparisons. Not just two screenshots side by sideβ€”I wanted that nice diagonal split effect.

My options:

  1. Photoshop - tedious to repeat every time
  2. Online tools - mostly paid, and you have to upload images
  3. Manual stitching - doesn't look professional

Couldn't find a good free tool, so I built one.

What I Built

demo

Try it: https://tageecc.github.io/theme-merge/

Features:

  • Upload two images (light/dark theme)
  • Diagonal split with adjustable angle
  • Paste from clipboard (Ctrl+V)
  • Everything runs locally in your browser
  • Free and open source

Tech Stack

Kept it simple:

  • HTML5 Canvas for image manipulation
  • Vanilla JavaScript (no frameworks)
  • GitHub Pages for hosting

Goal was to make something you can just open and use. No install, no signup.

How It Works

The Core Algorithm

The diagonal split uses Canvas's clip() method:

function drawMergedImage() {
  // Draw dark theme as base
  ctx.drawImage(darkImg, 0, 0);

  // Save state
  ctx.save();

  // Create diagonal clipping path
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(canvas.width, 0);
  ctx.lineTo(x1, y1);  // rotated endpoints
  ctx.lineTo(x2, y2);
  ctx.lineTo(0, canvas.height);
  ctx.closePath();

  // Apply clip and draw light theme
  ctx.clip();
  ctx.drawImage(lightImg, 0, 0);

  ctx.restore();
}
Enter fullscreen mode Exit fullscreen mode

Angle Calculation

My math isn't great, so this part took some trial and error:

const angle = parseFloat(angleSlider.value);
const angleRad = (angle * Math.PI) / 180;
const diagonal = Math.sqrt(canvas.width ** 2 + canvas.height ** 2);
const baseAngle = Math.atan2(canvas.height, canvas.width);

// Calculate rotated diagonal endpoints
const x1 = canvas.width + diagonal * Math.cos(baseAngle + angleRad);
const y1 = -diagonal * Math.sin(baseAngle + angleRad);
const x2 = -diagonal * Math.cos(baseAngle + angleRad);
const y2 = canvas.height + diagonal * Math.sin(baseAngle + angleRad);
Enter fullscreen mode Exit fullscreen mode

Formula looks complicated, but the logic is: base diagonal + angle offset = new diagonal position.

UX Improvements

Started with basic file upload, then added enhancements:

Click left/right to upload

Detect which side of the diagonal was clicked:

function isPointOnLightSide(x, y) {
  const centerX = canvas.width / 2;
  const centerY = canvas.height / 2;

  const dx = x - centerX;
  const dy = y - centerY;

  // Rotate coordinate system
  const rotatedX = dx * Math.cos(-angleRad) - dy * Math.sin(-angleRad);
  const rotatedY = dx * Math.sin(-angleRad) + dy * Math.cos(-angleRad);

  return rotatedY < rotatedX * baseSlope;
}
Enter fullscreen mode Exit fullscreen mode

Clipboard paste

Added this later because saving screenshots then uploading was annoying:

document.addEventListener('paste', (e) => {
  const items = e.clipboardData?.items;

  for (let i = 0; i < items.length; i++) {
    if (items[i].type.startsWith('image/')) {
      const file = items[i].getAsFile();
      loadImageToSide(file, determineSide());
      break;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Now you can screenshot and Ctrl+V directly. Much faster.

Gotchas

Issue 1: Canvas Size

Initially set canvas size via CSS. Images came out blurry. Turns out:

  • CSS controls display size
  • width/height attributes control actual resolution

Issue 2: Large Image Performance

4K images made pixel-by-pixel calculation slow. Solution: use Canvas's native clip() and let the browser handle it. Much faster.

Issue 3: Mobile Responsiveness

Canvas would overflow on mobile. Added max-width/height CSS:

canvas {
  max-width: 90vw;
  max-height: 70vh;
  width: auto;
  height: auto;
}
Enter fullscreen mode Exit fullscreen mode

Scales display while keeping original resolution for download.

Use Cases

Perfect for:

  • Documentation - showing feature differences
  • Portfolios - displaying design work
  • Blog posts - creating comparison images
  • READMEs - showcasing project themes

What's Next

Current version works for my needs, but could add:

  • Vertical split mode
  • More export formats (JPG, WebP)
  • Batch processing
  • Preset angle templates

Try It Out

Live demo: https://tageecc.github.io/theme-merge/

Source code: https://github.com/tageecc/theme-merge

Took about an hour to build, less than 500 lines of code. Canvas clip() works really well for this.

If you need something similar, feel free to fork and modify. Issues and PRs welcome!


What would you add?

Have ideas for features? Drop a comment below or open an issue on GitHub!


Originally published on my blog. Follow me for more web dev tips and tools.

Tags: #webdev #javascript #canvas #opensource #tools

Top comments (0)