图片任意切割工具(Python 3.8 实现)

news/2025/9/30 5:44:40/文章来源:https://www.cnblogs.com/felixwan/p/19119824

图片任意切割工具(Python 3.8 实现)

在日常工作或个人创作中,我们经常会遇到需要把一张图片按比例切割的情况,比如:

  • 将长截图拆分成若干小段,方便排版展示
  • 把一张大图切割成网格,用于拼接、打印或艺术化处理
  • 测试图像处理算法时,快速得到分块数据

为此,我开发了一个 图片任意切割工具(基于 Python 3.8 + Pillow + Tkinter),支持命令行和图形界面两种模式,简单易用。


功能特点

  • 多种切割模式

    • 横向切割(按行)
    • 纵向切割(按列)
    • 网格切割(行 × 列)
  • 灵活的保存顺序

    • 从上到下(tb)、从下到上(bt)
    • 从左到右(lr)、从右到左(rl)
    • 网格顺序:lr-tb / rl-tb / lr-bt / rl-bt / snake(蛇形) / reverse
    • 中心向外:按照与图片中心的距离由近到远保存
  • 双模式支持

    • 命令行模式:适合批量处理和自动化脚本
    • GUI 图形界面模式:如果不输入参数,自动弹出 Windows 界面,选择图片、份数、方向等信息即可
  • 自动输出目录

    • 默认在原图目录下生成 <图片名>_slices 文件夹,自动保存切割结果

使用方法

命令行模式

# 显示帮助(支持 ? 或 --help)
python image_slicer.py --help
python image_slicer.py ?# 例1:水平切成 4 份(自上而下)
python image_slicer.py input.jpg --mode horizontal --parts 4 --direction tb# 例2:垂直切成 3 份(右→左)
python image_slicer.py input.jpg --mode vertical --parts 3 --direction rl# 例3:网格切割 3x5,蛇形顺序保存
python image_slicer.py input.jpg --mode grid --grid 3x5 --direction snake# 例4:网格切割 4x4,按“中心→外围”距离排序保存
python image_slicer.py input.jpg --mode grid --grid 4x4 --center-out

GUI 模式

如果你直接运行而不带参数:

python image_slicer.py

会自动弹出一个 Windows 图形界面,在界面中可以:

  • 选择切割模式(水平 / 垂直 / 网格)
  • 输入份数或行列数
  • 设置切割顺序
  • 勾选是否“中心向外”
  • 点击“选择图片并执行切割”即可

无需记命令行参数,更适合普通用户。


代码:


