Passed
Push — master ( 47fbf0...a2ba4c )
by Vicens
06:47
created

Image::distort()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 35
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.439
c 0
b 0
f 0
cc 5
eloc 24
nc 6
nop 4
1
<?php
2
/**
3
 * @desc 图片类
4
 * @author vicens<[email protected]>
5
 */
6
7
namespace Vicens\Captcha;
8
9
use Symfony\Component\HttpFoundation\Response;
10
11
12
/**
13
 * Class Builder
14
 * @method width ($width)
15
 * @method height ($height)
16
 * @method textColor ($color)
17
 * @method backgroundColor ($color)
18
 * @method maxFrontLines ($maxFrontLines)
19
 * @method maxBehindLines ($maxBehindLines)
20
 * @method distortion ($distortion)
21
 * @method maxAngle ($maxAngle)
22
 * @method maxOffset ($maxOffset)
23
 * @method quality ($quality)
24
 * @package Vicens\Captcha
25
 */
26
class Image
27
{
28
29
    /**
30
     * 已生成的图片
31
     * @var resource
32
     */
33
    protected $image;
34
35
    /**
36
     * 验证码
37
     * @var string
38
     */
39
    protected $code;
40
41
    /**
42
     * 验证码设置
43
     * @var array
44
     */
45
    protected $config = array(
46
        /**
47
         * 默认验证码宽度
48
         * @var int
49
         */
50
        'width' => 150,
51
        /**
52
         * 默认验证码高度
53
         * @var int
54
         */
55
        'height' => 40,
56
        /**
57
         * 指定文字颜色
58
         * @var string
59
         */
60
        'textColor' => null,
61
        /**
62
         * 文字字体文件
63
         * @var string
64
         */
65
        'textFont' => null,
66
        /**
67
         * 指定图片背景色
68
         * @var string
69
         */
70
        'backgroundColor' => null,
71
        /**
72
         * 开启失真模式
73
         * @var bool
74
         */
75
        'distortion' => true,
76
        /**
77
         * 最大前景线条数
78
         * @var int
79
         */
80
        'maxFrontLines' => null,
81
        /**
82
         * 最大背景线条数
83
         * @val int
84
         */
85
        'maxBehindLines' => null,
86
        /**
87
         * 文字最大角度
88
         * @var int
89
         */
90
        'maxAngle' => 8,
91
        /**
92
         * 文字最大偏移量
93
         * @var int
94
         */
95
        'maxOffset' => 5,
96
        /**
97
         * 图片质量
98
         * @var int
99
         */
100
        'quality' => 90
101
    );
102
103
104
    public function __construct($code, array $config = array())
105
    {
106
        $this->code = $code;
107
108
        $this->setConfig($config);
109
    }
110
111
    /**
112
     * 设置验证码配置
113
     * @param $config
114
     * @param null $value
115
     * @return $this
116
     */
117
    public function setConfig($config, $value = null)
118
    {
119
        if (!is_array($config)) {
120
            $config = [$config => $value];
121
        }
122
123 View Code Duplication
        foreach ($config as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
124
            if (array_key_exists($key, $this->config)) {
125
                $this->config[$key] = $value;
126
            }
127
        }
128
        return $this;
129
    }
130
131
    /**
132
     * 获取配置
133
     * @param  $key
134
     * @return array|string|number
135
     */
136
    public function getConfig($key)
137
    {
138
        if ($key) {
139
            return $this->config[$key];
140
        }
141
        return $this->config;
142
    }
143
144
    /**
145
     * 获取图片
146
     * @return resource
147
     */
148
    public function getImage()
149
    {
150
        if (!$this->image) {
151
152
            $this->reset();
153
        }
154
155
        return $this->image;
156
    }
157
158
    /**
159
     * 重置图片
160
     * @return $this
161
     */
162
    public function reset()
163
    {
164
165
        $this->image = $this->build($this->code);
166
167
        return $this;
168
    }
169
170
    /**
171
     * 获取 base64的数据,用做image标签的src
172
     * @return string
173
     */
174
    public function getDataUrl()
175
    {
176
        return 'data:image/jpeg;base64,' . base64_encode($this->getContent());
177
    }
178
179
    /**
180
     * 输出为html
181
     * @param int $width
182
     * @param int $height
183
     * @return string
184
     */
185
    public function html($width = null, $height = null)
186
    {
187
        $html = '<img src="' . $this->getDataUrl() . '"" alt="captcha"';
188
        if (is_null($width)) {
189
            $html .= ' width="' . $width . '"';
190
        }
191
        if (is_null($height)) {
192
            $html .= ' height="' . $height . '"';
193
        }
194
        $html .= '>';
195
        return $html;
196
    }
197
198
    /**
199
     * 返回Response响应
200
     * @return Response
201
     */
202
    public function response()
203
    {
204
205
        return Response::create($this->getContent(), 200, array('Content-type' => 'image/jpeg'));
206
207
    }
208
209
210
    /**
211
     * 获取图片资源
212
     * @return mixed
213
     */
214
    public function getContext()
215
    {
216
        return $this->getImage();
217
    }
218
219
    /**
220
     * 保存为图片
221
     * @param $filename
222
     * @return $this
223
     */
224
    public function save($filename)
225
    {
226
        $this->output($filename);
227
228
        return $this;
229
    }
230
231
    /**
232
     * 直接输出
233
     * @param null $filename
234
     * @return $this
235
     */
236
    public function output($filename = null)
237
    {
238
        imagejpeg($this->getContext(), $filename, $this->getConfig('quality'));
239
240
        return $this;
241
    }
242
243
    /**
244
     * 获取输出内容
245
     * @return string
246
     */
247
    public function getContent()
248
    {
249
        ob_start();
250
        $this->output();
251
        return ob_get_clean();
252
    }
253
254
    /**
255
     * 创建验证码图片
256
     * @param $code
257
     * @return resource
258
     */
259
    protected function build($code)
260
    {
261
262
        // 图片宽
263
        $width = $this->getConfig('width');
264
        // 图片高
265
        $height = $this->getConfig('height');
266
        // 背景颜色
267
        $backgroundColor = $this->getConfig('backgroundColor');
268
269
270
        //随机取一个字体
271
        $font = $this->getTextFont();
272
273
        //根据宽高创建一个背景画布
274
        $image = imagecreatetruecolor($width, $height);
275
276
        if ($backgroundColor == null) {
277
            $backgroundColor = imagecolorallocate($image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(200, 255));
278
        } else {
279
            $color = $backgroundColor;
280
            $backgroundColor = imagecolorallocate($image, $color[0], $color[1], $color[2]);
281
        }
282
        //填充背景色
283
        imagefill($image, 0, 0, $backgroundColor);
284
285
        //绘制背景干扰线
286
        $this->drawLines($image, $this->getConfig('maxBehindLines'));
287
288
        //写入验证码文字
289
        $color = $this->renderText($image, $code, $font);
290
291
        //绘制前景干扰线
292
        $this->drawLines($image, $this->getConfig('maxFrontLines'), $color);
293
294
295
        if ($this->getConfig('distortion')) {
296
            //创建失真
297
            $image = $this->distort($image, $width, $height, $backgroundColor);
298
        }
299
300
        //如果不指定字体颜色和背景颜色,则使用图像过滤器修饰
301
        if (function_exists('imagefilter') && is_null($backgroundColor) && is_null($this->getConfig('textColor'))) {
302
            //颜色翻转 - 1/2几率
303
            if (mt_rand(0, 1) == 0) {
304
                imagefilter($image, IMG_FILTER_NEGATE);
305
            }
306
            //用边缘检测来突出图像的边缘 - 1/11几率
307
            if (mt_rand(0, 10) == 0) {
308
                imagefilter($image, IMG_FILTER_EDGEDETECT);
309
            }
310
            //改变图像的对比度
311
            imagefilter($image, IMG_FILTER_CONTRAST, mt_rand(-50, 10));
312
313
            if (mt_rand(0, 5) == 0) {
314
                //用高斯算法和指定颜色模糊图像
315
                imagefilter($image, IMG_FILTER_COLORIZE, mt_rand(-80, 50), mt_rand(-80, 50), mt_rand(-80, 50));
316
            }
317
        }
318
        return $image;
319
    }
320
321
    /**
322
     * 获取一个字体
323
     * @return string
324
     */
325
    protected function getTextFont()
326
    {
327
        //指定字体
328
        if ($this->config['textFont'] && file_exists($this->config['textFont'])) {
329
            return $this->config['textFont'];
330
        }
331
        //随机字体
332
        return __DIR__ . '/../fonts/' . mt_rand(0, 5) . '.ttf';
333
    }
334
335
    /**
336
     * 写入验证码到图片中
337
     * @param $image
338
     * @param $phrase
339
     * @param $font
340
     * @return int
341
     */
342
    protected function renderText($image, $phrase, $font)
343
    {
344
        $length = strlen($phrase);
345
        if ($length === 0) {
346
            return imagecolorallocate($image, 0, 0, 0);
347
        }
348
349
        // 计算文字尺寸
350
        $size = $this->config['width'] / $length - mt_rand(0, 3) - 1;
351
        $box = imagettfbbox($size, 0, $font, $phrase);
352
        $textWidth = $box[2] - $box[0];
353
        $textHeight = $box[1] - $box[7];
354
        $x = ($this->config['width'] - $textWidth) / 2;
355
        $y = ($this->config['height'] - $textHeight) / 2 + $size;
356
357
        if (!count($this->config['textColor'])) {
358
            $textColor = array(mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
359
        } else {
360
            $textColor = $this->config['textColor'];
361
        }
362
        $color = imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
363
364
        // 循环写入字符,随机角度
365
        for ($i = 0; $i < $length; $i++) {
366
            $box = imagettfbbox($size, 0, $font, $phrase[$i]);
367
            $w = $box[2] - $box[0];
368
            $angle = mt_rand(-$this->config['maxAngle'], $this->config['maxAngle']);
369
            $offset = mt_rand(-$this->config['maxOffset'], $this->config['maxOffset']);
370
            imagettftext($image, $size, $angle, $x, $y + $offset, $color, $font, $phrase[$i]);
371
            $x += $w;
372
        }
373
374
        return $color;
375
    }
376
377
    /**
378
     * 画线
379
     * @param $image
380
     * @param $width
381
     * @param $height
382
     * @param null $color
383
     */
384
    protected function renderLine($image, $width, $height, $color = null)
385
    {
386
        if ($color === null) {
387
            $color = imagecolorallocate($image, mt_rand(100, 255), mt_rand(100, 255), mt_rand(100, 255));
388
        }
389
390
        if (mt_rand(0, 1)) { // 横向
391
            $Xa = mt_rand(0, $width / 2);
392
            $Ya = mt_rand(0, $height);
393
            $Xb = mt_rand($width / 2, $width);
394
            $Yb = mt_rand(0, $height);
395
        } else { // 纵向
396
            $Xa = mt_rand(0, $width);
397
            $Ya = mt_rand(0, $height / 2);
398
            $Xb = mt_rand(0, $width);
399
            $Yb = mt_rand($height / 2, $height);
400
        }
401
        imagesetthickness($image, mt_rand(1, 3));
402
        imageline($image, $Xa, $Ya, $Xb, $Yb, $color);
403
    }
404
405
    /**
406
     * 画线
407
     * @param $image
408
     * @param $max
409
     * @param $color
410
     */
411
    protected function drawLines($image, $max, $color = null)
412
    {
413
        $square = $this->config['width'] * $this->config['height'];
414
        $effects = mt_rand($square / 3000, $square / 2000);
415
416
        // 计算线条数
417
        if ($max != null && $max > 0) {
418
            $effects = min($max, $effects);
419
        }
420
421
        if ($max !== 0) {
422
            for ($e = 0; $e < $effects; $e++) {
423
424
                if ($color) {
425
                    $this->renderLine($image, $this->config['width'], $this->config['height'], $color);
426
                } else {
427
                    $this->renderLine($image, $this->config['width'], $this->config['height']);
428
                }
429
430
            }
431
        }
432
    }
433
434
    /**
435
     * 创建失真
436
     * @param $image
437
     * @param $width
438
     * @param $height
439
     * @param $bg
440
     * @return resource
441
     */
442
    protected function distort($image, $width, $height, $bg)
443
    {
444
        $contents = imagecreatetruecolor($width, $height);
445
        $X = mt_rand(0, $width);
446
        $Y = mt_rand(0, $height);
447
        $phase = mt_rand(0, 10);
448
        $scale = 1.1 + mt_rand(0, 10000) / 30000;
449
        for ($x = 0; $x < $width; $x++) {
450
            for ($y = 0; $y < $height; $y++) {
451
                $Vx = $x - $X;
452
                $Vy = $y - $Y;
453
                $Vn = sqrt($Vx * $Vx + $Vy * $Vy);
454
455
                if ($Vn != 0) {
456
                    $Vn2 = $Vn + 4 * sin($Vn / 30);
457
                    $nX = $X + ($Vx * $Vn2 / $Vn);
458
                    $nY = $Y + ($Vy * $Vn2 / $Vn);
459
                } else {
460
                    $nX = $X;
461
                    $nY = $Y;
462
                }
463
                $nY = $nY + $scale * sin($phase + $nX * 0.2);
464
465
                $p = $this->getColor($image, round($nX), round($nY), $bg);
466
467
                if ($p == 0) {
468
                    $p = $bg;
469
                }
470
471
                imagesetpixel($contents, $x, $y, $p);
472
            }
473
        }
474
475
        return $contents;
476
    }
477
478
    /**
479
     * 获取颜色
480
     * @param $image
481
     * @param $x
482
     * @param $y
483
     * @param $background
484
     * @return int
485
     */
486
    protected function getColor($image, $x, $y, $background)
487
    {
488
        $L = imagesx($image);
489
        $H = imagesy($image);
490
        if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
491
            return $background;
492
        }
493
494
        return imagecolorat($image, $x, $y);
495
    }
496
497
    /**
498
     * @param $name
499
     * @param $arguments
500
     * @return $this
501
     */
502
    public function __call($name, $arguments)
503
    {
504
        if (array_key_exists($name, $this->config)) {
505
            $this->config[$name] = $arguments[0];
506
        }
507
508
        return $this;
509
    }
510
511
}