Recommended Free Tools
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
The right Python call depends on the object you are saving: use Image.save() for a Pillow image, fig.savefig() for a Matplotlib figure, and cv2.imwrite() for an OpenCV array. The examples below write PNG files, choose other formats explicitly, and show how to keep the result in memory instead of creating a disk file.
Choose the save function that matches your image object
Python image data is not one universal type. A Pillow Image, a Matplotlib figure, and an OpenCV array each have a different writer. Select the API before choosing a filename or format.
| Object you have | Function | Destination | Format selection |
|---|---|---|---|
| Pillow image | image.save() |
Path or binary file-like object | Usually inferred from the extension; pass format= when it cannot be inferred |
| Matplotlib chart or figure | fig.savefig() or plt.savefig() |
Path or file-like object | Selected by the output name or an explicit save option |
| OpenCV image array | cv2.imwrite() |
Path | Selected from the filename extension; optional format parameters can be supplied |
Save an existing or processed image with Pillow
Pillow’s Image.save() is the general-purpose choice for an image you opened, resized, cropped, or otherwise processed with Pillow. Give it a filename whose extension matches the format you want.
Save as PNG
from PIL import Image
with Image.open('input.jpg') as image:
image.save('output.png')
Here Pillow opens input.jpg and writes a PNG to output.png. The extension normally tells Pillow which writer to use. The with block closes the input image when the operation finishes.
#1 Best Overall
- 16MP Sensor: Captures detailed photos with a CMOS sensor for everyday shooting
- Optical Zoom: 4x optical zoom with a 27mm wide angle lens for flexible framing indoors or outdoors
- Full HD Video: Records 1080p video for travel clips, family moments, or simple vlogging
- Memory Support: Works with Class 10 SD, SDHC, or SDXC cards up to 512GB
- LCD Screen and Battery: 2.7in LCD screen with 2 AA alkaline batteries for convenient on-the-go use
Choose a format explicitly
If the filename has a nonstandard extension, specify the writer with format=. This prevents the extension from being mistaken for a format name.
from PIL import Image
with Image.open('input.jpg') as image:
image.save('output.data', format='PNG')
You can use the same pattern with another supported Pillow format by changing both the intended format and, when appropriate, the extension. Keep the extension and the actual format aligned so other programs can identify the file reliably.
Save an image already held in a variable
If earlier processing produced a Pillow image object, save it directly; there is no need to reopen it.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minutefrom PIL import Image, ImageFilter
with Image.open('input.jpg') as source:
softened = source.filter(ImageFilter.BLUR)
softened.save('softened.png')
Pass format-specific options only when they apply to the writer you selected. For example, keep quality or compression arguments with the format that defines them rather than passing them indiscriminately.
Save an image to memory instead of a named file
Pillow can write to a file-like object. Use a binary object that supports seek, tell, and write, and state the format explicitly because a stream has no filename extension for Pillow to inspect.
from io import BytesIO
from PIL import Image
with Image.open('input.jpg') as image:
buffer = BytesIO()
image.save(buffer, format='PNG')
png_bytes = buffer.getvalue()
# png_bytes now contains the complete PNG file in memory.
This approach is useful when the next step accepts bytes rather than a path. Keep the stream in binary mode; a text stream will not provide the interfaces required by the image writer.
Save a Matplotlib chart or figure
For a chart, save the figure object with fig.savefig(). The function accepts either a path or a file-like object and exposes options such as output format, resolution, and bounding-box handling.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, 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 minuteRank #2
- 16MP Sensor: Captures detailed photos with a CMOS sensor for everyday shooting
- Optical Zoom: 5x optical zoom with a 28mm wide angle lens for flexible framing indoors or outdoors
- Full HD Video: Records 1080p video for travel clips, family moments, or simple vlogging
- Memory Support: Works with Class 10 SD, SDHC, or SDXC cards up to 512GB
- LCD Screen and Battery: 2.7in LCD screen and a rechargeable lithium-ion battery for on-the-go use
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
fig.savefig('plot.png', dpi=300, bbox_inches='tight')
Using the figure object is explicit and works well when a script creates more than one chart. If you are using pyplot’s stateful interface, the equivalent call is plt.savefig('plot.png') after the desired figure has been drawn.
Write a figure to an in-memory stream
Because Matplotlib accepts file-like objects, you can avoid a named output file in the same way as Pillow.
from io import BytesIO
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
buffer = BytesIO()
fig.savefig(buffer, format='PNG', dpi=150)
chart_bytes = buffer.getvalue()
Specify format='PNG' (or another format supported by your Matplotlib installation) when writing to a stream. A stream has no extension to guide format selection.
Save an OpenCV image array
OpenCV stores an image as an array. Use cv2.imwrite(filename, image); the filename extension selects the output format, and format-specific parameters can be supplied as an optional third argument.
import cv2
image = cv2.imread('input.jpg')
ok = cv2.imwrite('output.png', image)
if not ok:
raise OSError('Image could not be written')
Check the Boolean return value. A false result means the write did not succeed, so your program can stop or report the failure instead of silently continuing.
Pass encoder parameters when needed
OpenCV accepts a params sequence for format-specific encoder settings. Keep those parameters tied to the extension and encoder you selected.
import cv2
image = cv2.imread('input.jpg')
params = [] # Add encoder-specific key/value parameters when required.
ok = cv2.imwrite('output.jpg', image, params)
if not ok:
raise OSError('Image could not be written')
Match the extension, format, and destination
- Use a matching extension. Pillow normally infers its writer from the filename; OpenCV selects its encoder from the filename extension.
- Use an explicit format for streams. A
BytesIOobject has no extension, so passformat='PNG'(or the format you intend). - Keep options with the correct API. Pillow writer options, Matplotlib save options, and OpenCV encoder parameters are not interchangeable.
- Choose a path your process can write. A valid image and a correct API call still fail if the destination directory is unavailable or unwritable.
Build a small, checkable save function
Wrapping the operation in a function makes the destination and failure behavior explicit. The Pillow version below accepts a format override, which is useful for generated filenames that do not end in a conventional extension.
Rank #3
- Latest Digital Camera Built-in Fill Light : This compact digital camera is paired with a powerful CMOS processor and image stabilization to help you take & record the most exciting moments in 44 MP quality images & FHD 1080P quality videos anywhere, anytime. Plus, there is also a built-in fill light to help you take high quality pictures even in low light&dark settings, making this the perfect camera for all indoors/outdoors situations.
- Long-Lasting Battery Life & 16X Digital Zoom :This point and shoot camera will retain its battery charge even after long use. The controls and functions are easy to operate making this the perfect choice for children, teens and younger. This kids camera supports 16x digital zoom, you can zoom in or out the subject by pressing the W/T button for taking still photos to zoom in or out on distant objects and capture all the details you need.
- Multifunctional & Portable Digital Camera: This cheap digital camera is slim enough to fit in your pocket. You'll easily be able to take it with you on all your indoor/outdoor activities and adventures and ideal for beginners, children and teenagers. This kids digital camera is equipped with 20 filters, anti-shaking, self-timer, continuous shooting, date stamp, time-lapse recording, smile capture, internal MIC and speaker (recording sound videos), great for your daily photography needs.
- WEBCAM & PAUSE FUNCTION : More than just a FHD 1080p digital camera, it also works as a webcam for video calls and vlogging. Connect the camera to the computer, press shutter and power button at the same time and the camera will automatically turn on webcam mode for all your video calling and live streaming needs. The pause function allows you to pause when seeing playback videos.
- A Must Have Photography Device : This digital camera with SD card made from high-quality materials, this retro camera is safe and durable. Perfect for all ages to develop & improve their photographic abilities and observation skills. Our dedicated and experienced 24/7 support team is available for all after purchase troubleshooting, questions and technical help.
from pathlib import Path
from PIL import Image
def save_pillow_image(source_path: str, destination: str, output_format: str | None = None) -> Path:
destination_path = Path(destination)
with Image.open(source_path) as image:
if output_format is None:
image.save(destination_path)
else:
image.save(destination_path, format=output_format)
if not destination_path.exists():
raise OSError(f'Expected output was not created: {destination_path}')
return destination_path
save_pillow_image('input.jpg', 'exports/output.png')
save_pillow_image('input.jpg', 'exports/output.data', 'PNG')
Create the exports directory before calling this function if it does not already exist. The function checks that a path now exists, while the OpenCV workflow separately checks its Boolean write result.
Troubleshooting common save failures
“Cannot identify image file” from Pillow
The input is not a readable image for Pillow, the path is wrong, or the file is incomplete. Verify the input path and open the file with Image.open() before attempting to save.
“unknown file extension” or a format error
The destination extension does not identify a Pillow writer. Supply an explicit format, for example image.save('output.data', format='PNG'), and ensure that the selected format is available in your Pillow installation.
The output is written in an unexpected format
Check that the extension matches your intention. With Pillow, an extension normally controls inference; with OpenCV, the extension selects the encoder. For a stream, always set the format explicitly.
OpenCV returns False
cv2.imwrite() reports failure through its return value. Check that the source array is valid, the destination directory exists, and the process can write there. Do not treat the call as successful until ok is true.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →The figure is clipped or has unwanted margins
Matplotlib exposes save options for layout and resolution. Adjust arguments such as bbox_inches='tight' and dpi on fig.savefig() for the output you need.
A stream write fails
Use a binary file-like object that supports seek, tell, and write. Pass an explicit format because the stream cannot provide an extension.
Rank #4
- 16MP Sensor: Captures detailed photos with a CMOS sensor for everyday shooting
- Optical Zoom: 5x optical zoom with a 28mm wide angle lens for flexible framing indoors or outdoors
- Full HD Video: Records 1080p video for travel clips, family moments, or simple vlogging
- Memory Support: Works with Class 10 SD, SDHC, or SDXC cards up to 512GB
- LCD Screen and Battery: 2.7in LCD screen and a rechargeable lithium-ion battery for on-the-go use
Performance and reliability considerations
For a single local image, the direct save calls are usually the simplest and clearest solution. When producing many files, keep the writer matched to the object type and choose destinations deliberately. In-memory streams avoid temporary files but retain the complete encoded image in memory, so release or reuse buffers after the next step has consumed the bytes.
Matplotlib’s resolution and bounding-box settings affect the resulting file and the time required to encode it. Pillow and OpenCV expose format-specific controls; apply them only when the selected writer supports them. Regardless of library, make failure visible: check OpenCV’s Boolean result, verify expected paths for file saves, and handle exceptions around input and output operations.
Or skip the browser setup
If the image you need is a webpage screenshot, you can avoid installing and driving a browser yourself with ScreenshotNeo. Its API accepts one GET request and returns a PNG, JPEG, WebP, or PDF. The following call saves the response directly to a file; see the ScreenshotNeo API documentation for the available parameters.
cURL
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}`);
ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf 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; every feature is available on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to get started.
FAQ
Frequently Asked Questions
How do I save image bytes returned by another Python library?
Open the destination in binary mode and write the bytes: with open('output.png', 'wb') as file: file.write(data). Use this when you already have encoded PNG, JPEG, or WebP bytes rather than a Pillow image, figure, or OpenCV array.
Free tools Windows power users keep installed
One-click scans. No signup required.
Can I choose a destination at runtime?
Yes. Build the destination from a string or pathlib.Path value, then pass it to the matching save function. Ensure the parent directory exists before writing.
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.

