Skip to main content

常用案例

图片模式转换(RGB to CMYK)

from PIL import Image, ImageCms
from typing import Literal
from os import path

ImgMode = Literal["CMYK", "RGB"]


def img_mode_transform(
img_path: str,
img_profile: str,
output_profile: str,
output_mode: ImgMode,
output_file: str = None,
) -> str:
"""
@Description 修改图片的颜色模式:`RGB`=>`CMYK`、`CMYK`=>`RGB`

- param img_path :{str} 文件绝对路径,仅支持`.jpg`、`.tif`文件
- param img_profile :{str} 文件的颜色文件:`icc`、`icm`等颜色文件
- param output_profile :{str} 要输出的颜色文件:`icc`、`icm`等颜色文件
- param output_mode :{ImgMode} `"CMYK"`"=>"`"RGB"`
- param output_file :{str} {description}

@returns `{ str }` 返回输出文件的路径,失败返回空字符
```py
input_file = "./img/mimi_rgb.jpg"
input_file_icc = "./img/sRGB IEC61966-21.icc"
output_icc = "./img/Japan Color 2001 Coated.icc"

img_mode_transform(input_file, input_file_icc, output_icc, "CMYK")
```
"""

try:
img = Image.open(img_path)

new_img = ImageCms.profileToProfile(
im=img,
inputProfile=img_profile,
outputProfile=output_profile,
outputMode=output_mode,
inPlace=False,
)

if not output_file:
name, ext = path.splitext(img_path)
output_file = f"{name}_{output_mode}{ext}"

new_img.save(output_file)

except Exception as e:
print("img_mode_transform failed: ", e)

四点仿射

PIL

# 矩阵计算函数
#
# @param pb 背景仿射
# @param pa 产品四点投射点数据
#
# @return The perspective transform.
#
def getPerspectiveTransform(pb, pa):
matrix = []
for p1, p2 in zip(pa, pb):
matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0] * p1[0], -p2[0] * p1[1]])
matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1] * p1[0], -p2[1] * p1[1]])
A = np.matrix(matrix, dtype=np.float)
B = np.array(pb).reshape(8)
res = np.dot(np.linalg.inv(A.T * A) * A.T, B)
return np.array(res).reshape(8)

##
# @brief { 修改产品透视,并和底图合并 }
##
# @param tar 目标文件, 绝对路径, string
# @param xy [(x,y),(x,y),(x,y),(x,y)] -左上角开始 顺时针 二维数组 内部可以是 turple
# @param bg The background
##
# @return { description_of_the_return_value }
##
def perspectiveByPIL(tar, bg, fg=None, xy=None, output=None, saveName=None):
# 实例化透视对象
# try:
nxy = []
saveName = saveName or os.path.join(output, os.path.basename(bg))

img = Image.open(tar).convert('RGBA')
bg_img = Image.open(bg).convert('RGBA')

# 获取宽高
img_width = bg_img.width
img_height = bg_img.height

# 将产品设置成场景相同尺寸
img = img.resize((bg_img.width, bg_img.height), Image.ANTIALIAS)

# 左上角开始 顺时针
if not xy:
left_top = [95, 134.796875] # 左上
right_top = [95, 134.796875] # 右上
right_down = [195, 209.28125] # 右下
left_down = [195, 209.28125] # 左下

# 偏移模式
# nxy = [left_top,
# (img_width-right_top[0],right_top[1]),
# (img_width-right_down[0],img_height-right_down[1]),
# (left_down[0],img_height-left_down[1])]

# 坐标模式
nxy = [left_top, right_top, right_down, left_down]

else:
# nxy = [xy[0],
# (img_width-xy[1][0],xy[1][1]),
# (img_width-xy[2][0],img_height-xy[2][1]),
# (xy[3][0],img_height-xy[3][1])]

nxy = [xy[0], xy[1], xy[2], xy[3]]

# 原图的范围
scope = [(0, 0), (img_width, 0), (img_width, img_height), (0, img_height)]

