使用 Java 创建分层 Tif 以在 Photoshop 中使用

Create Layered Tif with Java for use in Photoshop

我对使用 Java 创建分层的 tif 很感兴趣,Photoshop 可以识别图层。我能够 ,但 Photoshop 无法将页面识别为图层。这些页面可以用 Acrobat 查看。任何人都知道 Photoshop 如何存储 tif 图层数据以及如何使用 Java?

生成这些数据

谢谢。

我已经为我的 TIFF ImageIO plugin 研究了这个,据我所知,Photoshop 在 TIFF 中存储图层信息的方式是完全专有的,并且不使用标准的 TIFF 机制,例如使用链接或链接的多页文档嵌套的 IFD (330/SubIFD),或文件类型 (254/NewSubFileType),等等

相反,它存储图层信息, 连同层图像数据,在Photoshop specific TIFF tag中; 37724/ImageSourceData,其类型为 UNDEFINED(或 "just bytes")。幸运的是,此标签的内容记录在 Adobe Photoshop® 中 TIFF 技术说明.

此标签的内容将始终以 0 结尾的字符串开头 "Adobe Photoshop Document Data Block"。其余内容是各种 Photoshop 资源,由 Photoshop 4 字节资源标识符 8BIM 标识,后跟每个资源的 4 字节资源键和 4 字节长度。

关于 Photoshop 图层,此块中有趣的资源是用资源键 Layr 标识的资源。这与 Photoshop 文件格式 Layer and Mask Information Section 中记录的结构相同。

还有一个不同的标签,34377/Photoshop,其中包含由 Photoshop 读取和写入的其他图像资源。它也记录在上述文档的 Image Resources Section 中。它确实包含一些关于图层的有趣信息,但我不确定您需要编写多少信息。您可能需要使用 "real thing".

安装和测试 Photoshop

我确实有 读取 PSD ImageIO plugin 中这两个结构的代码,这可能值得一看,但它尚不支持写入。

当您可以编写内容 Photoshop TIFF 标签时,您应该能够将其作为 TIFF IIOMetadata 的一部分传递给 TIFFImageWriter,编写者会将其与任何其他元数据一起写入,并且您传递的像素数据。


因此,如您所见,这一切(大部分)都已记录在案,并且在 Java 中肯定可行,但仍不完全是微不足道的。

我开始了一个基于TinyTIFF, the from @haraldK on this SO question, the TIFF spec, and the Photoshop TIFF spec的解决方案。这是编写 TIFF 的最简单的可能方法。我把Photoshop部分的代码放进去了,但是还没写完

请注意,Photoshop 使用 TIFF 图像作为 "preview" 图像,类似于 PSD 文件末尾的拼合合成图像。 Photoshop TIFF 部分包含所有图层的像素数据(再次类似于 PSD)。 Adobe 以这种方式使用 TIFF 是相当肮脏的。您还不如使用(同样糟糕的)PSD 格式,因为将 PSD 数据粉碎成 TIFF 格式只会增加复杂性而没有任何好处。这就是为什么我没有完成下面的代码。如果您完成了它,请在此处 post。

输出 class 来自 Kryopixmap.getPixels()是每个像素4个字节,RGBA。

/* Copyright (c) 2008-2015 Jan W. Krieger (<jan@jkrieger.de>, <j.krieger@dkfz.de>), German Cancer Research Center (DKFZ) & IWR, University of Heidelberg
 * Copyright (c) 2018, Nathan Sweet, Esoteric Software LLC
 * All rights reserved.
 * 
 * This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
 * License (LGPL) as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later
 * version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You
 * should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */

public class TiffWriter {
    private Output out;
    private int width, height;

    private int ifdCount, ifdLastOffset, ifdData, headerStart;
    private Output header;

    public void start (OutputStream output, int width, int height) throws IOException {
        this.out = new Output(output);
        this.width = width;
        this.height = height;

        out.writeByte('M'); // Big endian.
        out.writeByte('M');
        out.writeShort(42); // Magic number.
        ifdLastOffset = out.total();
        out.writeInt(8); // Offset of first IFD.
    }

