Krita Source Code Documentation
Loading...
Searching...
No Matches
jp2_converter.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2019 Aaron Boxer <boxerab@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-only
5 */
6
7#include "jp2_converter.h"
8
9#include <openjpeg.h>
10
11#include <QApplication>
12
13#include <QMessageBox>
14
16#include <KoColorSpaceTraits.h>
19#include <KoColorSpace.h>
21
22#include <KisDocument.h>
23#include <kis_image.h>
24#include <kis_group_layer.h>
25#include <kis_paint_layer.h>
26#include <kis_paint_device.h>
27#include <kis_transaction.h>
28#include "kis_iterator_ng.h"
29#include <QThread>
31
32#include <iostream>
33#include <sstream>
34#include <cstring>
35#include <list>
36#include <utility>
37
38#define J2K_CFMT 0
39#define JP2_CFMT 1
40
42 m_doc = doc;
43 m_stop = false;
44}
45
48
52static void error_callback(const char *msg, void *client_data) {
53 JP2Converter *converter = (JP2Converter*) client_data;
54 converter->addErrorString(msg);
55}
56
60static void warning_callback(const char *msg, void *client_data) {
61 JP2Converter *converter = (JP2Converter*) client_data;
62 converter->addWarningString(msg);
63}
64
68static void info_callback(const char *msg, void *client_data) {
69 JP2Converter *converter = (JP2Converter*) client_data;
70 converter->addInfoString(msg);
71}
72
73static int getFileFormat(const char *filename) {
74 static const std::list<std::pair<const char*, int>> formats= {
75 {"j2k", J2K_CFMT},
76 {"jp2", JP2_CFMT},
77 {"j2c", J2K_CFMT},
78 {"jpc", J2K_CFMT},
79 {"jpx", J2K_CFMT},
80 {"jpf", J2K_CFMT}
81 };
82 const char *ext = strrchr(filename, '.');
83 if (ext == NULL) {
84 return -1;
85 }
86 ext++;
87 if (*ext) {
88 for (const auto &format : formats) {
89 if (strcasecmp(ext, format.first) == 0) {
90 return format.second;
91 }
92 }
93 }
94
95 return -1;
96}
97
98#define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
99#define JP2_MAGIC "\x0d\x0a\x87\x0a"
100#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51"
101
102int JP2Converter::infile_format(const char *fname) {
103 FILE *reader;
104 const char *s, *magic_s;
105 int ext_format, magic_format;
106 unsigned char buf[12];
107 OPJ_SIZE_T l_nb_read;
108
109 reader = fopen(fname, "rb");
110
111 if (reader == NULL) {
112 return -2;
113 }
114
115 memset(buf, 0, 12);
116 l_nb_read = fread(buf, 1, 12, reader);
117 fclose(reader);
118 if (l_nb_read != 12) {
119 return -1;
120 }
121 ext_format = getFileFormat(fname);
122 if (memcmp(buf, JP2_RFC3745_MAGIC, 12) == 0
123 || memcmp(buf, JP2_MAGIC, 4) == 0) {
124 magic_format = JP2_CFMT;
125 magic_s = ".jp2";
126 } else if (memcmp(buf, J2K_CODESTREAM_MAGIC, 4) == 0) {
127 magic_format = J2K_CFMT;
128 magic_s = ".j2k, .j2c, .jpc, .jpx, or .jpf";
129 } else {
130 return -1;
131 }
132
133 if (magic_format == ext_format) {
134 return ext_format;
135 }
136
137 if (strlen(fname) >= 4) {
138 s = fname + strlen(fname) - 4;
139 std::ostringstream buffer;
140 buffer << "The extension of this file is incorrect.\n"
141 << "Found " << s << " while it should be " << magic_s << ".";
142 addErrorString(buffer.str());
143 }
144 return magic_format;
145}
146
149 const QByteArray file_str = filename.toUtf8();
150 opj_codec_t *l_codec = 0;
151 opj_dparameters_t parameters;
152 bool hasColorSpaceInfo = false;
153 opj_stream_t *l_stream = NULL;
154 opj_image_t *image = NULL;
155 int pos = 0;
156 KisHLineIteratorSP it = NULL;
157 unsigned int numComponents = 0;
158 unsigned int precision = 0;
159 const KoColorSpace *colorSpace = 0;
160 QVector<int> channelorder;
161 KisPaintLayerSP layer;
162 bool isSigned;
163 int32_t signedCorrection = 0;
164 uint32_t w=0, h=0;
165
166 // decompression parameters
167 opj_set_default_decoder_parameters(&parameters);
168 // Determine the type
169 parameters.decod_format = infile_format(file_str.constData());
170 if (parameters.decod_format == -1) {
171 addErrorString("Not a JPEG 2000 file.");
173 goto beach;
174 }
175
176 // Decode the file
177 /* get a decoder handle */
178 switch (parameters.decod_format) {
179 case J2K_CFMT: {
180 l_codec = opj_create_decompress(OPJ_CODEC_J2K);
181 break;
182 }
183 case JP2_CFMT: {
184 l_codec = opj_create_decompress(OPJ_CODEC_JP2);
185 hasColorSpaceInfo = true;
186 break;
187 }
188 }
189 Q_ASSERT(l_codec);
190
191 opj_codec_set_threads( l_codec,QThread::idealThreadCount() );
192
193 /* setup the decoder decoding parameters using user parameters */
194 opj_setup_decoder(l_codec, &parameters);
195
196 l_stream = opj_stream_create_default_file_stream(file_str.constData(), 1);
197 if (!l_stream) {
198 addErrorString("Failed to create the stream");
200 goto beach;
201 }
202
203 // Setup an event handling
204 opj_set_info_handler(l_codec, info_callback, this);
205 opj_set_error_handler(l_codec, error_callback, this);
206 opj_set_warning_handler(l_codec, warning_callback, this);
207
208 if (!opj_read_header(l_stream, l_codec, &image)) {
209 addErrorString("Failed to read the header");
211 goto beach;
212 }
213
214 /* Get the decoded image */
215 if (!(opj_decode(l_codec, l_stream, image)
216 && opj_end_decompress(l_codec, l_stream))) {
217 addErrorString("Failed to decode image");
219 goto beach;
220 }
221
222 // Look for the colorspace
223 numComponents = image->numcomps;
224 if (image->numcomps == 0) {
225 addErrorString("Image must have at least one component");
227 goto beach;
228 }
229 precision = image->comps[0].prec;
230 for (uint32_t i = 1; i < numComponents; ++i) {
231 if (image->comps[i].prec != precision) {
232 std::ostringstream buffer;
233 buffer << "All components must have the same bit depth "
234 << precision;
235 addErrorString(buffer.str());
237 goto beach;
238 }
239 }
240 isSigned = false;
241 for (uint32_t i = 0; i < numComponents; ++i) {
242 if ((image->comps[i].dx != 1) || (image->comps[i].dy != 1)) {
243 addErrorString("Sub-sampling not supported");
245 goto beach;
246 }
247 isSigned = isSigned || (image->comps[0].sgnd);
248 }
249 if (isSigned)
250 signedCorrection = 1 << (precision - 1);
251
252 dbgFile
253 << "Image has " << numComponents << " numComponents and a bit depth of "
254 << precision << " for color space " << image->color_space;
255 channelorder = QVector<int>(numComponents);
256 if (!hasColorSpaceInfo) {
257 if (numComponents == 3) {
258 image->color_space = OPJ_CLRSPC_SRGB;
259 } else if (numComponents == 1) {
260 image->color_space = OPJ_CLRSPC_GRAY;
261 }
262 }
263 switch (image->color_space) {
264 case OPJ_CLRSPC_UNKNOWN:
265 case OPJ_CLRSPC_UNSPECIFIED:
266 break;
267 case OPJ_CLRSPC_SRGB: {
268 if (precision == 16 || precision == 12) {
269 colorSpace = KoColorSpaceRegistry::instance()->rgb16();
270 } else if (precision == 8) {
271 colorSpace = KoColorSpaceRegistry::instance()->rgb8();
272 }
273 if (numComponents != 3) {
274 std::ostringstream buffer;
275 buffer << "sRGB: number of numComponents " << numComponents
276 << " does not equal 3";
277 addErrorString(buffer.str());
279 goto beach;
280 }
281 channelorder[0] = KoBgrU16Traits::red_pos;
282 channelorder[1] = KoBgrU16Traits::green_pos;
283 channelorder[2] = KoBgrU16Traits::blue_pos;
284 break;
285 }
286 case OPJ_CLRSPC_GRAY: {
287 if (precision == 16 || precision == 12) {
290 } else if (precision == 8) {
293 }
294 if (numComponents != 1) {
295 std::ostringstream buffer;
296 buffer << "Grayscale: number of numComponents " << numComponents
297 << " greater than 1";
298 addErrorString(buffer.str());
300 goto beach;
301 }
302 channelorder[0] = 0;
303 break;
304 }
305 case OPJ_CLRSPC_SYCC:
306 addErrorString("YUV color space not supported");
308 goto beach;
309 break;
310 case OPJ_CLRSPC_EYCC:
311 addErrorString("eYCC color space not supported");
313 goto beach;
314 break;
315 case OPJ_CLRSPC_CMYK:
316 addErrorString("CMYK color space not supported");
318 goto beach;
319 break;
320 default:
321 break;
322 }
323
324 if (!colorSpace) {
325 addErrorString("No color space found for image");
327 goto beach;
328 }
329
330 // Create the image
331 w = (uint32_t)(image->x1 - image->x0);
332 h = (uint32_t)(image->y1 - image->y0);
333 if (m_image == 0) {
334 m_image = new KisImage(m_doc->createUndoStore(), w, h,
335 colorSpace, "built image");
336 }
337
338 // Create the layer
341 m_image->addNode(layer);
342
343 // Set the data
344 it = layer->paintDevice()->createHLineIteratorNG(0, 0, w);
345 for (OPJ_UINT32 v = 0; v < image->y1; ++v) {
346 if (precision == 16 || precision == 12) {
347 do {
348 quint16 *px = reinterpret_cast<quint16*>(it->rawData());
349 for (uint32_t i = 0; i < numComponents; ++i) {
350 px[channelorder[i]] = image->comps[i].data[pos]
351 + signedCorrection;
352 }
353 colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
354 ++pos;
355
356 } while (it->nextPixel());
357 } else if (precision == 8) {
358 do {
359 quint8 *px = it->rawData();
360 for (uint32_t i = 0; i < numComponents; ++i) {
361 px[channelorder[i]] = image->comps[i].data[pos]
362 + signedCorrection;
363 }
364 colorSpace->setOpacity(px, OPACITY_OPAQUE_U8, 1);
365 ++pos;
366
367 } while (it->nextPixel());
368 }
369 it->nextRow();
370 }
371
372beach:
373 if (l_stream)
374 opj_stream_destroy(l_stream);
375 if (l_codec)
376 opj_destroy_codec(l_codec);
377 if (image)
378 opj_image_destroy(image);
379 if (!err.empty())
380 m_doc->setErrorMessage(i18n(err.c_str()));
381 if (!warn.empty())
382 m_doc->setWarningMessage(i18n(warn.c_str()));
383 return res;
384}
385
389
391 KisPaintLayerSP layer, const JP2ConvertOptions &options) {
392 (void) layer;
393 (void) filename;
394 (void) options;
396}
397
399 m_stop = true;
400}
401
402void JP2Converter::addWarningString(const std::string &str) {
403 if (!warn.empty())
404 warn += "\n";
405 warn += str;
406}
407void JP2Converter::addInfoString(const std::string &str) {
408 dbgFile
409 << str.c_str();
410}
411
412void JP2Converter::addErrorString(const std::string &str) {
413 if (!err.empty())
414 err += "\n";
415 err += str;
416}
417
qreal v
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const quint8 OPACITY_OPAQUE_U8
KisDocument * m_doc
virtual ~JP2Converter()
KisImportExportErrorCode buildFile(const QString &filename, KisPaintLayerSP layer, const JP2ConvertOptions &options)
void addWarningString(const std::string &str)
KisImageSP m_image
void addInfoString(const std::string &str)
std::string warn
int infile_format(const char *fname)
virtual void cancel()
std::string err
void addErrorString(const std::string &str)
KisImageWSP image()
JP2Converter(KisDocument *doc)
KisImportExportErrorCode buildImage(const QString &filename)
KisUndoStore * createUndoStore()
void setErrorMessage(const QString &errMsg)
void setWarningMessage(const QString &warningMsg)
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
QString id() const
Definition KoID.cpp:63
#define J2K_CFMT
static int getFileFormat(const char *filename)
static void warning_callback(const char *msg, void *client_data)
#define JP2_RFC3745_MAGIC
#define J2K_CODESTREAM_MAGIC
#define JP2_MAGIC
static void error_callback(const char *msg, void *client_data)
#define JP2_CFMT
static void info_callback(const char *msg, void *client_data)
#define dbgFile
Definition kis_debug.h:53
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisPaintDeviceSP paintDevice
static const qint32 blue_pos
static const qint32 green_pos
static const qint32 red_pos
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorSpace * rgb16(const QString &profileName=QString())
#define strcasecmp
Definition xcftools.h:73