# 仿射点矩阵计算
perspective = getPerspectiveTransform(scope, nxy)

# 仿射生成图片
fg_img = img.transform((img_width, img_height), Image.PERSPECTIVE, perspective, Image.BICUBIC, fill=1, fillcolor=(200, 210, 0, 0))

# 合并图层[通过遮罩]
new_img = Image.composite(fg_img, bg_img, fg_img)

if fg:
# 读取前景
front_img =Image.open(fg).convert('RGBA')

# 将产品设置成场景相同尺寸
if front_img.width != bg_img.width:
front_img = front_img.resize((bg_img.width, bg_img.height))

# 将前景进行合成
new_img = Image.composite(front_img, new_img, front_img)

save_img = new_img.convert('RGB')

save_img.save(saveName)

return 'success'
# 使用
if (__name__ == "__main__"):
if len(sys.argv) < 2:
# 测试环境
config={
"factory_name": 'yibu',
"product_url": '/factorys/5f9c264cfac73e3d0cad5a18/5f9c2d22a16792339484ca61/004853a8be5310e06d53139e70551864.webp',
"front_url": '/factorys/yibu/scene/02/h/front.webp',
"background_url": '/factorys/yibu/scene/02/h/bg.webp',
"left_down": [277, 739],
"left_top": [279, 262],
"right_down": [1041, 768],
"right_top": [1042, 128],
"username": 'admin',
"product_path": r'D:\\CPS\\MyProject\\honegqi_online\\server\\app\\public\\factorys\\5f9c264cfac73e3d0cad5a18\\5f9c2d22a16792339484ca61\\004853a8be5310e06d53139e70551864.webp',
"background_path": r'D:\\CPS\\MyProject\\honegqi_online\\server\\app\\public\\factorys\\yibu\\scene\\02\\h\\bg.webp',
"output_path": r'D:\\CPS\\MyProject\\honegqi_online\\server\\app\\public\\factorys\\yibu\\scene\\02\\h',
"front_path": r'D:\\CPS\\MyProject\\honegqi_online\\server\\app\\public\\factorys\\yibu\\scene\\02\\h\\front.webp',
"saveName": r'D:\\CPS\\MyProject\\honegqi_online\\server\\app\\public\\factorys\\yibu\\scene\\02\\h\\0d5ba2724e80e6e38b35188793b81849.jpg'
}
tar = config['product_path']
bg = config['background_path']
output = config['output_path']
fg = config['front_path'] or None

xy = [
config['left_top'],
config['right_top'],
config['right_down'],
config['left_down'],
]
saveName = config['saveName'] or None
print(perspectiveByPIL(tar, bg, xy=xy, output=output, saveName=saveName, fg=fg))

else:
# 生成环境
# print(len(sys.argv))
config = json.loads(sys.argv[1].replace('@', '"'))
tar = config['product_path']
bg = config['background_path']
output = config['output_path']

if 'front_path' in config:
fg = config['front_path']
else:
fg=None

xy = [
config['left_top'],
config['right_top'],
config['right_down'],
config['left_down'],
]

if 'saveName' in config:
saveName = config['saveName'] or None
else:
saveName = None

print(perspectiveByPIL(tar, bg, xy=xy, output=output, saveName=saveName, fg=fg))
# print(config)

指定DPI导出A4图片

以下函数只能转换A4,

from os import path

import fitz
from PIL import Image
import io

def export_pdf_to_paper_size(pdf_path, output_path, dpi=300, paper_size="A4"):
# 定义A4和A3的尺寸(毫米)并转换为像素
paper_sizes = {"A4": (210, 297), "A3": (297, 420)}
width_mm, height_mm = paper_sizes.get(paper_size, ("A4"))

# 毫米转英寸再乘以DPI
target_width = int(width_mm / 25.4 * dpi)
target_height = int(height_mm / 25.4 * dpi)

# 打开PDF并获取页面
doc = fitz.open(pdf_path)
for page in doc:
original_width = page.rect.width # 原始宽度(点)
original_height = page.rect.height # 原始高度(点)

