Completed
Push — master ( 8dc2ed...04d962 )
by Roberto
05:56 queued 03:45
created

Graphics::convertBW()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
ccs 0
cts 11
cp 0
rs 9.4285
cc 1
eloc 10
nc 1
nop 0
crap 2
1
<?php
2
3
namespace Posprint\Graphics;
4
5
/**
6
 * Classe Graphics
7
 *
8
 * @category  NFePHP
9
 * @package   Posprint
10
 * @copyright Copyright (c) 2016
11
 * @license   http://www.gnu.org/licenses/lesser.html LGPL v3
12
 * @author    Roberto L. Machado <linux dot rlm at gmail dot com>
13
 * @link      http://github.com/nfephp-org/posprint for the canonical source repository
14
 */
15
16
use Posprint\Graphics\Basic;
17
use Endroid\QrCode\QrCode;
18
use RuntimeException;
19
use InvalidArgumentException;
20
21
class Graphics extends Basic
22
{
23
    /**
24
     * Image prixels in BW
25
     *
26
     * @var string
27
     */
28
    protected $imgData = null;
29
    /**
30
     * Image Raster bit
31
     *
32
     * @var string
33
     */
34
    protected $imgRasterData = null;
35
  
36
    /**
37
     * Constructor
38
     * Load a image, if passed a path to file and adjust dimentions
39
     *
40
     * @param  string $filename
41
     * @param  int    $width
42
     * @param  int    $height
43
     * @throws RuntimeException
44
     */
45 12
    public function __construct($filename = null, $width = null, $height = null)
46
    {
47 12
        if (! $this->isGdSupported()) {
48
            throw new RuntimeException("GD module not found.");
49
        }
50 12
        $this->imgHeight = 0;
51 12
        $this->imgWidth = 0;
52 12
        $this->imgData = null;
53 12
        $this->imgRasterData = null;
54
        // Load the image, if the patch was passed
55 12
        if (! is_null($filename)) {
56 9
            $this->load($filename, $width, $height);
57 7
        }
58 10
    }
59
    
60
    /**
61
     * Return a string of bytes
62
     * for inclusion on printer commands
63
     * This method change image to Black and White and 
64
     * reducing the color resolution of 1 bit per pixel
65
     * 
66
     * @return string
67
     */
68 2
    public function getRasterImage()
69
    {
70 2
        $this->resizeImage($this->imgWidth);
71 2
        $this->convertPixelBW();
72 2
        $this->convertRaster();
73 2
        return $this->imgRasterData;
74
    }
75
76
    /**
77
     * load
78
     * Load image file and adjust dimentions
79
     *
80
     * @param  string $filename path to image file
81
     * @param  float  $width
82
     * @param  float  $height
83
     * @throws InvalidArgumentException
84
     * @throws RuntimeException
85
     */
86 9
    public function load($filename, $width = null, $height = null)
87
    {
88 9
        if (! is_file($filename)) {
89 1
            throw new InvalidArgumentException("Image file not found.");
90
        }
91 8
        if (! is_readable($filename)) {
92
            throw new RuntimeException("The file can not be read due to lack of permissions.");
93
        }
94
        //identify type of image and load with GD
95 8
        $tipo = $this->identifyImg($filename);
96 8
        if ($tipo == 'BMP') {
97
            $img = $this->loadBMP($filename);
98
            if ($img === false) {
99
                throw new InvalidArgumentException("Image file is not a BMP");
100
            }
101
        } else {
102 8
            $func = 'imagecreatefrom' . strtolower($tipo);
103 8
            if (! function_exists($func)) {
104 1
                throw new RuntimeException("It is not possible to use or handle this type of image with GD");
105
            }
106 7
            $this->img = $func($filename);
107
        }
108 7
        if (! $this->img) {
109
            throw new RuntimeException("Failed to load image '$filename'.");
110
        }
111
        //get image dimentions
112 7
        $this->getDimImage();
113 7
        if ($width != null || $height != null) {
114
            $this->resizeImage($width, $height);
115
        }
116 7
    }
117
    
118
    /**
119
     * Converts a true color image to Black and white
120
     */
121
    public function convertBW()
122
    {
123
        $newimg = imagecreatetruecolor($this->imgWidth, $this->imgHeight);
124
        imagealphablending($newimg, false);
125
        imagesavealpha($newimg, true);
126
        imagecopyresampled($newimg, $this->img, 0, 0, 0, 0, $this->imgWidth, $this->imgHeight, $this->imgWidth, $this->imgHeight);
127
        $bcg = imagecolorallocate($newimg, 255, 255, 255);
128
        imagefill($newimg, 0, 0, $bcg);
129
        imagefilter($newimg, IMG_FILTER_GRAYSCALE);
130
        imagefilter($newimg, IMG_FILTER_CONTRAST, -255);
131
        $this->img = $newimg;
132
    }
133
    
134
    /**
135
     * Save image to file
136
     *
137
     * @param string $filename
138
     * @param string $type  PNG, JPG, GIF, BMP
139
     * @param integer $quality 0 - 100 default 75
140
     * @return boolean
141
     */
142 2
    public function save($filename = null, $type = 'PNG', $quality = 75)
143
    {
144 2
        $type = strtoupper($type);
145 2
        if ($type == 'BMP') {
146
            $this->convert2BMP($filename);
147
            return true;
148
        }
149 2
        $aTypes = ['PNG', 'JPG', 'JPEG',  'GIF'];
150 2
        if (! in_array($type, $aTypes)) {
151
            throw InvalidArgumentException('This file type is not supported.');
152
        }
153 2
        return $this->saveImage($filename, $this->img, $type, $quality);
154
    }
155
    
156
    /**
157
     * Convert a GD image into a BMP string representation
158
     *
159
     * @param string $filename
160
     * @return string
161
     */
162 3
    public function convert2BMP($filename = null)
163
    {
164 3
        if (! is_resource($this->img)) {
165
            return '';
166
        }
167
        //to remove alpha color and put white instead
168 3
        $img = $this->img;
169 3
        $imageX = imagesx($img);
170 3
        $imageY = imagesy($img);
171 3
        $bmp = '';
172 3
        for ($yInd = ($imageY - 1); $yInd >= 0; $yInd--) {
173 3
            $thisline = '';
174 3
            for ($xInd = 0; $xInd < $imageX; $xInd++) {
175 3
                $argb = self::getPixelColor($img, $xInd, $yInd);
176 3
                $thisline .= chr($argb['blue']).chr($argb['green']).chr($argb['red']);
177 3
            }
178 3
            while (strlen($thisline) % 4) {
179 3
                $thisline .= "\x00";
180 3
            }
181 3
            $bmp .= $thisline;
182 3
        }
183 3
        $bmpSize = strlen($bmp) + 14 + 40;
184
        // bitMapHeader [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
185 3
        $bitMapHeader  = 'BM'; // WORD bfType;
186 3
        $bitMapHeader .= self::littleEndian2String($bmpSize, 4); // DWORD bfSize;
187 3
        $bitMapHeader .= self::littleEndian2String(0, 2); // WORD bfReserved1;
188 3
        $bitMapHeader .= self::littleEndian2String(0, 2); // WORD bfReserved2;
189 3
        $bitMapHeader .= self::littleEndian2String(54, 4); // DWORD bfOffBits;
190
        // bitMapInfoHeader - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
191 3
        $bitMapInfoHeader  = self::littleEndian2String(40, 4); // DWORD biSize;
192 3
        $bitMapInfoHeader .= self::littleEndian2String($imageX, 4); // LONG biWidth;
193 3
        $bitMapInfoHeader .= self::littleEndian2String($imageY, 4); // LONG biHeight;
194 3
        $bitMapInfoHeader .= self::littleEndian2String(1, 2); // WORD biPlanes;
195 3
        $bitMapInfoHeader .= self::littleEndian2String(24, 2); // WORD biBitCount;
196 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biCompression;
197 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biSizeImage;
198 3
        $bitMapInfoHeader .= self::littleEndian2String(2835, 4); // LONG biXPelsPerMeter;
199 3
        $bitMapInfoHeader .= self::littleEndian2String(2835, 4); // LONG biYPelsPerMeter;
200 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biClrUsed;
201 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biClrImportant;
202 3
        $data = $bitMapHeader.$bitMapInfoHeader.$bmp;
203 3
        $this->saveImage($filename, $data);
204 2
        return $data;
205
    }
206
    
207
    /**
208
     * loadBMP
209
     * Create a GD image from BMP file
210
     *
211
     * @param  string $filename
212
     * @return boolean
213
     */
214 1
    public function loadBMP($filename)
215
    {
216
        //open file as binary
217 1
        if(! $f1 = fopen($filename,"rb")) {
218
            throw InvalidArgumentException('Can not open file.');
219
        }
220
        //get properties from image file
221 1
        $file = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1,14));
