2023.06.30 更新:扩充了很多功能,结果还是上传 github了……

GitHub - Eterance/Adaptive-Image-Resize-and-Conversion

警告

下面的内容已过时。请查看上面的 Github 链接。

上传一些 pixiv 美图的时候,原画师放出来的图非常大,如果直接上传对我的穷逼服务器非常不友好。特别是我关闭了 WordPress 自动生成不同尺寸缩略图的功能,所以都是直接发原图给访问者,如果不压缩,十几 M 的图片能让浏览者等到不耐烦

一开始我是用 PS 手动压缩分辨率。但用了一会儿发现很麻烦:

  • 不能精准指定把大小压缩到某一个区间,要么过大要么过小,尝试合适的压缩很耗时间;
  • 另存为 webp 格式时不支持调整大小。也就是说,要把一个 1000² 的 jpg 压缩并转换格式为 500² 的 webp,需要先压缩成 500² 的 jpg,再另存为 500² 的 webp。

这个时候我就想到:能用程序自动化解决的,就不要靠手工。

于是写了一个小程序。因为太小懒得传 github 了,直接丢博客里。

安装依赖

pip install pillow

或者

conda install pillow

代码

from PIL import Image
import os
import time

# 必要修改:改成你的 被压缩图片 所在的文件夹
picture_folder = r"E:\图片与视频\pixiv"
# 必要修改:改成你的 压缩后图片 保存的文件夹
save_folder = r"C:\Users\Eterance\Desktop"
# 必要修改:改成你的 被压缩图片 的文件名
image_name = "illust_66747002_20191010_103808.jpg"
# 可选修改:改成你的 压缩后图片 的格式,支持 jpg、png、webp
extension = 'webp'
# 可选修改:改成你的 压缩后图片 的后缀,例如 “-已缩小”。如果不需要后缀,改成空字符串 ""
suffix = "-已缩小"
# 可选修改:改成你的 压缩后图片 的质量,范围 0-100,越大越清晰,但是文件越大。推荐 90。
_quality = 90
# 可选修改:改成你的 压缩后图片 的目标大小上限,单位 KB。
target_size_kb_upper = 600
# 可选修改:改成你的 压缩后图片 的目标大小下限,单位 KB。不得大于上限。
target_size_kb_lower = 550


# 下面的代码不懂就不要改

multiple_upper = 1.00
multiple_lower = 0.10
current_multiple = 1
start_time = time.time()
image_file = Image.open(os.path.join(picture_folder, image_name))
image_file = image_file.convert("RGB") # PNG转换为RGB模式,否则无法保存为jpg
width, height = image_file.size
target_file_name = os.path.join(save_folder, f"{image_name.split('.')[0]}{suffix}.{extension}")

# 先尝试原尺寸压缩,如果满足要求就不用缩小了
image_file.save(target_file_name, quality= _quality)
file_size = os.path.getsize(target_file_name)
if (file_size < target_size_kb_upper * 1024):
    print(f"{image_name} 分辨率未改变,大小为 {int(file_size/1024)} KB,耗时 {round(time.time() - start_time, 2)} s,质量为 {_quality}")
else:
    os.remove(target_file_name)
    # 二分法寻找最适合压缩的分辨率
    while (current_multiple > 0.1):
        current_multiple = round(((multiple_upper + multiple_lower) / 2), 2)
        img_resized = image_file.resize((int(width*current_multiple),  int(height*current_multiple)), Image.LANCZOS)
        img_resized.save(target_file_name, quality= _quality)
        file_size = os.path.getsize(target_file_name)
        if (file_size > target_size_kb_upper * 1024):
            multiple_upper = current_multiple
            os.remove(target_file_name)
        elif (file_size < target_size_kb_lower * 1024):
            multiple_lower = current_multiple
            os.remove(target_file_name)
        else:
            break
    
    resized_width, resized_height = img_resized.size
    pixel_percentage = round((resized_width * resized_height) / (width * height), 2)
    
    print(f"已经将 {image_name} 的分辨率缩小到 {int(width*current_multiple)}x{int(height*current_multiple)} ({current_multiple*100}% 边长,{(pixel_percentage*100)}% 像素),大小为 {int(file_size/1024)} KB,耗时 {round(time.time() - start_time, 2)} s,质量为 {_quality}")
如果 压缩后图片保存的文件夹 有重名文件,会直接覆盖。请再三检查目标文件夹有没有重名文件。
上下限区间越小,二分查找到符合要求的分辨率所花费的时间越长。如果你对大小要求不是非常精准,可以适当放宽上下限以减少运行时间。

例子

原图,非常巨大

修改上下限:

target_size_kb_upper = 1400
target_size_kb_lower = 1300

控制台输出

已经将 84603466.jpg 的分辨率缩小到 3360x1519 (42.0% 边长,18.0% 像素),大小为 1345 KB,耗时 15.33 s,质量为 90

缩小后的 webp 图片

作为对比,使用 jpg:

extension = 'jpg'
target_size_kb_upper = 1400
target_size_kb_lower = 1300

控制台输出

已经将 84603466.jpg 的分辨率缩小到 3200x1447 (40.0% 边长,16.0% 像素),大小为 1342 KB,耗时 4.14 s,质量为 90

缩小后的 jpg 图片

这就是为什么推荐使用 webp:几乎相同的大小,webp 分辨率更胜一筹;反过来,相同分辨率,webp 更小。


封面图:https://www.pixiv.net/artworks/75028182