PHP Classes
elePHPant
Icontem

File: classPhpPsdReader.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Tim de Koning  >  PHP PSD Reader  >  classPhpPsdReader.php  >  Download  
File: classPhpPsdReader.php
Role: Class source
Content type: text/plain
Description: PHP PSD reader class
Class: PHP PSD Reader
Open images in the PhotoShop PSD format
Author: By
Last change: - Implemented support for CMYK, bitmaps and 16 bit color values
- Performance improvements and bugfixes
Date: 8 years ago
Size: 9,940 bytes
 

Contents

Class file image Download
<?
/* This file is released under the GPL, any version you like
*
* PHP PSD reader class, v1.3
*
* By Tim de Koning
*
* Kingsquare Information Services, 22 jan 2007
*
* example use:
* ------------
* <?php
* include_once('classPhpPsdReader.php')
* header("Content-type: image/jpeg");
* print imagejpeg(imagecreatefrompsd('test.psd'));
* ?>
*
* More info, bugs or requests, contact info@kingsquare.nl
*
* Latest version and demo: http://www.kingsquare.nl/phppsdreader
*
* TODO
* ----
* - read color values for "multichannel data" PSD files
* - find and implement (hunter)lab to RGB algorithm
* - fix 32 bit colors... has something to do with gamma and exposure available since CS2, but dunno how to read them...
*/


class PhpPsdReader {
    var
$infoArray;
    var
$fp;
    var
$fileName;
    var
$tempFileName;
    var
$colorBytesLength;

    function
PhpPsdReader($fileName) {
       
set_time_limit(0);
       
$this->infoArray = array();
       
$this->fileName = $fileName;
       
$this->fp = fopen($this->fileName,'r');

        if (
fread($this->fp,4)=='8BPS') {
           
$this->infoArray['version id'] = $this->_getInteger(2);
           
fseek($this->fp,6,SEEK_CUR); // 6 bytes of 0's
           
$this->infoArray['channels'] = $this->_getInteger(2);
           
$this->infoArray['rows'] = $this->_getInteger(4);
           
$this->infoArray['columns'] = $this->_getInteger(4);
           
$this->infoArray['colorDepth'] = $this->_getInteger(2);
           
$this->infoArray['colorMode'] = $this->_getInteger(2);


           
/* COLOR MODE DATA SECTION */ //4bytes Length The length of the following color data.
           
$this->infoArray['colorModeDataSectionLength'] = $this->_getInteger(4);
           
fseek($this->fp,$this->infoArray['colorModeDataSectionLength'],SEEK_CUR); // ignore this snizzle

            /* IMAGE RESOURCES */
           
$this->infoArray['imageResourcesSectionLength'] = $this->_getInteger(4);
           
fseek($this->fp,$this->infoArray['imageResourcesSectionLength'],SEEK_CUR); // ignore this snizzle

            /* LAYER AND MASK */
           
$this->infoArray['layerMaskDataSectionLength'] = $this->_getInteger(4);
           
fseek($this->fp,$this->infoArray['layerMaskDataSectionLength'],SEEK_CUR); // ignore this snizzle


            /* IMAGE DATA */
           
$this->infoArray['compressionType'] = $this->_getInteger(2);
           
$this->infoArray['oneColorChannelPixelBytes'] = $this->infoArray['colorDepth']/8;
           
$this->colorBytesLength = $this->infoArray['rows']*$this->infoArray['columns']*$this->infoArray['oneColorChannelPixelBytes'];

            if (
$this->infoArray['colorMode']==2) {
               
$this->infoArray['error'] = 'images with indexed colours are not supported yet';
                return
false;
            }
        } else {
           
$this->infoArray['error'] = 'invalid or unsupported psd';
            return
false;
        }
    }