222 1
        if ($file['file_type'] != 19778) {
223
            throw InvalidArgumentException('This file is not a BMP image.');
224
        }
225
        //get properties form image
226 1
        $bmp = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel'.
227 1
           '/Vcompression/Vsize_bitmap/Vhoriz_resolution'.
228 1
           '/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1,40));
229
        //check deep of colors
230 1
        $bmp['colors'] = pow(2, $bmp['bits_per_pixel']);
231 1
        if ($bmp['size_bitmap'] == 0) {
232 1
            $bmp['size_bitmap'] = $file['file_size'] - $file['bitmap_offset'];
233 1
        }
234 1
        $bmp['bytes_per_pixel'] = $bmp['bits_per_pixel']/8;
235 1
        $bmp['bytes_per_pixel2'] = ceil($bmp['bytes_per_pixel']);
236 1
        $bmp['decal'] = ($bmp['width']*$bmp['bytes_per_pixel']/4);
237 1
        $bmp['decal'] -= floor($bmp['width']*$bmp['bytes_per_pixel']/4);
238 1
        $bmp['decal'] = 4-(4*$bmp['decal']);
239 1
        if ($bmp['decal'] == 4) {
240
            $bmp['decal'] = 0;
241
        }
242 1
        $palette = array();
243 1
        if ($bmp['colors'] < 16777216) {
244
            $palette = unpack('V'.$bmp['colors'], fread($f1, $bmp['colors']*4));
245
        }
