1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "sct_p.h"
9#include "scanlineconverter_p.h"
10#include "util_p.h"
11
12#include <array>
13
14#include <QtGlobal>
15#include <QIODevice>
16#include <QBuffer>
17#include <QImage>
18#include <QLoggingCategory>
19
20Q_LOGGING_CATEGORY(LOG_SCTPLUGIN, "kf.imageformats.plugins.scitex", QtWarningMsg)
21
22#define CTRLBLOCK_SIZE 256
23#define PRMSBLOCK_SIZE_CT 256
24
25// For file stored on disk, each block is followed by 768 pads
26#define HEADER_SIZE_CT (CTRLBLOCK_SIZE + PRMSBLOCK_SIZE_CT + 768 + 768)
27
28#define FILETYPE_CT "CT"
29#define FILETYPE_LW "LW"
30#define FILETYPE_BM "BM"
31#define FILETYPE_PG "PG"
32#define FILETYPE_TX "TX"
33
34class ScitexCtrlBlock
35{
36 using pchar_t = char *;
37public:
38 ScitexCtrlBlock() {}
39 ScitexCtrlBlock(const ScitexCtrlBlock& other) = default;
40 ScitexCtrlBlock& operator =(const ScitexCtrlBlock& other) = default;
41
42 bool load(QIODevice *device)
43 {
44 auto ok = (device && device->isOpen());
45 ok = ok && device->read(data: pchar_t(_name.data()), maxlen: _name.size()) == qint64(_name.size());
46 ok = ok && device->read(data: pchar_t(_fileType.data()), maxlen: _fileType.size()) == qint64(_fileType.size());
47 ok = ok && device->read(data: pchar_t(_blockSize.data()), maxlen: _blockSize.size()) == qint64(_blockSize.size());
48 ok = ok && device->read(data: pchar_t(_reserved.data()), maxlen: _reserved.size()) == qint64(_reserved.size());
49 ok = ok && device->read(data: pchar_t(&_count), maxlen: sizeof(_count)) == qint64(sizeof(_count));
50 ok = ok && device->read(data: pchar_t(_padding.data()), maxlen: _padding.size()) == qint64(_padding.size());
51 return ok;
52 }
53
54 QString name() const
55 {
56 return QString::fromLatin1(str: pchar_t(_name.data()), size: _name.size());
57 }
58
59 QString fileType() const
60 {
61 return QString::fromLatin1(str: pchar_t(_fileType.data()), size: _fileType.size());
62 }
63
64 quint8 sequenceCount() const
65 {
66 return _count;
67 }
68
69 std::array<quint8, 80> _name = {};
70 std::array<quint8, 2> _fileType = {};
71 std::array<quint8, 12> _blockSize = {};
72 std::array<quint8, 12> _reserved = {};
73 quint8 _count = 0;
74 std::array<quint8, 149> _padding = {};
75};
76
77class ScitexParamsBlock
78{
79 using pchar_t = char *;
80public:
81 ScitexParamsBlock() {}
82 ScitexParamsBlock(const ScitexParamsBlock& other) = default;
83 ScitexParamsBlock& operator =(const ScitexParamsBlock& other) = default;
84
85 bool load(QIODevice *device)
86 {
87 auto ok = (device && device->isOpen());
88 ok = ok && device->read(data: pchar_t(&_unitsOfMeasurement), maxlen: sizeof(_unitsOfMeasurement)) == qint64(sizeof(_unitsOfMeasurement));
89 ok = ok && device->read(data: pchar_t(&_numOfColorSeparations), maxlen: sizeof(_numOfColorSeparations)) == qint64(sizeof(_numOfColorSeparations));
90 ok = ok && device->read(data: pchar_t(_separationBitMask.data()), maxlen: _separationBitMask.size()) == qint64(_separationBitMask.size());
91 ok = ok && device->read(data: pchar_t(_heightInUnits.data()), maxlen: _heightInUnits.size()) == qint64(_heightInUnits.size());
92 ok = ok && device->read(data: pchar_t(_widthInUnits.data()), maxlen: _widthInUnits.size()) == qint64(_widthInUnits.size());
93 ok = ok && device->read(data: pchar_t(_heightInPixels.data()), maxlen: _heightInPixels.size()) == qint64(_heightInPixels.size());
94 ok = ok && device->read(data: pchar_t(_widthInPixels.data()), maxlen: _widthInPixels.size()) == qint64(_widthInPixels.size());
95 ok = ok && device->read(data: pchar_t(&_scanDirection), maxlen: sizeof(_scanDirection)) == qint64(sizeof(_scanDirection));
96 ok = ok && device->read(data: pchar_t(_reserved.data()), maxlen: _reserved.size()) == qint64(_reserved.size());
97 return ok;
98 }
99
100 quint8 colorCount() const
101 {
102 return _numOfColorSeparations;
103 }
104
105 quint16 bitMask() const
106 {
107 return ((_separationBitMask.at(n: 0) << 8) | _separationBitMask.at(n: 1));
108 }
109
110 quint8 _unitsOfMeasurement = 0;
111 quint8 _numOfColorSeparations = 0;
112 std::array<quint8, 2> _separationBitMask = {};
113 std::array<quint8, 14> _heightInUnits = {};
114 std::array<quint8, 14> _widthInUnits = {};
115 std::array<quint8, 12> _heightInPixels = {};
116 std::array<quint8, 12> _widthInPixels = {};
117 quint8 _scanDirection = 0;
118 std::array<quint8, 199> _reserved = {};
119};
120
121class ScitexHandlerPrivate
122{
123 using pchar_t = char *;
124public:
125 ScitexHandlerPrivate()
126 {
127 }
128 ~ScitexHandlerPrivate()
129 {
130 }
131
132 /*!
133 * \brief isSupported
134 * \return If the plugin can load it.
135 */
136 bool isSupported() const
137 {
138 if (!isValid()) {
139 return false;
140 }
141 // Set a reasonable upper limit
142 if (width() > 300000 || height() > 300000) {
143 return false;
144 }
145 return m_cb.fileType() == QStringLiteral(FILETYPE_CT) && format() != QImage::Format_Invalid;
146 }
147
148 /*!
149 * \brief isValid
150 * \return True if is a valid Scitex image file.
151 */
152 bool isValid() const
153 {
154 if (width() == 0 || height() == 0) {
155 return false;
156 }
157 QStringList ft = {
158 QStringLiteral(FILETYPE_CT),
159 QStringLiteral(FILETYPE_LW),
160 QStringLiteral(FILETYPE_BM),
161 QStringLiteral(FILETYPE_PG),
162 QStringLiteral(FILETYPE_TX)
163 };
164 return ft.contains(str: m_cb.fileType());
165 }
166
167 QImage::Format format() const
168 {
169 auto format = QImage::Format_Invalid;
170#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
171 if (m_pb.colorCount() == 4) {
172 if (m_pb.bitMask() == 15)
173 format = QImage::Format_CMYK8888;
174 }
175#endif
176 if (m_pb.colorCount() == 3) {
177 if (m_pb.bitMask() == 7)
178 format = QImage::Format_RGB888;
179 }
180 if (m_pb.colorCount() == 1) {
181 if (m_pb.bitMask() == 8)
182 format = QImage::Format_Grayscale8;
183 }
184 return format;
185 }
186
187 quint32 width() const
188 {
189 auto ok = false;
190 auto&& px = m_pb._widthInPixels;
191 auto v = QString::fromLatin1(str: pchar_t(px.data()), size: px.size()).toUInt(ok: &ok);
192 return ok ? v : 0;
193 }
194
195 quint32 height() const
196 {
197 auto ok = false;
198 auto&& px = m_pb._heightInPixels;
199 auto v = QString::fromLatin1(str: pchar_t(px.data()), size: px.size()).toUInt(ok: &ok);
200 return ok ? v : 0;
201 }
202
203 qint32 dotsPerMeterX() const {
204 auto ok = false;
205 auto&& res = m_pb._widthInUnits;
206 auto v = QString::fromLatin1(str: pchar_t(res.data()), size: res.size()).toDouble(ok: &ok);
207 if (ok && v > 0) {
208 if (m_pb._unitsOfMeasurement) { // Inches
209 return qRoundOrZero(d: width() / v / 25.4 * 1000);
210 }
211 // Millimeters
212 return qRoundOrZero(d: width() / v * 1000);
213 }
214 return 0;
215 }
216
217 qint32 dotsPerMeterY() const {
218 auto ok = false;
219 auto&& res = m_pb._heightInUnits;
220 auto v = QString::fromLatin1(str: pchar_t(res.data()), size: res.size()).toDouble(ok: &ok);
221 if (ok && v > 0) {
222 if (m_pb._unitsOfMeasurement) { // Inches
223 return qRoundOrZero(d: height() / v / 25.4 * 1000);
224 }
225 // Millimeters
226 return qRoundOrZero(d: height() / v * 1000);
227 }
228 return 0;
229 }
230
231 QImageIOHandler::Transformation transformation() const
232 {
233 auto t = QImageIOHandler::TransformationNone;
234 switch (m_pb._scanDirection) {
235 case 1:
236 t = QImageIOHandler::TransformationFlip;
237 break;
238 case 2:
239 t = QImageIOHandler::TransformationMirror;
240 break;
241 case 3:
242 t = QImageIOHandler::TransformationRotate180;
243 break;
244 case 4:
245 t = QImageIOHandler::TransformationFlipAndRotate90;
246 break;
247 case 5:
248 t = QImageIOHandler::TransformationRotate270;
249 break;
250 case 6:
251 t = QImageIOHandler::TransformationRotate90;
252 break;
253 case 7:
254 t = QImageIOHandler::TransformationMirrorAndRotate90;
255 break;
256 default:
257 t = QImageIOHandler::TransformationNone;
258 break;
259 }
260 return t;
261 }
262
263 bool peekHeader(QIODevice *device)
264 {
265 if (device == nullptr) {
266 return false;
267 }
268 auto ba = device->peek(HEADER_SIZE_CT);
269 if (ba.size() != HEADER_SIZE_CT) {
270 return false;
271 }
272 QBuffer b;
273 b.setData(ba);
274 if (!b.open(openMode: QIODevice::ReadOnly)) {
275 return false;
276 }
277 return loadHeader(device: &b);
278 }
279
280 bool loadHeader(QIODevice *device) {
281 if (device == nullptr) {
282 return false;
283 }
284 if (!m_cb.load(device)) {
285 return false;
286 }
287 auto pad1 = device->read(maxlen: 768);
288 if (pad1.size() != 768) {
289 return false;
290 }
291 if (!m_pb.load(device)) {
292 return false;
293 }
294 auto pad2 = device->read(maxlen: 768);
295 if (pad2.size() != 768) {
296 return false;
297 }
298 return true;
299 }
300
301 ScitexCtrlBlock m_cb;
302 ScitexParamsBlock m_pb;
303};
304
305ScitexHandler::ScitexHandler()
306 : QImageIOHandler()
307 , d(new ScitexHandlerPrivate)
308{
309}
310
311bool ScitexHandler::canRead() const
312{
313 if (canRead(device: device())) {
314 setFormat("sct");
315 return true;
316 }
317 return false;
318}
319
320bool ScitexHandler::canRead(QIODevice *device)
321{
322 if (!device) {
323 qWarning(msg: "ScitexHandler::canRead() called with no device");
324 return false;
325 }
326 ScitexHandlerPrivate hp;
327 if (hp.peekHeader(device)) {
328 return hp.isSupported();
329 }
330 return false;
331}
332
333bool ScitexHandler::read(QImage *image)
334{
335 auto dev = device();
336 if (dev == nullptr) {
337 qWarning(msg: "ScitexHandler::read() called with no device");
338 return false;
339 }
340 if (!d->loadHeader(device: dev)) {
341 return false;
342 }
343 if (!d->isSupported()) {
344 return false;
345 }
346
347 auto img = imageAlloc(width: d->width(), height: d->height(), format: d->format());
348 if (img.isNull()) {
349 return false;
350 }
351
352 auto hres = d->dotsPerMeterX();
353 if (hres > 0) {
354 img.setDotsPerMeterX(hres);
355 }
356 auto vres = d->dotsPerMeterY();
357 if (vres > 0) {
358 img.setDotsPerMeterY(vres);
359 }
360
361 QByteArray line(img.width() * d->m_pb.colorCount(), char());
362 if (img.bytesPerLine() < line.size()) {
363 return false;
364 }
365 for (qint32 y = 0, h = img.height(); y < h; ++y) {
366 if (dev->read(data: line.data(), maxlen: line.size()) != line.size()) {
367 return false;
368 }
369 auto scanLine = img.scanLine(y);
370 for (qint32 c = 0, cc = d->m_pb.colorCount(); c < cc; ++c) {
371 for (qint32 x = 0, w = img.width(); x < w; ++x) {
372 scanLine[x * cc + c] = cc == 4 ? uchar(255) - uchar(line.at(i: c * w + x)) : uchar(line.at(i: c * w + x));
373 }
374 }
375 }
376
377 *image = img;
378 return true;
379}
380
381bool ScitexHandler::supportsOption(ImageOption option) const
382{
383 if (option == QImageIOHandler::Size) {
384 return true;
385 }
386
387 if (option == QImageIOHandler::ImageFormat) {
388 return true;
389 }
390
391 if (option == QImageIOHandler::ImageTransformation) {
392 return true;
393 }
394
395 return false;
396}
397
398QVariant ScitexHandler::option(ImageOption option) const
399{
400 QVariant v;
401
402 if (option == QImageIOHandler::Size) {
403 if (!d->isValid()) {
404 d->peekHeader(device: device());
405 }
406 if (d->isSupported()) {
407 v = QSize(d->width(), d->height());
408 }
409 }
410
411 if (option == QImageIOHandler::ImageFormat) {
412 if (!d->isValid()) {
413 d->peekHeader(device: device());
414 }
415 if (d->isSupported()) {
416 v = d->format();
417 }
418 }
419
420 if (option == QImageIOHandler::ImageTransformation) {
421 if (!d->isValid()) {
422 d->peekHeader(device: device());
423 }
424 if (d->isSupported()) {
425 v = int(d->transformation());
426 }
427 }
428
429 return v;
430}
431
432QImageIOPlugin::Capabilities ScitexPlugin::capabilities(QIODevice *device, const QByteArray &format) const
433{
434 if (format == "sct") {
435 return Capabilities(CanRead);
436 }
437 if (!format.isEmpty()) {
438 return {};
439 }
440 if (!device->isOpen()) {
441 return {};
442 }
443
444 Capabilities cap;
445 if (device->isReadable() && ScitexHandler::canRead(device)) {
446 cap |= CanRead;
447 }
448 return cap;
449}
450
451QImageIOHandler *ScitexPlugin::create(QIODevice *device, const QByteArray &format) const
452{
453 QImageIOHandler *handler = new ScitexHandler;
454 handler->setDevice(device);
455 handler->setFormat(format);
456 return handler;
457}
458
459#include "moc_sct_p.cpp"
460

source code of kimageformats/src/imageformats/sct.cpp