# 计算适应目标尺寸的缩放比例
scale_w = target_width / original_width
scale_h = target_height / original_height
scale = min(scale_w, scale_h) # 保持宽高比

# 生成缩放后的图像
matrix = fitz.Matrix(scale, scale)
pix = page.get_pixmap(matrix=matrix, dpi=dpi)
# pix.save(output_path + f"page-{page.number}.jpg", "jpg", jpg_quality=70)

img = Image.open(io.BytesIO(pix.tobytes()))

# 创建目标尺寸画布并居中粘贴
canvas = Image.new("RGB", (target_width, target_height), (255, 255, 255))
x = (target_width - img.width) // 2
y = (target_height - img.height) // 2
canvas.paste(img, (x, y))

# 保存结果
canvas.info["dpi"] = (dpi, dpi) # 写入DPI信息
canvas.save(output_path + f"page-{page.number}.jpg", "JPEG", quality=95, dpi=(dpi, dpi))

doc.close()


if __name__ == "__main__":
tar = path.abspath(r"../testData/广州市番禺区金光大道工程(富怡路至亚运大道以南段)防洪评价工作大纲及报价书.pdf")

# pdf_to_jpg(tar, ".")
export_pdf_to_paper_size(tar, ".")

A3

def pdf_to_a3_jpg(pdf_path: str, page_num: int, output_path: str, dpi=300, page_size="A4", ext="jpg", mode="stretch"):
"""
mode参数说明:
- "cover": 缩放填充整个画布,自动裁剪溢出部分(默认)
- "contain": 保持完整比例,可能留白
- "stretch": 拉伸变形填充(不推荐)
"""
# 定义纸张尺寸库(毫米)
paper_sizes = {"A4": (210, 297), "A3": (297, 420), "A3-Landscape": (420, 297)} # 新增横向尺寸支持

# 参数校验
if page_size not in paper_sizes:
raise ValueError(f"Unsupported page size: {page_size}")
if mode not in ["cover", "contain", "stretch"]:
raise ValueError(f"Invalid mode: {mode}")

# 获取目标尺寸(毫米转像素)
width_mm, height_mm = paper_sizes[page_size]
target_width = int(width_mm / 25.4 * dpi)
target_height = int(height_mm / 25.4 * dpi)

# 打开PDF并获取页面
doc = fitz.open(pdf_path)
page = doc.load_page(page_num)

# 获取实际内容区域(自动去除白边)
content_rect = page.rect # 默认使用整个页面
for annot in page.annots():
if annot.type[0] == 8: # 检查是否有裁剪标记
content_rect = annot.rect
break

original_width = content_rect.width
original_height = content_rect.height

# 计算缩放比例
if mode == "contain":
scale = min(target_width / original_width, target_height / original_height)
elif mode == "cover":
scale = max(target_width / original_width, target_height / original_height)
else: # stretch模式
scale_w = target_width / original_width
scale_h = target_height / original_height

# 生成缩放矩阵
matrix = fitz.Matrix(scale, scale) if mode != "stretch" else fitz.Matrix(scale_w, scale_h)

# 渲染时使用内容区域
pix = page.get_pixmap(matrix=matrix, clip=content_rect, dpi=dpi)

img = Image.open(io.BytesIO(pix.tobytes()))

# 最终尺寸处理
if mode == "stretch":
img = img.resize((target_width, target_height))
elif mode == "cover":
# 二次裁剪确保精确尺寸
img = img.crop(
(
(img.width - target_width) // 2,
(img.height - target_height) // 2,
(img.width + target_width) // 2,
(img.height + target_height) // 2,
)
)

# 直接使用渲染尺寸(不再创建空白画布)
img.info["dpi"] = (dpi, dpi)
img.save(output_path, quality=95, dpi=(dpi, dpi))

doc.close()
return output_path

从内存读取图片

# pix可以是然和实例带.tobytes()方法即可
img = Image.open(io.BytesIO(pix.tobytes()))