Reputation: 649
I want to reduce the file size of a PNG file using Python. I have gone through a lot of material on the internet, but I could not find anything where I would reduce the file size for an image without changing the dimensions i.e. height/width. What I have found is how to change the dimensions of the image, using PIL or some other library of Python.
How can I reduce image file size while keeping it's dimensions constant?
Upvotes: 6
Views: 8772
Reputation: 1820
from PIL import Image
import os
def bak(path):
copy(path, f"{path}.bak")
def rollback(path):
copy(f"{path}.bak", path)
os.remove(f"{path}.bak")
def copy(src, dst):
try:
with open(src, 'rb') as src, open(dst, 'wb') as f:
f.write(src.read())
except:
pass
def check(path):
try:
new = os.path.getsize(path)
old = os.path.getsize(f"{path}.bak")
if new > old:
rollback(path)
print(f"[rollback] ({new} > {old}); {path}")
except:
pass
def png(path, quality=65, compress_level=9):
try:
bak(path)
Image.open(path).save(
path,
"PNG",
quality=quality,
compress_level=compress_level,
optimize=True,
)
except:
print(f"[error] {path}")
def jpg(path, quality=65, compress_level=9):
try:
bak(path)
Image.open(path).save(
path,
"JPEG",
quality=quality,
compress_level=compress_level,
optimize=True,
)
except:
print(f"[error] {path}")
def all(dir):
for filename in os.listdir(dir):
path = os.path.join(dir, filename)
if os.path.isdir(path):
all(path)
continue
lower = filename.lower()
if lower.endswith(".png"):
png(path)
elif lower.endswith(".jpg") or lower.endswith(".jpeg"):
jpg(path)
check(path)
if __name__ == "__main__":
dir = "/Users/seunggabi/Downloads/uploads"
all(dir)
Upvotes: 0
Reputation: 60780
By default, most PNG writers will use the maximum compression setting, so trying to change that will not help much. Uncompressed PNGs exist, but they make no sense, I don't think you'll run into many of those.
Thus, the only way of making PNGs significantly smaller is to reduce the number of pixels it stores, or to reduce the number of colors it stores. You specifically said not to want to reduce the number of pixels, so the other option is to reduce the number of colors.
Most PNG files will be in "true color" mode (typically 24 bits per pixel, with one byte each for the red, green and blue components of each pixel). However, it is also possible to make indexed PNG files. These store a color map (a.k.a. palette), and a single value per pixel, the index into a color map. If you, for example, pick a color map of 64 entries, then each pixel will need 6 bits to encode the index. You'd store 64*3 bytes + 3/4 bytes per pixel (which of course compress as well). I found this web site comparing a few example images, what they'd look like and how big the file ends up being when reducing colors.
This other question shows how to use PIL to convert an RGB image to an indexed image:
img.convert("P", palette=Image.ADAPTIVE)
This seems to generate an 8-bit color map though, PIL has no support for smaller color maps. The PyPNG module would allow you to write PNG files with any number of colors in the color map.
Upvotes: 4
Reputation: 715
PNG is a lossless format but it still can be compressed in a lossless fashion. Pillow's Image class comes with a png writer accepting a compress_level
parameter.
It can easily be demonstrated that it works:
Step 1: Load generic rgb lena image in .png format:
import os
import requests
from PIL import Image
from io import BytesIO
img_url = 'http://pngnq.sourceforge.net/testimages/lena.png'
response = requests.get(img_url)
img = Image.open(BytesIO(response.content))
Step 2: Write png with compress_level=0
:
path_uncompressed = './lena_uncompressed.png'
img.save(path_uncompressed,compress_level=0)
print(os.path.getsize(path_uncompressed))
> 691968
Step 3: Write png with compress_level=9
:
path_compressed = './lena_compressed.png'
img.save(path_compressed,compress_level=9)
print(os.path.getsize(path_compressed))
> 406889
which in this case gives us a respectable 30% compression without any obvious image quality degradation (which should be expected for a lossless compression algorithm).
Upvotes: 7
Reputation: 151
PNG is lossless format and obviously it will consume more space.
If you are only concerned on resolution, then you can convert to any of the lossy form like JPG.
https://whatis.techtarget.com/definition/lossless-and-lossy-compression
The dimension after conversion would be the same, but quality depends on the compression needed
Snippet to convert PNG to JPG
from PIL import Image
im = Image.open("Picture2.png")
rgb_im = im.convert('RGB')
rgb_im.save('Picture2.jpg')
Upvotes: 4