Passed
Push — master ( 019c45...47fbf0 )
by Vicens
02:52
created

Builder::drawLines()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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