    public void frame (Pixmap pixmap, String name, int frame, int endFrame) throws IOException {
        ByteBuffer pixels = pixmap.getPixels();

        headerStart = out.total();
        ifdData = 2 + TIFF_HEADER_MAX_ENTRIES * 12;
        ifdCount = 0;
        header = new Output(TIFF_HEADER_SIZE + 2);
        header.setPosition(2);

        writeLongIFD(TIFF_FIELD_IMAGEWIDTH, width);
        writeLongIFD(TIFF_FIELD_IMAGELENGTH, height);
        writeShortIFD(TIFF_FIELD_BITSPERSAMPLE, 8, 8, 8);
        writeShortIFD(TIFF_FIELD_COMPRESSION, COMPRESSION_NO);
        writeShortIFD(TIFF_FIELD_PHOTOMETRICINTERPRETATION, PHOTOMETRIC_INTERPRETATION_RGB);
        writeLongIFD(TIFF_FIELD_STRIPOFFSETS, headerStart + 2 + TIFF_HEADER_SIZE);
        writeShortIFD(TIFF_FIELD_SAMPLESPERPIXEL, 4);
        writeLongIFD(TIFF_FIELD_ROWSPERSTRIP, height);
        writeLongIFD(TIFF_FIELD_STRIPBYTECOUNTS, width * height);
        writeRationalIFD(TIFF_FIELD_XRESOLUTION, 720000, 10000);
        writeRationalIFD(TIFF_FIELD_YRESOLUTION, 720000, 10000);
        writeShortIFD(TIFF_FIELD_PLANARCONFIG, PLANAR_CONFIGURATION_CHUNKY);
        writeShortIFD(TIFF_FIELD_RESOLUTIONUNIT, RESOLUTION_UNIT_INCH);
        writeShortIFD(TIFF_FIELD_EXTRASAMPLES, 1); // Adds alpha to last samples per pixel.
        // writeIFDEntrySHORT(TIFF_FIELD_SAMPLEFORMAT, SAMPLE_FORMAT_FLOAT);

        // Photoshop layer entry.
        ifdCount++;
        header.writeShort(TIFF_FIELD_PHOTOSHOP_IMAGESOURCEDATA);
        header.writeShort(TIFF_TYPE_UNDEFINED);
        int sizePosition = header.position();
        header.writeInt(0); // Size in bytes.
        header.writeInt(ifdData + headerStart);
        int pos = header.position();
        header.setPosition(ifdData);
        writeString(header, "Adobe Photoshop Document Data Block");
        // Unfinished!
        int size = header.position() - ifdData;
        ifdData = header.position();
        header.setPosition(sizePosition);
        header.writeInt(size);
        header.setPosition(pos);

        if (ifdCount > TIFF_HEADER_MAX_ENTRIES) throw new RuntimeException();

        header.setPosition(0);
        header.writeShort(ifdCount);

        header.setPosition(2 + ifdCount * 12); // header start + 12 bytes per IFD entry
        header.writeInt(headerStart + 2 + TIFF_HEADER_SIZE + width * height);

        out.writeBytes(header.getBuffer(), 0, TIFF_HEADER_SIZE + 2);

        ifdLastOffset = headerStart + 2 + ifdCount * 12;

        pixels.position(0);
        for (int i = 0, n = width * height * 4; i < n; i += 4) {
            byte a = pixels.get(i + 3);
            float pma = (a & 0xff) / 255f;
            out.writeByte((byte)((pixels.get(i) & 0xff) * pma));
            out.writeByte((byte)((pixels.get(i + 1) & 0xff) * pma));
            out.writeByte((byte)((pixels.get(i + 2) & 0xff) * pma));
            out.writeByte(a);
        }
        pixels.position(0);
    }

    public void end () throws IOException {
        out.close();

        // Erase last IFD offset.
        RandomAccessFile file = new RandomAccessFile("test.tif", "rw");
        file.seek(ifdLastOffset);
        file.write((byte)0);
        file.write((byte)0);
        file.write((byte)0);
        file.write((byte)0);
        file.close();
    }

    public void close () throws IOException {
        end();
    }

    private void writeString (Output output, String value) {
        for (int i = 0, n = value.length(); i < n; i++)
            output.writeByte(value.charAt(i));
        output.writeByte(0);
    }

    private void writeLongIFD (int tag, int data) {
        ifdCount++;
        header.writeShort(tag);
        header.writeShort(TIFF_TYPE_LONG);
        header.writeInt(1);
        header.writeInt(data);
    }

    private void writeShortIFD (int tag, int data) {
        ifdCount++;
        header.writeShort(tag);
        header.writeShort(TIFF_TYPE_SHORT);
        header.writeInt(1);
        header.writeShort(data);
        header.writeShort(0); // Pad bytes.
    }

