Claude Skills Guide

Building a Chrome extension that adds watermarks to images opens up practical possibilities for content creators, photographers, and developers working with visual assets. This guide walks you through the technical implementation, from understanding the core APIs to handling real-world use cases like batch processing and customizable watermark positioning.

Understanding the Core Challenge

Browser-based image manipulation requires working with the Canvas API, which provides powerful methods for drawing images and text. The key challenge lies in efficiently loading images from various sources—webpages, local files, or drag-and-drop inputs—and applying watermarks without degrading image quality.

Chrome extensions can access images through multiple pathways: the active tab’s DOM, user-uploaded files via the File System Access API, or clipboard data. Each pathway presents unique considerations for error handling and performance optimization.

Project Structure

A functional image watermarking extension requires these core components:

{
  "manifest_version": 3,
  "name": "Image Watermark Pro",
  "version": "1.0",
  "permissions": ["activeTab", "scripting", "downloads"],
  "action": {
    "default_popup": "popup.html"
  }
}

The manifest declares the extension’s capabilities. Version 3 requires explicit permission declarations, improving security posture compared to earlier versions.

Implementing the Watermark Engine

The core functionality lives in a background script or popup that handles image processing. Here’s a practical implementation using canvas manipulation:

class ImageWatermarker {
  constructor() {
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
  }

  async loadImage(source) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = source;
    });
  }

  applyWatermark(imageSource, options) {
    const { text, position, opacity, fontSize } = options;
    const img = await this.loadImage(imageSource);
    
    this.canvas.width = img.width;
    this.canvas.height = img.height;
    
    this.ctx.drawImage(img, 0, 0);
    this.ctx.globalAlpha = opacity;
    this.ctx.font = `${fontSize}px Arial`;
    this.ctx.fillStyle = 'white';
    
    const metrics = this.ctx.measureText(text);
    const pos = this.calculatePosition(position, img, metrics);
    
    // Add shadow for better visibility
    this.ctx.shadowColor = 'rgba(0,0,0,0.5)';
    this.ctx.shadowBlur = 4;
    this.ctx.fillText(text, pos.x, pos.y);
    
    return this.canvas.toDataURL('image/png');
  }

  calculatePosition(position, img, metrics) {
    const positions = {
      'bottom-right': { x: img.width - metrics.width - 20, y: img.height - 20 },
      'bottom-left': { x: 20, y: img.height - 20 },
      'top-right': { x: img.width - metrics.width - 20, y: metrics.actualBoundingBoxAscent + 20 },
      'top-left': { x: 20, y: metrics.actualBoundingBoxAscent + 20 },
      'center': { x: (img.width - metrics.width) / 2, y: img.height / 2 }
    };
    return positions[position] || positions['bottom-right'];
  }
}

This class demonstrates the fundamental approach: load an image onto a canvas, configure text rendering parameters, calculate positioning based on predefined locations, and export the result as a data URL.

Handling Tab Images

A common use case involves watermarking images directly from the active webpage. The Chrome Scripting API enables this:

async function watermarkTabImages(tabId, watermarkText) {
  // Inject content script to find all images
  const results = await chrome.scripting.executeScript({
    target: { tabId },
    func: () => {
      const images = document.querySelectorAll('img');
      return Array.from(images).map(img => ({
        src: img.src,
        width: img.naturalWidth,
        height: img.naturalHeight
      }));
    }
  });

  const watermarker = new ImageWatermarker();
  const processedImages = [];

  for (const imgData of results[0].result) {
    if (imgData.width > 100 && imgData.height > 100) { // Filter small icons
      try {
        const watermarked = await watermarker.applyWatermark(imgData.src, {
          text: watermarkText,
          position: 'bottom-right',
          opacity: 0.7,
          fontSize: Math.max(16, imgData.width / 20)
        });
        processedImages.push({ original: imgData.src, watermarked });
      } catch (e) {
        console.error('Failed to process image:', e);
      }
    }
  }

  return processedImages;
}

This script scans the active tab for images above a certain threshold, applies watermarks, and returns both original and processed versions. The font size scales proportionally with image dimensions, ensuring readability across different image sizes.

Batch Processing Implementation

For users who need to watermark multiple images simultaneously, implement batch processing with progress tracking:

async function batchWatermark(files, watermarkConfig) {
  const results = [];
  const watermarker = new ImageWatermarker();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const reader = new FileReader();

    const processed = await new Promise((resolve) => {
      reader.onload = async (e) => {
        const watermarked = await watermarker.applyWatermark(e.target.result, watermarkConfig);
        resolve({ name: file.name, data: watermarked, index: i });
      };
      reader.readAsDataURL(file);
    });

    results.push(processed);
    
    // Report progress to extension popup
    chrome.runtime.sendMessage({
      type: 'progress',
      current: i + 1,
      total: files.length
    });
  }

  return results;
}

The progress reporting enables UI updates, keeping users informed during potentially lengthy batch operations.

Building the User Interface

The popup interface should provide intuitive controls for watermark customization:

<input type="text" id="watermarkText" placeholder="Enter watermark text">
<select id="position">
  <option value="bottom-right">Bottom Right</option>
  <option value="bottom-left">Bottom Left</option>
  <option value="top-right">Top Right</option>
  <option value="top-left">Top Left</option>
  <option value="center">Center</option>
</select>
<input type="range" id="opacity" min="0.1" max="1" step="0.1" value="0.7">
<input type="number" id="fontSize" value="24" min="8" max="200">
<button id="processBtn">Apply Watermark</button>

Connect these elements to the watermarking logic through event listeners in your popup script.

Performance Considerations

Image processing in the browser can be memory-intensive. Implement these optimizations:

  1. Process sequentially: Rather than parallel processing, handle images one at a time to prevent memory spikes
  2. Use appropriate formats: When possible, work with the original format and only convert on export
  3. Revoke object URLs: If using URL.createObjectURL, always revoke when done to prevent memory leaks
  4. Web Workers: For intensive processing, move watermarking logic to a Web Worker to keep the UI responsive

Extension Distribution

When publishing to the Chrome Web Store, ensure your extension handles the review process requirements:

Built by theluckystrike — More at zovo.one