Stippled AI graphics turn to gray mush when resized: a downscale test

A dotted AI graphic can lose most of its contrast when downscaled. A Pillow test of nearest, bilinear and Lanczos on a stipple, and what to request instead.

5 min readSume
All posts

If you ask an image model for stippled, dotted or halftone minimalism at 4K and then shrink it for the web, resize with care, because a fine dot pattern loses most of its contrast in a normal downscale. In my test below, a one-pixel stipple at 2048 pixels dropped from a contrast standard deviation of 69.2 to 11.4 with bilinear and 14.6 with Lanczos, so the art turned a flat light gray.

Higher-resolution output is part of the FLUX 3 Image pitch (up to 4K, per BFL, read 2026-10-03), and the price list charges more for it. A big render is only useful if it survives the resize to the size you publish. This test tells you whether yours does.

The test

The script builds a synthetic white page with black one-pixel dots at 8 percent density, a stand-in for a stippled image, and shrinks it from 2048 to 512 pixels with three filters. It prints the standard deviation of the pixel values, which measures how much contrast is left, and how many distinct gray levels the result has. It is a synthetic test of the mechanism, not a measurement of any model's output.

import numpy as np
from PIL import Image

def stipple(size, density, seed=1):
    """White page, black one-pixel dots at the given density."""
    rng = np.random.default_rng(seed)
    a = np.full((size, size), 255, dtype=np.uint8)
    a[rng.random((size, size)) < density] = 0
    return Image.fromarray(a)

def std(im):
    return float(np.asarray(im, dtype=float).std())

src = stipple(2048, 0.08)
print("source", src.size, f"contrast std {std(src):.1f}")
for name, f in [("nearest", Image.NEAREST), ("bilinear", Image.BILINEAR), ("lanczos", Image.LANCZOS)]:
    small = src.resize((512, 512), f)
    print(f"{name:9}", f"std {std(small):.1f}", "unique levels", len(set(np.asarray(small).ravel().tolist())))

What it printed

Nearest-neighbor kept the contrast exactly (69.2) with two levels, since it just picks pixels, but it also picks a random-looking subset of dots and can alias into visible patterns on a real image. The two smoothing filters averaged each four-by-four block of dots toward the page color and flattened the image.

synthetic stipple, 2048 to 512 pixels, my run 2026-10-03
FilterContrast (std)What you see
Source 2048 px69.2Crisp dots
Nearest69.2Contrast kept, dots chosen by position, aliasing risk
Bilinear11.4Light gray haze
Lanczos14.6Light gray haze, slightly sharper

What to do about it

Ask for the output size you will publish, not a bigger one. Stipple density is set in generated pixels, so a 1024 pixel render with dots two pixels wide will hold up at 1024 and look right. If you must downscale, shrink by exact integer factors after thickening the dots, or render at the final size and upscale only the final export with a tool built for it; Sume's MCP surface has image_upscale_create for upscaling.

Always view the downscaled result at 100 percent before delivery. Thumbnails hide the problem, since a viewer scaling the image down hides the same loss you are checking for.

Why it happens

A smoothing filter computes each output pixel as a weighted average of the source pixels around it. In a stipple, most of those pixels are white page and a few are black dots, so the average is a pale gray whose darkness equals the dot density. That is accurate as a mean, and it is exactly what the viewer reads as haze. Nearest-neighbor avoids averaging but throws away three quarters of the dots, so patterns can fold into a moire.

This is a property of the pattern, not of the model that drew it. Any image with a dot, hatch or grain texture at the scale of one or two pixels will behave this way, which is why print designers pick the screen size before they pick the file size.

Which Sume row to try

Vector output avoids the issue altogether. Sume's docs list Recraft V4 as WebP and text-to-image only; see the Recraft vector post for what is available beyond that. For raster work, compare file sizes and quality at the final dimensions as in the format measurement post, and check legal sizes with the size validator. The Sume Image API docs are at docs.sume.com/models/images.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume