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 "pxr_p.h" |
9 | #include "util_p.h" |
10 | |
11 | #include <QIODevice> |
12 | #include <QImage> |
13 | #include <QLoggingCategory> |
14 | |
15 | Q_DECLARE_LOGGING_CATEGORY(LOG_PXRPLUGIN) |
16 | Q_LOGGING_CATEGORY(LOG_PXRPLUGIN, "kf.imageformats.plugins.pxr" , QtWarningMsg) |
17 | |
18 | class |
19 | { |
20 | private: |
21 | QByteArray ; |
22 | |
23 | quint16 (quint8 c1, quint8 c2) const { |
24 | return (quint16(c2) << 8) | quint16(c1); |
25 | } |
26 | |
27 | quint32 (quint8 c1, quint8 c2, quint8 c3, quint8 c4) const { |
28 | return (quint32(c4) << 24) | (quint32(c3) << 16) | (quint32(c2) << 8) | quint32(c1); |
29 | } |
30 | |
31 | public: |
32 | () |
33 | { |
34 | |
35 | } |
36 | |
37 | bool () const |
38 | { |
39 | return (m_rawHeader.size() == 512 && |
40 | m_rawHeader.startsWith(bv: QByteArray::fromRawData(data: "\x80\xE8\x00\x00" , size: 4))); |
41 | } |
42 | |
43 | bool () const |
44 | { |
45 | return format() != QImage::Format_Invalid; |
46 | } |
47 | |
48 | qint32 () const |
49 | { |
50 | if (!isValid()) { |
51 | return 0; |
52 | } |
53 | return qint32(ui16(c1: m_rawHeader.at(i: 418), c2: m_rawHeader.at(i: 419))); |
54 | } |
55 | |
56 | qint32 () const |
57 | { |
58 | if (!isValid()) { |
59 | return 0; |
60 | } |
61 | return qint32(ui16(c1: m_rawHeader.at(i: 416), c2: m_rawHeader.at(i: 417))); |
62 | } |
63 | |
64 | QSize () const |
65 | { |
66 | return QSize(width(), height()); |
67 | } |
68 | |
69 | qint32 () const |
70 | { |
71 | if (!isValid()) { |
72 | return 0; |
73 | } |
74 | return qint32(ui16(c1: m_rawHeader.at(i: 424), c2: m_rawHeader.at(i: 425))); |
75 | } |
76 | |
77 | qint32 () const |
78 | { |
79 | if (!isValid()) { |
80 | return 0; |
81 | } |
82 | return qint32(ui16(c1: m_rawHeader.at(i: 426), c2: m_rawHeader.at(i: 427))); |
83 | } |
84 | |
85 | // supposing the image offset (always 1024 on sample files) |
86 | qint32 () const |
87 | { |
88 | if (!isValid()) { |
89 | return 0; |
90 | } |
91 | return qint32(ui16(c1: m_rawHeader.at(i: 428), c2: m_rawHeader.at(i: 429))); |
92 | } |
93 | |
94 | QImage::Format () const |
95 | { |
96 | if (channel() == 14 && depth() == 2) { |
97 | return QImage::Format_RGB888; |
98 | } |
99 | if (channel() == 8 && depth() == 2) { |
100 | return QImage::Format_Grayscale8; |
101 | } |
102 | return QImage::Format_Invalid; |
103 | } |
104 | |
105 | qsizetype () const |
106 | { |
107 | if (format() == QImage::Format_RGB888) { |
108 | return width() * 3; |
109 | } |
110 | if (format() == QImage::Format_Grayscale8) { |
111 | return width(); |
112 | } |
113 | return 0; |
114 | } |
115 | |
116 | bool (QIODevice *d) |
117 | { |
118 | m_rawHeader = d->read(maxlen: 512); |
119 | return isValid(); |
120 | } |
121 | |
122 | bool (QIODevice *d) |
123 | { |
124 | m_rawHeader = d->peek(maxlen: 512); |
125 | return isValid(); |
126 | } |
127 | |
128 | bool (QIODevice *d) const |
129 | { |
130 | if (d->isSequential()) { |
131 | if (auto sz = std::max(a: offset() - qint32(m_rawHeader.size()), b: 0)) { |
132 | return d->read(maxlen: sz).size() == sz; |
133 | } |
134 | return true; |
135 | } |
136 | return d->seek(pos: offset()); |
137 | } |
138 | }; |
139 | |
140 | class PXRHandlerPrivate |
141 | { |
142 | public: |
143 | PXRHandlerPrivate() {} |
144 | ~PXRHandlerPrivate() {} |
145 | |
146 | PXRHeader m_header; |
147 | }; |
148 | |
149 | PXRHandler::PXRHandler() |
150 | : QImageIOHandler() |
151 | , d(new PXRHandlerPrivate) |
152 | { |
153 | } |
154 | |
155 | bool PXRHandler::canRead() const |
156 | { |
157 | if (canRead(device: device())) { |
158 | setFormat("pxr" ); |
159 | return true; |
160 | } |
161 | return false; |
162 | } |
163 | |
164 | bool PXRHandler::canRead(QIODevice *device) |
165 | { |
166 | if (!device) { |
167 | qCWarning(LOG_PXRPLUGIN) << "PXRHandler::canRead() called with no device" ; |
168 | return false; |
169 | } |
170 | |
171 | PXRHeader h; |
172 | if (!h.peek(d: device)) { |
173 | return false; |
174 | } |
175 | |
176 | return h.isSupported(); |
177 | } |
178 | |
179 | bool PXRHandler::read(QImage *image) |
180 | { |
181 | auto&& = d->m_header; |
182 | |
183 | if (!header.read(d: device())) { |
184 | qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() invalid header" ; |
185 | return false; |
186 | } |
187 | |
188 | auto img = imageAlloc(size: header.size(), format: header.format()); |
189 | if (img.isNull()) { |
190 | qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while allocating the image" ; |
191 | return false; |
192 | } |
193 | |
194 | auto d = device(); |
195 | if (!header.jumpToImageData(d)) { |
196 | qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while seeking image data" ; |
197 | return false; |
198 | } |
199 | |
200 | auto size = std::min(a: img.bytesPerLine(), b: header.strideSize()); |
201 | for (auto y = 0, h = img.height(); y < h; ++y) { |
202 | auto line = reinterpret_cast<char*>(img.scanLine(y)); |
203 | if (d->read(data: line, maxlen: size) != size) { |
204 | qCWarning(LOG_PXRPLUGIN) << "PXRHandler::read() error while reading image scanline" ; |
205 | return false; |
206 | } |
207 | } |
208 | |
209 | *image = img; |
210 | return true; |
211 | } |
212 | |
213 | bool PXRHandler::supportsOption(ImageOption option) const |
214 | { |
215 | if (option == QImageIOHandler::Size) { |
216 | return true; |
217 | } |
218 | if (option == QImageIOHandler::ImageFormat) { |
219 | return true; |
220 | } |
221 | return false; |
222 | } |
223 | |
224 | QVariant PXRHandler::option(ImageOption option) const |
225 | { |
226 | QVariant v; |
227 | |
228 | if (option == QImageIOHandler::Size) { |
229 | auto&& h = d->m_header; |
230 | if (h.isValid()) { |
231 | v = QVariant::fromValue(value: h.size()); |
232 | } else if (auto d = device()) { |
233 | if (h.peek(d)) { |
234 | v = QVariant::fromValue(value: h.size()); |
235 | } |
236 | } |
237 | } |
238 | |
239 | if (option == QImageIOHandler::ImageFormat) { |
240 | auto&& h = d->m_header; |
241 | if (h.isValid()) { |
242 | v = QVariant::fromValue(value: h.format()); |
243 | } else if (auto d = device()) { |
244 | if (h.peek(d)) { |
245 | v = QVariant::fromValue(value: h.format()); |
246 | } |
247 | } |
248 | } |
249 | |
250 | return v; |
251 | } |
252 | |
253 | QImageIOPlugin::Capabilities PXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
254 | { |
255 | if (format == "pxr" ) { |
256 | return Capabilities(CanRead); |
257 | } |
258 | if (!format.isEmpty()) { |
259 | return {}; |
260 | } |
261 | if (!device->isOpen()) { |
262 | return {}; |
263 | } |
264 | |
265 | Capabilities cap; |
266 | if (device->isReadable() && PXRHandler::canRead(device)) { |
267 | cap |= CanRead; |
268 | } |
269 | return cap; |
270 | } |
271 | |
272 | QImageIOHandler *PXRPlugin::create(QIODevice *device, const QByteArray &format) const |
273 | { |
274 | QImageIOHandler *handler = new PXRHandler; |
275 | handler->setDevice(device); |
276 | handler->setFormat(format); |
277 | return handler; |
278 | } |
279 | |
280 | #include "moc_pxr_p.cpp" |
281 | |