246
        //read all data form image but not the header
247 1
        $img = fread($f1, $bmp['size_bitmap']);
248 1
        fclose($f1);
249
        //create a true color GD resource
250 1
        $vide = chr(0);
251 1
        $res = imagecreatetruecolor($bmp['width'], $bmp['height']);
252 1
        $p = 0;
253 1
        $y = $bmp['height']-1;
254
        //read all bytes form original file
255 1
        while ($y >= 0) {
256 1
            $x=0;
257 1
            while ($x < $bmp['width']) {
258
                //get byte color from BMP
259 1
                $color = $this->getBMPColor($bmp['bits_per_pixel'], $img, $vide, $p, $palette);
0 ignored issues
show
Documentation introduced by
$palette is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
260 1
                if ($color === false) {
261
                    throw RuntimeException('Fail during conversion from BMP number bit per pixel incorrect!');
262
                }
263 1
                imagesetpixel($res, $x, $y, $color[1]);
264 1
                $x++;
265 1
                $p += $bmp['bytes_per_pixel'];
266 1
            }
267 1
            $y--;
268 1
            $p += $bmp['decal'];
269 1
        }
270 1
        $this->img = $res;
271 1
        return true;
272
    }
273
    
274
    /**
275
     * Get byte color form BMP
276
     * 
277
     * @param integer $bpp bytes_per_pixel
278
     * @param string $img bytes read of file
279
     * @param string $vide
280
     * @param integer $p
281
     * @param integer $palette
282
     * @return integer|boolean
283
     */
