Python 生成不失真的缩略图

  • 原创
  • Madman
  • /
  • /
  • 0
  • 9108 次阅读

Synopsis: Python 3 使用 Pillow 模块生成指定宽度和高度的缩略图

1. 要点说明

因为原始图片的 宽度高度 的比例一般与目标图片的宽高比不一致,此时如果直接用 PIL 模块的 thumbnail() 方法就会生成失真变形的缩略图

所以,我们首先要将原始图片的宽高比例调整到跟目标图的宽高比例一致,怎么做呢?

  1. 如果原始图片的宽高比例大,则将原始图片的宽度缩小
  2. 如果原始图片的宽高比例小,则将原始图片的高度缩小

计算出 切图 的新高度和宽度,但是不能超过原始图的边界,否则会出现黑边。然后用 PIL 模块的 crop() 方法将原始图进行切图,最后再对切图产生缩略图即可

2. 安装 Pillow

1. 激活虚拟环境
D:\MyCode\resize-images>python -m venv venv
D:\MyCode\resize-images>venv\Scripts\activate

2. 安装 Pillow 模块
(venv) D:\MyCode\resize-images>pip install Pillow

3. 单张图片生成缩略图

import logging
import os

from PIL import Image

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)


def image_resize(src_filename, dst_width, dst_height):
    """
    将原始图片的宽高比例调整到跟目标图的宽高比例一致,所以需要:
    1. 切图,缩小原始图片的宽度或者高度
    2. 将切图后的新图片生成缩略图
    :param src_filename: 原始图片的名字
    :param dst_width: 目标图片的宽度
    :param dst_height: 目标图片的高度
    """
    # 目标图片(缩略图)的命名
    fname, fextension = os.path.splitext(src_filename)
    thumbnail_filename = fname + '-{}x{}'.format(dst_width, dst_height) + fextension

    # 打开原始图片
    src_image = Image.open(src_filename)
    # 原始图片的宽度和高度
    src_width, src_height = src_image.size
    # 原始图片的宽高比例,保留2位小数
    src_ratio = float('%.2f' % (src_width / src_height))
    # 目标图片的宽高比例,保留2位小数
    dst_ratio = float('%.2f' % (dst_width / dst_height))

    # 如果原始图片的宽高比例大,则将原始图片的宽度缩小
    if src_ratio >= dst_ratio:
        # 切图后的新高度
        if src_height < dst_height:
            logging.warning('目标图片的高度({0} px)超过原始图片的高度({1} px),最终图片的高度为 {1} px'.format(dst_height, src_height))
        new_src_height = src_height
        # 切图后的新宽度
        new_src_width = int(new_src_height * dst_ratio)  # 向下取整
        if new_src_width > src_width:  # 比如原始图片(1280*480)和目标图片(800*300)的比例完全一致时,此时new_src_width=1281,可能四周会有一条黑线
            logging.warning('切图的宽度({0} px)超过原始图片的宽度({1} px),最终图片的宽度为 {1} px'.format(new_src_width, src_width))
            new_src_width = src_width
        blank = int((src_width - new_src_width) / 2)  # 左右两边的空白。向下取整
        # 左右两边留出同样的宽度,计算出新的 box: The crop rectangle, as a (left, upper, right, lower)-tuple
        box = (blank, 0, blank + new_src_width, new_src_height)
    # 如果原始图片的宽高比例小,则将原始图片的高度缩小
    else:
        # 切图后的新宽度
        if src_width < dst_width:
            logging.warning('目标图片的宽度({0} px)超过原始图片的宽度({1} px),最终图片的宽度为 {1} px'.format(dst_width, src_width))
        new_src_width = src_width
        # 切图后的新高度
        new_src_height = int(new_src_width / dst_ratio)  # 向下取整
        if new_src_height > src_height:
            logging.warning('切图的高度({0} px)超过原始图片的高度({1} px),最终图片的高度为 {1} px'.format(new_src_height, src_height))
            new_src_height = src_height
        blank = int((src_height - new_src_height) / 2)  # 上下两边的空白。向下取整
        # 上下两边留出同样的高度,计算出新的 box: The crop rectangle, as a (left, upper, right, lower)-tuple
        box = (0, blank, new_src_width, blank + new_src_height)

    # 切图
    new_src_image = src_image.crop(box)
    # 生成目标缩略图
    new_src_image.thumbnail((dst_width, dst_height))
    # 保存到磁盘上
    new_src_image.save(thumbnail_filename)

    logging.info('目标图片已生成: {}'.format(thumbnail_filename))


