需要将PNG缩放到特定的尺寸,并且对大小进行压缩,当然这些都很简单(网上太多了),让我自己做一个小工具的最大动力是,他们的要求必须是PNG格式。而网上大多数都是将PNG压缩成JPG,有损且格式不一致(文件头不一样)。
但是现在来看,他们公司并没有要求到那么严格,压缩后的jpg修改后缀为png即可。
Python 压缩
主要用到的库是Pillow,这个库可以对图片进行尺寸调整,并且对PNG进行一定的压缩,但是压缩效果并不太好(通常500x500的PNG图片压缩不到500K或者300K以下)。
# 使用pillow压缩图片
def pillow_compress_png(
file, compress_tag, compress_size, quality=95, inplace=False, is_resize=False, height=None, width=None):
# 如果图片被处理过
if compress_tag in file:
print(f"{file} 图片已被处理 {os.path.getsize(file) // 1024}")
return
# 打开图片
image = Image.open(file)
out_file = f"{file[:-4]}{compress_tag}.png"
# 如果图片本身满足大小
if (os.path.getsize(file) / 1024) <= compress_size:
print(f"{file} 图片无需处理 {os.path.getsize(file)//1024}")
return
# 如果需要进行像素拉伸
if is_resize:
assert width is not None
if height is None:
height = int(image.height / image.width * width)
pre_size = os.path.getsize(file)
image = image.resize((width, height), Image.ANTIALIAS)
image.save(f"{out_file[:-4]}_resize.png", "PNG")
print(f"{file} {pre_size//1024}->{os.path.getsize(file)//1024} 像素: {image.height, image.width}, ")
# 如果大小不满足要求,进行 RGB 改变
if os.path.getsize(file) / 1024 > compress_size:
image = image.convert("RGB")
image.save(out_file, "PNG", quality=quality)
# 如果大小还不能满足要求 进一步改变通道
if (os.path.getsize(out_file) / 1024) > compress_size:
image.quantize(colors=256, method=0)
image.save(out_file, "PNG")
# 依然无法满足需求,返回
if (os.path.getsize(out_file) / 1024) > compress_size:
if inplace:
os.remove(file)
os.rename(out_file, file)
if os.path.exists(f"{out_file[:-4]}_resize.png"):
os.remove(f"{out_file[:-4]}_resize.png")
return file
# 打印最终的处理结果
print(f"{file} {os.path.getsize(file)//1024}->{os.path.getsize(out_file)//1024}")
# 直接替换原来的文件
if inplace:
os.remove(file)
os.rename(out_file, file)
if os.path.exists(f"{out_file[:-4]}_resize.png"):
os.remove(f"{out_file[:-4]}_resize.png")
于是考虑使用Java的压缩API。
Java 压缩
没有想过Java也可以压缩PNG图片,得益于有人写了Java接口的包,将32位或者24位颜色通道的图片在尽量不损失清晰度的情况下,压缩到最小。
public static ArrayList<String> compressPNGPictures(String rootDir, Integer pictureSize, String compressTag){
// 无法被压缩到指定大小的图片
ArrayList<String> pictureException = new ArrayList<>();
// 获取路径
ArrayList<String> pictureDirs = new ArrayList<>();
getPNGPicturesDirectory(rootDir, pictureDirs, compressTag);
// 压缩 PNG 类型图片
for (String pictureDir: pictureDirs){
String fileString = compressPNGPicture(pictureDir, compressTag, pictureSize);
if (fileString != null) pictureException.add(fileString);
}
// 打印输出无法压缩至指定大小以下的图片
System.out.println("无法被压缩至指定大小的文件:");
for (String file: pictureException){
File tempFile = new File(file);
System.out.println(file + " : " + (tempFile.length()/1024));
}
// 返回无法被压缩至指定大小的图片
return pictureException;
}
联动压缩
利用Python的胶水特性,将这两部分代码给连接起来,流程为先利用Python的Pillow库压缩,如果不行(保留原图),则利用Java的接口再进行压缩。
# 压缩主函数
def compress_main(args):
# 是否本地更改
inplace = args['inplace']
# 是否需要调整尺寸
is_resize = args['is_resize']
# 尺寸要求
height = args['height']
width = args['width']
# 扫描图片类型
picture_type = args['pictures_type']
# 图片根目录
root_dir = args['root_dir']
# 压缩图片 处理标识
compress_tag = args['compress_tag']
# 压缩后的图片大小
compress_size = args['compress_size']
# 获取图片地址
picture_direc = []
get_pictures_dir(root_dir, picture_direc, compress_tag, picture_type)
# 处理图片
compress_pictures(
picture_direc, compress_tag=compress_tag, compress_size=compress_size,
inplace=inplace, is_resize=is_resize, height=height, width=width
)
# 打印日志
print("----------------------split line----------------------")
print("转由Java处理:")
# Java 压缩
jar_dir = args['jar_dir']
picture_exception = java_compress_png(jar_dir, [compress_tag, f"{compress_size}", root_dir])
if len(picture_exception) < 1:
picture_exception = None
return picture_exception
Java是使用Jpype1来调用的,Java首先需要打包成jar包,这部分就不在这里叙述了。
界面&选项定制
为了可以方便自己日后使用,不需要每次对着终端看输出,也为了方便我女朋友使用,我决定写一个UI界面。
代码比较多,就不贴上来了,直接看效果吧,完整的代码参见Github。
支持定制按“宽边缩放”、“自由缩放(普通模式)”、“仅压缩模式”,可以支持保留原图或者直接完成更名替换,可以输入文件限制,可以一次压缩多个目录下的所有图片。