Completed
Push — master ( e7d60a...f2d2e3 )
by Michael
02:44
created

classPhpPsdReader.php ➔ imagecreatefrompsd()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 9
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/* This file is released under the GPL, any version you like
3
*
4
*	PHP PSD reader class, v1.3
5
*
6
*	By Tim de Koning
7
*
8
*	Kingsquare Information Services, 22 jan 2007
9
*
10
*	example use:
11
*	------------
12
*	<?php
13
*	include_once('classPhpPsdReader.php')
14
*	header("Content-type: image/jpeg");
15
*	print imagejpeg(imagecreatefrompsd('test.psd'));
16
*	?>
17
*
18
*	More info, bugs or requests, contact [email protected]
19
*
20
*	Latest version and demo: http://www.kingsquare.nl/phppsdreader
21
*
22
*	TODO
23
*	----
24
*	- read color values for "multichannel data" PSD files
25
*	- find and implement (hunter)lab to RGB algorithm
26
*	- fix 32 bit colors... has something to do with gamma and exposure available since CS2, but dunno how to read them...
27
*/
28
29 View Code Duplication
class PhpPsdReader
0 ignored issues
show
Duplication introduced by
This class seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
30
{
31
    public $infoArray;
32
    public $fp;
33
    public $fileName;
34
    public $tempFileName;
35
    public $colorBytesLength;
36
37
    public function __construct($fileName)
38
    {
39
        set_time_limit(0);
40
        $this->infoArray = [];
41
        $this->fileName  = $fileName;
42
        $this->fp        = fopen($this->fileName, 'rb');
43
44
        if ('8BPS' === fread($this->fp, 4)) {
45
            $this->infoArray['version id'] = $this->_getInteger(2);
46
            fseek($this->fp, 6, SEEK_CUR); // 6 bytes of 0's
47
            $this->infoArray['channels']   = $this->_getInteger(2);
48
            $this->infoArray['rows']       = $this->_getInteger(4);
49
            $this->infoArray['columns']    = $this->_getInteger(4);
50
            $this->infoArray['colorDepth'] = $this->_getInteger(2);
51
            $this->infoArray['colorMode']  = $this->_getInteger(2);
52
53
            /* COLOR MODE DATA SECTION */ //4bytes Length The length of the following color data.
54
            $this->infoArray['colorModeDataSectionLength'] = $this->_getInteger(4);
55
            fseek($this->fp, $this->infoArray['colorModeDataSectionLength'], SEEK_CUR); // ignore this snizzle
56
57
            /*  IMAGE RESOURCES */
58
            $this->infoArray['imageResourcesSectionLength'] = $this->_getInteger(4);
59
            fseek($this->fp, $this->infoArray['imageResourcesSectionLength'], SEEK_CUR); // ignore this snizzle
60
61
            /*  LAYER AND MASK */
62
            $this->infoArray['layerMaskDataSectionLength'] = $this->_getInteger(4);
63
            fseek($this->fp, $this->infoArray['layerMaskDataSectionLength'], SEEK_CUR); // ignore this snizzle
64
65
            /*  IMAGE DATA */
66
            $this->infoArray['compressionType']           = $this->_getInteger(2);
67
            $this->infoArray['oneColorChannelPixelBytes'] = $this->infoArray['colorDepth'] / 8;
68
            $this->colorBytesLength                       = $this->infoArray['rows'] * $this->infoArray['columns'] * $this->infoArray['oneColorChannelPixelBytes'];
69
70
            if (2 == $this->infoArray['colorMode']) {
71
                $this->infoArray['error'] = 'images with indexed colours are not supported yet';
72
                return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
73
            }
74
        } else {
75
            $this->infoArray['error'] = 'invalid or unsupported psd';
76
            return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
77
        }
78
    }
79
80
    public function getImage()
81
    {
82
        // decompress image data if required
83
        switch ($this->infoArray['compressionType']) {
84
            // case 2:, case 3: zip not supported yet..
85
            case 1:
86
                // packed bits
87
                $this->infoArray['scanLinesByteCounts'] = [];
88
                for ($i = 0; $i < ($this->infoArray['rows'] * $this->infoArray['channels']); $i++) {
89
                    $this->infoArray['scanLinesByteCounts'][] = $this->_getInteger(2);
90
                }
91
                $this->tempFileName = tempnam(realpath('/tmp'), 'decompressedImageData');
92
                $tfp                = fopen($this->tempFileName, 'wb');
93
                foreach ($this->infoArray['scanLinesByteCounts'] as $scanLinesByteCount) {
94
                    fwrite($tfp, $this->_getPackedBitsDecoded(fread($this->fp, $scanLinesByteCount)));
95
                }
96
                fclose($tfp);
97
                fclose($this->fp);
98
                $this->fp = fopen($this->tempFileName, 'rb');
99
            default:
100
                // continue with current file handle;
101
                break;
102
        }
103
104
        // let's write pixel by pixel....
105
        $image = imagecreatetruecolor($this->infoArray['columns'], $this->infoArray['rows']);
106
107
        for ($rowPointer = 0; ($rowPointer < $this->infoArray['rows']); $rowPointer++) {
108
            for ($columnPointer = 0; ($columnPointer < $this->infoArray['columns']); $columnPointer++) {
109
                /* 	The color mode of the file. Supported values are: Bitmap=0;
110
                    Grayscale=1; Indexed=2; RGB=3; CMYK=4; Multichannel=7;
111
                    Duotone=8; Lab=9.
112
                */
113
                switch ($this->infoArray['colorMode']) {
114
                    case 2: // indexed... info should be able to extract from color mode data section. not implemented yet, so is grayscale
115
                        exit;
116
                        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
117
                    case 0:
118
                        // bit by bit
119
                        if (0 == $columnPointer) {
120
                            $bitPointer = 0;
121
                        }
122
                        if (0 == $bitPointer) {
0 ignored issues
show
Bug introduced by
The variable $bitPointer does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
123
                            $currentByteBits = str_pad(base_convert(bin2hex(fread($this->fp, 1)), 16, 2), 8, '0', STR_PAD_LEFT);
124
                        }
125
                        $r = $g = $b = (('1' == $currentByteBits[$bitPointer]) ? 0 : 255);
0 ignored issues
show
Bug introduced by
The variable $currentByteBits does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
126
                        $bitPointer++;
127
                        if (8 == $bitPointer) {
128
                            $bitPointer = 0;
129
                        }
130
                        break;
131
132
                    case 1:
133
                    case 8: // 8 is indexed with 1 color..., so grayscale
134
                        $r = $g = $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
135
                        break;
136
137
                    case 4: // CMYK
138
                        $c                 = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
139
                        $currentPointerPos = ftell($this->fp);
140
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
141
                        $m = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
142
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
143
                        $y = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
144
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
145
                        $k = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
146
                        fseek($this->fp, $currentPointerPos);
147
                        $r = round(($c * $k) / (pow(2, $this->infoArray['colorDepth']) - 1));
148
                        $g = round(($m * $k) / (pow(2, $this->infoArray['colorDepth']) - 1));
149
                        $b = round(($y * $k) / (pow(2, $this->infoArray['colorDepth']) - 1));
150
151
                        break;
152
153
                    case 9: // hunter Lab
154
                        // i still need an understandable lab2rgb convert algorithm... if you have one, please let me know!
155
                        $l                 = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
156
                        $currentPointerPos = ftell($this->fp);
157
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
158
                        $a = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
159
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
160
                        $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
161
                        fseek($this->fp, $currentPointerPos);
162
163
                        $r = $l;
164
                        $g = $a;
165
                        $b = $b;
0 ignored issues
show
Bug introduced by
Why assign $b to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
166
167
                        break;
168
                    default:
169
                        $r                 = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
170
                        $currentPointerPos = ftell($this->fp);
171
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
172
                        $g = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
173
                        fseek($this->fp, $this->colorBytesLength - 1, SEEK_CUR);
174
                        $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']);
175
                        fseek($this->fp, $currentPointerPos);
176
                        break;
177
                }
178
179
                if ((2 == $this->infoArray['oneColorChannelPixelBytes'])) {
180
                    $r = $r >> 8;
181
                    $g = $g >> 8;
182
                    $b = $b >> 8;
183
                } elseif ((4 == $this->infoArray['oneColorChannelPixelBytes'])) {
184
                    $r = $r >> 24;
185
                    $g = $g >> 24;
186
                    $b = $b >> 24;
187
                }
188
189
                $pixelColor = imagecolorallocate($image, $r, $g, $b);
190
                imagesetpixel($image, $columnPointer, $rowPointer, $pixelColor);
191
            }
192
        }
193
        fclose($this->fp);
194
        if (isset($this->tempFileName)) {
195
            unlink($this->tempFileName);
196
        }
197
        return $image;
198
    }
199
200
    /**
201
     *
202
     * PRIVATE FUNCTIONS
203
     * @param $string
204
     * @return string
205
     */
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
206
207
    public function _getPackedBitsDecoded($string)
208
    {
209
        /*
210
        The PackBits algorithm will precede a block of data with a one byte header n, where n is interpreted as follows:
211
        n Meaning
212
        0 to 127 Copy the next n + 1 symbols verbatim
213
        -127 to -1 Repeat the next symbol 1 - n times
214
        -128 Do nothing
215
216
        Decoding:
217
        Step 1. Read the block header (n).
218
        Step 2. If the header is an EOF exit.
219
        Step 3. If n is non-negative, copy the next n + 1 symbols to the output stream and go to step 1.
220
        Step 4. If n is negative, write 1 - n copies of the next symbol to the output stream and go to step 1.
221
222
        */
223
224
        $stringPointer = 0;
225
        $returnString  = '';
226
227
        while (1) {
228
            if (isset($string[$stringPointer])) {
229
                $headerByteValue = $this->_unsignedToSigned(hexdec(bin2hex($string[$stringPointer])), 1);
230
            } else {
231
                return $returnString;
232
            }
233
            $stringPointer++;
234
235
            if ($headerByteValue >= 0) {
236
                for ($i = 0; $i <= $headerByteValue; $i++) {
237
                    $returnString .= $string[$stringPointer];
238
                    $stringPointer++;
239
                }
240
            } else {
241
                if (-128 != $headerByteValue) {
242
                    $copyByte = $string[$stringPointer];
243
                    $stringPointer++;
244
245
                    for ($i = 0; $i < (1 - $headerByteValue); $i++) {
246
                        $returnString .= $copyByte;
247
                    }
248
                }
249
            }
250
        }
251
    }
252
253
    public function _unsignedToSigned($int, $byteSize = 1)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
254
    {
255
        switch ($byteSize) {
256
            case 1:
257
                if ($int < 128) {
258
                    return $int;
259
                } else {
260
                    return -256 + $int;
261
                }
262
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
263
264
            case 2:
265
                if ($int < 32768) {
266
                    return $int;
267
                } else {
268
                    return -65536 + $int;
269
                }
270
271
            case 4:
272
                if ($int < 2147483648) {
273
                    return $int;
274
                } else {
275
                    return -4294967296 + $int;
276
                }
277
278
            default:
279
                return $int;
280
        }
281
    }
282
283
    public function _hexReverse($hex)
284
    {
285
        $output = '';
286
        if (strlen($hex) % 2) {
287
            return false;
288
        }
289
        for ($pointer = strlen($hex); $pointer >= 0; $pointer -= 2) {
290
            $output .= substr($hex, $pointer, 2);
291
        }
292
        return $output;
293
    }
294
295
    public function _getInteger($byteCount = 1)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
296
    {
297
        switch ($byteCount) {
298
            case 4:
299
                // for some strange reason this is still broken...
300
                return @reset(unpack('N', fread($this->fp, 4)));
0 ignored issues
show
Bug introduced by
unpack('N', fread($this->fp, 4)) cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
301
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
302
303
            case 2:
304
                return @reset(unpack('n', fread($this->fp, 2)));
0 ignored issues
show
Bug introduced by
unpack('n', fread($this->fp, 2)) cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
305
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
306
307
            default:
308
                return hexdec($this->_hexReverse(bin2hex(fread($this->fp, $byteCount))));
309
        }
310
    }
311
}
312
313
/**
314
 * Returns an image identifier representing the image obtained from the given filename, using only GD, returns an empty string on failure
315
 *
316
 * @param string $fileName
317
 * @return image identifier
318
 */
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null|resource?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
319
320 View Code Duplication
function imagecreatefrompsd($fileName)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
321
{
322
    $psdReader = new PhpPsdReader($fileName);
323
    if (isset($psdReader->infoArray['error'])) {
324
        return '';
325
    } else {
326
        return $psdReader->getImage();
327
    }
328
}
329