if __name__ == '__main__':
    image_resize('D:\\MyCode\\resize-images\\flask-vuejs.png', 60, 60)
    image_resize('D:\\MyCode\\resize-images\\flask-vuejs.png', 480, 270)
    image_resize('D:\\MyCode\\resize-images\\flask-vuejs.png', 800, 300)
    image_resize('D:\\MyCode\\resize-images\\flask-vuejs.png', 960, 360)
    image_resize('D:\\MyCode\\resize-images\\flask-vuejs.png', 1280, 480)

执行后,就会在原图所在目录生成缩略图片:

(venv) D:\MyCode\resize-images>python resize_image.py
2019-11-03 10:20:39,687 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\flask-vuejs-60x60.png
2019-11-03 10:20:39,712 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\flask-vuejs-480x270.png
2019-11-03 10:20:39,713 - root - WARNING - 切图的宽度(1281 px)超过原始图片的宽度(1280 px),最终图片的宽度为 1280 px
2019-11-03 10:20:39,748 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\flask-vuejs-800x300.png
2019-11-03 10:20:39,749 - root - WARNING - 切图的宽度(1281 px)超过原始图片的宽度(1280 px),最终图片的宽度为 1280 px
2019-11-03 10:20:39,790 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\flask-vuejs-960x360.png
2019-11-03 10:20:39,791 - root - WARNING - 切图的宽度(1281 px)超过原始图片的宽度(1280 px),最终图片的宽度为 1280 px
2019-11-03 10:20:39,834 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\flask-vuejs-1280x480.png

比如原图的名称是 flask-vuejs.png,那么缩略图的名称为 flask-vuejs-480x270.png

4. 针对目录批量生成缩略图

if __name__ == '__main__':
    path = 'D:\\MyCode\\resize-images\\uploads'
    images = os.listdir(path)
    for image in images:
        absolute_image_path = os.path.join(path, image)
        image_resize(absolute_image_path, 480, 270)

执行:

(venv) D:\MyCode\resize-images>python resize_image.py
2019-11-03 10:26:37,948 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\uploads\CentOS安装ShadowSocks-min-480x270.png
2019-11-03 10:26:37,983 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\uploads\Python编码转换-480x270.png
2019-11-03 10:26:37,990 - root - INFO - 目标图片已生成: D:\MyCode\resize-images\uploads\安装Bodhi Linux-min-480x270.png

5. 删除目录下的缩略图

import logging
import os
import re

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)


def main():
    """删除指定目录下的所有缩略图"""
    path = 'D:\\MyCode\\resize-images\\uploads'
    images = os.listdir(path)  # list

    pattern = re.compile(r'.*-(\d)+?x(\d)+?\..*')
    count = 0  # 统计需要删除的图片数
    for image in images:
        result = re.search(pattern, image)
        if result is not None:
            absolute_image_path = os.path.join(path, image)
            os.remove(absolute_image_path)
            count += 1
    logging.info('已删除缩略图 [{}] 张'.format(count))


if __name__ == '__main__':
    main()

https://github.com/wangy8961/python3-resize-images

分类: Python
标签: Pillow PIL thumbnail crop
未经允许不得转载: LIFE & SHARE - 王颜公子 » Python 生成不失真的缩略图

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.