1<?php
2/*
3 * vim:set softtabstop=4 shiftwidth=4 expandtab:
4 *
5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
6 * Copyright 2001 - 2020 Ampache.org
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 *
21 */
22
23declare(strict_types=0);
24
25namespace Ampache\Module\Util\Captcha;
26
27/**
28 * Class easy_captcha_graphic_image_waved
29 *
30 * waived captcha image II
31 */
32class easy_captcha_graphic_image_waved extends easy_captcha_graphic
33{
34
35
36    /* returns jpeg file stream with unscannable letters encoded
37       in front of colorful disturbing background
38    */
39    /**
40     * @return false|string
41     */
42    public function jpeg()
43    {
44        #-- step by step
45        $this->img = $this->create();
46        $this->text();
47        //$this->debug_grid();
48        $this->fog();
49        $this->distort();
50
51        return $this->output();
52    }
53
54
55    #-- initialize in-memory image with gd library
56
57    /**
58     * @return false|resource
59     */
60    public function create()
61    {
62        $img = imagecreatetruecolor($this->width, $this->height);
63        // imagealphablending($img, TRUE);
64        imagefilledrectangle($img, 0, 0, $this->width, $this->height,
65            $this->inverse ? $this->bg ^ 0xFFFFFF : $this->bg); //$this->rgb(255,255,255)
66        if (function_exists("imageantialias")) {
67            imageantialias($img, true);
68        }
69
70        return ($img);
71    }
72
73
74    #-- add the real text to it
75    public function text()
76    {
77        $w    = $this->width;
78        $h    = $this->height;
79        $SIZE = rand(30, 36);
80        $DEG  = rand(-2, 9);
81        $LEN  = strlen($this->solution);
82        $left = $w - $LEN * 25;
83        $top  = ($h - $SIZE - abs($DEG * 2));
84        imagettftext($this->img, $SIZE, $DEG, rand(5, $left - 5), $h - rand(3, $top - 3), $this->rgb(0, 0, 0),
85            $this->font(), $this->solution);
86    }
87
88    #-- to visualize the sinus waves
89    public function debug_grid()
90    {
91        for ($x = 0; $x < 250; $x += 10) {
92            imageline($this->img, $x, 0, $x, 70, 0x333333);
93            imageline($this->img, 0, $x, 250, $x, 0x333333);
94        }
95    }
96
97    #-- add lines
98    public function fog()
99    {
100        $num = rand(10, 25);
101        $x   = $this->width;
102        $y   = $this->height;
103        $s   = rand(0, 270);
104        for ($n = 0; $n < $num; $n++) {
105            imagesetthickness($this->img, rand(1, 2));
106            imagearc($this->img, rand(0.1 * $x, 0.9 * $x), rand(0.1 * $y, 0.9 * $y),  // x,y
107                rand(0.1 * $x, 0.3 * $x), rand(0.1 * $y, 0.3 * $y),  // w,h
108                $s, rand($s + 5, $s + 90),     // s,e
109                rand(0, 1) ? 0xFFFFFF : 0x000000   // col
110            );
111        }
112        imagesetthickness($this->img, 1);
113    }
114
115    #-- distortion: wave-transform
116    public function distort()
117    {
118
119        #-- init
120        $single_pixel = (CAPTCHA_PIXEL <= 1);   // very fast
121        $greyscale2x2 = (CAPTCHA_PIXEL <= 2);   // quicker than exact smooth 2x2 copy
122        $width        = $this->width;
123        $height       = $this->height;
124        $image        = &$this->img;
125        $dest         = $this->create();
126
127        #-- URL param ?hires=1 influences used drawing scheme
128        if (filter_has_var(INPUT_GET, 'hires')) {
129            $single_pixel = 0;
130        }
131
132        #-- prepare distortion
133        $wave = new easy_captcha_dxy_wave($width, $height);
134        // $spike = new easy_captcha_dxy_spike($width, $height);
135
136        #-- generate each new x,y pixel individually from orig $img
137        for ($y = 0; $y < $height; $y++) {
138            for ($x = 0; $x < $width; $x++) {
139                #-- pixel movement
140                list($distortx, $distorty) = $wave->dxy($x, $y);   // x- and y- sinus wave
141                // list($qx, $qy) = $spike->dxy($x, $y);
142
143                #-- if not out of bounds
144                if (($distortx + $x >= 0) && ($distorty + $y >= 0) && ($distortx + $x < $width) && ($distorty + $y < $height)) {
145                    #-- get source pixel(s), paint dest
146                    if ($single_pixel) {
147                        // single source dot: one-to-one duplicate (unsmooth, hard edges)
148                        imagesetpixel($dest, $x, $y, imagecolorat($image, (int)$distortx + $x, (int)$distorty + $y));
149                    } elseif ($greyscale2x2) {
150                        // merge 2x2 simple/greyscale (3 times as slow)
151                        $cXY = $this->get_2x2_greyscale($image, (int)$distortx + $x, (int)$distorty + $y);
152                        imagesetpixel($dest, $x, $y, imagecolorallocate($dest, $cXY, $cXY, $cXY));
153                    } else {
154                        // exact and smooth transformation (5 times as slow)
155                        list($cXY_R, $cXY_G, $cXY_B) = $this->get_2x2_smooth($image, $x + $distortx, $y + $distorty);
156                        imagesetpixel($dest, $x, $y, imagecolorallocate($dest, (int)$cXY_R, (int)$cXY_G, (int)$cXY_B));
157                    }
158                }
159            }
160        }
161
162        #-- simply overwrite ->img
163        imagedestroy($image);
164        $this->img = $dest;
165    }
166
167    #-- get 4 pixels from source image, merges BLUE value simply
168
169    /**
170     * @param $image
171     * @param $xaxis
172     * @param $yaxis
173     * @return integer
174     */
175    public function get_2x2_greyscale(&$image, $xaxis, $yaxis)
176    {
177        // this is a pretty simplistic method, actually adds more artefacts
178        // than it "smoothes" it just merges the brightness from 4 adjoining pixels into one
179        $cXY = (imagecolorat($image, $xaxis, $yaxis) & 0xFF)
180            + (imagecolorat($image, $xaxis, $yaxis + 1) & 0xFF)
181            + (imagecolorat($image, $xaxis + 1, $yaxis) & 0xFF)
182            + (imagecolorat($image, $xaxis + 1, $yaxis + 1) & 0xFF);
183        $cXY = (int)($cXY / 4);
184
185        return $cXY;
186    }
187
188    #-- smooth pixel reading (with x,y being reals, not integers)
189
190    /**
191     * @param $i
192     * @param $x
193     * @param $y
194     * @return array
195     */
196    public function get_2x2_smooth(&$i, $x, $y)
197    {
198        // get R,G,B values from 2x2 source area
199        $c00 = $this->get_RGB($i, $x, $y);      //  +------+------+
200        $c01 = $this->get_RGB($i, $x, $y + 1);    //  |dx,dy | x1,y0|
201        $c10 = $this->get_RGB($i, $x + 1, $y);    //  | rx-> |      |
202        $c11 = $this->get_RGB($i, $x + 1, $y + 1);  //  +----##+------+
203        // weighting by $distortx/$distorty fraction part   //  |    ##|<-ry  |
204        $rx  = $x - floor($x);
205        $rx_ = 1 - $rx;  //  |x0,y1 | x1,y1|
206        $ry  = $y - floor($y);
207        $ry_ = 1 - $ry;  //  +------+------+
208        // this is extremely slow, but necessary for correct color merging,
209        // the source pixel lies somewhere in the 2x2 quadrant, that's why
210        // RGB values are added proportionately (rx/ry/_)
211        // we use no for-loop because that would slow it even further
212        $cXY_R = (int)(($c00[0]) * $rx_ * $ry_) + (int)(($c01[0]) * $rx_ * $ry)      // division by 4 not necessary,
213            + (int)(($c10[0]) * $rx * $ry_)      // because rx/ry/rx_/ry_ add up
214            + (int)(($c11[0]) * $rx * $ry);      // to 255 (=1.0) at most
215        $cXY_G = (int)(($c00[1]) * $rx_ * $ry_) + (int)(($c01[1]) * $rx_ * $ry) + (int)(($c10[1]) * $rx * $ry_) + (int)(($c11[1]) * $rx * $ry);
216        $cXY_B = (int)(($c00[2]) * $rx_ * $ry_) + (int)(($c01[2]) * $rx_ * $ry) + (int)(($c10[2]) * $rx * $ry_) + (int)(($c11[2]) * $rx * $ry);
217
218        return array($cXY_R, $cXY_G, $cXY_B);
219    }
220
221    #-- imagegetcolor from current ->$img split up into RGB array
222
223    /**
224     * @param $img
225     * @param $x
226     * @param $y
227     * @return array
228     */
229    public function get_RGB(&$img, $x, $y)
230    {
231        $rgb = imagecolorat($img, $x, $y);
232
233        return array(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, ($rgb) & 0xFF);
234    }
235}
236