/* 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 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; }