DEV Community

Amr elskaan
Amr elskaan

Posted on AI-assisted

Why your screenshot export looks different from the preview

A screenshot template can look right in the editor and still produce the wrong download. The phone frame is in place. The export finishes. But the uploaded app screen is missing, the headline wraps differently, or a background has changed.

It is tempting to start adjusting each template. Before doing that, find the first point where the output diverges.

Compare the whole path, not just the editor

Keep four artifacts from the same test run: the reference design, the editor preview, the downloaded image, and the image shown after upload.

These answer different questions:

Comparison What it helps isolate
Reference against preview Template import, asset selection, layout and font differences
Preview against download Export timing, drawing order, dimensions and renderer differences
Download against uploaded image The uploaded file, server-side processing and delivery

For the first comparison, use the template's original sample content. Replacing that content with a different app screenshot changes the expected pixels. Test screenshot replacement separately, with an explicit expected image and crop.

If twenty templates fail in a similar way, a shared loading or export step is a useful place to investigate. It is a hypothesis to test, not proof that every template has the same bug.

Check the exact asset before changing the layout

A filename is not a guarantee of identity.

Imagine a template that refers to screen-01.png, while an export helper substitutes screen-01.jpg. The second file might be a converted copy. It might also be an old screenshot, a thumbnail, or an unrelated asset with the same basename.

The problem is the unverified substitution, not the choice of JPEG itself.

Keep explicit asset references in the template data. When debugging, compare the selected asset, the resolved URL and the decoded dimensions in both the preview and export paths. If possible, compare content hashes of the actual files as well. Do not put private screenshots or signed URL tokens in public logs.

A missing required asset should produce a visible error. Silently replacing it with something that loads can turn a clear failure into a convincing but incorrect image.

Wait for the inputs the renderer actually uses

A fixed delay does not establish that every image is ready. Wait for each required asset, and propagate loading failures.

For ordinary browser images, HTMLImageElement.decode() provides a promise for image decoding. Here is a small loader for public assets served with appropriate cross-origin permissions:

async function loadExactImage(url) {
  const image = new Image();
  image.crossOrigin = 'anonymous';
  image.src = url;

  await image.decode();

  if (!image.naturalWidth || !image.naturalHeight) {
    throw new Error('Required image has no usable dimensions');
  }

  return image;
}

const images = await Promise.all(
  orderedImageLayers.map(layer => loadExactImage(layer.url))
);

// Draw using the template's order, not download completion order.
for (let index = 0; index < orderedImageLayers.length; index++) {
  drawLayer(orderedImageLayers[index], images[index]);
}
Enter fullscreen mode Exit fullscreen mode

Here, orderedImageLayers and drawLayer belong to your renderer; this is an integration pattern, not a complete export implementation. Resolve asset URLs before loading and keep the full scene's ordering intact when images are mixed with text and shapes. See MDN's decode() reference.

Setting crossOrigin does not grant permission by itself. The image server must allow the request. Drawing a cross-origin image without the required approval can taint a canvas and prevent pixel reads or export. Fix the asset-serving configuration instead of disabling browser protections. MDN explains the canvas CORS requirements.

Fonts need their own check. Ensure the template's required font families and weights have been requested before drawing text. document.fonts.ready can wait for document font loading and layout, but it does not prove that an undeclared font exists or that your renderer selected the intended face. FontFaceSet.ready reference.

Make export failure explicit

Treat an export as a result you can validate, not just a button click that finished.

For an HTML canvas, a small promise wrapper keeps an empty result from being treated as a successful file:

function exportPng(canvas) {
  return new Promise((resolve, reject) => {
    canvas.toBlob(blob => {
      if (!blob) {
        reject(new Error('PNG export returned no image'));
        return;
      }
      resolve(blob);
    }, 'image/png');
  });
}
Enter fullscreen mode Exit fullscreen mode

The callback can receive null when encoding fails; a security error thrown by toBlob() also rejects this promise. This wrapper does not wait for your renderer to finish: call it only after the scene has been drawn. HTMLCanvasElement.toBlob() reference.

Then inspect the encoded image's dimensions and contents. A valid PNG can still contain the wrong screenshot.

Look at the worst region, not only the average score

A large plain background can dominate an image comparison while the small phone-screen region is completely wrong.

Compare the whole image, but also inspect the screenshot slot, headline and device frame separately. Keep the reference, output and difference image together so a reviewer can see what changed.

Use a controlled rendering environment for regression comparisons. Small text-edge differences may come from rasterization; a missing screen, changed line break or incorrect crop deserves a different diagnosis. Avoid reducing those cases to one unexplained similarity percentage.

For a template catalog, report coverage as well as quality. Checking every exported screen means something different from checking only the first screen of every template. Keep the weakest cases visible after fixing a shared issue.

A useful regression checklist

Before calling an export issue fixed, check that:

  • The renderer uses the intended screenshot asset without an unverified fallback.
  • Required assets finish loading, and missing ones stop the export visibly.
  • Layer order, crop, fonts and output dimensions match the expected design.
  • The downloaded file is the one sent through the upload path.
  • The uploaded result is checked separately from the local download.
  • The previously failing cases pass again, including with an empty cache.

The most useful debugging question is: at which step did the result first become wrong? Answering that usually gives you a smaller, more testable fix than adjusting the final image by eye.


AI disclosure: This article was generated with AI. Its examples are illustrative and are not claims about a product's measured results.

Top comments (0)