mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-18 03:54:18 -04:00
Move kimageformats code to the root directory.
This commit is contained in:
330
src/imageformats/psd.cpp
Normal file
330
src/imageformats/psd.cpp
Normal file
@ -0,0 +1,330 @@
|
||||
/* This file is part of the KDE project
|
||||
Copyright (C) 2003 Ignacio Castaño <castano@ludicon.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the Lesser GNU General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This code is based on Thacher Ulrich PSD loading code released
|
||||
on public domain. See: http://tulrich.com/geekstuff/
|
||||
*/
|
||||
|
||||
/* this code supports:
|
||||
* reading:
|
||||
* rle and raw psd files
|
||||
* writing:
|
||||
* not supported
|
||||
*/
|
||||
|
||||
#include "psd.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QtCore/QDataStream>
|
||||
// #include <QDebug>
|
||||
|
||||
typedef quint32 uint;
|
||||
typedef quint16 ushort;
|
||||
typedef quint8 uchar;
|
||||
|
||||
namespace // Private.
|
||||
{
|
||||
|
||||
enum ColorMode {
|
||||
CM_BITMAP = 0,
|
||||
CM_GRAYSCALE = 1,
|
||||
CM_INDEXED = 2,
|
||||
CM_RGB = 3,
|
||||
CM_CMYK = 4,
|
||||
CM_MULTICHANNEL = 7,
|
||||
CM_DUOTONE = 8,
|
||||
CM_LABCOLOR = 9
|
||||
};
|
||||
|
||||
struct PSDHeader {
|
||||
uint signature;
|
||||
ushort version;
|
||||
uchar reserved[6];
|
||||
ushort channel_count;
|
||||
uint height;
|
||||
uint width;
|
||||
ushort depth;
|
||||
ushort color_mode;
|
||||
};
|
||||
|
||||
static QDataStream & operator>> (QDataStream & s, PSDHeader & header)
|
||||
{
|
||||
s >> header.signature;
|
||||
s >> header.version;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
s >> header.reserved[i];
|
||||
}
|
||||
s >> header.channel_count;
|
||||
s >> header.height;
|
||||
s >> header.width;
|
||||
s >> header.depth;
|
||||
s >> header.color_mode;
|
||||
return s;
|
||||
}
|
||||
static bool seekBy(QDataStream& s, unsigned int bytes)
|
||||
{
|
||||
char buf[4096];
|
||||
while (bytes) {
|
||||
unsigned int num = qMin(bytes, (unsigned int)sizeof(buf));
|
||||
unsigned int l = num;
|
||||
s.readRawData(buf, l);
|
||||
if (l != num)
|
||||
return false;
|
||||
bytes -= num;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that the header is a valid PSD.
|
||||
static bool IsValid(const PSDHeader & header)
|
||||
{
|
||||
if (header.signature != 0x38425053) { // '8BPS'
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that the header is supported.
|
||||
static bool IsSupported(const PSDHeader & header)
|
||||
{
|
||||
if (header.version != 1) {
|
||||
return false;
|
||||
}
|
||||
if (header.channel_count > 16) {
|
||||
return false;
|
||||
}
|
||||
if (header.depth != 8) {
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode != CM_RGB) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load the PSD image.
|
||||
static bool LoadPSD(QDataStream & s, const PSDHeader & header, QImage & img)
|
||||
{
|
||||
// Create dst image.
|
||||
img = QImage(header.width, header.height, QImage::Format_RGB32);
|
||||
|
||||
uint tmp;
|
||||
|
||||
// Skip mode data.
|
||||
s >> tmp;
|
||||
s.device()->seek(s.device()->pos() + tmp);
|
||||
|
||||
// Skip image resources.
|
||||
s >> tmp;
|
||||
s.device()->seek(s.device()->pos() + tmp);
|
||||
|
||||
// Skip the reserved data.
|
||||
s >> tmp;
|
||||
s.device()->seek(s.device()->pos() + tmp);
|
||||
|
||||
// Find out if the data is compressed.
|
||||
// Known values:
|
||||
// 0: no compression
|
||||
// 1: RLE compressed
|
||||
ushort compression;
|
||||
s >> compression;
|
||||
|
||||
if (compression > 1) {
|
||||
// Unknown compression type.
|
||||
return false;
|
||||
}
|
||||
|
||||
uint channel_num = header.channel_count;
|
||||
|
||||
// Clear the image.
|
||||
if (channel_num < 4) {
|
||||
img.fill(qRgba(0, 0, 0, 0xFF));
|
||||
} else {
|
||||
// Enable alpha.
|
||||
img = img.convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
// Ignore the other channels.
|
||||
channel_num = 4;
|
||||
}
|
||||
|
||||
const uint pixel_count = header.height * header.width;
|
||||
|
||||
static const uint components[4] = {2, 1, 0, 3}; // @@ Is this endian dependant?
|
||||
|
||||
if (compression) {
|
||||
|
||||
// Skip row lengths.
|
||||
if (!seekBy(s, header.height * header.channel_count * sizeof(ushort)))
|
||||
return false;
|
||||
|
||||
// Read RLE data.
|
||||
for (uint channel = 0; channel < channel_num; channel++) {
|
||||
|
||||
uchar * ptr = img.bits() + components[channel];
|
||||
|
||||
uint count = 0;
|
||||
while (count < pixel_count) {
|
||||
uchar c;
|
||||
if (s.atEnd())
|
||||
return false;
|
||||
s >> c;
|
||||
uint len = c;
|
||||
|
||||
if (len < 128) {
|
||||
// Copy next len+1 bytes literally.
|
||||
len++;
|
||||
count += len;
|
||||
if (count > pixel_count)
|
||||
return false;
|
||||
|
||||
while (len != 0) {
|
||||
s >> *ptr;
|
||||
ptr += 4;
|
||||
len--;
|
||||
}
|
||||
} else if (len > 128) {
|
||||
// Next -len+1 bytes in the dest are replicated from next source byte.
|
||||
// (Interpret len as a negative 8-bit int.)
|
||||
len ^= 0xFF;
|
||||
len += 2;
|
||||
count += len;
|
||||
if (s.atEnd() || count > pixel_count)
|
||||
return false;
|
||||
uchar val;
|
||||
s >> val;
|
||||
while (len != 0) {
|
||||
*ptr = val;
|
||||
ptr += 4;
|
||||
len--;
|
||||
}
|
||||
} else if (len == 128) {
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...)
|
||||
// where each channel consists of an 8-bit value for each pixel in the image.
|
||||
|
||||
// Read the data by channel.
|
||||
for (uint channel = 0; channel < channel_num; channel++) {
|
||||
|
||||
uchar * ptr = img.bits() + components[channel];
|
||||
|
||||
// Read the data.
|
||||
uint count = pixel_count;
|
||||
while (count != 0) {
|
||||
s >> *ptr;
|
||||
ptr += 4;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Private
|
||||
|
||||
|
||||
PSDHandler::PSDHandler()
|
||||
{
|
||||
}
|
||||
|
||||
bool PSDHandler::canRead() const
|
||||
{
|
||||
if (canRead(device())) {
|
||||
setFormat("psd");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PSDHandler::read(QImage *image)
|
||||
{
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
PSDHeader header;
|
||||
s >> header;
|
||||
|
||||
// Check image file format.
|
||||
if (s.atEnd() || !IsValid(header)) {
|
||||
// qDebug() << "This PSD file is not valid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's a supported format.
|
||||
if (!IsSupported(header)) {
|
||||
// qDebug() << "This PSD file is not supported.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage img;
|
||||
if (!LoadPSD(s, header, img)) {
|
||||
// qDebug() << "Error loading PSD file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
*image = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PSDHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
qWarning("PSDHandler::canRead() called with no device");
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 oldPos = device->pos();
|
||||
|
||||
char head[4];
|
||||
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, "8BPS", 4) == 0;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "psd")
|
||||
return Capabilities(CanRead);
|
||||
if (!format.isEmpty())
|
||||
return 0;
|
||||
if (!device->isOpen())
|
||||
return 0;
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && PSDHandler::canRead(device))
|
||||
cap |= CanRead;
|
||||
return cap;
|
||||
}
|
||||
|
||||
QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
QImageIOHandler *handler = new PSDHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
Reference in New Issue
Block a user