    private void writeShortIFD (int tag, int... data) {
        ifdCount++;
        header.writeShort(tag);
        header.writeShort(TIFF_TYPE_SHORT);
        header.writeInt(data.length);
        if (data.length == 1)
            header.writeInt(data[0]);
        else {
            header.writeInt(ifdData + headerStart);
            int pos = header.position();
            header.setPosition(ifdData);
            for (int value : data)
                header.writeShort(value);
            ifdData = header.position();
            header.setPosition(pos);
        }
    }

    private void writeRationalIFD (int tag, int numerator, int denominator) {
        ifdCount++;
        header.writeShort(tag);
        header.writeShort(TIFF_TYPE_RATIONAL);
        header.writeInt(1);
        header.writeInt(ifdData + headerStart);
        int pos = header.position();
        header.setPosition(ifdData);
        header.writeInt(numerator);
        header.writeInt(denominator);
        ifdData = header.position();
        header.setPosition(pos);
    }

    static private final int TIFF_HEADER_SIZE = 510;
    static private final int TIFF_HEADER_MAX_ENTRIES = 16;

    static private final int TIFF_FIELD_IMAGEWIDTH = 256;
    static private final int TIFF_FIELD_IMAGELENGTH = 257;
    static private final int TIFF_FIELD_BITSPERSAMPLE = 258;
    static private final int TIFF_FIELD_COMPRESSION = 259;
    static private final int TIFF_FIELD_PHOTOMETRICINTERPRETATION = 262;
    static private final int TIFF_FIELD_IMAGEDESCRIPTION = 270;
    static private final int TIFF_FIELD_STRIPOFFSETS = 273;
    static private final int TIFF_FIELD_SAMPLESPERPIXEL = 277;
    static private final int TIFF_FIELD_ROWSPERSTRIP = 278;
    static private final int TIFF_FIELD_STRIPBYTECOUNTS = 279;
    static private final int TIFF_FIELD_XRESOLUTION = 282;
    static private final int TIFF_FIELD_YRESOLUTION = 283;
    static private final int TIFF_FIELD_PLANARCONFIG = 284;
    static private final int TIFF_FIELD_RESOLUTIONUNIT = 296;
    static private final int TIFF_FIELD_EXTRASAMPLES = 338;
    static private final int TIFF_FIELD_SAMPLEFORMAT = 339;
    static private final int TIFF_FIELD_PHOTOSHOP_IMAGESOURCEDATA = 37724;

    static private final int TIFF_TYPE_BYTE = 1;
    static private final int TIFF_TYPE_ASCII = 2;
    static private final int TIFF_TYPE_SHORT = 3;
    static private final int TIFF_TYPE_LONG = 4;
    static private final int TIFF_TYPE_RATIONAL = 5;
    static private final int TIFF_TYPE_UNDEFINED = 7;

    static private final int SAMPLE_FORMAT_UNSIGNED_INT = 1;
    static private final int SAMPLE_FORMAT_SIGNED_INT = 2;
    static private final int SAMPLE_FORMAT_FLOAT = 3;
    static private final int SAMPLE_FORMAT_UNDEFINED = 4;

    static private final int COMPRESSION_NO = 1;
    static private final int COMPRESSION_CCITT_HUFFMAN = 2;
    static private final int COMPRESSION_T4 = 3;
    static private final int COMPRESSION_T6 = 4;
    static private final int COMPRESSION_LZW = 5;
    static private final int COMPRESSION_JPEG_OLD = 6;
    static private final int COMPRESSION_JPEG_NEW = 7;
    static private final int COMPRESSION_DEFLATE = 8;

    static private final int PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0;
    static private final int PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 1;
    static private final int PHOTOMETRIC_INTERPRETATION_RGB = 2;
    static private final int PHOTOMETRIC_INTERPRETATION_PALETTE = 3;
    static private final int PHOTOMETRIC_INTERPRETATION_TRANSPARENCY = 4;

    static private final int PLANAR_CONFIGURATION_CHUNKY = 1;
    static private final int PLANAR_CONFIGURATION_PLANAR = 2;

    static private final int RESOLUTION_UNIT_NO = 1;
    static private final int RESOLUTION_UNIT_INCH = 2;
    static private final int RESOLUTION_UNIT_CENTIMETER = 3;

    static public void main (String[] args) throws Exception {
        FileOutputStream output = new FileOutputStream("test.tif");
        TiffWriter writer = new TiffWriter();
        writer.start(output, imageWidth, imageHeight);
        for (int i = 0; i < 16; i++) {
            Pixmap pixmap = new Pixmap(...);
            writer.frame(pixmap, "run", i, 16);
        }
        writer.end();
        writer.close();
    }
}