What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use Pillow’s Image.open() to read the source, then call resize((width, height), resample=...) and save the returned image. The size tuple is always (width, height) in pixels:
from PIL import Image
with Image.open("input.jpg") as image:
resized = image.resize((800, 600), Image.Resampling.LANCZOS)
resized.save("output.jpg")
This produces an 800 × 600 image. If that ratio does not match the source, the result will be stretched or squashed; use thumbnail() or an ImageOps method when preserving the original proportions matters.
Install Pillow and verify the import
Pillow is the actively maintained Python imaging library that provides the PIL package. Install it in the environment that will run your script:
python -m pip install Pillow
Then check that Python can import it:
python -c "from PIL import Image; print(Image.__version__)"
If your system uses python3, replace python in these commands with python3.
Recommended Free Tools
#1 Best Overall
Resize to exact pixel dimensions with Image.open()
Image.open(path) creates an image object. The object’s resize() method returns a new image at the requested dimensions; it does not change the original object. Pass a two-item tuple in width-then-height order.
from PIL import Image
source_path = "input.jpg"
output_path = "output.jpg"
target_size = (800, 600) # (width, height)
with Image.open(source_path) as image:
resized = image.resize(target_size, resample=Image.Resampling.LANCZOS)
resized.save(output_path)
print(f"Saved {output_path} at {target_size[0]}x{target_size[1]} pixels")
Image.Resampling.LANCZOS is a quality-oriented choice, especially for photographic downsizing. Pillow documents BICUBIC as the default for typical image modes, but specifying the filter makes your intent explicit.
Read the source dimensions first
from PIL import Image
with Image.open("input.jpg") as image:
print(image.size) # (width, height)
print(image.mode) # for example, RGB or RGBA
resized = image.resize((1200, 800), Image.Resampling.LANCZOS)
resized.save("output.jpg")
Inspecting image.size helps you catch a width-height reversal before writing files.
Preserve aspect ratio instead of forcing a rectangle
Exact resize() dimensions are correct when a fixed canvas is required, but they can distort content if the requested ratio differs from the source. Choose the operation that matches the visual result you need:
| Goal | Method | Behavior |
|---|---|---|
| Exact dimensions, distortion acceptable | image.resize((width, height)) |
Returns a new image at exactly those pixels; proportions can change. |
| Fit within maximum bounds | image.thumbnail((max_width, max_height)) |
Preserves proportions and keeps both dimensions at or below the bounds; mutates the image in place. |
| Fit inside a box without cropping | ImageOps.contain(image, size) |
Preserves proportions and may leave unused space. |
| Fill a box while preserving proportions | ImageOps.cover(image, size) |
Scales enough to cover the box; portions outside the target ratio can extend beyond it. |
| Exact dimensions with a crop | ImageOps.fit(image, size) |
Resizes and crops to the requested dimensions. |
| Exact dimensions with background space | ImageOps.pad(image, size, color=...) |
Resizes proportionally and adds padding to reach the target size. |
Calculate proportional dimensions and use resize()
This pattern chooses a maximum width and computes the matching height. It returns a new image, leaving the opened image available for other work.
Rank #2
from PIL import Image
max_width = 1200
with Image.open("input.jpg") as image:
width, height = image.size
if width > max_width:
new_height = round(height * max_width / width)
resized = image.resize((max_width, new_height), Image.Resampling.LANCZOS)
else:
resized = image.copy()
resized.save("scaled.jpg")
Use thumbnail() for a maximum bounding box
thumbnail() keeps the aspect ratio and mutates the image object in place. Copy the image first if you still need the original dimensions or pixels.
from PIL import Image
with Image.open("input.jpg") as image:
working = image.copy()
working.thumbnail((1200, 800), resample=Image.Resampling.LANCZOS)
working.save("thumbnail.jpg")
The output can be smaller than both limits because the source ratio is retained. It will not be enlarged when it already fits within the specified bounds.
Choose an ImageOps operation for a fixed box
Import ImageOps when the destination has a strict width and height but you need controlled cropping or padding.
from PIL import Image, ImageOps
with Image.open("input.jpg") as image:
contained = ImageOps.contain(image, (800, 600))
contained.save("contained.jpg")
covered = ImageOps.cover(image, (800, 600))
covered.save("covered.jpg")
cropped = ImageOps.fit(image, (800, 600))
cropped.save("cropped.jpg")
padded = ImageOps.pad(image, (800, 600), color=(245, 245, 245))
padded.save("padded.jpg")
- Choose
containwhen every source pixel must remain visible. - Choose
coverwhen filling the entire region matters and overhang or cropping is acceptable. - Choose
fitwhen the output must be exact and a crop is the intended composition. - Choose
padwhen the whole image must remain visible and a solid background is acceptable.
Correct EXIF orientation before resizing
JPEG and TIFF files can contain EXIF instructions that rotate or mirror the displayed image without changing the stored pixel grid. If the resized pixels themselves must reflect that orientation, transpose first:
from PIL import Image, ImageOps
with Image.open("camera-photo.jpg") as image:
oriented = ImageOps.exif_transpose(image)
resized = oriented.resize((1600, 1200), Image.Resampling.LANCZOS)
resized.save("camera-photo-resized.jpg")
Applying the orientation step before choosing dimensions prevents a portrait image from being treated as landscape merely because of its stored pixel order.
Resampling filters: quality, speed and image type
The filter controls how source pixels contribute to the new grid:
NEARESTselects the nearest source pixel. It avoids blending and is appropriate for pixel art or categorical masks where intermediate colors would be wrong.BILINEARuses linear interpolation and is generally faster than higher-quality filters.BICUBICuses cubic interpolation. Pillow documents it as the default for typical image modes.LANCZOSis a high-quality truncated-sinc filter and a practical general choice for photographic downsizing, with lower speed than the faster filters.
Pillow’s comparison is qualitative rather than a universal timing or image-quality benchmark. Test the filter on your own images when throughput is more important than maximum detail.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallfrom PIL import Image
with Image.open("photo.jpg") as image:
fast = image.resize((800, 600), Image.Resampling.BILINEAR)
high_quality = image.resize((800, 600), Image.Resampling.LANCZOS)
fast.save("photo-fast.jpg")
high_quality.save("photo-lanczos.jpg")
Palette and bilevel images
For mode 1 (bilevel) and palette mode P, Pillow uses NEAREST regardless of the requested resampling filter. If smooth interpolation is required, convert deliberately to a suitable full-color mode before resizing, then verify that the conversion is appropriate for your asset.
from PIL import Image
with Image.open("indexed.png") as image:
color_image = image.convert("RGB")
resized = color_image.resize((800, 600), Image.Resampling.LANCZOS)
resized.save("indexed-resized.jpg")
Save the resized result correctly
Save the object returned by resize(), contain(), cover(), fit() or pad(). Saving the original object is a common reason an apparently successful script produces an unchanged file.
from PIL import Image
with Image.open("input.png") as image:
output = image.resize((1024, 768), Image.Resampling.LANCZOS)
output.save("output.png")
Use an output extension and format that match your intended file. Keep a source with transparency in a mode that supports it, such as RGBA, when the destination needs an alpha channel.
Resize many files safely
For a directory, create a separate output directory and close each source promptly with a context manager:
Free tools Windows power users keep installed
One-click scans. No signup required.
from pathlib import Path
from PIL import Image, ImageOps
source_dir = Path("images")
output_dir = Path("resized")
output_dir.mkdir(exist_ok=True)
for source_path in source_dir.iterdir():
if source_path.suffix.lower() not in {".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp"}:
continue
destination = output_dir / source_path.name
try:
with Image.open(source_path) as image:
oriented = ImageOps.exif_transpose(image)
working = oriented.copy()
working.thumbnail((1600, 1600), Image.Resampling.LANCZOS)
working.save(destination)
except (OSError, ValueError) as error:
print(f"Skipped {source_path}: {error}")
This keeps the original files untouched, limits the largest dimension, and reports files Pillow cannot decode or save.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting common failures
“cannot identify image file”
The path may point to a non-image, a damaged file, or a download that is actually an HTML error page. Confirm the path, file type and that the file was completely written before calling Image.open().
The output is stretched
Your target ratio differs from the source. Use proportional dimension calculation, thumbnail(), contain(), cover(), fit() or pad() according to the desired crop or border behavior.
The image is rotated after processing
Apply ImageOps.exif_transpose() before resizing. This handles the orientation instruction stored in EXIF metadata.
Best Value
The filter appears to have no effect
Check image.mode. Modes 1 and P force nearest-neighbor behavior. Convert deliberately to RGB or RGBA when interpolation between colors is needed.
The script saves the original size
resize() returns a new image, while thumbnail() changes the object in place. Assign the resize() result and save that variable; save the same object after calling thumbnail().
Memory usage grows during batch work
Keep each Image.open() inside a with block, save the result, and let temporary objects go out of scope before processing the next file. Avoid retaining a list of full-resolution images unless you specifically need it.
Performance, reliability and cost considerations
- Downscaling to a smaller target generally requires less output storage, but the source still has to be decoded first.
- LANCZOS favors quality over speed; BILINEAR or BICUBIC may be preferable for high-volume jobs after you compare the result on representative images.
- Use deterministic output dimensions and names so a rerun can replace or skip known outputs safely.
- Keep originals until the resized files have been inspected. A resize operation does not provide a reversible copy of detail discarded during downsampling.
- Pillow itself is local software; this workflow has no per-image API charge. Your costs are the machine time and storage used by your process.
Or skip the browser setup
If the image you need is a live web page rather than a local file, ScreenshotNeo can return a screenshot through one request. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →For a WebP screenshot of Stripe:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
See the complete parameter list and response behavior in the ScreenshotNeo documentation. The service also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Sign up for the free 1,000-shot plan.
Frequently Asked Questions
Does Image.open() resize an image by itself?
No. It opens the image and returns an image object. Call resize(), thumbnail() or an ImageOps function, then save the resulting object.
Why are my width and height reversed?
Pillow uses (width, height) everywhere in these APIs. For a portrait output, put the smaller width first and the larger height second.
Which Pillow filter should I use for pixel art?
Use Image.Resampling.NEAREST so neighboring colors are not blended. This is especially important for discrete masks and indexed artwork.
Can I keep the original image after calling thumbnail()?
Yes. Make a copy first, as in working = image.copy(), because thumbnail() mutates the object it receives.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

