Completed
Push — master ( 5c5d67...fe7cae )
by Roberto
31:11 queued 28:45
created

Graphics::loadBMP()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 78
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 8.0007

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 78
ccs 43
cts 44
cp 0.9773
rs 6.102
cc 8
eloc 40
nc 8
nop 1
crap 8.0007

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        // Include option to Imagick
48 12
        if (! $this->isGdSupported()) {
49
            throw new RuntimeException("GD module not found.");
50
        }
51 12
        $this->imgHeight = 0;
52 12
        $this->imgWidth = 0;
53 12
        $this->imgData = null;
54 12
        $this->imgRasterData = null;
55
        // Load the image, if the patch was passed
56 12
        if (! is_null($filename)) {
57 9
            $this->load($filename, $width, $height);
58 7
        }
59 10
    }
60
    
61
    /**
62
     * Return a string of bytes
63
     *
64
     * @return string
65
     */
66 2
    public function getRasterImage()
67
    {
68 2
        $this->resizeImage($this->imgWidth);
69 2
        $this->convertPixelBW();
70 2
        $this->convertRaster();
71 2
        return $this->imgRasterData;
72
    }
73
74
    /**
75
     * load
76
     * Load image file and adjust dimentions
77
     *
78
     * @param  string $filename path to image file
79
     * @param  float  $width
80
     * @param  float  $height
81
     * @throws InvalidArgumentException
82
     * @throws RuntimeException
83
     */
84 9
    public function load($filename, $width = null, $height = null)
85
    {
86 9
        if (! is_file($filename)) {
87 1
            throw new InvalidArgumentException("Image file not found.");
88
        }
89 8
        if (! is_readable($filename)) {
90
            throw new RuntimeException("The file can not be read due to lack of permissions.");
91
        }
92
        //identify type of image and load with GD
93 8
        $tipo = $this->identifyImg($filename);
94 8
        if ($tipo == 'BMP') {
95
            $img = $this->loadBMP($filename);
96
            if ($img === false) {
97
                throw new InvalidArgumentException("Image file is not a BMP");
98
            }
99
            $this->img = $img;
100
        } else {
101 8
            $func = 'imagecreatefrom' . strtolower($tipo);
102 8
            if (! function_exists($func)) {
103 1
                throw new RuntimeException("It is not possible to use or handle this type of image with GD");
104
            }
105 7
            $this->img = $func($filename);
106
        }
107 7
        if (! $this->img) {
108
            throw new RuntimeException("Failed to load image '$filename'.");
109
        }
110
        //get image dimentions
111 7
        $this->getDimImage();
112 7
        if ($width != null || $height != null) {
113
            $this->resizeImage($width, $height);
114
        }
115 7
    }
116
    
117
    /**
118
     * Save image to PNG file
119
     *
120
     * @param string $filename
121
     */
122 2
    public function save($filename = null)
123
    {
124 2
        $this->saveImage($filename, $this->img);
125 2
    }
126
    
127
    /**
128
     * Convert a GD image into a BMP string representation
129
     *
130
     * @param  string $filename path to image BMP file
131
     * @return string
132
     */
133 3
    public function convert2BMP($filename = null)
