How pictoolkit actually works.

Every tool, every algorithm, fully documented. No black boxes, no magic — just the actual technical approaches we use, so you can verify, contribute, or replicate.

Our overall approach

pictoolkit runs entirely in your browser. Every image you process is handled by JavaScript executing locally on your device — nothing uploads, nothing transmits, no server-side processing. This is verifiable: open your browser's Network tab while using any tool and you'll see no outbound file traffic.

The technical stack is intentionally boring: standard Web APIs (Canvas, OffscreenCanvas, WebAssembly, Web Workers for large jobs), plus a few well-maintained open-source libraries for format-specific work. No proprietary algorithms, no custom binaries, nothing you couldn't reimplement.

Image compression

Our compression tool uses the browser's native Canvas.toBlob() API for JPG and WebP output. Here's the exact pipeline:

  1. Decode the input file into an ImageBitmap using createImageBitmap(). This is hardware-accelerated in all modern browsers.
  2. Draw the bitmap to an off-screen OffscreenCanvas sized to match the input.
  3. Encode via canvas.toBlob(type, quality) where quality is your slider value (0.0–1.0).
  4. Compare the output blob size against the input. If the new file is bigger than the original (rare, but happens with already-optimized JPGs), we return the original to avoid wasted compression.

The actual JPEG encoding (DCT, quantization, Huffman coding) is done by the browser's native libjpeg-turbo or equivalent — the same library used in Chrome, Firefox, and Safari for image rendering. WebP encoding uses libwebp. Both are highly optimized C implementations exposed to JavaScript via the Canvas API.

For PNG output, we use lossless encoding (the only mode PNG supports). The browser handles DEFLATE compression internally.

Format conversion

Format conversion uses the same decode-encode pipeline but with different input and output handlers:

  • JPG, PNG, WebP, BMP, GIF, AVIF inputs are decoded by createImageBitmap(). Modern browsers handle all of these natively.
  • TIFF inputs are decoded using the utif library (a pure-JS TIFF parser) since browsers don't support TIFF natively.
  • HEIC inputs are decoded using heic2any, a JavaScript wrapper around libheif compiled to WebAssembly.
  • SVG inputs are rasterized by drawing them into a Canvas at the target resolution.

Output encoding goes through Canvas.toBlob() for JPG, PNG, WebP, and AVIF. AVIF output works in browsers that support AVIF encoding (Chrome 85+, Firefox 93+, Safari 16.4+).

Image resizing

Resizing uses bilinear interpolation by default, the same algorithm browsers use for img tag scaling. The full pipeline:

  1. Decode the input as an ImageBitmap.
  2. Calculate target dimensions based on user input (pixel size, percent, or preset). For fit mode, we maintain aspect ratio. For fill mode, we crop the longer dimension. For stretch mode, we distort.
  3. Create an OffscreenCanvas at the target size.
  4. Call ctx.drawImage(bitmap, 0, 0, targetW, targetH). The browser handles the actual interpolation.
  5. Encode the result via canvas.toBlob().

For large downscales (more than 2× reduction), we apply progressive downscaling — resizing in halves until we're close to the target, then doing the final step. This produces noticeably better quality than a single large downscale.

HEIC decoding

HEIC is the format iPhones use by default. It's based on HEIF (High Efficiency Image Format) and uses HEVC (H.265) for the actual compression. Browsers don't support HEIC natively (except Safari in limited contexts), so we use heic2any — a JavaScript port of libheif compiled to WebAssembly.

The library is loaded on demand from a CDN when you visit any HEIC-related tool. It's about 2 MB compressed and cached aggressively by the browser, so subsequent uses are instant. Decoding a 12 MP HEIC photo takes 500ms-2s depending on device.

Batch processing

When you drop multiple files, we process them sequentially by default to avoid memory exhaustion on lower-end devices. Each image goes through the same pipeline as a single-file conversion. Progress is shown per file.

The ZIP download is built using JSZip, a well-known JavaScript ZIP library. The ZIP is constructed entirely in memory and offered as a Blob download — no server involvement.

EXIF metadata handling

By default, our conversion and resize tools strip EXIF metadata from outputs. This is a side effect of how Canvas-based re-encoding works — the Canvas API doesn't preserve metadata.

Our forthcoming EXIF viewer (currently in development) will parse metadata using the exifr library, displaying GPS coordinates, camera settings, timestamps, and software signatures. It will also allow selective stripping (e.g., remove GPS but keep camera settings).

Quality settings

The "quality" slider in our compression tool maps directly to the second argument of canvas.toBlob(type, quality):

  • JPG quality: 0.0–1.0 maps to JPEG quality 0–100 in the standard sense. We default to 0.85.
  • WebP quality: Similar 0.0–1.0 range, mapping to libwebp's quality parameter. We default to 0.82.
  • AVIF quality: 0.0–1.0 range. AVIF's perceptual quality curve is non-linear vs. JPG, so we expose a separate slider for AVIF.
  • PNG quality: No quality parameter — PNG is lossless. We use the browser's default DEFLATE compression level.

Coming-soon algorithms

Background removal

We're evaluating RMBG-1.4 and BiRefNet — both open-source neural networks trained on salient object detection. The plan is to run inference via ONNX Runtime Web with WebGPU acceleration where available, WebGL fallback otherwise. Model size is the main constraint — we're targeting under 50 MB downloaded once and cached.

Image upscaling

For AI upscaling we're evaluating Real-ESRGAN and lighter variants. The trade-off is the same — quality vs. model size vs. inference speed.

OCR

Image-to-text will use Tesseract.js, a JavaScript port of the well-established Tesseract OCR engine. Tesseract handles 100+ languages with model files loaded on demand.

Face blur

Face detection will use the browser's built-in FaceDetector API where available (Chrome on Android), with face-api.js as fallback. Detected face regions are then blurred via Canvas filter operations.

Why everything is open and documented

Two reasons. First, transparency builds trust — you don't have to take our word that "processing happens in your browser" when you can read the actual code and verify with browser developer tools. Second, none of this is proprietary IP. We use standard browser APIs and open-source libraries. There's no secret sauce to hide.

If you spot a bug, an inefficiency, or a place where we've made a poor algorithmic choice, tell us. We'd rather be corrected than wrong.

Performance and limits

Browser-based image processing has real limits. We've found:

  • Memory: Most browsers cap a single Canvas at 16384×16384 or thereabouts. Practical limit is 8000×8000 before memory pressure on average devices.
  • File size: We don't enforce an upload limit because there's no upload. Memory is the actual constraint. A 100 MB image is usually fine on a desktop; 200 MB may crash a tab.
  • Speed: For typical photos (2-10 MB), compression/conversion is sub-second. HEIC decoding adds 1-2s due to the WebAssembly initialization. Batch ZIP creation scales linearly.
  • Mobile: All tools work in mobile Safari and Chrome. Processing is slower than on desktop (especially WebAssembly), but functional.

Have a technical question we didn't address? Email us — we publish answers to good questions.