Retina screenshots

A retina, or high-DPI, screenshot draws every CSS pixel with more than one device pixel. Text edges, icons and thin lines stay sharp on phones and modern laptops, where a 1x image looks soft. You get one with a single parameter, viewport.scale. This guide shows what each scale costs in file size, so you can choose on numbers. It is part of the capture guides.

The minimal request

Set RS_API_KEY to your key first (free sign-up).

GET:

curl -G "https://api.renderscreenshot.com/v1/screenshot" \
  -H "Authorization: Bearer $RS_API_KEY" \
  --data-urlencode "url=https://example.com" \
  -d width=1280 -d height=800 -d scale=2 \
  -o [email protected]

POST:

curl -X POST https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "viewport": { "width": 1280, "height": 800, "scale": 2 },
    "output": { "format": "webp", "quality": 80 }
  }' \
  -o [email protected]

Both return an image of 2560 x 1600 pixels showing the same 1280 x 800 layout.

How scale works

viewport.scale is the device pixel ratio (DPR), the same value as window.devicePixelRatio in a browser.

Scale Layout width (CSS px) Image for a 1280 x 800 viewport Pixels per CSS pixel
1 1280 1280 x 800 1
2 1280 2560 x 1600 4
3 1280 3840 x 2400 9

Three facts follow from this:

  • The layout does not change. Media queries on width see the same value. Only resolution media queries and srcset choices see the higher density.
  • Pixel count grows with the square of the scale. Scale 2 has 4 times the pixels, scale 3 has 9 times.
  • The page may load larger images. Sites that use srcset or image-set() serve their 2x assets when the scale is 2, which can add download time.

Measured: bytes and encode time by scale and format

We captured one test page (a gradient header, text, a code block, an SVG chart and three cards) at each scale and format. The capture used the same Puppeteer calls that a hosted renderer makes: setViewport with deviceScaleFactor, then screenshot with the format and quality.

Scale Format Image (px) File size Encode time
1 PNG 1280 x 800 186 KB 84 ms
1 JPEG q80 1280 x 800 73 KB 37 ms
1 WebP q80 1280 x 800 43 KB 85 ms
2 PNG 2560 x 1600 498 KB 202 ms
2 JPEG q80 2560 x 1600 191 KB 51 ms
2 WebP q80 2560 x 1600 104 KB 205 ms
3 PNG 3840 x 2400 930 KB 372 ms
3 JPEG q80 3840 x 2400 344 KB 78 ms
3 WebP q80 3840 x 2400 167 KB 433 ms

Method: Node 22.18, Puppeteer 24.10.0, Chrome 137, macOS, a static local test page loaded with waitUntil: 'load', median of 3 runs per cell, measured on 2026-10-15. File size is the byte length of the returned buffer (1 KB = 1,000 bytes). Encode time is the screenshot() call only, not navigation. Your pages compress differently (photos favour JPEG and WebP even more, flat UI favours PNG), so treat the ratios as the result, not the absolute numbers:

  • Going from scale 1 to scale 2 multiplied file size by about 2.4 to 2.7, not by 4. Flat areas compress well.
  • Scale 3 cost about 5 times the bytes of scale 1 in PNG and about 4 times in WebP.
  • JPEG was the fastest to encode at every scale. WebP was the smallest file and about as slow to encode as PNG.

When to use each scale

Destination Scale Format Why
Social cards (og_card, twitter_card) 1 PNG or JPEG Platforms resize and recompress; the 1200 x 630 size is what they expect
Thumbnails in a dashboard or link preview 1, then output.resize WebP or JPEG Small files, many per page
Images in docs, blogs or app stores, viewed on phones and laptops 2 WebP or PNG Sharp text on high-density screens
Element captures of charts, code or UI for print or zoom 2 or 3 PNG Crisp lines and text when enlarged
Input for a vision model 1 PNG or JPEG Models usually downscale large images anyway
Full-page captures of long pages 1 JPEG or WebP Height times scale grows fast

Device presets already include a scale (3 for iphone_14, for example). If you only need the mobile layout, use a custom viewport with scale: 1, as shown in mobile screenshots.

Serve 1x and 2x with srcset

To serve sharp images without making every visitor download the 2x file, render both and let the browser choose:

<img
  src="/shots/[email protected]"
  srcset="/shots/[email protected] 1x, /shots/[email protected] 2x"
  width="1280" height="800"
  alt="Pricing page with three plans side by side">

Set width and height to the CSS size (the 1x size). The browser reserves the space and avoids layout shift, whichever file it picks. Two requests at scale 1 and 2 are two renders and use two credits. If credits matter more than download size, render only at 2x and let 1x screens downscale.

SDK examples

Node.js

import { writeFile } from 'node:fs/promises';
import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);

for (const scale of [1, 2]) {
  const image = await client.take(
    TakeOptions.url('https://example.com').width(1280).height(800).scale(scale).format('webp').quality(80)
  );
  await writeFile(`page@${scale}x.webp`, image);
}

Python

import os
from renderscreenshot import Client, TakeOptions

client = Client(os.environ['RS_API_KEY'])

for scale in (1, 2):
    image = client.take(
        TakeOptions.url('https://example.com')
        .width(1280).height(800).scale(scale).format('webp').quality(80)
    )
    with open(f'page@{scale}x.webp', 'wb') as f:
        f.write(image)

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'))

[1, 2].each do |scale|
  image = client.take(
    RenderScreenshot::TakeOptions
      .url('https://example.com')
      .width(1280).height(800).scale(scale).format('webp').quality(80)
  )
  File.binwrite("page@#{scale}x.webp", image)
end

PHP

use RenderScreenshot\Client;
use RenderScreenshot\TakeOptions;

$client = new Client(getenv('RS_API_KEY'));

foreach ([1, 2] as $scale) {
    $image = $client->take(
        TakeOptions::url('https://example.com')
            ->width(1280)->height(800)->scale($scale)->format('webp')->quality(80)
    );
    file_put_contents("page@{$scale}x.webp", $image);
}

Java

Client client = new Client(System.getenv("RS_API_KEY"));

for (double scale : new double[] {1.0, 2.0}) {
    byte[] image = client.take(
        TakeOptions.url("https://example.com")
            .width(1280).height(800).scale(scale).format("webp").quality(80)
    );
    Files.write(Path.of("page@" + (int) scale + "x.webp"), image);
}

Go

for _, scale := range []float64{1, 2} {
    image, err := client.Take(ctx,
        rs.URL("https://example.com").
            Width(1280).Height(800).Scale(scale).
            Format(rs.FormatWebP).Quality(80),
    )
    if err != nil {
        log.Fatal(err)
    }
    os.WriteFile(fmt.Sprintf("page@%.0fx.webp", scale), image, 0o644)
}

Troubleshooting

Symptom Likely cause Fix
Image looks blurry on a phone or laptop Rendered at scale 1 and displayed larger Render at scale: 2 and set the CSS size to half the pixel size
Image is twice as large as expected on the page Displayed at its pixel size Set width and height attributes to the 1x size
Layout differs from your laptop browser Your browser width differs from viewport.width, not the scale Match viewport.width; scale does not change layout
validation_error for scale Value outside 1 to 3 Use 1, 2 or 3 (fractions such as 1.5 are allowed within the range)
Files too large for email or chat Scale 2 or 3 with PNG Use JPEG or WebP, or output.resize
Very tall full-page image Height multiplied by scale Use scale 1 for full pages, or cap capture.scroll.max_height
Photos on the page look the same at 2x The site serves the same image file to every density Expected; scale sharpens what the browser draws, not the source photo

Related in this topic

Was this page helpful?