/[dtapublic]/projs/dtats/trunk/projs/20161008_web_page_thumbnail_make/pics_thumbnails_make.php
ViewVC logotype

Contents of /projs/dtats/trunk/projs/20161008_web_page_thumbnail_make/pics_thumbnails_make.php

Parent Directory Parent Directory | Revision Log Revision Log


Revision 117 - (show annotations) (download)
Sun Jan 1 04:06:02 2017 UTC (7 years, 2 months ago) by dashley
File size: 25318 byte(s)
Correct minor typo.
1 <?php
2 //-------------------------------------------------------------------------------------------------
3 //$Header$
4 //-------------------------------------------------------------------------------------------------
5 //This source code and any program in which it is compiled/used is provided under the MIT
6 //LICENSE (full license text below).
7 //-------------------------------------------------------------------------------------------------
8 //pics_thumbnails_make.php, Copyright (c) 2016 David T. Ashley
9 //
10 //Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
11 //associated documentation files (the "Software"), to deal in the Software without restriction,
12 //including without limitation the rights to use, copy, modify, merge, publish, distribute,
13 //sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
14 //furnished to do so, subject to the following conditions:
15 //
16 //The above copyright notice and this permission notice shall be included in all copies or
17 //substantial portions of the Software.
18 //
19 //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
20 //NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 //NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22 //DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 //-------------------------------------------------------------------------------------------------
25 //This program, a PHP script, creates thumbnails from recognized image types. This program is part
26 //of a 3-program suite designed to collectively create a web page directly from digital camera
27 //files (and of course the web page can be customized by hand-editing after it is automatically
28 //generated).
29 //
30 //The 3 programs in the 3-program suite are:
31 //
32 // pics_filenames_canonize.php:
33 // Converts all names of files in a directory to lower-case, and makes substitutions for any
34 // unusual characters.
35 //
36 // pics_thumbnails_make.php (this script):
37 // Creates thumbnails from recognized image types. The thumbnails are named relative to the
38 // original image file with the suffix "_small". To support shared hosting environments where
39 // CPU utilization of a script may be capped, there is a configuration constant in the script
40 // that will perform only a fixed number of conversions per invocation. If this configuration
41 // option is used, the script should be run repeatedly until it indicates that it has no more
42 // thumbnails to create.
43 //
44 // If any full-sized photos are modified, any corresponding thumbnails should be deleted and
45 // pics_thumbnails_make.php and pics_indexfile_make.php should be run again.
46 //
47 // pics_indexfile_make.php:
48 // Scans a directory and makes an index file ("index2.php") displaying all the thumbnail
49 // images, each of which link to the corresponding full-sized image. The index file is
50 // tailored to Dave Ashley's needs, but the created file can be edited and most of the
51 // content pasted into an HTML file. To avoid the accidental loss of information, any
52 // existing "index2.php" file is renamed out of the way.
53 //
54 //This script is designed to be run manually (rather than automatically invoked as a result of a
55 //web page request). It was written in PHP for convenience simply because DreamHost (the web
56 //hosting company Dave Ashley used at the time these scripts were written) has as part of its
57 //hosting environment PHP with the ImageMagick library compiled in.
58 //
59 //Usually, this script is invoked using "php <path>/pics_thumbnails_make.php", but the method of
60 //invocation may vary based on computing platform details.
61 //
62 //Using Fedora Linux, I had trouble getting PHP to use ImageMagick (the error was something like
63 //"Class Imagick not found"). Finally, I figured out that the missing package was
64 //php-pecl-imagemagick and "dnf install php-pecl-imagick" too care of it.
65 //
66 //After all conversions are complete, this script terminates with a segmentation fault under
67 //Linux. The segmentation fault is definitely tied to ImageMagick. The reason is not known, but
68 //the problem appears here and there on the Internet. All of the photo conversions occur
69 //correctly, and the problem seems to occur during the termination phase of the PHP script.
70 //-------------------------------------------------------------------------------------------------
71 //C O N F I G U R A T I O N
72 //-------------------------------------------------------------------------------------------------
73 //Configuration switch combinations were not tested. E-mail me any program
74 //corrections (dashley@gmail.com).
75 define ("CFG_PROGNAME", "pics_thumbnails_make.php");
76 //Program name.
77 define ("CFG_CONSOLE_STD_LINE_LEN", 78);
78 //Number of characters per line preferred for console output.
79 define ("CFG_THUMBNAIL_DIMENSION_MAX", 125);
80 //The maximum dimension of the created thumbnails. Thumbnails are sized so
81 //that the longest dimension is this many pixels.
82 define ("CFG_THUMBNAIL_BEVELED_BORDER_WIDTH", 4);
83 //The number of pixels that the thumbnail beveled borders should be.
84 define ("CFG_THUMBNAIL_FILENAME_SUFFIX", "_small");
85 //String added just before the filename extension to choose thumbnail names
86 //based on name of full-sized image.
87 define ("CFG_LANCZOS_FILTER_APPLY", FALSE);
88 //TRUE if should apply the Lanczos filter when making the thumbnail, FALSE
89 //otherwise. I have no idea if using a Lanczos filter improves the quality
90 //of the thumbnails, but the filter was applied in the PHP example I found
91 //online. Applying the Lanczos filter typically adds a few seconds to the
92 //time required to create each thumbnail.
93 define ("CFG_MAX_THUMBNAILS_PER_INVOCATION", 0);
94 //The maximum number of thumbnails that should be created per invocation.
95 //This is to prevent the program from being involuntarily terminated
96 //for consuming too much CPU time (mostly for use in shared hosting
97 //environments that cap CPU usage). Setting this to 0 means no limit is
98 //applied (unlimited number of photos processed per invocation).
99 //--------------------------------------------------------------------------------
100 //Calculates important integer indices related to creating a thumbnail, in order
101 //to minimize distortion in the thumbnail.
102 //
103 //When creating a thumbnail, the longer side may be relatively short (perhaps
104 //100 pixels), and the shorter side may be even shorter (perhaps even as short
105 //as 10 pixels). If the dimensions of the original image were not changed,
106 //converting the image to a thumbnail may result in distortion of up to about
107 //one percent. As an example, suppose that a 1600 x 900 image is converted to a
108 //thumbnail with the longer side of 125 pixels. The ideal dimension for the
109 //shorter side would be (900/1600) * 125 = 70.3125 pixels. The aspect ratio of
110 //the thumbnail can't be made to match the aspect ratio of the original image.
111 //
112 //To avoid any aspect ratio distortion that might be noticeable, this program
113 //chooses the shorter dimension for the thumbnail that is the ceiling of the actual
114 //quotient, then crops the longer dimension of the original image before the
115 //conversion to try and match the aspect ratios as closely as possible.
116 //
117 //In the example a paragraph or two above, the shorter side of the thumbnail
118 //would be chosen to be 71 pixels.
119 //
120 //Once this choice is made, we want to trim the original image before conversion
121 //so that n/900 is as close as possible to 125/71. n = (900 * 125) / 71 =
122 //1584.5070 pixels, so we choose 1585. This keeps the aspect ratio of the
123 //original image and the thumbnail as close as possible.
124 //
125 //Although the longer side of the original image may be reduced prior to the
126 //conversion to a thumbnail, this information is not written to disk, and
127 //the original image is not modified. The adjustment of the longer side is
128 //just a conversion trick to hopefully get better thumbnails.
129 function calc_thumbnail_conversion_pars
130 (
131 $in_orig_longer,
132 //Longer dimension of original image.
133 $in_orig_shorter,
134 //Shorter dimension of original image.
135 $in_thumbnail_longer,
136 //Longer dimension of desired thumbnail.
137 & $out_thumbnail_longer,
138 //Longer dimension of thumbnail that should be created.
139 & $out_thumbnail_shorter,
140 //Shorter dimension of thumbnail that should be created.
141 & $out_orig_crop_dim_longer,
142 //The size to crop to on the longer axis of the original.
143 & $out_orig_crop_dim_shorter,
144 //The size to crop to on the shorter axis of the original.
145 & $out_orig_crop_start_longer,
146 //For use with the Imagick:cropImage() method, the start
147 //position of the crop on the longer axis.
148 & $out_orig_crop_start_shorter
149 //For use with the Imagick:cropImage() method, the start
150 //position of the crop on the shorter axis.
151 )
152 {
153 //Set the thumbnail shorter dimension to the floor. This means would need to trim
154 //longest dimension of original to match aspect ratio as closely as possible.
155 $out_thumbnail_longer = $in_thumbnail_longer;
156 $out_thumbnail_shorter = ceil(((float)$in_thumbnail_longer *
157 (float)$in_orig_shorter) / (float)$in_orig_longer);
158 settype($out_thumbnail_shorter, "integer");
159
160 //The aspect ratio of the thumbnail is now set. Try to match the aspect ratio of the larger
161 //image as closely as possible by selecting a smaller number for the long axis of the
162 //original image.
163 $out_orig_crop_dim_longer = round(((float)$in_thumbnail_longer *
164 (float)$in_orig_shorter) / (float)$out_thumbnail_shorter);
165 settype($out_orig_crop_dim_longer, "integer");
166
167 //The original keeps its shorter dimension unchanged.
168 $out_orig_crop_dim_shorter = $in_orig_shorter;
169
170 //Set the cropping of the longer side of the original to cover half the necessary
171 //reduction.
172 $out_orig_crop_start_longer = round(((float)$in_orig_longer -
173 (float)$out_orig_crop_dim_longer) / 2.0);
174 settype($out_orig_crop_start_longer, "integer");
175
176 //No cropping of original shorter side.
177 $out_orig_crop_start_shorter = 0;
178 }
179 //--------------------------------------------------------------------------------
180 //Repeats a character to the console output a certain number of times.
181 function rep_char_con($c, $n)
182 {
183 while ($n--)
184 echo $c;
185 }
186 //--------------------------------------------------------------------------------
187 //Writes a standard thick horizontal line to the console.
188 function hor_line_thick()
189 {
190 rep_char_con("=", CFG_CONSOLE_STD_LINE_LEN);
191 echo "\n";
192 }
193 //--------------------------------------------------------------------------------
194 //Writes a standard thin horizontal line to the console.
195 function hor_line_thin()
196 {
197 rep_char_con("-", CFG_CONSOLE_STD_LINE_LEN);
198 echo "\n";
199 }
200 //--------------------------------------------------------------------------------
201 //Returns an array of all files in the working directory.
202 //If no files can be found, returns FALSE.
203 function get_file_names_in_dir()
204 {
205 //Get directory list.
206 $rv = scandir (".");
207
208 //If the list is empty, something went wrong. Return FALSE.
209 if ($rv === FALSE)
210 return FALSE;
211
212 return $rv;
213 }
214 //--------------------------------------------------------------------------------
215 //Returns TRUE if a file name appears to be a valid full-sized image name,
216 //or FALSE otherwise.
217 function is_full_sized_image_file_name($in_filename)
218 {
219 //Convert the string name to all lower case. This will do for
220 //comparisons and tests.
221 $in_filename = strtolower($in_filename);
222
223 //Attempt to split the name into a base and an extension. Any failure
224 //means it is an unsuitable name.
225 $extension_start = strrpos($in_filename, ".");
226 //Find position of last "." in string. This should precede the
227 //file extension.
228
229 if ($extension_start === FALSE)
230 {
231 //Bad name. Unsuitable.
232 return FALSE;
233 }
234
235 //Calculate the base and extension.
236 $filename_base = substr($in_filename, 0, $extension_start);
237 $filename_extension = substr($in_filename, $extension_start + 1);
238
239 //If the extension is not "jpg", "jpeg", "gif", or "png"; it isn't anything we recognize.
240 if (
241 ($filename_extension != "jpg")
242 &&
243 ($filename_extension != "jpeg")
244 &&
245 ($filename_extension != "gif")
246 &&
247 ($filename_extension != "png")
248 )
249 return FALSE;
250
251 //If the filename base is empty, the filename is unsuitable.
252 if (strlen($filename_base) == 0)
253 return FALSE;
254
255 //If the last characters of the base are the CFG_THUMBNAIL_FILENAME_SUFFIX,
256 //the name is unsuitable.
257 if (strlen($filename_base) >= strlen(CFG_THUMBNAIL_FILENAME_SUFFIX))
258 {
259 if (substr($filename_base, strlen($filename_base) - strlen(CFG_THUMBNAIL_FILENAME_SUFFIX))
260 == CFG_THUMBNAIL_FILENAME_SUFFIX)
261 return FALSE;
262 }
263
264 //Looks good.
265 return TRUE;
266 }
267 //--------------------------------------------------------------------------------
268 //As a function of the file name, creates the file name for the thumbnail.
269 function file_name_to_thumbnail_name($in_filename)
270 {
271 $extension_start = strrpos($in_filename, ".");
272 //Find position of last "." in string. This should precede the
273 //file extension.
274 if ($extension_start === FALSE)
275 {
276 //"." not found. Should not happen. Filenames were checked in advance.
277 echo "Fatal internal error at line " . __LINE__ . "\n";
278 exit(1);
279 }
280
281 $filename_prefix = substr($in_filename, 0, $extension_start);
282 $filename_extension = substr($in_filename, $extension_start);
283
284 $rv = $filename_prefix . CFG_THUMBNAIL_FILENAME_SUFFIX . $filename_extension;
285
286 return $rv;
287 }
288 //--------------------------------------------------------------------------------
289 //Actually creates the thumbnail, and returns some information about what was
290 //done.
291 //
292 //The calls to the ImagMagick library take on the order of 5s per image if there
293 //is filtering.
294 function create_thumbnail( $in_filename,
295 $in_thumbnailname,
296 & $out_filename_filesize,
297 & $out_filename_xdim,
298 & $out_filename_ydim,
299 & $out_thumbnailname_filesize,
300 & $out_thumbnailname_xdim,
301 & $out_thumbnailname_ydim)
302 {
303 //Assign output parameters just in case something doesn't get assigned.
304 $out_filename_filesize = 0;
305 $out_filename_xdim = 0;
306 $out_filename_ydim = 0;
307 $out_thumbnailname_filesize = 0;
308 $out_thumbnailname_xdim = 0;
309 $out_thumbnailname_ydim = 0;
310
311 //Establish target dimensions. Two cases, depending on which is the longer
312 //side.
313
314 //Construct.
315 $imagick = new Imagick();
316
317 //Load image.
318 $imagick->readImage($in_filename);
319
320 //Get the dimensions of the image we just loaded.
321 $geo = $imagick->getImageGeometry();
322 $out_filename_xdim = $geo['width'];
323 $out_filename_ydim = $geo['height'];
324
325 //Calculate target sizes. We rearrange parameters based on which is our
326 //longest side.
327 if ($out_filename_xdim >= $out_filename_ydim)
328 {
329 //Longer width (x-dimension), or square.
330 calc_thumbnail_conversion_pars
331 (
332 $out_filename_xdim,
333 $out_filename_ydim,
334 CFG_THUMBNAIL_DIMENSION_MAX,
335 $out_thumbnailname_xdim,
336 $out_thumbnailname_ydim,
337 $orig_crop_dim_x,
338 $orig_crop_dim_y,
339 $orig_crop_start_x,
340 $orig_crop_start_y
341 );
342 }
343 else
344 {
345 //Longer height (y-dimension).
346 calc_thumbnail_conversion_pars
347 (
348 $out_filename_ydim,
349 $out_filename_xdim,
350 CFG_THUMBNAIL_DIMENSION_MAX,
351 $out_thumbnailname_ydim,
352 $out_thumbnailname_xdim,
353 $orig_crop_dim_y,
354 $orig_crop_dim_x,
355 $orig_crop_start_y,
356 $orig_crop_start_x
357 );
358 }
359
360 //For debugging only, might want to know intermediate calculation results.
361 //echo "xcropdim, ycropdim, xcropstart, ycropstart: "
362 // .
363 // $orig_crop_dim_x
364 // .
365 // " "
366 // .
367 // $orig_crop_dim_y
368 // .
369 // " "
370 // .
371 // $orig_crop_start_x
372 // .
373 // " "
374 // .
375 // $orig_crop_start_y
376 // .
377 // "\n";
378
379 //Crop the original to try to preserve the aspect ratio of the thumbnail
380 //as precisely as possible.
381 $imagick->cropImage(
382 $orig_crop_dim_x,
383 $orig_crop_dim_y,
384 $orig_crop_start_x,
385 $orig_crop_start_y
386 );
387
388 //For debugging only, might want to get a look at the cropped image, to
389 //be sure nothing unexpected happens on the canvas.
390 //$imagick->writeImage($in_filename . ".cropped.jpg");
391
392 //Resize to thumbnail size.
393 if (CFG_LANCZOS_FILTER_APPLY)
394 {
395 $imagick->resizeImage($out_thumbnailname_xdim,
396 $out_thumbnailname_ydim,
397 Imagick::FILTER_LANCZOS,
398 1);
399 }
400 else
401 {
402 $imagick->resizeImage($out_thumbnailname_xdim,
403 $out_thumbnailname_ydim,
404 0,
405 1);
406 }
407
408 //Create the border.
409 $imagick->raiseImage(CFG_THUMBNAIL_BEVELED_BORDER_WIDTH,
410 CFG_THUMBNAIL_BEVELED_BORDER_WIDTH,
411 0,
412 0,
413 1);
414
415 //Set compression to get a smaller thumbnail written, and strip
416 //header information. stripImage() seems to have the largest effect
417 //on thumbnail file size, so leaving the thumbnail quality near 100%
418 //is feasible. The jump in file size between 90% and 95% seemed to be
419 //fairly large (40% to 50%), so I left it at 90%. My rationale is
420 //that with the proliferation of mobile devices and cellular data,
421 //getting the thumbnail as small as possible is more important than
422 //the thumbnail looking perfect. If the viewer wants a perfect image,
423 //they can view the full-sized image.
424 $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
425 $imagick->setImageCompressionQuality(90);
426 $imagick->stripImage();
427
428 //Write the thumbnail.
429 $imagick->writeImage($in_thumbnailname);
430
431 //Destroy to prevent possible memory leak.
432 $imagick->destroy();
433
434 //All of the writing is done. Try to obtain the file sizes.
435 $fsize = filesize($in_filename);
436 if ($fsize !== FALSE)
437 $out_filename_filesize = $fsize;
438 $fsize = filesize($in_thumbnailname);
439 if ($fsize !== FALSE)
440 $out_thumbnailname_filesize = $fsize;
441 }
442 //--------------------------------------------------------------------------------
443 //Write introductory message.
444 hor_line_thick();
445 echo CFG_PROGNAME . ", Copyright (c) 2016 David T. Ashley\n";
446 echo "This program comes with ABSOLUTELY NO WARRANTY; and is licensed under the\n";
447 echo "MIT License. A copy of this license is provided in the source code\n";
448 echo "of this program.\n";
449 hor_line_thin();
450 //-----------------------------------------------------------------------------
451 //Get and emit the names of everything in the directory.
452 $file_list = get_file_names_in_dir();
453 if ($file_list === FALSE)
454 {
455 echo "List of files from PHP function scandir() is empty (rv === FALSE).\n";
456 echo "Serious internal error, or nothing to do. Script cannot continue.\n";
457 hor_line_thick();
458 exit(1);
459 }
460 else
461 {
462 echo "Files in working directory (unsorted, unfiltered, "
463 .
464 count($file_list)
465 .
466 " files):\n";
467 for ($i = 0; $i < count($file_list); $i++)
468 echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . "\n";
469 }
470 hor_line_thin();
471 //-----------------------------------------------------------------------------
472 //Remove the standard directory entries "." and ".." from the list, and
473 //remove any directories.
474 $temp_list = $file_list;
475 unset($file_list);
476 $n = 0;
477 for ($i = 0; $i < count($temp_list); $i++)
478 {
479 //echo "Checking " . $temp_list[$i] . "\n";
480
481 if (strcmp($temp_list[$i], ".") == 0)
482 {
483 //. entry, not a file.
484 }
485 else if (strcmp($temp_list[$i], "..") == 0)
486 {
487 //.. entry, not a file.
488 }
489 else if (is_file($temp_list[$i]))
490 {
491 //This is a regular file.
492 $file_list[] = $temp_list[$i];
493 $n++;
494 }
495 }
496
497 if ($n == 0)
498 $file_list = FALSE;
499
500 unset($n);
501 unset($temp_list);
502 //-----------------------------------------------------------------------------
503 //If there is nothing to do, end the script.
504 if ($file_list === FALSE)
505 {
506 echo "No files to process.\n";
507 hor_line_thick();
508 exit(0);
509 }
510 //-----------------------------------------------------------------------------
511 //Sort the list. This is a non-event. The only rationale for sorting is that
512 //it ensures that the same set of files will be processed in the same order,
513 //regardless of the order provided by the underlying OS internals.
514 sort($file_list);
515 //-----------------------------------------------------------------------------
516 //Emit the names we now have.
517 echo "Files in working directory (directory entries removed, sorted, "
518 .
519 count($file_list)
520 .
521 " files):\n";
522 for ($i = 0; $i < count($file_list); $i++)
523 echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . "\n";
524 hor_line_thin();
525
526 //-----------------------------------------------------------------------------
527 //For each file that is appropriate and where the thumbnail does not already
528 //exist, up to the maximum we may do in one invocation, create the thumbnail.
529 $i = 0;
530 $completed = 0;
531 while (
532 (
533 (CFG_MAX_THUMBNAILS_PER_INVOCATION == 0)
534 ||
535 ($completed < CFG_MAX_THUMBNAILS_PER_INVOCATION)
536 )
537 &&
538 ($i < count($file_list))
539 )
540 {
541 if (is_full_sized_image_file_name($file_list[$i]))
542 {
543 $thumbnail_name = file_name_to_thumbnail_name($file_list[$i]);
544
545 if (file_exists($thumbnail_name))
546 {
547 echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . " : skipping because corresponding thumbnail exists.\n";
548 hor_line_thin();
549 }
550 else
551 {
552 echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . " : creating thumbnail.\n";
553
554 echo "Creating thumbnail \"" .
555 $thumbnail_name .
556 "\" from image \"" .
557 $file_list[$i] .
558 "\".\n";
559
560 create_thumbnail($file_list[$i],
561 $thumbnail_name,
562 $filename_filesize,
563 $filename_xdim,
564 $filename_ydim,
565 $thumbnail_filesize,
566 $thumbnail_xdim,
567 $thumbnail_ydim);
568
569 echo "Conversion complete.\n";
570 echo " Full-sized image file size/xdim/ydim = " .
571 $filename_filesize . "/" . $filename_xdim . "/" . $filename_ydim .
572 ",\n";
573 echo " Thumbnail image filesize/xdim/ydim = " .
574 $thumbnail_filesize . "/" . $thumbnail_xdim . "/" . $thumbnail_ydim .
575 ",\n";
576
577 hor_line_thin();
578 $completed++;
579 }
580 }
581 else
582 {
583 //Unsuitable base name. Can't use it.
584 echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . " : skipping due to unsuitable name.\n";
585 hor_line_thin();
586 }
587
588 $i++;
589 }
590
591 //Emit a message about whether the program should be run again. I am aware of
592 //the uncovered case--where the last thumbnail was made on the last iteration
593 //of this invocation--but I will leave it uncovered for now. All that happens
594 //is the user runs the program unnecessarily one more time.
595 if ($completed == 0)
596 {
597 echo "No thumbnails were created--this program is done creating thumbnails.\n";
598 echo "It is not necessary to run this program again.\n";
599 hor_line_thin();
600 }
601 else
602 {
603 echo $completed . " thumbnail(s) were created. Please run this program repeatedly again\n";
604 echo "until no more thumbnails are created.\n";
605 hor_line_thin();
606 }
607
608 echo CFG_PROGNAME . " execution ends.\n";
609 hor_line_thick();
610 //--------------------------------------------------------------------------------
611 //End of File
612 //--------------------------------------------------------------------------------
613 ?>

Properties

Name Value
svn:eol-style native
svn:executable *
svn:keywords Header

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25