134
    {
135 3
        if (! is_resource($this->img)) {
136
            return '';
137
        }
138
        //to remove alpha color and put white instead
139 3
        $img = $this->img;
140 3
        $imageX = imagesx($img);
141 3
        $imageY = imagesy($img);
142 3
        $bmp = '';
143 3
        for ($yInd = ($imageY - 1); $yInd >= 0; $yInd--) {
144 3
            $thisline = '';
145 3
            for ($xInd = 0; $xInd < $imageX; $xInd++) {
146 3
                $argb = self::getPixelColor($img, $xInd, $yInd);
147
                //change transparency to white color
148 3
                if ($argb['alpha'] == 0 && $argb['blue'] == 0 && $argb['green'] == 0 && $argb['red'] == 0) {
149
                    $thisline .= chr(255).chr(255).chr(255);
150
                } else {
151 3
                    $thisline .= chr($argb['blue']).chr($argb['green']).chr($argb['red']);
152
                }
153 3
            }
154 3
            while (strlen($thisline) % 4) {
155 3
                $thisline .= "\x00";
156 3
            }
157 3
            $bmp .= $thisline;
158 3
        }
159 3
        $bmpSize = strlen($bmp) + 14 + 40;
160
        // bitMapHeader [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
161 3
        $bitMapHeader  = 'BM'; // WORD bfType;
162 3
        $bitMapHeader .= self::littleEndian2String($bmpSize, 4); // DWORD bfSize;
163 3
        $bitMapHeader .= self::littleEndian2String(0, 2); // WORD bfReserved1;
164 3
        $bitMapHeader .= self::littleEndian2String(0, 2); // WORD bfReserved2;
165 3
        $bitMapHeader .= self::littleEndian2String(54, 4); // DWORD bfOffBits;
166
        // bitMapInfoHeader - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
167 3
        $bitMapInfoHeader  = self::littleEndian2String(40, 4); // DWORD biSize;
168 3
        $bitMapInfoHeader .= self::littleEndian2String($imageX, 4); // LONG biWidth;
169 3
        $bitMapInfoHeader .= self::littleEndian2String($imageY, 4); // LONG biHeight;
170 3
        $bitMapInfoHeader .= self::littleEndian2String(1, 2); // WORD biPlanes;
171 3
        $bitMapInfoHeader .= self::littleEndian2String(24, 2); // WORD biBitCount;
172 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biCompression;
173 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biSizeImage;
174 3
        $bitMapInfoHeader .= self::littleEndian2String(2835, 4); // LONG biXPelsPerMeter;
175 3
        $bitMapInfoHeader .= self::littleEndian2String(2835, 4); // LONG biYPelsPerMeter;
176 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biClrUsed;
177 3
        $bitMapInfoHeader .= self::littleEndian2String(0, 4); // DWORD biClrImportant;
178 3
        $data = $bitMapHeader.$bitMapInfoHeader.$bmp;
179 3
        $this->saveImage($filename, $data);
180 2
        return $data;
181
    }
182
    
183
    /**
184
     * loadBMP
185
     * Create a GD image from BMP file
186
     *
187
     * @param  string $filename
188
     * @return GD object
189
     */
190 1
    public function loadBMP($filename)
191
    {
192
        //Load the image into a string
193 1
        $file = fopen($filename, "rb");
194 1
        $read = fread($file, 10);
195
        //continue at the end of file
196 1
        while (! feof($file) && ($read <> "")) {
197 1
            $read .= fread($file, 1024);
198 1
        }
199 1
        fclose($file);
200 1
        $temp = unpack("H*", $read);
201 1
        $hex = $temp[1];
202 1
        $header = substr($hex, 0, 108);
203
        //Process the header
204
        //Structure: http://www.fastgraph.com/help/bmp_header_format.html
205 1
        if (substr($header, 0, 4) != "424d") {
206
            //is not a BMP file
207
            return false;
208
        }
209
        //Cut it in parts of 2 bytes
210 1
        $headerParts = str_split($header, 2);
211
        //Get the width 4 bytes
212 1
        $width = hexdec($headerParts[19].$headerParts[18]);
213
        //Get the height 4 bytes
214 1
        $height = hexdec($headerParts[23].$headerParts[22]);
215
        // Unset the header params
216 1
        unset($headerParts);
217
        // Define starting X and Y
218 1
        $xPos = 0;
219 1
        $yPos = 1;
220
        //Create new gd image
221 1
        $image = imagecreatetruecolor($width, $height);
222
        //Grab the body from the image
223 1
        $body = substr($hex, 108);
224
        //Calculate if padding at the end-line is needed
225
        //Divided by two to keep overview.
226
        //1 byte = 2 HEX-chars
227 1
        $bodySize = (strlen($body) / 2);
228 1
        $headerSize = ($width * $height);
229
        //Use end-line padding? Only when needed
230 1
        $usePadding = ($bodySize > ($headerSize * 3) + 4);
231
        //Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
232
        //Calculate the next DWORD-position in the body
233 1
        for ($iCount = 0; $iCount < $bodySize; $iCount += 3) {
234
            //Calculate line-ending and padding
235 1
            if ($xPos >= $width) {
236
                //If padding needed, ignore image-padding
237
                //Shift i to the ending of the current 32-bit-block
238 1
                if ($usePadding) {
239 1
                    $iCount += $width % 4;
240 1
                }
241
                //Reset horizontal position
242 1
                $xPos = 0;
243
                //Raise the height-position (bottom-up)
244 1
                $yPos++;
245
                //Reached the image-height? Break the for-loop
246 1
                if ($yPos > $height) {
247 1
                    break;
248
                }
249 1
            }
250
            //Calculation of the RGB-pixel (defined as BGR in image-data)
251
            //Define $i_pos as absolute position in the body
252 1
            $iPos = $iCount * 2;
253 1
            $red = hexdec($body[$iPos + 4] . $body[$iPos + 5]);
254 1
            $green = hexdec($body[$iPos + 2] . $body[$iPos + 3]);
255 1
            $blue = hexdec($body[$iPos] . $body[$iPos + 1]);
256
            //Calculate and draw the pixel
257 1
            $color = imagecolorallocate($image, $red, $green, $blue);
258 1
            imagesetpixel($image, $xPos, $height-$yPos, $color);
259
            //Raise the horizontal position
260 1
            $xPos++;
261 1
        }
262
        //Unset the body / free the memory
263 1
        unset($body);
264
        //Return GD image-object
265 1
        $this->img = $image;
266 1
        return $image;
267
    }
