Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tiff_psd_writer_visitor.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <QBuffer>
9
10#include <memory>
11
12#include <tiff.h>
13
15#include <KoColorProfile.h>
16#include <KoColorSpace.h>
18#include <KoConfig.h>
19#include <KoID.h>
20#include <KoUnit.h>
21#include <kis_group_layer.h>
22#include <kis_image.h>
23#include <kis_iterator_ng.h>
24#include <kis_painter.h>
26#include <psd_resource_block.h>
27
28#ifdef HAVE_OPENEXR
29#include <half.h>
30#endif
31
32#include "kis_tiff_converter.h"
35
37 : KisTIFFBaseWriter(image, options)
38{
39}
40
42
44{
45 dbgFile << "Starting write of Photoshop layer data";
46
52 if (layer->image()->width() > MAX_PSD_SIZE || layer->image()->height() > MAX_PSD_SIZE) {
53 dbgFile << "This TIFF file is too big to be represented as a PSD blob!";
55 }
56
57 dbgFile << "Writing root layer projection";
58 KisPaintDeviceSP pd = layer->projection();
59
60 uint16_t color_type = 0;
61 uint16_t sample_format = SAMPLEFORMAT_UINT;
62 const KoColorSpace *destColorSpace = nullptr;
63 // Check colorspace
64 if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format, destColorSpace)) { // unsupported colorspace
65 if (!destColorSpace) {
66 dbgFile << "Unsupported colorspace" << pd->colorSpace()->name();
68 }
69 pd.attach(new KisPaintDevice(*pd));
70 pd->convertTo(destColorSpace);
71 }
72
73 {
74 // WORKAROUND: block any attempts to use YCbCr with alpha channels.
75 // This should not happen because alpha is disabled by default
76 // and the checkbox is blocked for YCbCr and CMYK.
77 KIS_SAFE_ASSERT_RECOVER(color_type != PHOTOMETRIC_YCBCR
78 || !m_options->alpha)
79 {
80 warnFile << "TIFF does not support exporting alpha channels with "
81 "YCbCr. Skipping...";
82 m_options->alpha = false;
83 }
84 }
85
86 // Save depth
87 uint32_t depth = 8 * pd->pixelSize() / pd->channelCount();
88 TIFFSetField(image(), TIFFTAG_BITSPERSAMPLE, depth);
89
90 {
91 // WORKAROUND: block any attempts to use JPEG with >= 8 bits
92
93 if (m_options->compressionType == COMPRESSION_JPEG && depth != 8) {
94 warnFile << "Attempt to export JPEG with multi-byte depth, "
95 "disabling compression";
96 m_options->compressionType = COMPRESSION_NONE;
97 }
98 }
99
100 // Save number of samples
101 if (m_options->alpha) {
102 TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount());
103 const std::array<uint16_t, 1> sampleinfo = {EXTRASAMPLE_UNASSALPHA};
104 TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 1, sampleinfo.data());
105 } else {
106 TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount() - 1);
107 TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 0);
108 }
109
110 // Save colorspace information
111 TIFFSetField(image(), TIFFTAG_PHOTOMETRIC, color_type);
112 TIFFSetField(image(), TIFFTAG_SAMPLEFORMAT, sample_format);
113 TIFFSetField(image(), TIFFTAG_IMAGEWIDTH, layer->image()->width());
114 TIFFSetField(image(), TIFFTAG_IMAGELENGTH, layer->image()->height());
115
116 // Set the compression options
117 TIFFSetField(image(), TIFFTAG_COMPRESSION, m_options->compressionType);
118 if (m_options->compressionType == COMPRESSION_JPEG) {
119 TIFFSetField(image(), TIFFTAG_JPEGQUALITY, m_options->jpegQuality);
120 } else if (m_options->compressionType == COMPRESSION_ADOBE_DEFLATE) {
121 TIFFSetField(image(), TIFFTAG_ZIPQUALITY, m_options->deflateCompress);
122 } else if (m_options->compressionType == COMPRESSION_PIXARLOG) {
123 TIFFSetField(image(),
124 TIFFTAG_PIXARLOGQUALITY,
126 }
127
128 // Set the predictor
129 if (m_options->compressionType == COMPRESSION_LZW
130 || m_options->compressionType == COMPRESSION_ADOBE_DEFLATE)
131 TIFFSetField(image(), TIFFTAG_PREDICTOR, m_options->predictor);
132
133 // Use contiguous configuration
134 TIFFSetField(image(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
135
136 // Do not set the rowsperstrip, as it's incompatible with JPEG
137
138 // But do set YCbCr 4:4:4 if applicable
139 if (color_type == PHOTOMETRIC_YCBCR) {
140 TIFFSetField(image(), TIFFTAG_YCBCRSUBSAMPLING, 1, 1);
141 TIFFSetField(image(), TIFFTAG_YCBCRPOSITIONING, YCBCRPOSITION_CENTERED);
142 if (m_options->compressionType == COMPRESSION_JPEG) {
143 TIFFSetField(image(), TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RAW);
144 }
145 }
146
147 // Save profile
148 if (m_options->saveProfile) {
149 const KoColorProfile *profile = pd->colorSpace()->profile();
150 if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) {
151 QByteArray ba = profile->rawData();
152 TIFFSetField(image(), TIFFTAG_ICCPROFILE, ba.size(), ba.constData());
153 }
154 }
155 tsize_t stripsize = TIFFStripSize(image());
156 std::unique_ptr<std::remove_pointer_t<tdata_t>, decltype(&_TIFFfree)> buff(
157 _TIFFmalloc(stripsize),
158 &_TIFFfree);
160 buff && "Unable to allocate buffer for TIFF!",
162 qint32 height = layer->image()->height();
163 qint32 width = layer->image()->width();
164 bool r = true;
165 for (qint32 y = 0; y < height; y++) {
167 switch (color_type) {
168 case PHOTOMETRIC_MINISBLACK: {
169 const std::array<quint8, 5> poses = {0, 1};
170 r = copyDataToStrips(it,
171 buff.get(),
172 depth,
173 sample_format,
174 1,
175 poses);
176 } break;
177 case PHOTOMETRIC_RGB: {
178 const auto poses = [&]() -> std::array<quint8, 5> {
179 if (sample_format == SAMPLEFORMAT_IEEEFP) {
180 return {0, 1, 2, 3};
181 } else {
182 return {2, 1, 0, 3};
183 }
184 }();
185 r = copyDataToStrips(it,
186 buff.get(),
187 depth,
188 sample_format,
189 3,
190 poses);
191 } break;
192 case PHOTOMETRIC_SEPARATED: {
193 const std::array<quint8, 5> poses = {0, 1, 2, 3, 4};
194 r = copyDataToStrips(it,
195 buff.get(),
196 depth,
197 sample_format,
198 4,
199 poses);
200 } break;
201 case PHOTOMETRIC_ICCLAB:
202 case PHOTOMETRIC_YCBCR: {
203 const std::array<quint8, 5> poses = {0, 1, 2, 3};
204 r = copyDataToStrips(it,
205 buff.get(),
206 depth,
207 sample_format,
208 3,
209 poses);
210 } break;
211 }
212 if (!r)
214 TIFFWriteScanline(image(),
215 buff.get(),
216 static_cast<quint32>(y),
217 (tsample_t)-1);
218 }
219 buff.reset();
220
222
227 {
228 const bool haveLayers = layer->childCount() > 1 || KisPainter::checkDeviceHasTransparency(layer->firstChild()->projection());
229
230 QBuffer buf;
231 buf.open(QIODevice::WriteOnly);
232
233 dbgFile << "m_image->rootLayer->childCount" << layer->childCount() << buf.pos();
234
235 if (haveLayers) {
236 KisTiffPsdLayerRecord layerSection(
237 TIFFIsBigEndian(image()),
238 static_cast<uint32_t>(width),
239 static_cast<uint32_t>(height),
240 static_cast<uint16_t>(depth),
241 static_cast<uint16_t>(pd->channelCount()),
242 color_type,
243 true);
244
245 if (!layerSection.write(buf, layer, static_cast<psd_compression_type>(m_options->psdCompressionType))) {
246 dbgFile << "failed to write layer section. Error:" << layerSection.record()->error << buf.pos();
248 }
249 } else {
250 // else write a zero length block
251 dbgFile << "No layers, saving empty layers/mask block" << buf.pos();
252 psdwrite(buf, (quint32)0);
253 }
254
255 buf.close();
256 buf.open(QIODevice::ReadOnly);
257
258 if (!TIFFSetField(image(), TIFFTAG_IMAGESOURCEDATA, static_cast<uint32_t>(buf.size()), buf.data().constData())) {
259 dbgFile << "Failed to write the PSD image block to the TIFF field";
261 }
262 }
263
268 {
269 // IMAGE RESOURCES SECTION
270 KisTiffPsdResourceRecord resourceSection;
271
272 for (vKisAnnotationSP_it it = layer->image()->beginAnnotations(); it != layer->image()->endAnnotations(); ++it) {
273 KisAnnotationSP annotation = (*it);
274 if (!annotation || annotation->type().isEmpty()) {
275 dbgFile << "Warning: empty annotation";
276 continue;
277 }
278
279 dbgFile << "Annotation:" << annotation->type() << annotation->description();
280
281 if (annotation->type().startsWith(QString("PSD Resource Block:"))) { //
282 PSDResourceBlock *resourceBlock =
283 dynamic_cast<PSDResourceBlock *>(annotation.data());
284 if (resourceBlock) {
285 dbgFile << "Adding PSD Resource Block" << resourceBlock->identifier;
286 resourceSection.resources[(KisTiffPsdResourceRecord::PSDResourceID)resourceBlock->identifier] = resourceBlock;
287 }
288 }
289 }
290
291 // Add resolution block
292 {
293 auto *resInfo = new RESN_INFO_1005();
294 resInfo->hRes = static_cast<int>(INCH_TO_POINT(layer->image()->xRes()));
295 resInfo->vRes = static_cast<int>(INCH_TO_POINT(layer->image()->yRes()));
296 auto *block = new PSDResourceBlock();
297 block->identifier = KisTiffPsdResourceRecord::RESN_INFO;
298 block->resource = resInfo;
299 resourceSection.resources[KisTiffPsdResourceRecord::RESN_INFO] = block;
300 }
301
302 // Add icc block
303 {
304 auto *profileInfo = new ICC_PROFILE_1039();
305 profileInfo->icc = layer->image()->profile()->rawData();
306 auto *block = new PSDResourceBlock();
307 block->identifier = KisTiffPsdResourceRecord::ICC_PROFILE;
308 block->resource = profileInfo;
309 resourceSection.resources[KisTiffPsdResourceRecord::ICC_PROFILE] = block;
310 }
311
312 dbgFile << "Resource section ready to write";
313
314 QBuffer buf;
315 buf.open(QIODevice::WriteOnly);
316
317 if (!resourceSection.write(buf)) {
318 dbgFile << "Failed to write resource section. Error:" << resourceSection.error << buf.pos();
320 }
321
322 buf.close();
323 buf.open(QIODevice::WriteOnly);
324
325 if (!TIFFSetField(image(), TIFFTAG_PHOTOSHOP, static_cast<uint32_t>(buf.size()), buf.data().data())) {
326 dbgFile << "Failed to write the PSD resource block to the TIFF field";
328 }
329 }
330
336
337 TIFFWriteDirectory(image());
339}
constexpr qreal INCH_TO_POINT(qreal inch)
Definition KoUnit.h:38
vKisAnnotationSP_it endAnnotations()
qint32 width() const
double xRes() const
double yRes() const
qint32 height() const
const KoColorProfile * profile() const
vKisAnnotationSP_it beginAnnotations()
quint32 pixelSize() const
quint32 channelCount() const
const KoColorSpace * colorSpace() const
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
static bool checkDeviceHasTransparency(KisPaintDeviceSP dev)
void attach(T *p)
bool copyDataToStrips(KisHLineConstIteratorSP it, tdata_t buff, uint32_t depth, uint16_t sample_format, uint8_t nbcolorssamples, const std::array< quint8, 5 > &poses)
KisTIFFOptions * m_options
static bool writeColorSpaceInformation(TIFF *image, const KoColorSpace *cs, uint16_t &color_type, uint16_t &sample_format, const KoColorSpace *&destColorSpace)
bool write(QIODevice &io, KisNodeSP rootLayer, psd_compression_type compressionType)
std::shared_ptr< PSDLayerMaskSection > record() const
QMap< PSDResourceID, PSDResourceBlock * > resources
KisImportExportErrorCode writeImage(KisGroupLayerSP rootLayer)
~KisTiffPsdWriter() override
KisTiffPsdWriter(TIFF *image, KisTIFFOptions *options)
virtual const KoColorProfile * profile() const =0
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define warnFile
Definition kis_debug.h:95
#define dbgFile
Definition kis_debug.h:53
vKisAnnotationSP::iterator vKisAnnotationSP_it
Definition kis_types.h:181
psd_compression_type
Definition psd.h:39
const int MAX_PSD_SIZE
Definition psd.h:29
std::enable_if_t< std::is_arithmetic< T >::value, bool > psdwrite(QIODevice &io, T v)
Definition psd_utils.h:170
virtual KisPaintDeviceSP projection() const =0
KisImageWSP image
KisPaintDeviceSP projection() const override
Definition kis_layer.cc:820
KisNodeSP firstChild() const
Definition kis_node.cpp:361
quint32 childCount() const
Definition kis_node.cpp:414
virtual QByteArray rawData() const
virtual QString type() const