You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python脚本生成TIFF:如何设置300DPI与eciRGB v2色彩模型?

解决TIFF文件的300DPI与eciRGB v2色彩模型设置问题

关键修改点说明

1. 设置300DPI

DPI属于TIFF文件的元数据,直接在imwrite函数中配置即可,无需修改图像像素数据:

  • 添加resolution=(300, 300)指定横向/纵向分辨率
  • 添加resolutionunit=2指定单位为英寸(对应DPI的定义)

2. 配置eciRGB v2色彩模型

TIFF的photometric参数仅定义基础色彩类型(如RGB),无法直接指定eciRGB v2这类特定色彩空间,必须通过嵌入ICC配置文件实现:

  1. 下载eciRGB v2的ICC文件(可从ECI官方渠道获取)
  2. 读取ICC文件二进制数据,在imwrite中通过icc参数传入
  3. 若psdtags库支持对应枚举值,可同步调整图层元数据中的色彩空间定义

修改后的完整代码

from psdtags import *
from tifffile import imwrite
from imagecodecs import imread
import numpy as np
from PIL import Image


productImage = Image.open('GPD_TEST_Product.png')
reflectionImage = Image.open('GPD_TEST_Reflection.png')
shadowImage = Image.open('GPD_TEST_Shadow.png')

w, h = productImage.size

# 创建白色背景
backgroundImage = Image.new(mode="RGBA", size=(w,h), color=(255, 255, 255,255))

background = np.array(backgroundImage)
product = np.array(productImage)
reflection = np.array(reflectionImage)
shadow = np.array(shadowImage)

# 合成缩略图
thumbnail = Image.alpha_composite(backgroundImage, reflectionImage)
thumbnail = Image.alpha_composite(thumbnail, shadowImage)
thumbnail = Image.alpha_composite(thumbnail, productImage)
thumbnail = np.array(thumbnail)

# 加载eciRGB v2 ICC配置文件,替换为你的实际文件路径
with open('eciRGB_v2.icc', 'rb') as f:
    eci_icc_data = f.read()

image_source_data = TiffImageSourceData(
    name='Layered TIFF Test',
    psdformat=PsdFormat.LE32BIT,
    layers=PsdLayers(
        key=PsdKey.LAYER,
        has_transparency=False,
        layers=[
            PsdLayer(
                name='Background',
                rectangle=PsdRectangle(0, 0, *background.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5
                | PsdLayerFlag.TRANSPARENCY_PROTECTED,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Background'),
                ],
            ),
            PsdLayer(
                name='Reflection',
                rectangle=PsdRectangle(0, 0, *reflection.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Reflection'),
                ],
            ),
            PsdLayer(
                name='Shadow',
                rectangle=PsdRectangle(0, 0, *shadow.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Shadow'),
                ],
            ),
            PsdLayer(
                name='Product',
                rectangle=PsdRectangle(0, 0, *product.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Product'),
                ],
            ),
        ],
    ),
    usermask=PsdUserMask(
        # 若psdtags支持eciRGB色彩空间枚举,可替换此处的RGB
        colorspace=PsdColorSpaceType.RGB,
        components=(65535, 0, 0, 0),
        opacity=50,
    ),
    info=[
        PsdEmpty(PsdKey.PATTERNS),
        PsdFilterMask(
            # 同上,若支持则替换色彩空间
            colorspace=PsdColorSpaceType.RGB,
            components=(65535, 0, 0, 0),
            opacity=50,
        ),
    ],
)


imwrite(
    'LayeredTiffTest.tif',
    thumbnail,
    photometric='rgb',
    metadata=None,
    extratags=[image_source_data.tifftag()],
    # 设置300DPI
    resolution=(300, 300),
    resolutionunit=2,
    # 嵌入eciRGB v2 ICC配置文件
    icc=eci_icc_data
)

注意事项

  • 需替换代码中eciRGB_v2.icc为你的实际文件路径
  • 如果原始图像是sRGB色彩空间,建议先将图像转换为eciRGB v2再合成,否则仅嵌入ICC文件可能导致色彩显示偏差(可使用PIL或colorio库完成色彩转换)

内容的提问来源于stack exchange,提问作者konradns

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 16:16:06