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