284
    private function getBMPColor($bpp, $img, $vide, $p, $palette)
285
    {
286
        switch ($bpp) {
287
            case 24:
288
                return unpack("V", substr($img, $p, 3).$vide);
289
                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...
290
            case 16:
291
                $color = unpack("v", substr($img, $p, 2));
292
                $blue = ($color[1] & 0x001f) << 3;
293
                $green = ($color[1] & 0x07e0) >> 3;
294
                $red = ($color[1] & 0xf800) >> 8;
295
                $color[1] = $red * 65536 + $green * 256 + $blue;
296
                return $color;
297
                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...
298
            case 8:    
299
                $color = unpack("n", $vide.substr($img, $p, 1));
300
                $color[1] = $palette[$color[1]+1];
301
                return $color;
302
                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...
303
            case 4:
304
                $color = unpack("n", $vide.substr($img, floor($p), 1));
305
                if (($p*2)%2 == 0) {
306
                   $color[1] = ($color[1] >> 4) ;
307
                } else {
308
                   $color[1] = ($color[1] & 0x0F);
309
                }
310
                $color[1] = $palette[$color[1]+1];
311
                return $color;
312
                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...
313
            case 1:
314
                $color = unpack("n", $vide.substr($img, floor($p), 1));
315
                if (($p*8)%8 == 0) {
316
                    $color[1] = $color[1]>>7;
317
                } elseif (($p*8)%8 == 1) {
318
                    $color[1] = ($color[1] & 0x40)>>6;
319
                } elseif (($p*8)%8 == 2) {
320
                    $color[1] = ($color[1] & 0x20)>>5;
321
                } elseif (($p*8)%8 == 3) {
322
                    $color[1] = ($color[1] & 0x10)>>4;
323
                } elseif (($P*8)%8 == 4) {
0 ignored issues
show
Bug introduced by
The variable $P does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
324
                    $color[1] = ($color[1] & 0x8)>>3;
325
                } elseif (($p*8)%8 == 5) {
326
                    $color[1] = ($color[1] & 0x4)>>2;
327
                } elseif (($p*8)%8 == 6) {
328
                    $color[1] = ($color[1] & 0x2)>>1;
329
                } elseif (($p*8)%8 == 7) {
330
                    $color[1] = ($color[1] & 0x1);
331
                }
332
                $color[1] = $palette[$color[1]+1];
333
                return $color;
334
                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...
335
            default:
336
                return false;
337
        }
338
    }
339
340
341
    /**
342
     * resizeImage
343
     * Resize an image
344
     * NOTE: the width is always set to the multiple of 8 more
345
     * next, why? printers have a resolution of 8 dots per mm
346
     *
347
     * @param  float $width
348
     * @param  float $height
349
     * @throws InvalidArgumentException
350
     */
351 3
    public function resizeImage($width = null, $height = null)
352
    {
353 3
        if ($width == null && $height == null) {
354 1
            throw new InvalidArgumentException("No dimensions was passed.");
355
        }
356 2
        if ($width != null) {
357 1
            $width = $this->closestMultiple($width);
358 1
            $razao = $width / $this->imgWidth;
359 1
            $height = (int) round($razao * $this->imgHeight);
360 2
        } elseif ($width == null && $height != null) {
361 1
            $razao = $height / $this->imgHeight;
362 1
            $width = (int) round($razao * $this->imgWidth);
363 1
            $width = $this->closestMultiple($width);
364 1
        }
365 2
        $tempimg = imagecreatetruecolor($width, $height);
366 2
        imagecopyresampled($tempimg, $this->img, 0, 0, 0, 0, $width, $height, $this->imgWidth, $this->imgHeight);
367 2
        $this->img = $tempimg;
368 2
        $this->getDimImage();
369 2
    }
