mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-16 03:24:17 -04:00
* speeds up incremental builds as changes to a header will not always
need the full mocs_compilation.cpp for all the target's headers rebuild,
while having a moc file sourced into a source file only adds minor
extra costs, due to small own code and the used headers usually
already covered by the source file, being for the same class/struct
* seems to not slow down clean builds, due to empty mocs_compilation.cpp
resulting in those quickly processed, while the minor extra cost of the
sourced moc files does not outweigh that in summary.
Measured times actually improved by some percent points.
(ideally CMake would just skip empty mocs_compilation.cpp & its object
file one day)
* enables compiler to see all methods of a class in same compilation unit
to do some sanity checks
* potentially more inlining in general, due to more in the compilation unit
* allows to keep using more forward declarations in the header, as with the
moc code being sourced into the cpp file there definitions can be ensured
and often are already for the needs of the normal class methods
(cherry picked from commit 34ed3bad27
)
797 lines
18 KiB
C++
797 lines
18 KiB
C++
/*
|
|
This file is part of the KDE project
|
|
SPDX-FileCopyrightText: 2002-2005 Nadeem Hasan <nhasan@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
|
|
#include "pcx_p.h"
|
|
#include "util_p.h"
|
|
|
|
#include <QColor>
|
|
#include <QDataStream>
|
|
#include <QDebug>
|
|
#include <QImage>
|
|
|
|
#pragma pack(push, 1)
|
|
class RGB
|
|
{
|
|
public:
|
|
quint8 r;
|
|
quint8 g;
|
|
quint8 b;
|
|
|
|
static RGB from(const QRgb color)
|
|
{
|
|
RGB c;
|
|
c.r = qRed(color);
|
|
c.g = qGreen(color);
|
|
c.b = qBlue(color);
|
|
return c;
|
|
}
|
|
};
|
|
|
|
class Palette
|
|
{
|
|
public:
|
|
void setColor(int i, const QRgb color)
|
|
{
|
|
RGB &c = rgb[i];
|
|
c.r = qRed(color);
|
|
c.g = qGreen(color);
|
|
c.b = qBlue(color);
|
|
}
|
|
|
|
QRgb color(int i) const
|
|
{
|
|
return qRgb(rgb[i].r, rgb[i].g, rgb[i].b);
|
|
}
|
|
|
|
class RGB rgb[16];
|
|
};
|
|
|
|
class PCXHEADER
|
|
{
|
|
public:
|
|
PCXHEADER();
|
|
|
|
inline int width() const
|
|
{
|
|
return (XMax - XMin) + 1;
|
|
}
|
|
inline int height() const
|
|
{
|
|
return (YMax - YMin) + 1;
|
|
}
|
|
inline bool isCompressed() const
|
|
{
|
|
return (Encoding == 1);
|
|
}
|
|
|
|
quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
|
|
quint8 Version; // Version information·
|
|
// 0 = Version 2.5 of PC Paintbrush·
|
|
// 2 = Version 2.8 w/palette information·
|
|
// 3 = Version 2.8 w/o palette information·
|
|
// 4 = PC Paintbrush for Windows(Plus for
|
|
// Windows uses Ver 5)·
|
|
// 5 = Version 3.0 and > of PC Paintbrush
|
|
// and PC Paintbrush +, includes
|
|
// Publisher's Paintbrush . Includes
|
|
// 24-bit .PCX files·
|
|
quint8 Encoding; // 1 = .PCX run length encoding
|
|
quint8 Bpp; // Number of bits to represent a pixel
|
|
// (per Plane) - 1, 2, 4, or 8·
|
|
quint16 XMin;
|
|
quint16 YMin;
|
|
quint16 XMax;
|
|
quint16 YMax;
|
|
quint16 HDpi;
|
|
quint16 YDpi;
|
|
Palette ColorMap;
|
|
quint8 Reserved; // Should be set to 0.
|
|
quint8 NPlanes; // Number of color planes
|
|
quint16 BytesPerLine; // Number of bytes to allocate for a scanline
|
|
// plane. MUST be an EVEN number. Do NOT
|
|
// calculate from Xmax-Xmin.·
|
|
quint16 PaletteInfo; // How to interpret palette- 1 = Color/BW,
|
|
// 2 = Grayscale ( ignored in PB IV/ IV + )·
|
|
quint16 HScreenSize; // Horizontal screen size in pixels. New field
|
|
// found only in PB IV/IV Plus
|
|
quint16 VScreenSize; // Vertical screen size in pixels. New field
|
|
// found only in PB IV/IV Plus
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
static QDataStream &operator>>(QDataStream &s, RGB &rgb)
|
|
{
|
|
quint8 r;
|
|
quint8 g;
|
|
quint8 b;
|
|
|
|
s >> r >> g >> b;
|
|
rgb.r = r;
|
|
rgb.g = g;
|
|
rgb.b = b;
|
|
|
|
return s;
|
|
}
|
|
|
|
static QDataStream &operator>>(QDataStream &s, Palette &pal)
|
|
{
|
|
for (int i = 0; i < 16; ++i) {
|
|
s >> pal.rgb[i];
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
|
|
{
|
|
quint8 m;
|
|
quint8 ver;
|
|
quint8 enc;
|
|
quint8 bpp;
|
|
s >> m >> ver >> enc >> bpp;
|
|
ph.Manufacturer = m;
|
|
ph.Version = ver;
|
|
ph.Encoding = enc;
|
|
ph.Bpp = bpp;
|
|
quint16 xmin;
|
|
quint16 ymin;
|
|
quint16 xmax;
|
|
quint16 ymax;
|
|
s >> xmin >> ymin >> xmax >> ymax;
|
|
ph.XMin = xmin;
|
|
ph.YMin = ymin;
|
|
ph.XMax = xmax;
|
|
ph.YMax = ymax;
|
|
quint16 hdpi;
|
|
quint16 ydpi;
|
|
s >> hdpi >> ydpi;
|
|
ph.HDpi = hdpi;
|
|
ph.YDpi = ydpi;
|
|
Palette colorMap;
|
|
quint8 res;
|
|
quint8 np;
|
|
s >> colorMap >> res >> np;
|
|
ph.ColorMap = colorMap;
|
|
ph.Reserved = res;
|
|
ph.NPlanes = np;
|
|
quint16 bytesperline;
|
|
s >> bytesperline;
|
|
ph.BytesPerLine = bytesperline;
|
|
quint16 paletteinfo;
|
|
s >> paletteinfo;
|
|
ph.PaletteInfo = paletteinfo;
|
|
quint16 hscreensize;
|
|
quint16 vscreensize;
|
|
s >> hscreensize;
|
|
ph.HScreenSize = hscreensize;
|
|
s >> vscreensize;
|
|
ph.VScreenSize = vscreensize;
|
|
|
|
// Skip the rest of the header
|
|
quint8 byte;
|
|
for (auto i = 0; i < 54; ++i) {
|
|
s >> byte;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static QDataStream &operator<<(QDataStream &s, const RGB rgb)
|
|
{
|
|
s << rgb.r << rgb.g << rgb.b;
|
|
|
|
return s;
|
|
}
|
|
|
|
static QDataStream &operator<<(QDataStream &s, const Palette &pal)
|
|
{
|
|
for (int i = 0; i < 16; ++i) {
|
|
s << pal.rgb[i];
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static QDataStream &operator<<(QDataStream &s, const PCXHEADER &ph)
|
|
{
|
|
s << ph.Manufacturer;
|
|
s << ph.Version;
|
|
s << ph.Encoding;
|
|
s << ph.Bpp;
|
|
s << ph.XMin << ph.YMin << ph.XMax << ph.YMax;
|
|
s << ph.HDpi << ph.YDpi;
|
|
s << ph.ColorMap;
|
|
s << ph.Reserved;
|
|
s << ph.NPlanes;
|
|
s << ph.BytesPerLine;
|
|
s << ph.PaletteInfo;
|
|
s << ph.HScreenSize;
|
|
s << ph.VScreenSize;
|
|
|
|
quint8 byte = 0;
|
|
for (int i = 0; i < 54; ++i) {
|
|
s << byte;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
PCXHEADER::PCXHEADER()
|
|
{
|
|
// Initialize all data to zero
|
|
QByteArray dummy(128, 0);
|
|
dummy.fill(0);
|
|
QDataStream s(&dummy, QIODevice::ReadOnly);
|
|
s >> *this;
|
|
}
|
|
|
|
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
|
{
|
|
quint32 i = 0;
|
|
quint32 size = buf.size();
|
|
quint8 byte;
|
|
quint8 count;
|
|
|
|
if (header.isCompressed()) {
|
|
// Uncompress the image data
|
|
while (i < size) {
|
|
count = 1;
|
|
s >> byte;
|
|
if (byte > 0xc0) {
|
|
count = byte - 0xc0;
|
|
s >> byte;
|
|
}
|
|
while (count-- && i < size) {
|
|
buf[i++] = byte;
|
|
}
|
|
}
|
|
} else {
|
|
// Image is not compressed (possible?)
|
|
while (i < size) {
|
|
s >> byte;
|
|
buf[i++] = byte;
|
|
}
|
|
}
|
|
|
|
return (s.status() == QDataStream::Ok);
|
|
}
|
|
|
|
static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|
{
|
|
QByteArray buf(header.BytesPerLine, 0);
|
|
|
|
img = imageAlloc(header.width(), header.height(), QImage::Format_Mono);
|
|
img.setColorCount(2);
|
|
|
|
if (img.isNull()) {
|
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
|
return false;
|
|
}
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
if (s.atEnd()) {
|
|
return false;
|
|
}
|
|
|
|
if (!readLine(s, buf, header)) {
|
|
return false;
|
|
}
|
|
|
|
uchar *p = img.scanLine(y);
|
|
unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
|
|
for (unsigned int x = 0; x < bpl; ++x) {
|
|
p[x] = buf[x];
|
|
}
|
|
}
|
|
|
|
// Set the color palette
|
|
img.setColor(0, qRgb(0, 0, 0));
|
|
img.setColor(1, qRgb(255, 255, 255));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|
{
|
|
QByteArray buf(header.BytesPerLine * 4, 0);
|
|
QByteArray pixbuf(header.width(), 0);
|
|
|
|
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
|
|
img.setColorCount(16);
|
|
if (img.isNull()) {
|
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
|
return false;
|
|
}
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
if (s.atEnd()) {
|
|
return false;
|
|
}
|
|
|
|
pixbuf.fill(0);
|
|
if (!readLine(s, buf, header)) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
quint32 offset = i * header.BytesPerLine;
|
|
for (int x = 0; x < header.width(); ++x) {
|
|
if (buf[offset + (x / 8)] & (128 >> (x % 8))) {
|
|
pixbuf[x] = (int)(pixbuf[x]) + (1 << i);
|
|
}
|
|
}
|
|
}
|
|
|
|
uchar *p = img.scanLine(y);
|
|
if (!p) {
|
|
qWarning() << "Failed to get scanline for" << y << "might be out of bounds";
|
|
}
|
|
for (int x = 0; x < header.width(); ++x) {
|
|
p[x] = pixbuf[x];
|
|
}
|
|
}
|
|
|
|
// Read the palette
|
|
for (int i = 0; i < 16; ++i) {
|
|
img.setColor(i, header.ColorMap.color(i));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|
{
|
|
QByteArray buf(header.BytesPerLine, 0);
|
|
|
|
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
|
|
img.setColorCount(256);
|
|
|
|
if (img.isNull()) {
|
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
|
return false;
|
|
}
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
if (s.atEnd()) {
|
|
return false;
|
|
}
|
|
|
|
if (!readLine(s, buf, header)) {
|
|
return false;
|
|
}
|
|
|
|
uchar *p = img.scanLine(y);
|
|
if (!p) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
|
|
for (unsigned int x = 0; x < bpl; ++x) {
|
|
p[x] = buf[x];
|
|
}
|
|
}
|
|
|
|
// by specification, the extended palette starts at file.size() - 769
|
|
quint8 flag = 0;
|
|
if (auto device = s.device()) {
|
|
if (device->isSequential()) {
|
|
while (flag != 12 && s.status() == QDataStream::Ok) {
|
|
s >> flag;
|
|
}
|
|
}
|
|
else {
|
|
device->seek(device->size() - 769);
|
|
s >> flag;
|
|
}
|
|
}
|
|
|
|
// qDebug() << "Palette Flag: " << flag;
|
|
if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
|
|
// Read the palette
|
|
quint8 r;
|
|
quint8 g;
|
|
quint8 b;
|
|
for (int i = 0; i < 256; ++i) {
|
|
s >> r >> g >> b;
|
|
img.setColor(i, qRgb(r, g, b));
|
|
}
|
|
}
|
|
|
|
return (s.status() == QDataStream::Ok);
|
|
}
|
|
|
|
static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|
{
|
|
QByteArray r_buf(header.BytesPerLine, 0);
|
|
QByteArray g_buf(header.BytesPerLine, 0);
|
|
QByteArray b_buf(header.BytesPerLine, 0);
|
|
|
|
img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32);
|
|
|
|
if (img.isNull()) {
|
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
|
return false;
|
|
}
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
if (s.atEnd()) {
|
|
return false;
|
|
}
|
|
|
|
if (!readLine(s, r_buf, header)) {
|
|
return false;
|
|
}
|
|
if (!readLine(s, g_buf, header)) {
|
|
return false;
|
|
}
|
|
if (!readLine(s, b_buf, header)) {
|
|
return false;
|
|
}
|
|
|
|
uint *p = (uint *)img.scanLine(y);
|
|
for (int x = 0; x < header.width(); ++x) {
|
|
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool writeLine(QDataStream &s, QByteArray &buf)
|
|
{
|
|
quint32 i = 0;
|
|
quint32 size = buf.size();
|
|
quint8 count;
|
|
quint8 data;
|
|
char byte;
|
|
|
|
while (i < size) {
|
|
count = 1;
|
|
byte = buf[i++];
|
|
|
|
while ((i < size) && (byte == buf[i]) && (count < 63)) {
|
|
++i;
|
|
++count;
|
|
}
|
|
|
|
data = byte;
|
|
|
|
if (count > 1 || data >= 0xc0) {
|
|
count |= 0xc0;
|
|
s << count;
|
|
}
|
|
|
|
s << data;
|
|
}
|
|
return (s.status() == QDataStream::Ok);
|
|
}
|
|
|
|
static bool writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
|
|
{
|
|
if (img.format() != QImage::Format_Mono) {
|
|
img = img.convertToFormat(QImage::Format_Mono);
|
|
}
|
|
if (img.isNull() || img.colorCount() < 1) {
|
|
return false;
|
|
}
|
|
auto rgb = img.color(0);
|
|
auto minIsBlack = (qRed(rgb) + qGreen(rgb) + qBlue(rgb)) / 3 < 127;
|
|
|
|
header.Bpp = 1;
|
|
header.NPlanes = 1;
|
|
header.BytesPerLine = img.bytesPerLine();
|
|
if (header.BytesPerLine == 0) {
|
|
return false;
|
|
}
|
|
|
|
s << header;
|
|
|
|
QByteArray buf(header.BytesPerLine, 0);
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
quint8 *p = img.scanLine(y);
|
|
|
|
// Invert as QImage uses reverse palette for monochrome images?
|
|
for (int i = 0; i < header.BytesPerLine; ++i) {
|
|
buf[i] = minIsBlack ? p[i] : ~p[i];
|
|
}
|
|
|
|
if (!writeLine(s, buf)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
|
|
{
|
|
header.Bpp = 1;
|
|
header.NPlanes = 4;
|
|
header.BytesPerLine = header.width() / 8;
|
|
if (header.BytesPerLine == 0) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
header.ColorMap.setColor(i, img.color(i));
|
|
}
|
|
|
|
s << header;
|
|
|
|
QByteArray buf[4];
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
buf[i].resize(header.BytesPerLine);
|
|
}
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
quint8 *p = img.scanLine(y);
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
buf[i].fill(0);
|
|
}
|
|
|
|
for (int x = 0; x < header.width(); ++x) {
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (*(p + x) & (1 << i)) {
|
|
buf[i][x / 8] = (int)(buf[i][x / 8]) | 1 << (7 - x % 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (!writeLine(s, buf[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
|
|
{
|
|
header.Bpp = 8;
|
|
header.NPlanes = 1;
|
|
header.BytesPerLine = img.bytesPerLine();
|
|
if (header.BytesPerLine == 0) {
|
|
return false;
|
|
}
|
|
|
|
s << header;
|
|
|
|
QByteArray buf(header.BytesPerLine, 0);
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
quint8 *p = img.scanLine(y);
|
|
|
|
for (int i = 0; i < header.BytesPerLine; ++i) {
|
|
buf[i] = p[i];
|
|
}
|
|
|
|
if (!writeLine(s, buf)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Write palette flag
|
|
quint8 byte = 12;
|
|
s << byte;
|
|
|
|
// Write palette
|
|
for (int i = 0; i < 256; ++i) {
|
|
s << RGB::from(img.color(i));
|
|
}
|
|
|
|
return (s.status() == QDataStream::Ok);
|
|
}
|
|
|
|
static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
|
{
|
|
header.Bpp = 8;
|
|
header.NPlanes = 3;
|
|
header.BytesPerLine = header.width();
|
|
if (header.BytesPerLine == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) {
|
|
img = img.convertToFormat(QImage::Format_RGB32);
|
|
}
|
|
if (img.isNull()) {
|
|
return false;
|
|
}
|
|
|
|
s << header;
|
|
|
|
QByteArray r_buf(header.width(), 0);
|
|
QByteArray g_buf(header.width(), 0);
|
|
QByteArray b_buf(header.width(), 0);
|
|
|
|
for (int y = 0; y < header.height(); ++y) {
|
|
auto p = (QRgb*)img.scanLine(y);
|
|
|
|
for (int x = 0; x < header.width(); ++x) {
|
|
QRgb rgb = *p++;
|
|
r_buf[x] = qRed(rgb);
|
|
g_buf[x] = qGreen(rgb);
|
|
b_buf[x] = qBlue(rgb);
|
|
}
|
|
|
|
if (!writeLine(s, r_buf)) {
|
|
return false;
|
|
}
|
|
if (!writeLine(s, g_buf)) {
|
|
return false;
|
|
}
|
|
if (!writeLine(s, b_buf)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
PCXHandler::PCXHandler()
|
|
{
|
|
}
|
|
|
|
bool PCXHandler::canRead() const
|
|
{
|
|
if (canRead(device())) {
|
|
setFormat("pcx");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PCXHandler::read(QImage *outImage)
|
|
{
|
|
QDataStream s(device());
|
|
s.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
if (s.device()->size() < 128) {
|
|
return false;
|
|
}
|
|
|
|
PCXHEADER header;
|
|
|
|
s >> header;
|
|
|
|
if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
|
|
return false;
|
|
}
|
|
|
|
auto ok = false;
|
|
QImage img;
|
|
if (header.Bpp == 1 && header.NPlanes == 1) {
|
|
ok = readImage1(img, s, header);
|
|
} else if (header.Bpp == 1 && header.NPlanes == 4) {
|
|
ok = readImage4(img, s, header);
|
|
} else if (header.Bpp == 8 && header.NPlanes == 1) {
|
|
ok = readImage8(img, s, header);
|
|
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
|
ok = readImage24(img, s, header);
|
|
}
|
|
|
|
if (img.isNull() || !ok) {
|
|
return false;
|
|
}
|
|
|
|
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
|
|
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
|
|
*outImage = img;
|
|
return true;
|
|
}
|
|
|
|
bool PCXHandler::write(const QImage &image)
|
|
{
|
|
QDataStream s(device());
|
|
s.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
QImage img = image;
|
|
|
|
const int w = img.width();
|
|
const int h = img.height();
|
|
|
|
if (w > 65536 || h > 65536) {
|
|
return false;
|
|
}
|
|
|
|
PCXHEADER header;
|
|
|
|
header.Manufacturer = 10;
|
|
header.Version = 5;
|
|
header.Encoding = 1;
|
|
header.XMin = 0;
|
|
header.YMin = 0;
|
|
header.XMax = w - 1;
|
|
header.YMax = h - 1;
|
|
header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
|
|
header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
|
|
header.Reserved = 0;
|
|
header.PaletteInfo = 1;
|
|
|
|
auto ok = false;
|
|
if (img.depth() == 1) {
|
|
ok = writeImage1(img, s, header);
|
|
} else if (img.depth() == 8 && img.colorCount() <= 16) {
|
|
ok = writeImage4(img, s, header);
|
|
} else if (img.depth() == 8) {
|
|
ok = writeImage8(img, s, header);
|
|
} else if (img.depth() >= 24) {
|
|
ok = writeImage24(img, s, header);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool PCXHandler::canRead(QIODevice *device)
|
|
{
|
|
if (!device) {
|
|
qWarning("PCXHandler::canRead() called with no device");
|
|
return false;
|
|
}
|
|
|
|
qint64 oldPos = device->pos();
|
|
|
|
char head[1];
|
|
qint64 readBytes = device->read(head, sizeof(head));
|
|
if (readBytes != sizeof(head)) {
|
|
if (device->isSequential()) {
|
|
while (readBytes > 0) {
|
|
device->ungetChar(head[readBytes-- - 1]);
|
|
}
|
|
} else {
|
|
device->seek(oldPos);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (device->isSequential()) {
|
|
while (readBytes > 0) {
|
|
device->ungetChar(head[readBytes-- - 1]);
|
|
}
|
|
} else {
|
|
device->seek(oldPos);
|
|
}
|
|
|
|
return qstrncmp(head, "\012", 1) == 0;
|
|
}
|
|
|
|
QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
if (format == "pcx") {
|
|
return Capabilities(CanRead | CanWrite);
|
|
}
|
|
if (!format.isEmpty()) {
|
|
return {};
|
|
}
|
|
if (!device->isOpen()) {
|
|
return {};
|
|
}
|
|
|
|
Capabilities cap;
|
|
if (device->isReadable() && PCXHandler::canRead(device)) {
|
|
cap |= CanRead;
|
|
}
|
|
if (device->isWritable()) {
|
|
cap |= CanWrite;
|
|
}
|
|
return cap;
|
|
}
|
|
|
|
QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
QImageIOHandler *handler = new PCXHandler;
|
|
handler->setDevice(device);
|
|
handler->setFormat(format);
|
|
return handler;
|
|
}
|
|
|
|
#include "moc_pcx_p.cpp"
|