我基于TinyTIFF、@haraldK 关于这个 SO 问题的answer、TIFF spec 和Photoshop TIFF spec 开始了一个解决方案。这是编写 TIFF 的最简单方法。我输入了编写 Photoshop 部分的代码,但是还没有完成。
请注意,Photoshop 使用 TIFF 图像作为“预览”图像,类似于 PSD 文件末尾的拼合合成图像。 Photoshop TIFF 部分包含所有图层的像素数据(再次类似于 PSD)。 Adobe 以这种方式使用 TIFF 非常肮脏。您不妨只使用(也很糟糕的)PSD 格式,因为将 PSD 数据粉碎成 TIFF 格式只会增加复杂性而没有任何好处。这就是为什么我没有完成下面的代码。如果你完成了,请在此处发布。
输出类来自Kryo。 pixmap.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();
}
}