    function
getImage() {
       
// decompress image data if required
       
switch($this->infoArray['compressionType']) {
           
// case 2:, case 3: zip not supported yet..
           
case 1:
               
// packed bits
               
$this->infoArray['scanLinesByteCounts'] = array();
                for (
$i=0; $i<($this->infoArray['rows']*$this->infoArray['channels']); $i++) $this->infoArray['scanLinesByteCounts'][] = $this->_getInteger(2);
               
$this->tempFileName = tempnam(realpath('/tmp'),'decompressedImageData');
               
$tfp = fopen($this->tempFileName,'wb');
                foreach (
$this->infoArray['scanLinesByteCounts'] as $scanLinesByteCount) {
                   
fwrite($tfp,$this->_getPackedBitsDecoded(fread($this->fp,$scanLinesByteCount)));
                }
               
fclose($tfp);
               
fclose($this->fp);
               
$this->fp = fopen($this->tempFileName,'r');
            default:
               
// continue with current file handle;
               
break;
        }

       
// let's write pixel by pixel....
       
$image = imagecreatetruecolor($this->infoArray['columns'],$this->infoArray['rows']);

        for (
$rowPointer = 0; ($rowPointer < $this->infoArray['rows']); $rowPointer++) {
            for (
$columnPointer = 0; ($columnPointer < $this->infoArray['columns']); $columnPointer++) {
               
/* The color mode of the file. Supported values are: Bitmap=0;
                    Grayscale=1; Indexed=2; RGB=3; CMYK=4; Multichannel=7;
                    Duotone=8; Lab=9.
                */
               
switch ($this->infoArray['colorMode']) {
                    case
2: // indexed... info should be able to extract from color mode data section. not implemented yet, so is grayscale
                       
exit;
                        break;
                    case
0:
                       
// bit by bit
                       
if ($columnPointer == 0) $bitPointer = 0;
                        if (
$bitPointer==0) $currentByteBits = str_pad(base_convert(bin2hex(fread($this->fp,1)), 16, 2),8,'0',STR_PAD_LEFT);
                       
$r = $g = $b = (($currentByteBits[$bitPointer]=='1')?0:255);
                       
$bitPointer++;
                        if (
$bitPointer==8) $bitPointer = 0;
                        break;

                    case
1:
                    case
8: // 8 is indexed with 1 color..., so grayscale
                       
$r = $g = $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                        break;

                    case
4: // CMYK
                       
$c = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
$currentPointerPos = ftell($this->fp);
                       
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                       
$m = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                       
$y = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                       
$k = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
fseek($this->fp,$currentPointerPos);
                       
$r = round(($c * $k) / (pow(2,$this->infoArray['colorDepth'])-1));
                       
$g = round(($m * $k) / (pow(2,$this->infoArray['colorDepth'])-1));
                       
$b = round(($y * $k) / (pow(2,$this->infoArray['colorDepth'])-1));

                          break;

                          case
9: // hunter Lab
                              // i still need an understandable lab2rgb convert algorithm... if you have one, please let me know!
                           
$l = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                           
$currentPointerPos = ftell($this->fp);
                           
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                           
$a = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                           
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                           
$b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                           
fseek($this->fp,$currentPointerPos);

                           
$r = $l;
                           
$g = $a;
                           
$b = $b;

                        break;
                    default:
                       
$r = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
$currentPointerPos = ftell($this->fp);
                       
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                       
$g = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR);
                       
$b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
                       
fseek($this->fp,$currentPointerPos);
                        break;

                }

                if ((
$this->infoArray['oneColorChannelPixelBytes']==2)) {
                   
$r = $r >> 8;
                   
$g = $g >> 8;
                   
$b = $b >> 8;
                } elseif ((
$this->infoArray['oneColorChannelPixelBytes']==4)) {
                   
$r = $r >> 24;
                   
$g = $g >> 24;
                   
$b = $b >> 24;
                }

               
$pixelColor = imagecolorallocate($image,$r,$g,$b);
               
imagesetpixel($image,$columnPointer,$rowPointer,$pixelColor);
            }
        }
       
fclose($this->fp);
        if (isset(
$this->tempFileName)) unlink($this->tempFileName);
        return
$image;
    }

   
/**
     *
     * PRIVATE FUNCTIONS
     *
     */

   
function _getPackedBitsDecoded($string) {
       
/*
        The PackBits algorithm will precede a block of data with a one byte header n, where n is interpreted as follows:
        n Meaning
        0 to 127 Copy the next n + 1 symbols verbatim
        -127 to -1 Repeat the next symbol 1 - n times
        -128 Do nothing

        Decoding:
        Step 1. Read the block header (n).
        Step 2. If the header is an EOF exit.
        Step 3. If n is non-negative, copy the next n + 1 symbols to the output stream and go to step 1.
        Step 4. If n is negative, write 1 - n copies of the next symbol to the output stream and go to step 1.

        */

       
$stringPointer = 0;
       
$returnString = '';

        while (
1) {
            if (isset(
$string[$stringPointer])) $headerByteValue = $this->_unsignedToSigned(hexdec(bin2hex($string[$stringPointer])),1);
            else return
$returnString;
           
$stringPointer++;

            if (
$headerByteValue >= 0) {
                for (
$i=0; $i <= $headerByteValue; $i++) {
                   
$returnString .= $string[$stringPointer];
                   
$stringPointer++;
                }
            } else {
                if (
$headerByteValue != -128) {
                   
$copyByte = $string[$stringPointer];
                   
$stringPointer++;

                    for (
$i=0; $i < (1-$headerByteValue); $i++) {
                       
$returnString .= $copyByte;
                    }
                }
            }
        }
    }

    function
_unsignedToSigned($int,$byteSize=1) {
        switch(
$byteSize) {
            case
1:
                if (
$int<128) return $int;
                else return -
256+$int;
                break;

            case
2:
                if (
$int<32768) return $int;
                else return -
65536+$int;

            case
4:
                if (
$int<2147483648) return $int;
                else return -
4294967296+$int;

            default:
                return
$int;
        }
    }

    function
_hexReverse($hex) {
       
$output = '';
        if (
strlen($hex)%2) return false;
        for (
$pointer = strlen($hex);$pointer>=0;$pointer-=2) $output .= substr($hex,$pointer,2);
        return
$output;
    }

    function
_getInteger($byteCount=1) {
        switch (
$byteCount) {
            case
4:
               
// for some strange reason this is still broken...
               
return @reset(unpack('N',fread($this->fp,4)));
                break;

            case
2:
                return @
reset(unpack('n',fread($this->fp,2)));
                break;

            default:
                return
hexdec($this->_hexReverse(bin2hex(fread($this->fp,$byteCount))));
        }
    }
}

/**
 * Returns an image identifier representing the image obtained from the given filename, using only GD, returns an empty string on failure
 *
 * @param string $fileName
 * @return image identifier
 */

function imagecreatefrompsd($fileName) {
   
$psdReader = new PhpPsdReader($fileName);
    if (isset(
$psdReader->infoArray['error'])) return '';
    else return
$psdReader->getImage();
}
?>