You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/kimgio/psd.cpp

283 lines
6.1 KiB

/* 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.h>
#include <qdatastream.h>
#include <kdebug.h>
typedef Q_UINT32 uint;
typedef Q_UINT16 ushort;
typedef Q_UINT8 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,sizeof(buf));
unsigned int l = num;
s.readRawBytes(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.
if( !img.create( header.width, header.height, 32 )) {
return false;
}
uint tmp;
// Skip mode data.
s >> tmp;
s.device()->at( s.device()->at() + tmp );
// Skip image resources.
s >> tmp;
s.device()->at( s.device()->at() + tmp );
// Skip the reserved data.
s >> tmp;
s.device()->at( s.device()->at() + 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.setAlphaBuffer( true );
// 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
void kimgio_psd_read( QImageIO *io )
{
QDataStream s( io->ioDevice() );
s.setByteOrder( QDataStream::BigEndian );
PSDHeader header;
s >> header;
// Check image file format.
if( s.atEnd() || !IsValid( header ) ) {
kdDebug(399) << "This PSD file is not valid." << endl;
io->setImage( 0 );
io->setStatus( -1 );
return;
}
// Check if it's a supported format.
if( !IsSupported( header ) ) {
kdDebug(399) << "This PSD file is not supported." << endl;
io->setImage( 0 );
io->setStatus( -1 );
return;
}
QImage img;
if( !LoadPSD(s, header, img) ) {
kdDebug(399) << "Error loading PSD file." << endl;
io->setImage( 0 );
io->setStatus( -1 );
return;
}
io->setImage( img );
io->setStatus( 0 );
}
void kimgio_psd_write( QImageIO * )
{
// TODO Stub!
}