Highway to PSR2
[openemr.git] / portal / sign / lib / sigconvert.php
blob4706bd6262d244dbf6dfc50c6d3ddfdcffbb8016
1 <?php
2 /**
4 * Copyright (C) 2016-2017 Jerry Padgett <sjpadgett@gmail.com>
6 * LICENSE: This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * @package OpenEMR
20 * @author Jerry Padgett <sjpadgett@gmail.com>
21 * @link http://www.open-emr.org
24 /**
25 * @param string|array $json
26 * @param array $options OPTIONAL; the options for image creation
27 * imageSize => array(width, height)
28 * bgColour => array(red, green, blue) | transparent
29 * penWidth => int
30 * penColour => array(red, green, blue)
31 * drawMultiplier => int
33 * @return object
36 function sigJsonToImage($json, $options = array())
38 $defaultOptions = array(
39 'imageSize' => array(240,70)
40 ,'bgColour' => 'transparent'
41 ,'penWidth' => 6
42 ,'penColour' => array(0x14, 0x53, 0x94)
43 ,'drawMultiplier'=> 4
46 $options = array_merge($defaultOptions, $options);
48 $img = imagecreatetruecolor($options['imageSize'][0] * $options['drawMultiplier'], $options['imageSize'][1] * $options['drawMultiplier']);
50 if ($options['bgColour'] == 'transparent') {
51 imagesavealpha($img, true);
52 $bg = imagecolorallocatealpha($img, 0, 0, 0, 127);
53 } else {
54 $bg = imagecolorallocate($img, $options['bgColour'][0], $options['bgColour'][1], $options['bgColour'][2]);
57 $pen = imagecolorallocate($img, $options['penColour'][0], $options['penColour'][1], $options['penColour'][2]);
58 imagefill($img, 0, 0, $bg);
60 if (is_string($json)) {
61 $json = json_decode(stripslashes($json));
64 foreach ($json as $v) {
65 drawThickLine($img, $v->lx * $options['drawMultiplier'], $v->ly * $options['drawMultiplier'], $v->mx * $options['drawMultiplier'], $v->my * $options['drawMultiplier'], $pen, $options['penWidth'] * ($options['drawMultiplier'] / 2));
68 $imgDest = imagecreatetruecolor($options['imageSize'][0], $options['imageSize'][1]);
70 if ($options['bgColour'] == 'transparent') {
71 imagealphablending($imgDest, false);
72 imagesavealpha($imgDest, true);
75 imagecopyresampled($imgDest, $img, 0, 0, 0, 0, $options['imageSize'][0], $options['imageSize'][0], $options['imageSize'][0] * $options['drawMultiplier'], $options['imageSize'][0] * $options['drawMultiplier']);
76 imagedestroy($img);
78 return $imgDest;
80 /**
81 * image resize function
82 * @param $file - file name to resize
83 * @param $string - The image data, as a string
84 * @param $width - new image width
85 * @param $height - new image height
86 * @param $proportional - keep image proportional, default is no
87 * @param $output - name of the new file (include path if needed)
88 * @param $delete_original - if true the original image will be deleted
89 * @param $use_linux_commands - if set to true will use "rm" to delete the image, if false will use PHP unlink
90 * @param $quality - enter 1-100 (100 is best quality) default is 100
91 * @param $cropFromTop - if false crop will be from center, if true crop will be from top
92 * @return boolean|resource
94 function smart_resize_image(
95 $file,
96 $string = null,
97 $width = 0,
98 $height = 0,
99 $proportional = false,
100 $output = 'file',
101 $delete_original = true,
102 $use_linux_commands = false,
103 $quality = 100,
104 $cropFromTop = false
106 if ($height <= 0 && $width <= 0) {
107 return false;
110 if ($file === null && $string === null) {
111 return false;
114 # Setting defaults and meta
115 $info = $file !== null ? getimagesize($file) : getimagesizefromstring($string);
116 $image = '';
117 $final_width = 0;
118 $final_height = 0;
119 list($width_old, $height_old) = $info;
120 $cropHeight = $cropWidth = 0;
121 # Calculating proportionality
122 if ($proportional) {
123 if ($width == 0) {
124 $factor = $height/$height_old;
125 } elseif ($height == 0) {
126 $factor = $width/$width_old;
127 } else {
128 $factor = min($width / $width_old, $height / $height_old);
131 $final_width = round($width_old * $factor);
132 $final_height = round($height_old * $factor);
133 } else {
134 $final_width = ( $width <= 0 ) ? $width_old : $width;
135 $final_height = ( $height <= 0 ) ? $height_old : $height;
136 $widthX = $width_old / $width;
137 $heightX = $height_old / $height;
138 $x = min($widthX, $heightX);
139 $cropWidth = ($width_old - $width * $x) / 2;
140 $cropHeight = ($height_old - $height * $x) / 2;
143 # Loading image to memory according to type
144 switch ($info[2]) {
145 case IMAGETYPE_JPEG:
146 $file !== null ? $image = imagecreatefromjpeg($file) : $image = imagecreatefromstring($string);
147 break;
148 case IMAGETYPE_GIF:
149 $file !== null ? $image = imagecreatefromgif($file) : $image = imagecreatefromstring($string);
150 break;
151 case IMAGETYPE_PNG:
152 $file !== null ? $image = imagecreatefrompng($file) : $image = imagecreatefromstring($string);
153 break;
154 default:
155 return false;
158 # This is the resizing/resampling/transparency-preserving magic
159 $image_resized = imagecreatetruecolor($final_width, $final_height);
160 if (($info[2] == IMAGETYPE_GIF) || ($info[2] == IMAGETYPE_PNG)) {
161 $transparency = imagecolortransparent($image);
162 $palletsize = imagecolorstotal($image);
163 if ($transparency >= 0 && $transparency < $palletsize) {
164 $transparent_color = imagecolorsforindex($image, $transparency);
165 $transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
166 imagefill($image_resized, 0, 0, $transparency);
167 imagecolortransparent($image_resized, $transparency);
168 } elseif ($info[2] == IMAGETYPE_PNG) {
169 imagealphablending($image_resized, false);
170 $color = imagecolorallocatealpha($image_resized, 0, 0, 0, 127);
171 imagefill($image_resized, 0, 0, $color);
172 imagesavealpha($image_resized, true);
176 if ($cropFromTop) {
177 $cropHeightFinal = 0;
178 } else {
179 $cropHeightFinal = $cropHeight;
182 imagecopyresampled($image_resized, $image, 0, 0, $cropWidth, $cropHeightFinal, $final_width, $final_height, $width_old - 2 * $cropWidth, $height_old - 2 * $cropHeight);
183 # Taking care of original, if needed
184 if ($delete_original) {
185 if ($use_linux_commands) {
186 exec('rm '.$file);
187 } else {
188 @unlink($file);
192 # Preparing a method of providing result
193 switch (strtolower($output)) {
194 case 'browser':
195 $mime = image_type_to_mime_type($info[2]);
196 header("Content-type: $mime");
197 $output = null;
198 break;
199 case 'file':
200 $output = $file;
201 break;
202 case 'return':
203 imagedestroy($image);
204 return $image_resized;
205 break;
206 default:
207 break;
210 # Writing image according to type to the output destination and image quality
211 switch ($info[2]) {
212 case IMAGETYPE_GIF:
213 imagegif($image_resized, $output);
214 break;
215 case IMAGETYPE_JPEG:
216 imagejpeg($image_resized, $output, $quality);
217 break;
218 case IMAGETYPE_PNG:
219 $quality = 9 - (int)((0.9*$quality)/10.0);
220 imagepng($image_resized, $output, $quality);
221 break;
222 default:
223 return false;
226 return true;
229 * Draws a thick line
230 * Changing the thickness of a line using imagesetthickness doesn't produce as nice of result
232 * @param object $img
233 * @param int $startX
234 * @param int $startY
235 * @param int $endX
236 * @param int $endY
237 * @param object $colour
238 * @param int $thickness
240 * @return void
242 function drawThickLine($img, $startX, $startY, $endX, $endY, $colour, $thickness)
244 $angle = (atan2(($startY - $endY), ($endX - $startX)));
246 $dist_x = $thickness * (sin($angle));
247 $dist_y = $thickness * (cos($angle));
249 $p1x = ceil(($startX + $dist_x));
250 $p1y = ceil(($startY + $dist_y));
251 $p2x = ceil(($endX + $dist_x));
252 $p2y = ceil(($endY + $dist_y));
253 $p3x = ceil(($endX - $dist_x));
254 $p3y = ceil(($endY - $dist_y));
255 $p4x = ceil(($startX - $dist_x));
256 $p4y = ceil(($startY - $dist_y));
258 $array = array(0=>$p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $p4x, $p4y);
259 imagefilledpolygon($img, $array, (count($array)/2), $colour);
262 class sigToSvg
265 * Associative array of options.
266 * @var array|null
268 private $options;
270 * An array of indexed coordinates [lx, ly, mx, my]
271 * @var array|null
273 private $coords;
275 * Maximum image width and height.
276 * @var array
278 public $max = array(0,0);
280 * @param string|array $json Can accept a JSON string or an array of SigPad coord objects.
281 * @param array $options
282 * title : @var string ['Signature'] Text description of the image
283 * penWidth : @var int [2] width of the line
284 * penColour : @var string ['#145394'] hexidecimal color of the signature
285 * @throws Exception If failure on JSON parsing.
287 public function __construct($json, $options = array())
289 $this->options = array_merge($this->getDefaultOptions(), $options);
290 if (is_string($json)) {
291 $this->coords = json_decode($json, true); // force to assoc array
292 if (is_null($this->coords)) {
293 $jErr = '';
294 if (function_exists('json_last_error')) { // allow for php 5.2
295 switch (json_last_error()) {
296 case JSON_ERROR_DEPTH:
297 $jErr = ' - Maximum stack depth exceeded';
298 break;
299 case JSON_ERROR_CTRL_CHAR:
300 $jErr = ' - Unexpected control character found';
301 break;
302 case JSON_ERROR_SYNTAX:
303 $jErr = ' - Syntax error, malformed JSON';
304 break;
305 case JSON_ERROR_NONE:
306 $jErr = ' - Unknown error';
307 break;
311 throw new Exception("Cannot decode the JSON string.$jErr", 1000);
314 $this->coords = array_map('array_values', $this->coords); // flatten the array
315 } elseif (is_array($json)) {
316 $this->coords = array();
317 foreach ($json as $obj) {
318 $this->coords[] = array_values((array)$obj);
320 } else {
321 throw new Exception('Data passed to constructor is invalid.', 1001);
325 * Svg Mime Type
326 * @return string
328 public static function getMimeType()
330 return 'image/svg+xml';
333 * @return array Name value pairs
335 private function getDefaultOptions()
337 return array(
338 'title' => 'Signature',
339 'penWidth' => 2,
340 'penColour' => '#145394'
344 * Determine the maximum height and width of the image.
345 * @param array $coord
346 * @return null
348 private function setMax($coord)
350 foreach ($coord as $i => $pt) {
351 if ($pt > $this->max[$i%2]) {
352 $this->max[$i%2] = $pt;
357 * Get the SVG line elements.
358 * @return string
360 private function getLineElements()
362 $lines = '';
363 foreach ($this->coords as $coord) {
364 $lines .= vsprintf('<line x1="%d" y1="%d" x2="%d" y2="%d"/>', $coord);
365 $this->setMax($coord);
368 return $lines;
371 * Get the image boundaries.
372 * @param bool $axis False is x-axis, True is y-axis
373 * @return int
375 private function getBound($axis = 0)
377 return round($this->max[(int)$axis] + ($this->options['penWidth'] / 2));
380 * Get the full XML SVG image.
381 * @return string
383 public function getImage()
385 $max[0] = $this->getBound(0);
386 $max[1] = $this->getBound(1);
387 $lines = $this->getLineElements();
388 return '<?xml version="1.0"?><svg baseProfile="tiny" width="' . $this->getBound(0) . '" height="' . $this->getBound(1) . '" version="1.2" xmlns="http://www.w3.org/2000/svg"><g fill="red" stroke="' . $this->options['penColour'] . '" stroke-width="' . (int)$this->options['penWidth'] . '" stroke-linecap="round" stroke-lingjoin="round"><title>' . htmlspecialchars($this->options['title']) . '</title>' . $lines . '</g></svg>';
391 * Compress the SVG using gzip.
392 * @return binary
394 public function getImageGz()
396 if (!function_exists('gzencode')) {
397 throw new Exception('Cannot get gzip image. Check that Zlib is installed.', 2000);
400 return gzencode($this->getImage(), 9);