268
    
269
    /**
270
     * resizeImage
271
     * Resize an image
272
     * NOTE: the width is always set to the multiple of 8 more
273
     * next, why? printers have a resolution of 8 dots per mm
274
     *
275
     * @param  float $width
276
     * @param  float $height
277
     * @throws InvalidArgumentException
278
     */
279 3
    public function resizeImage($width = null, $height = null)
280
    {
281 3
        if ($width == null && $height == null) {
282 1
            throw new InvalidArgumentException("No dimensions was passed.");
283
        }
284 2
        if ($width != null) {
285 1
            $width = $this->closestMultiple($width);
286 1
            $razao = $width / $this->imgWidth;
287 1
            $height = (int) round($razao * $this->imgHeight);
288 2
        } elseif ($width == null && $height != null) {
289 1
            $razao = $height / $this->imgHeight;
290 1
            $width = (int) round($razao * $this->imgWidth);
291 1
            $width = $this->closestMultiple($width);
292 1
        }
293 2
        $tempimg = imagecreatetruecolor($width, $height);
294 2
        imagecopyresampled($tempimg, $this->img, 0, 0, 0, 0, $width, $height, $this->imgWidth, $this->imgHeight);
295 2
        $this->img = $tempimg;
296 2
        $this->getDimImage();
297 2
    }
298
    
299
    /**
300
     * Creates a  GD QRCode image
301
     *
302
     * @param string $dataText
303
     * @param int    $width
304
     * @param int    $padding
305
     * @param string $errCorretion
306
     */
307 1
    public function imageQRCode(
308
        $dataText = 'NADA NADA NADA NADA NADA NADA NADA NADA NADA NADA NADA NADA',
309
        $width = 200,
310
        $padding = 10,
311
        $errCorretion = 'medium'
312
    ) {
313
        //adjust width for a closest multiple of 8
314 1
        $width = $this->closestMultiple($width, 8);
315 1
        $qrCode = new QrCode();
316 1
        $qrCode->setText($dataText)
317 1
            ->setImageType('png')
318 1
            ->setSize($width)
319 1
            ->setPadding($padding)
320 1
            ->setErrorCorrection($errCorretion)
321 1
            ->setForegroundColor(array('r' => 0, 'g' => 0, 'b' => 0, 'a' => 0))
322 1
            ->setBackgroundColor(array('r' => 255, 'g' => 255, 'b' => 255, 'a' => 0))
323 1
            ->setLabel('')
324 1
            ->setLabelFontSize(8);
325 1
        $this->img = $qrCode->getImage();
326 1
        $this->getDimImage();
327 1
    }
328
    
329
    /**
330
     * Convert image from GD resource
331
     * into Black and White pixels image
332
     *
333
     * @return string Representation of bytes image in BW
334
     */
335 1
    protected function convertPixelBW()
336
    {
337
        // Make a string of 1's and 0's
338 1
        $this->imgData = str_repeat("\0", $this->imgHeight * $this->imgWidth);
339 1
        for ($yInd = 0; $yInd < $this->imgHeight; $yInd++) {
340 1
            for ($xInd = 0; $xInd < $this->imgWidth; $xInd++) {
341
                //get colors from byte image
342 1
                $cols = imagecolorsforindex($this->img, imagecolorat($this->img, $xInd, $yInd));
343
                //convert to greyness color 1 for white, 0 for black
344 1
                $greyness = (int)(($cols['red'] + $cols['green'] + $cols['blue']) / 3) >> 7;
345
                //switch to Black and white
346
                //1 for black, 0 for white, taking into account transparency color
347 1
                $black = (1 - $greyness) >> ($cols['alpha'] >> 6);
348 1
                $this->imgData[$yInd * $this->imgWidth + $xInd] = $black;
349 1
            }
350 1
        }
351 1
        return $this->imgData;
352
    }
353
354
    /**
355
     * Output the image in raster (row) format.
356
     * This can result in padding on the right of the image,
357
     * if its width is not divisible by 8.
358
     *
359
     * @throws RuntimeException Where the generated data is
360
     *         unsuitable for the printer (indicates a bug or oversized image).
361
     * @return string The image in raster format.
362
     */
