KImageFormats

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

KDE's Doxygen guidelines are available online.