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

我很有趣用Photoshop创建一个分层的tif,Photoshop会识别这些层。 我能够创建多页tif ,但Photoshop不会将页面识别为图层。 这些页面可以通过Acrobat查看。 有谁知道Photoshop如何存储tif图层数据以及如何使用Java生成?

谢谢。

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

相反,它将图层信息与图层图像数据一起存储在Photoshop特定的TIFF标签中 ; 37724/ImageSourceData ,类型为UNDEFINED (或“只是字节”)。 幸运的是,此标签的内容记录在AdobePhotoshop®TIFF技术说明中 。

此标记的内容将始终以0终止字符串"Adobe Photoshop Document Data Block"开头。 其余内容是各种Photoshop资源,由Photoshop 4字节资源标识符8BIM标识,每个资源密钥为4字节资源密钥,长度为4字节。

关于Photoshop图层,此块中有趣的资源是使用资源键Layr标识的资源。 这与Photoshop文件格式中的图层和蒙版信息部分中记录的结构相同。

还有一个不同的标签, 34377/Photoshop ,其中包含由Photoshop读取和写入的其他图像资源。 它也记录在上述文档的图像资源部分中。 它确实包含一些有关层的有趣信息,但我不确定你需要编写多少这些信息。 您可能需要使用“真实的东西”进行Photoshop安装和测试。

我有代码在PSD ImageIO插件中 读取这两个结构,这可能值得一看,但它还不支持写入。

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


因此,正如您所看到的,这些(大部分)都记录在案,并且肯定在Java中可行,但仍然不是完全无关紧要的。

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

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

Output类来自Kryo 。 pixmap.getPixels()是每像素4个字节,RGBA。

 /* Copyright (c) 2008-2015 Jan W. Krieger (, ), 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 . */ 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(); } }