# -*- coding: utf-8 -*-
"""
Image Slicer Tool (Python 3.8)
Author: ChatGPT
"""
import os
import math
import argparse
from typing import List, Tuple, Optionalfrom PIL import Image# ——— For file dialog when no input path ———
def pick_image_by_dialog() -> Optional[str]:try:import tkinter as tkfrom tkinter import filedialogroot = tk.Tk()root.withdraw()path = filedialog.askopenfilename(title="选择要切割的图片",filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff;*.webp"),("All files", "*.*"),],)root.update()root.destroy()return path or Noneexcept Exception as e:print("无法打开文件选择器:", e)return Nonedef parse_grid(s: str) -> Tuple[int, int]:"""Parse 'rowsxcols' (e.g. 3x5 or 3×5) into (rows, cols)"""s = s.lower().replace("×", "x")if "x" not in s:raise argparse.ArgumentTypeError("网格格式应为 rowsxcols,例如 3x5 或 3×5")r, c = s.split("x", 1)rows = int(r.strip())cols = int(c.strip())if rows <= 0 or cols <= 0:raise argparse.ArgumentTypeError("rows/cols 必须为正整数")return rows, colsdef make_outdir(img_path: str, outdir: Optional[str]) -> str:if outdir:os.makedirs(outdir, exist_ok=True)return outdirbase = os.path.splitext(os.path.basename(img_path))[0]parent = os.path.dirname(img_path) or os.getcwd()out = os.path.join(parent, f"{base}_slices")os.makedirs(out, exist_ok=True)return outdef slice_boxes_horizontal(w: int, h: int, parts: int) -> List[Tuple[int, int, int, int]]:"""Horizontal cutting → split along horizontal lines (top->bottom bands).Each box: (left, top, right, bottom)"""band_heights = []for i in range(parts):bh = (h // parts) + (1 if i < (h % parts) else 0)band_heights.append(bh)boxes = []y = 0for bh in band_heights:boxes.append((0, y, w, y + bh))y += bhreturn boxesdef slice_boxes_vertical(w: int, h: int, parts: int) -> List[Tuple[int, int, int, int]]:"""Vertical cutting → split along vertical lines (left->right slices)."""col_widths = []for i in range(parts):cw = (w // parts) + (1 if i < (w % parts) else 0)col_widths.append(cw)boxes = []x = 0for cw in col_widths:boxes.append((x, 0, x + cw, h))x += cwreturn boxesdef slice_boxes_grid(w: int, h: int, rows: int, cols: int) -> List[Tuple[int, int, int, int]]:# distribute remainder pixels to front rows/colsrow_heights = [(h // rows) + (1 if i < (h % rows) else 0) for i in range(rows)]col_widths = [(w // cols) + (1 if j < (w % cols) else 0) for j in range(cols)]boxes = []y = 0for i, rh in enumerate(row_heights):x = 0for j, cw in enumerate(col_widths):boxes.append((x, y, x + cw, y + rh))x += cwy += rhreturn boxes  # row-major order (top→bottom, left→right)def sort_indices_for_direction(boxes: List[Tuple[int, int, int, int]],w: int,h: int,mode: str,direction: str,center_out: bool,rows_cols: Tuple[int, int] = (0, 0),
) -> List[int]:idxs = list(range(len(boxes)))if center_out:cx_img, cy_img = w / 2.0, h / 2.0centers = []for i, (l, t, r, b) in enumerate(boxes):cx = (l + r) / 2.0cy = (t + b) / 2.0d = (cx - cx_img) ** 2 + (cy - cy_img) ** 2centers.append((d, i))centers.sort(key=lambda x: x[0])return [i for _, i in centers]if mode == "horizontal":if direction in ("tb", "t2b", "top-bottom"):return idxselif direction in ("bt", "b2t", "bottom-top", "reverse"):return list(reversed(idxs))elif mode == "vertical":if direction in ("lr", "l2r", "left-right"):return idxselif direction in ("rl", "r2l", "right-left", "reverse"):return list(reversed(idxs))elif mode == "grid":rows, cols = rows_colsdef rc_positions():pos = []k = 0for r in range(rows):for c in range(cols):pos.append((r, c, k))k += 1return pospos = rc_positions()if direction == "lr-tb":order = sorted(pos, key=lambda x: (x[0], x[1]))elif direction == "rl-tb":order = sorted(pos, key=lambda x: (x[0], -x[1]))elif direction == "lr-bt":order = sorted(pos, key=lambda x: (-x[0], x[1]))elif direction == "rl-bt":order = sorted(pos, key=lambda x: (-x[0], -x[1]))elif direction == "snake":order = []for r in range(rows):row_items = [p for p in pos if p[0] == r]row_items = sorted(row_items, key=lambda x: x[1])if r % 2 == 1:row_items = list(reversed(row_items))order.extend(row_items)elif direction in ("reverse", "bt-lr", "rb-lt"):order = list(reversed(pos))else:order = sorted(pos, key=lambda x: (x[0], x[1]))return [k for _, __, k in order]return idxsdef save_tiles(img: Image.Image,boxes: List[Tuple[int, int, int, int]],outdir: str,prefix: str,order: List[int],
) -> None:digits = max(2, int(math.ceil(math.log10(len(boxes) + 1))))for out_idx, i in enumerate(order, 1):box = boxes[i]tile = img.crop(box)filename = f"{prefix}_{str(out_idx).zfill(digits)}.png"tile.save(os.path.join(outdir, filename))def run(image_path: Optional[str],outdir: Optional[str],mode: str,parts: Optional[int],grid: Optional[Tuple[int, int]],direction: str,center_out: bool,
) -> str:if not image_path:image_path = pick_image_by_dialog()if not image_path:raise SystemExit("未选择图片,已退出。")img = Image.open(image_path)w, h = img.sizeout_dir = make_outdir(image_path, outdir)base = os.path.splitext(os.path.basename(image_path))[0]prefix = f"{base}_{mode}"if mode == "horizontal":if not parts or parts < 1:raise SystemExit("--parts 必须是正整数(水平切割需要指定份数)")boxes = slice_boxes_horizontal(w, h, parts)order = sort_indices_for_direction(boxes, w, h, mode, direction, center_out)elif mode == "vertical":if not parts or parts < 1:raise SystemExit("--parts 必须是正整数(垂直切割需要指定份数)")boxes = slice_boxes_vertical(w, h, parts)order = sort_indices_for_direction(boxes, w, h, mode, direction, center_out)elif mode == "grid":if grid is None:raise SystemExit("--grid 需要指定,格式 rowsxcols 例如 3x5")rows, cols = gridboxes = slice_boxes_grid(w, h, rows, cols)order = sort_indices_for_direction(boxes, w, h, mode, direction, center_out, (rows, cols))else:raise SystemExit("未知的 mode")save_tiles(img, boxes, out_dir, prefix, order)return out_dirdef build_argparser() -> argparse.ArgumentParser:p = argparse.ArgumentParser(description="图片任意切割工具:支持水平/垂直/网格切割、方向控制、中心向外排序。Python 3.8",epilog=("示例:\n""  1) 水平切成 4 份,自上而下保存:\n""     python image_slicer.py input.jpg --mode horizontal --parts 4 --direction tb\n""  2) 垂直切成 3 份,自右向左保存:\n""     python image_slicer.py input.jpg --mode vertical --parts 3 --direction rl\n""  3) 网格切割 3x5,按蛇形顺序保存:\n""     python image_slicer.py input.jpg --mode grid --grid 3x5 --direction snake\n""  4) 网格切割 4×4,按“中心 → 外围”距离排序保存:\n""     python image_slicer.py input.jpg --mode grid --grid 4x4 --center-out\n""  5) 不带任何参数运行,将弹出文件选择器:\n""     python image_slicer.py"),formatter_class=argparse.RawTextHelpFormatter,)p.add_argument("image", nargs="?", help="输入图片路径;省略则弹出文件选择器")p.add_argument("--outdir", help="输出文件夹;默认为与图片同目录的 <name>_slices")p.add_argument("--mode",choices=["horizontal", "vertical", "grid"],default="grid",help="切割模式:horizontal(横向/按行)、vertical(纵向/按列)、grid(网格);默认 grid",)p.add_argument("--parts", type=int, help="水平/垂直模式下切成几份(正整数)")p.add_argument("--grid", type=parse_grid, help="网格模式下的行×列,例如 3x5 或 3×5")p.add_argument("--direction",default="lr-tb",help=("保存顺序(非中心模式):\n""  horizontal: tb(上→下) | bt(下→上)\n""  vertical  : lr(左→右) | rl(右→左)\n""  grid      : lr-tb | rl-tb | lr-bt | rl-bt | snake | reverse\n""默认 lr-tb"),)p.add_argument("--center-out",action="store_true",help="按与图像中心的距离由近到远排序保存(适用于任意模式)",)return pdef main():parser = build_argparser()# 如果命令里带 "?",直接显示帮助import sysif "?" in sys.argv:parser.print_help()returnargs = parser.parse_args()# 如果没有任何输入图片参数,进入 GUI 模式if not args.image and len(sys.argv) == 1:try:import tkinter as tkfrom tkinter import ttk, filedialog, messageboxexcept ImportError:print("缺少 tkinter,无法打开 GUI,请安装 tkinter 或传入参数")returnroot = tk.Tk()root.title("图片切割工具 - GUI模式")# ========== 界面元素 ==========tk.Label(root, text="切割模式:").grid(row=0, column=0, sticky="w")mode_var = tk.StringVar(value="grid")ttk.Combobox(root, textvariable=mode_var, values=["horizontal", "vertical", "grid"]).grid(row=0, column=1)tk.Label(root, text="份数 (水平/垂直):").grid(row=1, column=0, sticky="w")parts_var = tk.IntVar(value=2)tk.Entry(root, textvariable=parts_var).grid(row=1, column=1)tk.Label(root, text="网格 (行x列):").grid(row=2, column=0, sticky="w")grid_var = tk.StringVar(value="2x2")tk.Entry(root, textvariable=grid_var).grid(row=2, column=1)tk.Label(root, text="方向:").grid(row=3, column=0, sticky="w")dir_var = tk.StringVar(value="lr-tb")ttk.Combobox(root, textvariable=dir_var,values=["tb", "bt", "lr", "rl", "lr-tb", "rl-tb", "lr-bt", "rl-bt", "snake", "reverse"]).grid(row=3, column=1)center_out_var = tk.BooleanVar(value=False)tk.Checkbutton(root, text="中心向外排序", variable=center_out_var).grid(row=4, columnspan=2)def run_gui():img_path = filedialog.askopenfilename(title="选择要切割的图片")if not img_path:messagebox.showerror("错误", "未选择图片")returngrid_tuple = Noneif mode_var.get() == "grid":try:grid_tuple = parse_grid(grid_var.get())except:messagebox.showerror("错误", "网格格式错误,应为 3x5")returnoutdir = run(image_path=img_path,outdir=None,mode=mode_var.get(),parts=parts_var.get() if mode_var.get() != "grid" else None,grid=grid_tuple,direction=dir_var.get(),center_out=center_out_var.get())messagebox.showinfo("完成", f"切割完成,输出目录:{outdir}")tk.Button(root, text="选择图片并执行切割", command=run_gui).grid(row=5, columnspan=2, pady=10)root.mainloop()return# 命令行模式out_dir = run(image_path=args.image,outdir=args.outdir,mode=args.mode,parts=args.parts,grid=args.grid,direction=args.direction.lower() if args.direction else "lr-tb",center_out=args.center_out,)print("切割完成,输出目录:", out_dir)if __name__ == "__main__":main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/922501.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费手机端网站模板下载安装天津设计网站公司

问题描述 元宵佳节&#xff0c;一场别开生面的灯笼大赛热闹非凡。NN 位技艺精湛的灯笼师依次落座&#xff0c;每位师傅都有相应的资历值&#xff0c;其中第 ii 位师傅的资历值为 AiAi​。从左到右&#xff0c;师傅们的资历值逐级递增&#xff08;即 A1<A2<⋯<ANA1​&l…

咸宁手机网站建设全屋定制十大品牌排行榜前十名

继上一篇文章《阿里云ECS服务器无法发送邮件问题解决方案》之后&#xff0c;又发现登录的时候发送邮件中的时间和自己windows上的时间不一样&#xff0c;大概找了一下原因&#xff0c;是LocaDateTime使用的时区不一样导致的远程服务器和本机时间不一致。 只需要在LocaDateTime…

免费写作网站视频在线生成链接

文章目录1. 题目2. 解题1. 题目 小易有一个古老的游戏机&#xff0c;上面有着经典的游戏俄罗斯方块。因为它比较古老&#xff0c;所以规则和一般的俄罗斯方块不同。 首先&#xff0c;荧幕上一共有 n 列&#xff0c;每次都会有一个 1 x 1 的方块随机落下&#xff0c;在同一列中…

和平网站建设公司品牌建设的路径

在众多编程语言中&#xff0c;似乎已经没有什么能够阻挡Python的步伐。本月Python又是第一名&#xff0c;市场份额达到了13.42%&#xff0c;在2023年&#xff0c;Python已经连续7个月蝉联榜首&#xff0c;遥遥领先于其他对手。 每个月榜单发布后&#xff0c;都有小伙伴会好奇&…

58同城 网站建设 成都行业网站推广外包

默认配置&#xff0c;根据实际配置即可。

做网站怎么那么难网站上传程序流程

华为设备提供了多条display命令用于查看硬件部件、接口及软件的状态信息。通常这些状态信息可以为用户故障处理提供定位思路。 常用的故障信息搜集的命令如下&#xff1a; 路由器常用维护命令表 交换机常用的故障信息搜集 关注 工 仲 好&#xff1a;IT运维大本营&#xff0c;获…

建设企业资质双网是哪两个网站设计包装

在Sublime中设置中文的步骤如下&#xff1a; 1.打开Sublime Text&#xff0c;使用快捷键ShiftCtrlP&#xff08;MacOS下cmdShiftP&#xff09;&#xff0c;弹出查找栏。 2.在搜索框中输入关键字"install"&#xff0c;出现下拉选项&#xff0c;点击选择其中的"P…

国外的有名的网站阿里云虚拟主机多个网站

2、happens-before 关系 在 Java 中&#xff0c;volatile 关键字用于变量的修饰&#xff0c;它确保对该变量的所有读写操作都是直接从主内存中进行的&#xff0c;而不是从线程的本地缓存 中读取。volatile 关键字可以保证某些类型的内存可见性&#xff0c;并在一定程度上防止…

网站架构设计文档深圳宝安区繁华吗

实践3 类与对象实践3.4修改Menu类&#xff0c;增加显示普通员工、经理、管理员对应的功能菜单的方法。package com.dh.hrmanager.util;import java.util.Scanner;public class Menu {/*** 返回登陆菜单*/public void showLoginMenu() {System.out.println("\n\n\t\t欢迎进…

公司网站建设岗位2023年ppt模板免费

微调&#xff08;Fine-Tuning&#xff09;&#xff1a; 微调是一种用于预训练语言模型的技术。在预训练阶段&#xff0c;语言模型&#xff08;如GPT-3.5&#xff09;通过大规模的文本数据集进行训练&#xff0c;从而学会了语言的语法、语义和世界知识。然后&#xff0c;在微调阶…

商务网站建设与维护考试网站外链有什么用

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)public MessageId(Long id) {this.id = id;}<

0539 网站百度账户推广登陆

1. 下载和安装PuTTY 访问PuTTY官网下载PuTTY的最新版本。 2. 打开PuTTY 解压下载的文件后&#xff0c;找到PuTTY文件并双击打开。 3. 配置SSH连接 在ubuntu下安装ssh服务在安装ssh时&#xff0c;我一直遇到一个问题&#xff0c;原因是我的虚拟机连不上网&#xff0c;反复实…

温州建设局老网站wordpress修改链接出现404

在数字时代&#xff0c;跨境电商已经成为全球贸易的主要驱动力之一。随着互联网的普及和物流的改善&#xff0c;企业有机会将产品和服务推向全球市场。 然而&#xff0c;随着全球市场的扩大&#xff0c;文化多样性也成为了一个重要的考虑因素。本文将深入探讨跨境电商与文化多…

佛山新网站制作公司wordpress主题在哪里

Service Worker 处理网络请求的后台服务。适用于离线和后台同步数据或推送信息。不能直接和dom交互。通过postMessage方法交互。 Web Worker 模拟多线程&#xff0c;允许复杂计算功能的脚本在后台运行而不会阻碍到其他脚本的运行。适用于处理器占用量大而又不阻碍的情形。不能直…

gif素材网站推荐集团企业网站建设

fping是一个用于网络扫描的工具&#xff0c;它可以在 Linux 系统上使用。fping可以发送 ICMP ECHO_REQUEST&#xff08;即 ping&#xff09;数据包到指定的网络地址范围&#xff0c;并等待响应。通过这种方式&#xff0c;fping可以用来检测哪些 IP 地址是活跃的。 可以测试多个…

网站搭建免费免费企业建站选哪家

Matplotlib库 若没有matplotlib库&#xff0c;则使用一下命令进行安装: pip install matplotlibconda install matplotlib要将 float64 类型的 NumPy 数据转换为图像并保存&#xff0c;可以使用 matplotlib 库。以下是使用 matplotlib 的示例代码&#xff1a; import numpy …

微商城网站建设价位深圳服装网站建设

本篇文章主要讲解IDEA、phpStrom、webStrom、pyCharm等jetbrains系列编辑器无法进行全局搜索内容问题的主要原因及解决办法。 日期&#xff1a;2025年3月22日 作者&#xff1a;任聪聪 现象描述&#xff1a; 1.按下ctrlshiftf 输入法转为了繁体。 2.快捷键ctrlshiftr 可以全局检…

蔬菜配送网站建设权威的南昌网站设计

sort 函数位于头文件 #include <algorithm> 中&#xff0c;起到排序数组类型的数据结构的作用&#xff0c;对于从小到大排序&#xff0c;sort 函数的默认快排就可以做到&#xff1a; #include <iostream> #include <algorithm> using namespace std; int mai…

从零构建能自我优化的AI Agent:Reflection和Reflexion机制对比详解与实现

AI能否像人类一样从错误中学习?反思型Agent系统不仅能生成回答,还会主动审视自己的输出,找出问题并持续改进。 反思策略本质上就是让LLM对自己的行为进行自我批评。有时反思器还会调用外部工具或检索系统来提升批评…

黄页号码认证wordpress访问优化插件

简介 以太坊(Ethereum)是一提供个智能合约(smart contract)功能的公共区块链(BlockChain)平台. 本文介绍了一个简单的以太坊智能合约的开发过程. 开发环境 在以太坊上开发应用&#xff0c;首先需要安装其客户端&#xff0c;本文使用基于Go语言的Geth, 其官网为https://github.c…