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>
30
31#include <iostream>
32#include <sstream>
33#include <cstring>
34#include <list>
35#include <utility>
36
37#define J2K_CFMT 0
38#define JP2_CFMT 1
39
41 m_doc = doc;
42 m_stop = false;
43}
44
47
51static void error_callback(const char *msg, void *client_data) {
52 JP2Converter *converter = (JP2Converter*) client_data;
53 converter->addErrorString(msg);
54}
55
59static void warning_callback(const char *msg, void *client_data) {
60 JP2Converter *converter = (JP2Converter*) client_data;
61 converter->addWarningString(msg);
62}
63
67static void info_callback(const char *msg, void *client_data) {
68 JP2Converter *converter = (JP2Converter*) client_data;
69 converter->addInfoString(msg);
70}
71
72static int getFileFormat(const char *filename) {
73 static const std::list<std::pair<const char*, int>> formats= {
74 {"j2k", J2K_CFMT},
75 {"jp2", JP2_CFMT},
76 {"j2c", J2K_CFMT},
77 {"jpc", J2K_CFMT},
78 {"jpx", J2K_CFMT},
79 {"jpf", J2K_CFMT}
80 };
81 const char *ext = strrchr(filename, '.');
82 if (ext == NULL) {
83 return -1;
84 }
85 ext++;
86 if (*ext) {
87 for (const auto &format : formats) {
88 if (strcasecmp(ext, format.first) == 0) {
89 return format.second;
90 }
91 }
92 }
93
94 return -1;
95}
96
97#define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
98#define JP2_MAGIC "\x0d\x0a\x87\x0a"
99#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51"
100
101int JP2Converter::infile_format(const char *fname) {
102 FILE *reader;
103 const char *s, *magic_s;
104 int ext_format, magic_format;
105 unsigned char buf[12];
106 OPJ_SIZE_T l_nb_read;
107
108 reader = fopen(fname, "rb");
109
110 if (reader == NULL) {
111 return -2;
112 }
113
114 memset(buf, 0, 12);
115 l_nb_read = fread(buf, 1, 12, reader);
116 fclose(reader);
117 if (l_nb_read != 12) {
118 return -1;
119 }
120 ext_format = getFileFormat(fname);
121 if (memcmp(buf, JP2_RFC3745_MAGIC, 12) == 0
122 || memcmp(buf, JP2_MAGIC, 4) == 0) {
123 magic_format = JP2_CFMT;
124 magic_s = ".jp2";
125 } else if (memcmp(buf, J2K_CODESTREAM_MAGIC, 4) == 0) {
126 magic_format = J2K_CFMT;
127 magic_s = ".j2k, .j2c, .jpc, .jpx, or .jpf";
128 } else {
129 return -1;
130 }
131
132 if (magic_format == ext_format) {
133 return ext_format;
134 }
135
136 if (strlen(fname) >= 4) {
137 s = fname + strlen(fname) - 4;
138 std::ostringstream buffer;
139 buffer << "The extension of this file is incorrect.\n"
140 << "Found " << s << " while it should be " << magic_s << ".";
141 addErrorString(buffer.str());
142 }
143 return magic_format;
144}
145
148 const QByteArray file_str = filename.toUtf8();
149 opj_codec_t *l_codec = 0;
150 opj_dparameters_t parameters;
151 bool hasColorSpaceInfo = false;
152 opj_stream_t *l_stream = NULL;
153 opj_image_t *image = NULL;
154 int pos = 0;
155 KisHLineIteratorSP it = NULL;
156 unsigned int numComponents = 0;
157 unsigned int precision = 0;
158 const KoColorSpace *colorSpace = 0;
159 QVector<int> channelorder;
160 KisPaintLayerSP layer;
161 bool isSigned;
162 int32_t signedCorrection = 0;
163 uint32_t w=0, h=0;
164
165 // decompression parameters
166 opj_set_default_decoder_parameters(&parameters);
167 // Determine the type
168 parameters.decod_format = infile_format(file_str.constData());
169 if (parameters.decod_format == -1) {
170 addErrorString("Not a JPEG 2000 file.");
172 goto beach;
173 }
174
175 // Decode the file
176 /* get a decoder handle */
177 switch (parameters.decod_format) {
178 case J2K_CFMT: {
179 l_codec = opj_create_decompress(OPJ_CODEC_J2K);
180 break;
181 }
182 case JP2_CFMT: {
183 l_codec = opj_create_decompress(OPJ_CODEC_JP2);
184 hasColorSpaceInfo = true;
185 break;
186 }
187 }
188 Q_ASSERT(l_codec);
189
190 opj_codec_set_threads( l_codec,QThread::idealThreadCount() );
191
192 /* setup the decoder decoding parameters using user parameters */
193 opj_setup_decoder(l_codec, &parameters);
194
195 l_stream = opj_stream_create_default_file_stream(file_str.constData(), 1);
196 if (!l_stream) {
197 addErrorString("Failed to create the stream");
199 goto beach;
200 }
201
202 // Setup an event handling
203 opj_set_info_handler(l_codec, info_callback, this);
204 opj_set_error_handler(l_codec, error_callback, this);
205 opj_set_warning_handler(l_codec, warning_callback, this);
206
207 if (!opj_read_header(l_stream, l_codec, &image)) {
208 addErrorString("Failed to read the header");
210 goto beach;
211 }
212
213 /* Get the decoded image */
214 if (!(opj_decode(l_codec, l_stream, image)
215 && opj_end_decompress(l_codec, l_stream))) {
216 addErrorString("Failed to decode image");
218 goto beach;
219 }
220
221 // Look for the colorspace
222 numComponents = image->numcomps;
223 if (image->numcomps == 0) {
224 addErrorString("Image must have at least one component");
226 goto beach;
227 }
228 precision = image->comps[0].prec;
229 for (uint32_t i = 1; i < numComponents; ++i) {
230 if (image->comps[i].prec != precision) {
231 std::ostringstream buffer;
232 buffer << "All components must have the same bit depth "
233 << precision;
234 addErrorString(buffer.str());
236 goto beach;
237 }
238 }
239 isSigned = false;
240 for (uint32_t i = 0; i < numComponents; ++i) {
241 if ((image->comps[i].dx != 1) || (image->comps[i].dy != 1)) {
242 addErrorString("Sub-sampling not supported");
244 goto beach;
245 }
246 isSigned = isSigned || (image->comps[0].sgnd);
247 }
248 if (isSigned)
249 signedCorrection = 1 << (precision - 1);
250
251 dbgFile
252 << "Image has " << numComponents << " numComponents and a bit depth of "
253 << precision << " for color space " << image->color_space;
254 channelorder = QVector<int>(numComponents);
255 if (!hasColorSpaceInfo) {
256 if (numComponents == 3) {
257 image->color_space = OPJ_CLRSPC_SRGB;
258 } else if (numComponents == 1) {
259 image->color_space = OPJ_CLRSPC_GRAY;
260 }
261 }
262 switch (image->color_space) {
263 case OPJ_CLRSPC_UNKNOWN:
264 case OPJ_CLRSPC_UNSPECIFIED:
265 break;
266 case OPJ_CLRSPC_SRGB: {
267 if (precision == 16 || precision == 12) {
268 colorSpace = KoColorSpaceRegistry::instance()->rgb16();
269 } else if (precision == 8) {
270 colorSpace = KoColorSpaceRegistry::instance()->rgb8();
271 }
272 if (numComponents != 3) {
273 std::ostringstream buffer;
274 buffer << "sRGB: number of numComponents " << numComponents
275 << " does not equal 3";
276 addErrorString(buffer.str());
278 goto beach;
279 }
280 channelorder[0] = KoBgrU16Traits::red_pos;
281 channelorder[1] = KoBgrU16Traits::green_pos;
282 channelorder[2] = KoBgrU16Traits::blue_pos;
283 break;
284 }
285 case OPJ_CLRSPC_GRAY: {
286 if (precision == 16 || precision == 12) {
289 } else if (precision == 8) {
292 }
293 if (numComponents != 1) {
294 std::ostringstream buffer;
295 buffer << "Grayscale: number of numComponents " << numComponents
296 << " greater than 1";
297 addErrorString(buffer.str());
299 goto beach;
300 }
301 channelorder[0] = 0;
302 break;
303 }
304 case OPJ_CLRSPC_SYCC:
305 addErrorString("YUV color space not supported");
307 goto beach;
308 break;
309 case OPJ_CLRSPC_EYCC:
310 addErrorString("eYCC color space not supported");
312 goto beach;
313 break;
314 case OPJ_CLRSPC_CMYK:
315 addErrorString("CMYK color space not supported");
317 goto beach;
318 break;
319 default:
320 break;
321 }
322
323 if (!colorSpace) {
324 addErrorString("No color space found for image");
326 goto beach;
327 }
328
329 // Create the image
330 w = (uint32_t)(image->x1 - image->x0);
331 h = (uint32_t)(image->y1 - image->y0);
332 if (m_image == 0) {
333 m_image = new KisImage(m_doc->createUndoStore(), w, h,
334 colorSpace, "built image");
335 }
336
337 // Create the layer
340 m_image->addNode(layer);
341
342 // Set the data
343 it = layer->paintDevice()->createHLineIteratorNG(0, 0, w);
344 for (OPJ_UINT32 v = 0; v < image->y1; ++v) {
345 if (precision == 16 || precision == 12) {
346 do {
347 quint16 *px = reinterpret_cast<quint16*>(it->rawData());
348 for (uint32_t i = 0; i < numComponents; ++i) {
349 px[channelorder[i]] = image->comps[i].data[pos]
350 + signedCorrection;
351 }
352 colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
353 ++pos;
354
355 } while (it->nextPixel());
356 } else if (precision == 8) {
357 do {
358 quint8 *px = it->rawData();
359 for (uint32_t i = 0; i < numComponents; ++i) {
360 px[channelorder[i]] = image->comps[i].data[pos]
361 + signedCorrection;
362 }
363 colorSpace->setOpacity(px, OPACITY_OPAQUE_U8, 1);
364 ++pos;
365
366 } while (it->nextPixel());
367 }
368 it->nextRow();
369 }
370
371beach:
372 if (l_stream)
373 opj_stream_destroy(l_stream);
374 if (l_codec)
375 opj_destroy_codec(l_codec);
376 if (image)
377 opj_image_destroy(image);
378 if (!err.empty())
379 m_doc->setErrorMessage(i18n(err.c_str()));
380 if (!warn.empty())
381 m_doc->setWarningMessage(i18n(warn.c_str()));
382 return res;
383}
384
388
390 KisPaintLayerSP layer, const JP2ConvertOptions &options) {
391 (void) layer;
392 (void) filename;
393 (void) options;
395}
396
398 m_stop = true;
399}
400
401void JP2Converter::addWarningString(const std::string &str) {
402 if (!warn.empty())
403 warn += "\n";
404 warn += str;
405}
406void JP2Converter::addInfoString(const std::string &str) {
407 dbgFile
408 << str.c_str();
409}
410
411void JP2Converter::addErrorString(const std::string &str) {
412 if (!err.empty())
413 err += "\n";
414 err += str;
415}
416
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:742
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:56
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())