/* This file is part of the KDE project Copyright (C) 2003 Ignacio Castaņo 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 #include #include typedef TQ_UINT32 uint; typedef TQ_UINT16 ushort; typedef TQ_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 TQDataStream & operator>> ( TQDataStream & 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(TQDataStream& s, unsigned int bytes) { char buf[4096]; while (bytes) { unsigned int num= TQMIN(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( TQDataStream & s, const PSDHeader & header, TQImage & 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(tqRgba(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( TQImageIO *io ) { TQDataStream s( io->ioDevice() ); s.setByteOrder( TQDataStream::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( TQImage() ); 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( TQImage() ); io->setStatus( -1 ); return; } TQImage img; if( !LoadPSD(s, header, img) ) { kdDebug(399) << "Error loading PSD file." << endl; io->setImage( TQImage() ); io->setStatus( -1 ); return; } io->setImage( img ); io->setStatus( 0 ); } void kimgio_psd_write( TQImageIO * ) { // TODO Stub! }