CWIS Developer Documentation
Axis--Image.php
Go to the documentation of this file.
1 <?PHP
2 
3 #
4 # Axis--Image.php
5 # A PHP Object to Support Image File Manipulation
6 #
7 # NOTE: To use this object, either PHP must have internal support for the image formats
8 # to be manipulated (can be checked with the imagetypes() function) or the web server
9 # must be running with safe_mode off and certain external executables must be available
10 # to be invoked by the server. (A list of the needed executables can be obtained via
11 # the Image::RequiredExternalExecutables() method.)
12 #
13 # Copyright 2002-2004 Axis Data
14 # This code is free software that can be used or redistributed under the
15 # terms of Version 2 of the GNU General Public License, as published by the
16 # Free Software Foundation (http://www.fsf.org).
17 #
18 # Author: Edward Almasy (ealmasy@axisdata.com)
19 #
20 # Part of the AxisPHP library v1.2.5
21 # For more information see http://www.axisdata.com/AxisPHP/
22 #
23 
24 class Image {
25 
26  # ---- PUBLIC INTERFACE --------------------------------------------------
27 
29  {
30  # set debug level
31  $this->DebugLevel = $DebugLevel;
32 
33  # save source file name
34  $this->SourceFileName = $SourceFileName;
35 
36  # set default values
37  $this->JpegSaveQuality = 80;
38  $this->ErrorStatus = AI_OKAY;
39  $this->FailedCommand = "";
40 
41  # get GD library version
42  if (extension_loaded("gd"))
43  {
44  if (in_array("imagecreatetruecolor", get_extension_funcs("gd")))
45  {
46  $this->GDVersion = 2;
47  }
48  else
49  {
50  $this->GDVersion = 1;
51  }
52  }
53  else
54  {
55  $this->GDVersion = 0;
56  }
57 
58  # if source file is readable
59  if (is_readable(realpath($SourceFileName)))
60  {
61  # if internal support is available for this image type
62  if ($this->ImageFormatSupportedByPhp())
63  {
64  if ($this->DebugLevel > 0) { print("AI: using internal funcs for decoding image<br>\n"); }
65 
66  # create PHP image object
67  switch ($this->Type())
68  {
69  case IMGTYPE_JPEG:
70  if ($this->DebugLevel > 1) { print("AI: file format is JPEG<br>\n"); }
71  $this->ImageObj = imagecreatefromjpeg($this->SourceFileName);
72  break;
73 
74  case IMGTYPE_GIF:
75  if ($this->DebugLevel > 1) { print("AI: file format is GIF<br>\n"); }
76  $this->ImageObj = imagecreatefromgif($this->SourceFileName);
77  break;
78 
79  case IMGTYPE_BMP:
80  if ($this->DebugLevel > 1) { print("AI: file format is BMP<br>\n"); }
81  $this->ImageObj = imagecreatefrombmp($this->SourceFileName);
82  break;
83 
84  case IMGTYPE_PNG:
85  if ($this->DebugLevel > 1) { print("AI: file format is PNG<br>\n"); }
86  $this->ImageObj = imagecreatefrompng($this->SourceFileName);
87  break;
88 
89  default:
90  $this->ErrorStatus = AI_INTERNALERROR;
91  break;
92  }
93 
94  # if PHP image object creation failed
95  if (FALSE === $this->ImageObj)
96  {
97  # set error status
98  $this->ErrorStatus = AI_IMGOBJCREATEFAILED;
99  }
100  }
101 
102  # if external command execution possible
103  if (ini_get("safe_mode") != "1")
104  {
105  # determine external save command to convert image to portable format
106  switch ($this->Type())
107  {
108  case IMGTYPE_BMP:
109  $this->DecodeCommand = "bmptoppm ";
110  break;
111 
112  case IMGTYPE_GIF:
113  $this->DecodeCommand = "giftopnm ";
114  break;
115 
116  case IMGTYPE_PNG:
117  $this->DecodeCommand = "pngtopnm ";
118  break;
119 
120  case IMGTYPE_JPEG:
121  $this->DecodeCommand = "djpeg ";
122  break;
123 
124  default:
125  $this->ErrorStatus = AI_UNKNOWNTYPE;
126  break;
127  }
128  $this->DecodeCommand .= realpath($this->SourceFileName)." ";
129  }
130  else
131  {
132  # if format wasn't supported internally
133  if (!$this->ImageFormatSupportedByPhp())
134  {
135  # set error status to indicate unsupported image format
136  $this->ErrorStatus = AI_UNSUPPORTEDFORMAT;
137  }
138  }
139  }
140  else
141  {
142  # set error status
143  $this->ErrorStatus = AI_FILEUNREADABLE;
144  }
145  }
146 
147  # save image with a new name and (optionally) a new type
148  function SaveAs($FileName, $NewImageType = NULL)
149  {
150  # assume we will succeed
151  $this->ErrorStatus = AI_OKAY;
152 
153  # if destination file exists and is not writable
154  if (file_exists($FileName) && (is_writable($FileName) != TRUE))
155  {
156  # set error code
157  $this->ErrorStatus = AI_DESTINATIONUNWRITABLE;
158  }
159  # else if destination directory is not writable
160  elseif (is_writable(dirname($FileName)) != TRUE)
161  {
162  # set error code
163  $this->ErrorStatus = AI_DESTINATIONUNWRITABLE;
164  }
165  else
166  {
167  # if no image type specified try to determine based on file name or use source file type
168  if ($NewImageType == NULL)
169  {
170  if ($this->Type($FileName) != IMGTYPE_UNKNOWN)
171  { $NewImageType = $this->Type($FileName); }
172  else
173  { $NewImageType = $this->Type(); }
174  }
175 
176  # if input and output types both supported by internal functions
177  if ($this->ImageFormatSupportedByPhp() && $this->ImageFormatSupportedByPhp($NewImageType))
178  {
179  # if image cropping or scaling was requested
180  if (isset($this->CroppedXSize)
181  || isset($this->ScaledXSize)
182  || isset($this->ScaledYSize))
183  {
184  # determine destination image size
185  if (isset($this->ScaledXSize) && isset($this->ScaledYSize)
186  && ($this->MaintainAspectRatio != TRUE))
187  {
188  $DstXSize = $this->ScaledXSize;
189  $DstYSize = $this->ScaledYSize;
190  }
191  elseif (isset($this->ScaledXSize)
192  || ($this->ScaledXSize > $this->ScaledYSize))
193  {
194  $DstXSize = $this->ScaledXSize;
195  $DstYSize = ($this->ScaledXSize * $this->YSize())
196  / $this->XSize();
197  }
198  elseif (isset($this->ScaledYSize))
199  {
200  $DstXSize = ($this->ScaledYSize * $this->XSize())
201  / $this->YSize();
202  $DstYSize = $this->ScaledYSize;
203  }
204  elseif (isset($this->CroppedXSize))
205  {
206  $DstXSize = $this->CroppedXSize;
207  $DstYSize = $this->CroppedYSize;
208  }
209  else
210  {
211  $DstXSize = $this->XSize();
212  $DstYSize = $this->YSize();
213  }
214 
215  # create destination image object
216  if (($NewImageType == IMGTYPE_GIF) || ($this->GDVersion < 2))
217  {
218  $DstImage = imagecreate($DstXSize, $DstYSize);
219  }
220  else
221  {
222  $DstImage = imagecreatetruecolor($DstXSize, $DstYSize);
223  }
224 
225  # determine area of source image to use
226  if (isset($this->CroppedXSize))
227  {
228  $SrcXSize = $this->CroppedXSize;
229  $SrcYSize = $this->CroppedYSize;
230  }
231  else
232  {
233  $SrcXSize = $this->XSize();
234  $SrcYSize = $this->YSize();
235  }
236 
237  # copy/scale portion of original image to destination image
238  if ($this->GDVersion >= 2)
239  {
240  imagecopyresampled($DstImage, $this->ImageObj,
241  0, 0,
242  $this->CroppedXOrigin, $this->CroppedYOrigin,
243  $DstXSize, $DstYSize,
244  $SrcXSize, $SrcYSize);
245  }
246  else
247  {
248  imagecopyresized($DstImage, $this->ImageObj,
249  0, 0,
250  $this->CroppedXOrigin, $this->CroppedYOrigin,
251  $DstXSize, $DstYSize,
252  $SrcXSize, $SrcYSize);
253  }
254  }
255  else
256  {
257  $DstImage =& $this->ImageObj;
258  }
259 
260  # save image to new file
261  switch ($NewImageType)
262  {
263  case IMGTYPE_GIF:
264  imagegif($DstImage, $FileName);
265  break;
266 
267  case IMGTYPE_JPEG:
268  imagejpeg($DstImage, $FileName, $this->JpegSaveQuality);
269  break;
270 
271  case IMGTYPE_PNG:
272  imagepng($DstImage, $FileName);
273  break;
274 
275  case IMGTYPE_BMP:
276  imagewbmp($DstImage, $FileName);
277 
278  default:
279  $this->ErrorStatus = AI_INTERNALERROR;
280  break;
281  }
282  }
283  else
284  {
285  # build command (convert image to intermediate form)
286  $Command = $this->DecodeCommand;
287 
288  # build command (crop if requested)
289  if (isset($this->CroppedXSize))
290  {
291  $Command .= "| pnmcut ".$this->CroppedXOrigin." ".$this->CroppedYOrigin." "
292  .$this->CroppedXSize." ".$this->CroppedYSize." ";
293  }
294 
295  # build command (scale if requested)
296  if (isset($this->ScaledXSize) || isset($this->ScaledYSize))
297  {
298  $Command .= "| pnmscale ";
299  if ($this->MaintainAspectRatio
300  && isset($this->ScaledXSize) && isset($this->ScaledYSize))
301  {
302  $Command .= "-xysize ".$this->ScaledXSize." ".$this->ScaledYSize;
303  }
304  else
305  {
306  if (isset($this->ScaledXSize)) { $Command .= "-xsize ".$this->ScaledXSize." "; }
307  if (isset($this->ScaledYSize)) { $Command .= "-ysize ".$this->ScaledYSize." "; }
308  }
309  }
310 
311  # build command (convert to final form)
312  switch ($NewImageType)
313  {
314  case IMGTYPE_BMP:
315  $Command .= "| ppmquant 256 | ppmtobmp -windows ";
316  break;
317 
318  case IMGTYPE_GIF:
319  $Command .= "| ppmquant 256 | ppmtogif ";
320  break;
321 
322  case IMGTYPE_PNG:
323  $Command .= "| ppmquant 256 | pnmtopng ";
324  break;
325 
326  case IMGTYPE_JPEG:
327  default:
328  $Command .= "| cjpeg -quality ".$this->JpegSaveQuality." ";
329  break;
330  }
331 
332  # build command (send output to new image file)
333  $Command .= "> ".$FileName;
334 
335  # execute command
336 
337  $CommandResult = system($Command);
338 
339  # set error status if command failed
340  if ($CommandResult === FALSE)
341  {
342  $this->ErrorStatus = AI_PPMCMDFAILED;
343  $this->FailedCommand = $Command;
344  }
345  }
346  }
347 
348  # report success or failure to caller
349  return $this->ErrorStatus;
350  }
351 
352  # return the X (horizontal) image size in pixels
353  function XSize()
354  {
355  $this->ReadSize();
356  return $this->ImageXSize;
357  }
358 
359  # return the Y (vertical) image size in pixels
360  function YSize()
361  {
362  $this->ReadSize();
363  return $this->ImageYSize;
364  }
365 
366  # specify the size to scale the image to for the next SaveAs()
368  {
369  # save size for scaling
370  $this->ScaledXSize = $ScaledXSize;
371  $this->ScaledYSize = $ScaledYSize;
372  $this->MaintainAspectRatio = $MaintainAspectRatio;
373  }
374 
375  # specify the size to crop the image to for the next SaveAs()
377  {
378  # save origin and size for cropping
379  $this->CroppedXSize = $CroppedXSize;
380  $this->CroppedYSize = $CroppedYSize;
381  $this->CroppedXOrigin = $CroppedXOrigin;
382  $this->CroppedYOrigin = $CroppedYOrigin;
383  }
384 
385  # return the image type
386  function Type($FileName = NULL)
387  {
388  if ($FileName == NULL) { $FileName = $this->SourceFileName; }
389  if (preg_match("/.*\\.jp[e]{0,1}g$/i", $FileName)) { return IMGTYPE_JPEG; }
390  elseif (preg_match("/.*\\.gif$/i", $FileName)) { return IMGTYPE_GIF; }
391  elseif (preg_match("/.*\\.bmp$/i", $FileName)) { return IMGTYPE_BMP; }
392  elseif (preg_match("/.*\\.png$/i", $FileName)) { return IMGTYPE_PNG; }
393  else { return IMGTYPE_UNKNOWN; }
394  }
395 
396  # return the file name extension for the image
397  static function Extension($Type = NULL)
398  {
399  if ($Type === NULL)
400  {
401  return Image::$AxisImageFileExtensions[$this->Type()];
402  }
403  else
404  {
405  if (isset(Image::$AxisImageFileExtensions[$Type]))
406  {
407  return Image::$AxisImageFileExtensions[$Type];
408  }
409  else
410  {
411  return NULL;
412  }
413  }
414  }
415 
416  # set/get the quality (0-100) for JPEG images created with SaveAs()
417  function JpegQuality($NewSetting = NULL)
418  {
419  if ($NewSetting != NULL) { $this->JpegSaveQuality = $NewSetting; }
420  return $this->JpegSaveQuality;
421  }
422 
423  # check availability of external executables and return list of any that are not found
425  {
426  # start with empty list of missing executables
427  $MissingExecutables = array();
428 
429  # for each required executable
430  foreach (Image::RequiredExternalExecutables() as $Executable)
431  {
432  # if executable did not appear to be available
433  if (Image::ExternalExecutableIsAvailable($Executable) == FALSE)
434  {
435  # add executable to list of missing
436  $MissingExecutables[] = $Executable;
437  }
438  }
439 
440  # return list of missing executables to caller
441  return $MissingExecutables;
442  }
443 
444  # return list of all required external executables
445  function RequiredExternalExecutables($ImageTypes = NULL)
446  {
447  # start with the assumption that no executables are required
448  $RequiredExecutables = array();
449 
450  # if no image types specified assume all image types
451  if ($ImageTypes == NULL)
452  {
453  $ImageTypes = IMGTYPE_JPEG | IMGTYPE_GIF | IMGTYPE_BMP | IMGTYPE_PNG;
454  }
455 
456  # add format-specific executables that may or may not be needed
457  if (($ImageTypes & IMGTYPE_JPEG)
458  && (!function_exists("imagetypes") || !(imagetypes() & IMG_JPG)))
459  {
460  $RequiredExecutables[] = "djpeg";
461  $RequiredExecutables[] = "cjpeg";
462  }
463  if (($ImageTypes & IMGTYPE_GIF)
464  && (!function_exists("imagetypes") || !(imagetypes() & IMG_GIF)))
465  {
466  $RequiredExecutables[] = "giftopnm";
467  $RequiredExecutables[] = "ppmtogif";
468  }
469  if (($ImageTypes & IMGTYPE_PNG)
470  && (!function_exists("imagetypes") || !(imagetypes() & IMG_PNG)))
471  {
472  $RequiredExecutables[] = "pngtopnm";
473  $RequiredExecutables[] = "pnmtopng";
474  }
475  if ($ImageTypes & IMGTYPE_BMP)
476  {
477  $RequiredExecutables[] = "bmptoppm";
478  $RequiredExecutables[] = "ppmtobmp";
479  }
480 
481  # if any format-specific executables needed
482  if (count($RequiredExecutables) != 0)
483  {
484  # add basic manipulation executables
485  $RequiredExecutables[] = "pnmcut";
486  $RequiredExecutables[] = "pnmscale";
487  $RequiredExecutables[] = "ppmquant";
488  $RequiredExecutables[] = "pnmfile";
489  }
490 
491  # return list of required executables to caller
492  return $RequiredExecutables;
493  }
494 
495  # return supported image formats
496  static function SupportedFormats()
497  {
498  # start out assuming no formats are supported
499  $Supported = 0;
500 
501  # if JPEG is supported by PHP or needed external executables are available
502  if ((function_exists("imagetypes") && defined("IMG_JPG")
503  && (imagetypes() & IMG_JPG))
506  {
507  # add JPEG to list of supported formats
508  $Supported |= IMGTYPE_JPEG;
509  }
510 
511  # if GIF is supported by PHP or needed external executables are available
512  if ((function_exists("imagetypes") && defined("IMG_GIF")
513  && (imagetypes() & IMG_GIF))
515  && Image::ExternalExecutableIsAvailable("ppmtogif")))
516  {
517  # add GIF to list of supported formats
518  $Supported |= IMGTYPE_GIF;
519  }
520 
521  # if PNG is supported by PHP or needed external executables are available
522  if ((function_exists("imagetypes") && defined("IMG_PNG")
523  && (imagetypes() & IMG_PNG))
525  && Image::ExternalExecutableIsAvailable("pnmtopng")))
526  {
527  # add PNG to list of supported formats
528  $Supported |= IMGTYPE_PNG;
529  }
530 
531  # if needed external executables are available for BMP
532  # needed executables being present is not sufficient for BMP support in PHP
533  # test is being shortcutted to false to reflect no PHP support for BMPs
534  if (0 && Image::ExternalExecutableIsAvailable("bmptoppm")
536  {
537  # add BMP to list of supported formats
538  $Supported |= IMGTYPE_BMP;
539  }
540 
541  # report to caller what formats are supported
542  return $Supported;
543  }
544 
545  # return names (upper-case extensions) of supported image formats
546  static function SupportedFormatNames()
547  {
548  # assume that no formats are supported
549  $FormatNames = array();
550 
551  # retrieve supported formats
552  $SupportedFormats = Image::SupportedFormats();
553 
554  # for each possible supported format
555  foreach (Image::$AxisImageFileExtensions as $ImageType => $ImageExtension)
556  {
557  # if format is supported
558  if ($ImageType & $SupportedFormats)
559  {
560  # add format extension to list of supported image format names
561  $FormatNames[] = strtoupper($ImageExtension);
562  }
563  }
564 
565  # return supported image format names to caller
566  return $FormatNames;
567  }
568 
569  # return the error status set by the constructor or the last call to SaveAs()
570  function Status()
571  {
572  return $this->ErrorStatus;
573  }
574 
575  # return string containing external command that failed
577  {
578  return $this->FailedCommand;
579  }
580 
581 
582  # ---- PRIVATE INTERFACE -------------------------------------------------
583 
601 
602  # image file extensions
603  private static $AxisImageFileExtensions = array(
604  IMGTYPE_JPEG => "jpg",
605  IMGTYPE_GIF => "gif",
606  IMGTYPE_BMP => "bmp",
607  IMGTYPE_PNG => "png",
608  );
609 
610  function ReadSize()
611  {
612  # if we do not already have image info
613  if (!isset($this->ImageXSize))
614  {
615  # if we are using internal image functions
616  if ($this->ImageFormatSupportedByPhp())
617  {
618  # read size information from image object
619  $this->ImageXSize = imagesx($this->ImageObj);
620  $this->ImageYSize = imagesy($this->ImageObj);
621  }
622  else
623  {
624  # retrieve image info string
625  $Command = $this->DecodeCommand."| pnmfile ";
626  $Result = exec("$Command");
627 
628  # parse image info string
629  $Pieces = preg_split("/\s+/", $Result);
630  $this->ImageXSize = $Pieces[3];
631  $this->ImageYSize = $Pieces[5];
632  }
633  }
634  }
635 
636  function ImageFormatSupportedByPhp($Format = NULL)
637  {
638  if ($Format == NULL) { $Format = $this->Type(); }
639 
640  if (!function_exists("imagetypes")) { return FALSE; }
641 
642  switch ($Format)
643  {
644  case IMGTYPE_JPEG:
645  return (imagetypes() & IMG_JPG) ? TRUE : FALSE;
646  break;
647 
648  case IMGTYPE_GIF:
649  return (imagetypes() & IMG_GIF) ? TRUE : FALSE;
650  break;
651 
652  case IMGTYPE_BMP:
653  return FALSE;
654  break;
655 
656  case IMGTYPE_PNG:
657  return (imagetypes() & IMG_PNG) ? TRUE : FALSE;
658  break;
659 
660  default:
661  return FALSE;
662  break;
663  }
664  }
665 
666  # check whether external executable is available and report result back to caller
667  function ExternalExecutableIsAvailable($ExecutableName)
668  {
669  static $ExecutableAvailable;
670 
671  if (!isset($ExecutableAvailable[$ExecutableName]))
672  {
673  $Result = exec("which ".$ExecutableName." 2>&1");
674  $ExecutableAvailable[$ExecutableName] =
675  (basename($Result) == $ExecutableName) ? TRUE : FALSE;
676  }
677  return $ExecutableAvailable[$ExecutableName];
678  }
679 }
680 
681 # image type definitions (these are purposefully different from those defined by PHP GD lib)
682 define("IMGTYPE_UNKNOWN", 0);
683 define("IMGTYPE_JPEG", 1);
684 define("IMGTYPE_GIF", 2);
685 define("IMGTYPE_BMP", 4);
686 define("IMGTYPE_PNG", 8);
687 
688 # error status definitions
689 define("AI_OKAY", 0);
690 define("AI_FILEUNREADABLE", 1);
691 define("AI_IMGOBJCREATEFAILED", 2);
692 define("AI_PPMCMDFAILED", 4);
693 define("AI_INTERNALERROR", 8);
694 define("AI_UNKNOWNTYPE", 16);
695 define("AI_UNSUPPORTEDFORMAT", 32);
696 define("AI_DESTINATIONUNWRITABLE", 64);
697 
698 # supply imagetypes() function if not defined
699 if (!function_exists("imagetypes"))
700 {
701  # (returning 0 indicates no image types supported)
702  function imagetypes() { return 0; }
703 }
704 
705 ?>