Я начал решение на основе 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 (<[email protected]>, <[email protected]>), 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();
}
}
person
NateS
schedule
02.08.2018