363 2
    protected function convertRaster()
364
    {
365 2
        if (! is_null($this->imgRasterData)) {
366 1
             return $this->imgRasterData;
367
        }
368 2
        if (is_null($this->imgData)) {
369
            $this->convertPixelBW();
370
        }
371
        //get width in Pixels
372 2
        $widthPixels = $this->getWidth();
373
        //get heightin in Pixels
374 2
        $heightPixels = $this->getHeight();
375
        //get width in Bytes
376 2
        $widthBytes = $this->getWidthBytes();
377
        //initialize vars
378 2
        $xCount = $yCount = $bit = $byte = $byteVal = 0;
379
        //create a string for converted bytes
380 2
        $data = str_repeat("\0", $widthBytes * $heightPixels);
381 2
        if (strlen($data) == 0) {
382
            return $data;
383
        }
384
        /* Loop through and convert format */
385
        do {
386 2
            $byteVal |= (int) $this->imgData[$yCount * $widthPixels + $xCount] << (7 - $bit);
387 2
            $xCount++;
388 2
            $bit++;
389 2
            if ($xCount >= $widthPixels) {
390 2
                $xCount = 0;
391 2
                $yCount++;
392 2
                $bit = 8;
393 2
                if ($yCount >= $heightPixels) {
394 2
                    $data[$byte] = chr($byteVal);
395 2
                    break;
396
                }
397 2
            }
398 2
            if ($bit >= 8) {
399 2
                $data[$byte] = chr($byteVal);
400 2
                $byteVal = 0;
401 2
                $bit = 0;
402 2
                $byte++;
403 2
            }
404 2
        } while (true);
405 2
        if (strlen($data) != ($this->getWidthBytes() * $this->getHeight())) {
406
            throw new RuntimeException("Bug in " . __FUNCTION__ . ", wrong number of bytes.");
407
        }
408 2
         $this->imgRasterData = $data;
409 2
         return $this->imgRasterData;
410
    }
411
    
412
    /**
413
     * Save safety binary image file
414
     *
415
     * @param  string               $filename
416
     * @param  resource|string|null $data
417
     * @return boolean
418
     * @throws InvalidArgumentException
419
     * @throws RuntimeException
420
     */
421 4
    private function saveImage($filename = null, $data = null)
422
    {
423 4
        if (is_null($filename) || is_null($data)) {
424
            return false;
425
        }
426 4
        if (is_resource($data)) {
427
            //use GD to save image to file
428 2
            $result = imagepng($data, $filename);
429 2
            if (!$result) {
430
                throw new InvalidArgumentException("Fail to write in $filename.");
431
            }
432 2
            return true;
433
        }
434 2
        $handle = @fopen($filename, 'w');
435 2
        if (!is_resource($handle)) {
436 1
            throw new InvalidArgumentException("Cant open file $filename. Check permissions.");
437
        }
438 1
        $nbytes = fwrite($handle, $data);
439 1
        fclose($handle);
440 1
        if (!$nbytes) {
441
            throw new RuntimeException("Fail to write in $filename.");
442
        }
443 1
        return true;
444
    }
445
    
446
    /**
447
     * Converts Litte Endian Bytes do String
448
     *
449
     * @param  int $number
450
     * @param  int $minbytes
451
     * @return string
452
     */
453 2
    private static function littleEndian2String($number, $minbytes = 1)
454
    {
455 2
        $intstring = '';
456 2
        while ($number > 0) {
457 2
            $intstring = $intstring.chr($number & 255);
458 2
            $number >>= 8;
459 2
        }
460 2
        return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
461
    }
462
    
463
    /**
464
     * Get pixel colors
465
     *
466
     * @param  resource $img
467
     * @param  int      $x
468
     * @param  int      $y
469
     * @return array
470
     */
471 2
    private static function getPixelColor($img, $x, $y)
472
    {
473 2
        return imageColorsForIndex($img, imageColorAt($img, $x, $y));
474
    }
475
    
476
    /**
477
     * Ajusta o numero para o multiplo mais proximo de base
478
     *
479
     * @param  float $num
480
     * @param  int   $num
481
     * @return int
482
     */
483 3
    private function closestMultiple($num = 0, $base = 8)
484
    {
485 3
        $iNum = ceil($num);
486 3
        if (($iNum % $base) === 0) {
487 1
            return $iNum;
488
        }
489 2
        return round($num/$base) * $base;
490
    }
491
}
492