【问题标题】:Create Layered Tif with Java for use in Photoshop使用 Java 创建分层 Tif 以在 Photoshop 中使用
【发布时间】:2018-08-02 19:54:08
【问题描述】:

我对使用 Java 创建分层 tif 以使 Photoshop 能够识别图层的方式很感兴趣。我可以create a multi-page tif,但 Photoshop 无法将页面识别为图层。不过,这些页面可以使用 Acrobat 查看。有谁知道 Photoshop 如何存储 tif 图层数据以及如何使用 Java 生成这些数据?

谢谢。

【问题讨论】:

    标签: java image-processing photoshop tiff javax.imageio


    【解决方案1】:

    我为我的TIFF ImageIO plugin 对此进行了研究,据我了解,Photoshop 在 TIFF 中存储图层信息的方式是完全专有的,不使用标准 TIFF 机制,例如使用链接或嵌套 IFD 的多页文档 (@ 987654328@),或文件类型(254/NewSubFileType)等

    相反,它存储层信息, 连同图层图像数据,在Photoshop specific TIFF tag中; 37724/ImageSourceData,其类型为 UNDEFINED(或“仅字节”)。幸运的是,这个标签的内容记录在Adobe Photoshop® TIFF Technical Notes

    这个标签的内容总是以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 中。它确实包含一些关于层的有趣信息,但我不确定你需要写多少。您可能需要安装 Photoshop 并使用“真实的东西”进行测试。

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

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


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

    【讨论】:

    • 感谢您的研究、详细信息和链接。为开始实施过程提供了良好的基础。
    • @jonD02 你有没有机会实现这个?
    【解决方案2】:

    我基于TinyTIFF、@haraldK 关于这个 SO 问题的answerTIFF specPhotoshop TIFF spec 开始了一个解决方案。这是编写 TIFF 的最简单方法。我输入了编写 Photoshop 部分的代码,但是还没有完成

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

    输出类来自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();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-09-20
      • 2016-07-02
      • 1970-01-01
      • 2019-12-05
      • 2021-02-19
      • 2018-06-16
      • 1970-01-01
      • 1970-01-01
      • 2013-08-28
      相关资源
      最近更新 更多