9#include "scanlineconverter_p.h"
15#include <QImageReader>
23#ifndef JP2_MAX_IMAGE_WIDTH
24#define JP2_MAX_IMAGE_WIDTH 300000
26#ifndef JP2_MAX_IMAGE_HEIGHT
27#define JP2_MAX_IMAGE_HEIGHT JP2_MAX_IMAGE_WIDTH
38#define JP2_SUBTYPE QByteArrayLiteral("JP2")
39#define J2K_SUBTYPE QByteArrayLiteral("J2K")
41static void error_callback(
const char *msg,
void *client_data)
47static void warning_callback(
const char *msg,
void *client_data)
53static void info_callback(
const char *msg,
void *client_data)
59static OPJ_SIZE_T jp2_read(
void *p_buffer, OPJ_SIZE_T p_nb_bytes,
void *p_user_data)
63 return OPJ_SIZE_T(-1);
65 return OPJ_SIZE_T(dev->read((
char*)p_buffer, (qint64)p_nb_bytes));
68static OPJ_SIZE_T jp2_write(
void *p_buffer, OPJ_SIZE_T p_nb_bytes,
void *p_user_data)
72 return OPJ_SIZE_T(-1);
74 return OPJ_SIZE_T(dev->write((
char*)p_buffer, (qint64)p_nb_bytes));
77static OPJ_BOOL jp2_seek(OPJ_OFF_T p_nb_bytes,
void *p_user_data)
83 return dev->seek(p_nb_bytes) ? OPJ_TRUE : OPJ_FALSE;
86static OPJ_OFF_T jp2_skip(OPJ_OFF_T p_nb_bytes,
void *p_user_data)
92 if (dev->seek(dev->pos() + p_nb_bytes)) {
98class JP2HandlerPrivate
102 : m_jp2_stream(nullptr)
103 , m_jp2_image(nullptr)
104 , m_jp2_codec(nullptr)
106 , m_subtype(JP2_SUBTYPE)
113 opj_image_destroy(m_jp2_image);
114 m_jp2_image =
nullptr;
117 opj_stream_destroy(m_jp2_stream);
118 m_jp2_stream =
nullptr;
121 opj_destroy_codec(m_jp2_codec);
122 m_jp2_codec =
nullptr;
131 OPJ_CODEC_FORMAT detectDecoderFormat(QIODevice *device)
const
133 auto ba = device->
peek(32);
137 return OPJ_CODEC_JP2;
140 return OPJ_CODEC_J2K;
142 return OPJ_CODEC_UNKNOWN;
145 bool createStream(QIODevice *device,
bool read)
147 if (device ==
nullptr) {
150 if (m_jp2_stream ==
nullptr) {
151 m_jp2_stream = opj_stream_default_create(read ? OPJ_TRUE : OPJ_FALSE);
153 if (m_jp2_stream ==
nullptr) {
156 opj_stream_set_user_data(m_jp2_stream, device,
nullptr);
157 opj_stream_set_user_data_length(m_jp2_stream, read ? device->
size() : 0);
158 opj_stream_set_read_function(m_jp2_stream, jp2_read);
159 opj_stream_set_write_function(m_jp2_stream, jp2_write);
160 opj_stream_set_skip_function(m_jp2_stream, jp2_skip);
161 opj_stream_set_seek_function(m_jp2_stream, jp2_seek);
165 bool isImageValid(
const opj_image_t *i)
const
167 return i && i->comps && i->numcomps > 0;
170 void enableThreads(opj_codec_t *codec)
const
172 if (!opj_has_thread_support()) {
173 qInfo() <<
"OpenJPEG doesn't support multi-threading!";
175 qWarning() <<
"Unable to enable multi-threading!";
179 bool createDecoder(QIODevice *device)
184 auto jp2Format = detectDecoderFormat(device);
185 if (jp2Format == OPJ_CODEC_UNKNOWN) {
188 m_jp2_codec = opj_create_decompress(jp2Format);
189 if (m_jp2_codec ==
nullptr) {
192 enableThreads(m_jp2_codec);
197 opj_set_error_handler(m_jp2_codec, error_callback,
nullptr);
201 bool readHeader(QIODevice *device)
203 if (!createStream(device,
true)) {
211 if (!createDecoder(device)) {
215 opj_set_default_decoder_parameters(&m_dparameters);
216 if (!opj_setup_decoder(m_jp2_codec, &m_dparameters)) {
217 qCritical() <<
"Failed to setup JP2 decoder!";
221 if (!opj_read_header(m_jp2_stream, m_jp2_codec, &m_jp2_image)) {
222 qCritical() <<
"Failed to read JP2 header!";
226 return isImageValid(m_jp2_image);
230 bool jp2ToImage(QImage *img)
const
232 Q_ASSERT(img->
depth() == 8 *
sizeof(T) || img->
depth() == 32 *
sizeof(T));
233 for (qint32 c = 0, cc = m_jp2_image->numcomps; c < cc; ++c) {
234 auto cs = cc == 1 ? 1 : 4;
235 auto &&jc = m_jp2_image->comps[c];
236 if (jc.data ==
nullptr)
238 if (qint32(jc.w) != img->
width() || qint32(jc.h) != img->
height())
242 if (std::numeric_limits<T>::is_integer) {
244 if (jc.prec >
sizeof(T) * 8) {
246 divisor = std::max(1,
int(((1ll << jc.prec) - 1) / ((1ll << (
sizeof(T) * 8)) - 1)));
248 for (qint32 y = 0, h = img->
height(); y < h; ++y) {
249 auto ptr =
reinterpret_cast<T *
>(img->
scanLine(y));
250 for (qint32 x = 0, w = img->
width(); x < w; ++x) {
251 qint32 v = jc.data[y * w + x] / divisor;
253 v -= std::numeric_limits<typename std::make_signed<T>::type>::min();
254 *(ptr + x * cs + c) = std::clamp(v, qint32(std::numeric_limits<T>::lowest()), qint32(std::numeric_limits<T>::max()));
258 for (qint32 y = 0, h = img->
height(); y < h; ++y) {
259 auto ptr =
reinterpret_cast<T *
>(img->
scanLine(y));
260 for (qint32 x = 0, w = img->
width(); x < w; ++x) {
261 *(ptr + x * cs + c) = jc.data[y * w + x];
270 void alphaFix(QImage *img)
const
272 if (m_jp2_image->numcomps == 3) {
273 Q_ASSERT(img->
depth() == 32 *
sizeof(T));
274 for (qint32 y = 0, h = img->
height(); y < h; ++y) {
275 auto ptr =
reinterpret_cast<T *
>(img->
scanLine(y));
276 for (qint32 x = 0, w = img->
width(); x < w; ++x) {
277 *(ptr + x * 4 + 3) = std::numeric_limits<T>::is_iec559 ? 1 : std::numeric_limits<T>::max();
283 QImage readImage(QIODevice *device)
285 if (!readHeader(device)) {
289 auto img = imageAlloc(size(), format());
294 if (!opj_decode(m_jp2_codec, m_jp2_stream, m_jp2_image)) {
295 qCritical() <<
"Failed to decoding JP2 image!";
301 if (!jp2ToImage<quint32>(&img))
303 alphaFix<float>(&img);
305 if (!jp2ToImage<quint16>(&img))
307 alphaFix<quint16>(&img);
309 if (!jp2ToImage<quint8>(&img))
311 alphaFix<quint8>(&img);
319 bool checkSizeLimits(qint32 width, qint32 height, qint32 nchannels)
const
321 if (width > JP2_MAX_IMAGE_WIDTH || height > JP2_MAX_IMAGE_HEIGHT || width < 1 || height < 1) {
322 qCritical() <<
"Maximum image size is limited to" << JP2_MAX_IMAGE_WIDTH <<
"x" << JP2_MAX_IMAGE_HEIGHT <<
"pixels";
328 auto neededBytes = qint64(width) * height * nchannels * 4;
329 if (maxBytes > 0 && neededBytes > maxBytes) {
330 qCritical() <<
"Allocation limit set to" << (maxBytes / 1024 / 1024) <<
"MiB but" << (neededBytes / 1024 / 1024) <<
"MiB are needed!";
337 bool checkSizeLimits(
const QSize &size, qint32 nchannels)
const
339 return checkSizeLimits(size.width(), size.height(), nchannels);
345 if (isImageValid(m_jp2_image)) {
346 auto &&c0 = m_jp2_image->comps[0];
347 auto tmp = QSize(c0.w, c0.h);
348 if (checkSizeLimits(tmp, m_jp2_image->numcomps))
357 if (isImageValid(m_jp2_image)) {
358 auto &&c0 = m_jp2_image->comps[0];
360 for (quint32 c = 1; c < m_jp2_image->numcomps; ++c) {
361 auto &&cc = m_jp2_image->comps[c];
365 auto jp2cs = m_jp2_image->color_space;
366 if (jp2cs == OPJ_CLRSPC_UNKNOWN || jp2cs == OPJ_CLRSPC_UNSPECIFIED) {
367 if (m_jp2_image->numcomps == 1)
368 jp2cs = OPJ_CLRSPC_GRAY;
370 jp2cs = OPJ_CLRSPC_SRGB;
374 if (jp2cs == OPJ_CLRSPC_SRGB) {
375 if (m_jp2_image->numcomps == 3 || m_jp2_image->numcomps == 4) {
376 auto hasAlpha = m_jp2_image->numcomps == 4;
386 }
else if (jp2cs == OPJ_CLRSPC_GRAY) {
387 if (m_jp2_image->numcomps == 1) {
393 }
else if (jp2cs == OPJ_CLRSPC_CMYK) {
394 if (m_jp2_image->numcomps == 4) {
395#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
396 if (prec == 8 || prec == 16)
397 fmt = QImage::Format_CMYK8888;
405 QColorSpace colorSpace()
const
409 if (m_jp2_image->icc_profile_buf && m_jp2_image->icc_profile_len > 0) {
413 if (m_jp2_image->color_space == OPJ_CLRSPC_SRGB)
424 bool isSupported()
const
429 QByteArray subType()
const
433 void setSubType(
const QByteArray &type)
438 qint32 quality()
const
442 void setQuality(qint32 quality)
444 m_quality = std::clamp(quality, -1, 100);
451 OPJ_CODEC_FORMAT encoderFormat()
const
453 return subType() == J2K_SUBTYPE ? OPJ_CODEC_J2K : OPJ_CODEC_JP2;
456 bool imageToJp2(
const QImage &image)
460 auto cs = OPJ_CLRSPC_SRGB;
461 auto convFormat = image.
format();
462 auto isFloat =
false;
470 cs = OPJ_CLRSPC_GRAY;
476 cs = OPJ_CLRSPC_GRAY;
480 cs = OPJ_CLRSPC_SRGB;
487 cs = OPJ_CLRSPC_GRAY;
496 cs = OPJ_CLRSPC_UNSPECIFIED;
516 cs = OPJ_CLRSPC_UNSPECIFIED;
528#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
529 case QImage::Format_CMYK8888:
530 if (strcmp(opj_version(),
"2.5.3") >= 0) {
532 cs = OPJ_CLRSPC_CMYK;
539 if (image.
depth() > 32) {
540 qWarning() <<
"The image is saved losing precision!";
546 if (!checkSizeLimits(image.
size(), ncomp)) {
550 opj_set_default_encoder_parameters(&m_cparameters);
551 m_cparameters.cod_format = encoderFormat();
552 m_cparameters.tile_size_on = 1;
553 m_cparameters.cp_tdx = 1024;
554 m_cparameters.cp_tdy = 1024;
556 if (m_quality > -1 && m_quality < 100) {
557 m_cparameters.irreversible = 1;
558 m_cparameters.tcp_numlayers = 1;
559 m_cparameters.cp_disto_alloc = 1;
560 m_cparameters.tcp_rates[0] = 100.0 - (m_quality < 10 ? m_quality : 10 + (std::log10(m_quality) - 1) * 90);
563 std::unique_ptr<opj_image_cmptparm_t> cmptparm(
new opj_image_cmptparm_t[ncomp]);
564 for (
int i = 0; i < ncomp; ++i) {
565 auto &&p = cmptparm.get() + i;
566 memset(p, 0,
sizeof(opj_image_cmptparm_t));
567 p->dx = m_cparameters.subsampling_dx;
568 p->dy = m_cparameters.subsampling_dy;
569 p->w = image.
width();
577 m_jp2_image = opj_image_create(ncomp, cmptparm.get(), cs);
578 if (m_jp2_image ==
nullptr) {
581 if (
int(m_jp2_image->numcomps) != ncomp) {
584 m_jp2_image->x1 = image.
width();
585 m_jp2_image->y1 = image.
height();
587 ScanLineConverter scl(convFormat);
588 if (prec < 32 && isFloat) {
591 if (cs == OPJ_CLRSPC_SRGB) {
596 for (qint32 c = 0; c < ncomp; ++c) {
597 auto &&comp = m_jp2_image->comps[c];
598 auto mul = ncomp == 1 ? 1 : 4;
599 for (qint32 y = 0, h = image.
height(); y < h; ++y) {
601 auto ptr =
reinterpret_cast<const quint8 *
>(scl.convertedScanLine(image, y));
602 for (qint32 x = 0, w = image.
width(); x < w; ++x)
603 comp.data[y * w + x] = ptr[x * mul + c];
604 }
else if (prec == 16) {
605 auto ptr =
reinterpret_cast<const quint16 *
>(scl.convertedScanLine(image, y));
606 for (qint32 x = 0, w = image.
width(); x < w; ++x)
607 comp.data[y * w + x] = ptr[x * mul + c];
608 }
else if (prec == 32) {
609 auto ptr =
reinterpret_cast<const quint32 *
>(scl.convertedScanLine(image, y));
610 for (qint32 x = 0, w = image.
width(); x < w; ++x)
611 comp.data[y * w + x] = ptr[x * mul + c];
617 if (cs == OPJ_CLRSPC_UNKNOWN || cs == OPJ_CLRSPC_UNSPECIFIED) {
618 auto colorSpace = scl.targetColorSpace().iccProfile();
619 if (!colorSpace.isEmpty()) {
620 m_jp2_image->icc_profile_buf =
reinterpret_cast<OPJ_BYTE *
>(malloc(colorSpace.size()));
621 if (m_jp2_image->icc_profile_buf) {
622 memcpy(m_jp2_image->icc_profile_buf, colorSpace.data(), colorSpace.size());
623 m_jp2_image->icc_profile_len = colorSpace.size();
631 bool writeImage(QIODevice *device,
const QImage &image)
633 if (!imageToJp2(image)) {
634 qCritical() <<
"Error while creating JP2 image!";
638 std::unique_ptr<opj_codec_t, std::function<void(opj_codec_t *)>> codec(opj_create_compress(encoderFormat()), opj_destroy_codec);
639 if (codec ==
nullptr) {
640 qCritical() <<
"Error while creating encoder!";
643 enableThreads(codec.get());
648 opj_set_error_handler(m_jp2_codec, error_callback,
nullptr);
650 if (!opj_setup_encoder(codec.get(), &m_cparameters, m_jp2_image)) {
654 if (!createStream(device,
false)) {
658 if (!opj_start_compress(codec.get(), m_jp2_image, m_jp2_stream)) {
661 if (!opj_encode(codec.get(), m_jp2_stream)) {
664 if (!opj_end_compress(codec.get(), m_jp2_stream)) {
673 opj_stream_t *m_jp2_stream;
675 opj_image_t *m_jp2_image;
678 opj_codec_t *m_jp2_codec;
680 opj_dparameters_t m_dparameters;
683 opj_cparameters_t m_cparameters;
687 QByteArray m_subtype;
691JP2Handler::JP2Handler()
693 , d(new JP2HandlerPrivate)
697bool JP2Handler::canRead()
const
699 if (canRead(device())) {
706bool JP2Handler::canRead(
QIODevice *device)
709 qWarning(
"JP2Handler::canRead() called with no device");
717 JP2HandlerPrivate handler;
718 return handler.detectDecoderFormat(device) != OPJ_CODEC_UNKNOWN;
721bool JP2Handler::read(
QImage *image)
724 if (dev ==
nullptr) {
727 auto img = d->readImage(dev);
735bool JP2Handler::write(
const QImage &image)
741 if (dev ==
nullptr) {
744 return d->writeImage(dev, image);
747bool JP2Handler::supportsOption(ImageOption option)
const
767void JP2Handler::setOption(ImageOption option,
const QVariant &value)
776 auto q = value.
toInt(&ok);
783QVariant JP2Handler::option(ImageOption option)
const
788 if (d->readHeader(device())) {
794 if (d->readHeader(device())) {
816 if (format ==
"jp2" || format ==
"j2k") {
820 if (format ==
"jpf") {
831 if (device->
isReadable() && JP2Handler::canRead(device)) {
848#include "moc_jp2_p.cpp"
QFlags< Capability > Capabilities
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QByteArray fromHex(const QByteArray &hexEncoded)
bool isEmpty() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
bool isValid() const const
QColorSpace colorSpace() const const
bool hasAlphaChannel() const const
bool isGrayscale() const const
bool isNull() const const
void setColorSpace(const QColorSpace &colorSpace)
void setDevice(QIODevice *device)
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
bool isWritable() const const
QByteArray peek(qint64 maxSize)
virtual qint64 size() const const
QVariant fromValue(T &&value)
QByteArray toByteArray() const const
int toInt(bool *ok) const const