370
    
371
    /**
372
     * Creates a  GD QRCode image
373
     *
374
     * @param string $dataText
375
     * @param int    $width
376
     * @param int    $padding
377
     * @param string $errCorretion  LOW, MEDIUM, QUARTILE, HIGH
378
     * @return void
379
     */
380 1
    public function imageQRCode(
381
        $dataText = 'NADA NADA NADA NADA NADA NADA NADA NADA NADA NADA NADA NADA',
382
        $width = 200,
383
        $padding = 10,
384
        $errCorretion = 'MEDIUM'
385
    ) {
386
        //adjust width for a closest multiple of 8
387 1
        $width = $this->closestMultiple($width, 8);
388 1
        $qrCode = new QrCode();
389 1
        $qrCode->setText($dataText)
390 1
            ->setImageType('png')
391 1
            ->setSize($width)
392 1
            ->setPadding($padding)
393 1
            ->setErrorCorrection($errCorretion)
394 1
            ->setForegroundColor(array('r' => 0, 'g' => 0, 'b' => 0, 'a' => 0))
395 1
            ->setBackgroundColor(array('r' => 255, 'g' => 255, 'b' => 255, 'a' => 0))
396 1
            ->setLabel('')
397 1
            ->setLabelFontSize(8);
398 1
        $this->img = $qrCode->getImage();
399 1
        $this->getDimImage();
400 1
    }
401
    
402
    /**
403
     * Convert image from GD resource
404
     * into Black and White pixels image
405
     *
406
     * @return string Representation of bytes image in BW
407
     */
408 1
    protected function convertPixelBW()
409
    {
410
        // Make a string of 1's and 0's
411 1
        $this->imgData = str_repeat("\0", $this->imgHeight * $this->imgWidth);
412 1
        for ($yInd = 0; $yInd < $this->imgHeight; $yInd++) {
413 1
            for ($xInd = 0; $xInd < $this->imgWidth; $xInd++) {
414
                //get colors from byte image
415 1
                $cols = imagecolorsforindex($this->img, imagecolorat($this->img, $xInd, $yInd));
416
                //convert to greyness color 1 for white, 0 for black
417 1
                $greyness = (int)(($cols['red'] + $cols['green'] + $cols['blue']) / 3) >> 7;
418
                //switch to Black and white
419
                //1 for black, 0 for white, taking into account transparency color
420 1
                $black = (1 - $greyness) >> ($cols['alpha'] >> 6);
421 1
                $this->imgData[$yInd * $this->imgWidth + $xInd] = $black;
422 1
            }
423 1
        }
424 1
        return $this->imgData;
425
    }
426
427
    /**
428
     * Output the image in raster (row) format.
429
     * This can result in padding on the right of the image,
430
     * if its width is not divisible by 8.
431
     *
432
     * @throws RuntimeException Where the generated data is
433
     *         unsuitable for the printer (indicates a bug or oversized image).
434
     * @return string The image in raster format.
435
     */
436 2
    protected function convertRaster()
437
    {
438 2
        if (! is_null($this->imgRasterData)) {
439 1
             return $this->imgRasterData;
440
        }
441 2
        if (is_null($this->imgData)) {
442
            $this->convertPixelBW();
443
        }
444
        //get width in Pixels
445 2
        $widthPixels = $this->getWidth();
446
        //get heightin in Pixels
447 2
        $heightPixels = $this->getHeight();
448
        //get width in Bytes
449 2
        $widthBytes = $this->getWidthBytes();
450
        //initialize vars
451 2
        $xCount = $yCount = $bit = $byte = $byteVal = 0;
452
        //create a string for converted bytes
453 2
        $data = str_repeat("\0", $widthBytes * $heightPixels);
454 2
        if (strlen($data) == 0) {
455
            return $data;
456
        }
457
        /* Loop through and convert format */
458
        do {
459 2
            $byteVal |= (int) $this->imgData[$yCount * $widthPixels + $xCount] << (7 - $bit);
460 2
            $xCount++;
461 2
            $bit++;
462 2
            if ($xCount >= $widthPixels) {
463 2
                $xCount = 0;
464 2
                $yCount++;
465 2
                $bit = 8;
466 2
                if ($yCount >= $heightPixels) {
467 2
                    $data[$byte] = chr($byteVal);
468 2
                    break;
469
                }
470 2
            }
471 2
            if ($bit >= 8) {
472 2
                $data[$byte] = chr($byteVal);
473 2
                $byteVal = 0;
474 2
                $bit = 0;
475 2
                $byte++;
476 2
            }
477 2
        } while (true);
478 2
        if (strlen($data) != ($this->getWidthBytes() * $this->getHeight())) {
479
            throw new RuntimeException("Bug in " . __FUNCTION__ . ", wrong number of bytes.");
480
        }
481 2
         $this->imgRasterData = $data;
482 2
         return $this->imgRasterData;
483
    }
484
    
485
    /**
486
     * Save safety binary image file
487
     *
488
     * @param string               $filename
489
     * @param resource|string|null $data
490
     * @param string $type PNG, JPG, GIF, BMP
491
     * @param integer $quality
492
     * @return boolean
493
     * @throws InvalidArgumentException
494
     * @throws RuntimeException
495
     */
496 4
    public function saveImage($filename = null, $data = null, $type = 'PNG', $quality = 75)
497
    {
498 4
        if (empty($filename) || empty($data)) {
499
            return false;
500
        }
501 4
        if (is_resource($data)) {
502
            //use GD to save image to file
503
            switch ($type) {
504 2
                case 'JPG':
505 2
                case 'JPEG':
506
                    $result = imagejpeg($data, $filename, $quality);
507
                    break;
508 2
                case 'GIF':
509
                    $result = imagegif($data, $filename);
510
                    break;
511 2
                default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
512 2
                    $result = imagepng($data, $filename);
513 2
                    break;
514 2
            }
515 2
            if (!$result) {
516
                throw new InvalidArgumentException("Fail to write in $filename.");
517
            }
518 2
            return true;
519
        }
520 2
        $handle = @fopen($filename, 'w');
521 2
        if (!is_resource($handle)) {
522 1
            throw new InvalidArgumentException("Cant open file $filename. Check permissions.");
523
        }
524 1
        $nbytes = fwrite($handle, $data);
525 1
        fclose($handle);
526 1
        if (!$nbytes) {
527
            throw new RuntimeException("Fail to write in $filename.");
528
        }
529 1
        return true;
530
    }
531
    
532
    /**
533
     * Converts Litte Endian Bytes do String
534
     *
535
     * @param  int $number
536
     * @param  int $minbytes
537
     * @return string
538
     */
539 2
    private static function littleEndian2String($number, $minbytes = 1)
540
    {
541 2
        $intstring = '';
542 2
        while ($number > 0) {
543 2
            $intstring = $intstring.chr($number & 255);
544 2
            $number >>= 8;
545 2
        }
546 2
        return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
547
    }
548
    
549
    /**
550
     * Get pixel colors
551
     *
552
     * @param  resource $img
553
     * @param  int      $x
554
     * @param  int      $y
555
     * @return array
556
     */
557 2
    private static function getPixelColor($img, $x, $y)
558
    {
559 2
        return imageColorsForIndex($img, imageColorAt($img, $x, $y));
560
    }
561
    
562
    /**
563
     * Ajusta o numero para o multiplo mais proximo de base
564
     *
565
     * @param  float $num
566
     * @param  int   $num
567
     * @return int
568
     */
569 3
    private function closestMultiple($num = 0, $base = 8)
570
    {
571 3
        $iNum = ceil($num);
572 3
        if (($iNum % $base) === 0) {
573 1
            return $iNum;
574
        }
575 2
        return round($num/$base) * $base;
576
    }
577
}
578