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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 110 - (hide annotations) (download)
Sat Dec 31 19:42:04 2016 UTC (7 years, 9 months ago) by dashley
Original Path: projs/dtats/trunk/projs/20161008_web_page_thumbnail_make/thumbnails_make.php
File size: 22011 byte(s)
Update keywords, license.
1 dashley 107 <?php
2     //-------------------------------------------------------------------------------------------------
3 dashley 110 //$Header$
4 dashley 107 //-------------------------------------------------------------------------------------------------
5 dashley 110 //This source code and any program in which it is compiled/used is provided under the MIT
6     //LICENSE (full license text below).
7 dashley 107 //-------------------------------------------------------------------------------------------------
8 dashley 110 //Copyright (c) 2016 David T. Ashley
9 dashley 107 //
10 dashley 110 //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 dashley 107 //
16 dashley 110 //The above copyright notice and this permission notice shall be included in all copies or
17     //substantial portions of the Software.
18 dashley 107 //
19 dashley 110 //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 dashley 107 //-------------------------------------------------------------------------------------------------
25     //--------------------------------------------------------------------------------
26     //C O N F I G U R A T I O N
27     //--------------------------------------------------------------------------------
28     //Configuration switch combinations were not tested. E-mail me any program
29     //corrections (dashley@gmail.com).
30     define ("CFG_PROGNAME", "thumbnails_make.php");
31     //Number of characters per line preferred for console output.
32     define ("CFG_CONSOLE_STD_LINE_LEN", 78);
33     //Number of characters per line preferred for console output.
34     define ("CFG_THUMBNAIL_DIMENSION_MAX", 125);
35     //The maximum dimension of the created thumbnails. Thumbnails are sized so
36     //that the longest dimension is this many pixels.
37     define ("CFG_THUMBNAIL_BEVELED_BORDER_WIDTH", 4);
38     //The number of pixels that the thumbnail beveled borders should be.
39     define ("CFG_THUMBNAIL_FILENAME_SUFFIX", "_small");
40     //String added just before the filename extension to choose thumbnail names
41     //based on name of full-sized image.
42     define ("CFG_LANCZOS_FILTER_APPLY", FALSE);
43     //TRUE if should apply the Lanczos filter when making the thumbnail, FALSE
44     //otherwise. I have no idea if using a Lanczos filter improves the quality
45     //of the thumbnails, but the filter was applied in the PHP example I found
46     //online. Applying the Lanczos filter typically adds a few seconds to the
47     //time required to create each thumbnail.
48     define ("CFG_MAX_THUMBNAILS_PER_INVOCATION", 20);
49     //The maximum number of thumbnails that should be created per invocation.
50     //This is to prevent the program from being involuntarily terminated
51     //for taking too much CPU time.
52     //--------------------------------------------------------------------------------
53     //Calculates important integer indices related to creating a thumbnail, in order
54     //to minimize distortion in the thumbnail.
55     //
56     //When creating a thumbnail, the longer side may be relatively short (perhaps
57     //100 pixels), and the shorter side may be even shorter (perhaps even as short
58     //as 10 pixels). If the dimensions of the original image were not changed,
59     //converting the image to a thumbnail may result in distortion of up to about
60     //one percent. As an example, suppose that a 1600 x 900 image is converted to a
61     //thumbnail with the longer side of 125 pixels. The ideal dimension for the
62     //shorter side would be (900/1600) * 125 = 70.3125 pixels. The aspect ratio of
63     //the thumbnail can't be made to match the aspect ratio of the original image.
64     //
65     //To avoid any aspect ratio distortion that might be noticeable, this program
66     //chooses the shorter dimension for the thumbnail that is the ceiling of the actual
67     //quotient, then crops the longer dimension of the original image before the
68     //conversion to try and match the aspect ratios as closely as possible.
69     //
70     //In the example a paragraph or two above, the shorter side of the thumbnail
71     //would be chosen to be 71 pixels.
72     //
73     //Once this choice is made, we want to trim the original image before conversion
74     //so that n/900 is as close as possible to 125/71. n = (900 * 125) / 71 =
75     //1584.5070 pixels, so we choose 1585. This keeps the aspect ratio of the
76     //original image and the thumbnail as close as possible.
77     //
78     //Although the longer side of the original image may be reduced prior to the
79     //conversion to a thumbnail, this information is not written to disk, and
80     //the original image is not modified. The adjustment of the longer side is
81     //just a conversion trick to hopefully get better thumbnails.
82     function calc_thumbnail_conversion_pars
83     (
84     $in_orig_longer,
85     //Longer dimension of original image.
86     $in_orig_shorter,
87     //Shorter dimension of original image.
88     $in_thumbnail_longer,
89     //Longer dimension of desired thumbnail.
90     & $out_thumbnail_longer,
91     //Longer dimension of thumbnail that should be created.
92     & $out_thumbnail_shorter,
93     //Shorter dimension of thumbnail that should be created.
94     & $out_orig_crop_dim_longer,
95     //The size to crop to on the longer axis of the original.
96     & $out_orig_crop_dim_shorter,
97     //The size to crop to on the shorter axis of the original.
98     & $out_orig_crop_start_longer,
99     //For use with the Imagick:cropImage() method, the start
100     //position of the crop on the longer axis.
101     & $out_orig_crop_start_shorter
102     //For use with the Imagick:cropImage() method, the start
103     //position of the crop on the shorter axis.
104     )
105     {
106     //Set the thumbnail shorter dimension to the floor. This means would need to trim
107     //longest dimension of original to match aspect ratio as closely as possible.
108     $out_thumbnail_longer = $in_thumbnail_longer;
109     $out_thumbnail_shorter = ceil(((float)$in_thumbnail_longer *
110     (float)$in_orig_shorter) / (float)$in_orig_longer);
111     settype($out_thumbnail_shorter, "integer");
112    
113     //The aspect ratio of the thumbnail is now set. Try to match the aspect ratio of the larger
114     //image as closely as possible by selecting a smaller number for the long axis of the
115     //original image.
116     $out_orig_crop_dim_longer = round(((float)$in_thumbnail_longer *
117     (float)$in_orig_shorter) / (float)$out_thumbnail_shorter);
118     settype($out_orig_crop_dim_longer, "integer");
119    
120     //The original keeps its shorter dimension unchanged.
121     $out_orig_crop_dim_shorter = $in_orig_shorter;
122    
123     //Set the cropping of the longer side of the original to cover half the necessary
124     //reduction.
125     $out_orig_crop_start_longer = round(((float)$in_orig_longer -
126     (float)$out_orig_crop_dim_longer) / 2.0);
127     settype($out_orig_crop_start_longer, "integer");
128    
129     //No cropping of original shorter side.
130     $out_orig_crop_start_shorter = 0;
131     }
132     //--------------------------------------------------------------------------------
133     //Repeats a character to the console output a certain number of times.
134     function rep_char_con($c, $n)
135     {
136     while ($n--)
137     echo $c;
138     }
139     //--------------------------------------------------------------------------------
140     //Writes a standard thick horizontal line to the console.
141     function hor_line_thick()
142     {
143     rep_char_con("=", CFG_CONSOLE_STD_LINE_LEN);
144     echo "\n";
145     }
146     //--------------------------------------------------------------------------------
147     //Writes a standard thin horizontal line to the console.
148     function hor_line_thin()
149     {
150     rep_char_con("-", CFG_CONSOLE_STD_LINE_LEN);
151     echo "\n";
152     }
153     //--------------------------------------------------------------------------------
154     //Returns an array of all files in the working directory.
155     //If no files can be found, returns FALSE.
156     function get_file_names_in_dir()
157     {
158     //Get directory list.
159     $rv = scandir (".");
160    
161     //If the list is empty, something went wrong. Return FALSE.
162     if ($rv === FALSE)
163     return FALSE;
164    
165     return $rv;
166     }
167     //--------------------------------------------------------------------------------
168     //Returns TRUE if a file name appears to be a valid full-sized image name,
169     //or FALSE otherwise.
170     function is_full_sized_image_file_name($in_filename)
171     {
172     //Convert the string name to all lower case. This will do for
173     //comparisons and tests.
174     $s = strtolower($s);
175    
176     //Attempt to split the name into a base and an extension. Any failure
177     //means it is an unsuitable name.
178     $extension_start = strrpos($in_filename, ".");
179     //Find position of last "." in string. This should precede the
180     //file extension.
181    
182     if ($extension_start === FALSE)
183     {
184     //Bad name. Unsuitable.
185     return FALSE;
186     }
187    
188     //Calculate the base and extension.
189     $filename_base = substr($in_filename, 0, $extension_start);
190     $filename_extension = substr($in_filename, $extension_start + 1);
191    
192     //If the extension is neither "jpg" nor "jpeg", it is unsuitable.
193     if (($filename_extension != "jpg") && ($filename_extension != "jpeg"))
194     return FALSE;
195    
196     //If the filename base is empty, the filename is unsuitable.
197     if (strlen($filename_base) == 0)
198     return FALSE;
199    
200     //If the last characters of the base are the CFG_THUMBNAIL_FILENAME_SUFFIX,
201     //the name is unsuitable.
202     if (strlen($filename_base) >= strlen(CFG_THUMBNAIL_FILENAME_SUFFIX))
203     {
204     if (substr($filename_base, strlen($filename_base) - strlen(CFG_THUMBNAIL_FILENAME_SUFFIX))
205     == CFG_THUMBNAIL_FILENAME_SUFFIX)
206     return FALSE;
207     }
208    
209     //Looks good.
210     return TRUE;
211     }
212     //--------------------------------------------------------------------------------
213     //As a function of the file name, creates the file name for the thumbnail.
214     function file_name_to_thumbnail_name($in_filename)
215     {
216     $extension_start = strrpos($in_filename, ".");
217     //Find position of last "." in string. This should precede the
218     //file extension.
219     if ($extension_start === FALSE)
220     {
221     //"." not found. Should not happen. Filenames were checked in advance.
222     echo "Fatal internal error at line " . __LINE__ . "\n";
223     exit(1);
224     }
225    
226     $filename_prefix = substr($in_filename, 0, $extension_start);
227     $filename_extension = substr($in_filename, $extension_start);
228    
229     $rv = $filename_prefix . CFG_THUMBNAIL_FILENAME_SUFFIX . $filename_extension;
230    
231     return $rv;
232     }
233     //--------------------------------------------------------------------------------
234     //Actually creates the thumbnail, and returns some information about what was
235     //done.
236     //
237     //The calls to the ImagMagick library take on the order of 5s per image if there
238     //is filtering.
239     function create_thumbnail( $in_filename,
240     $in_thumbnailname,
241     & $out_filename_filesize,
242     & $out_filename_xdim,
243     & $out_filename_ydim,
244     & $out_thumbnailname_filesize,
245     & $out_thumbnailname_xdim,
246     & $out_thumbnailname_ydim)
247     {
248     //Assign output parameters just in case something doesn't get assigned.
249     $out_filename_filesize = 0;
250     $out_filename_xdim = 0;
251     $out_filename_ydim = 0;
252     $out_thumbnailname_filesize = 0;
253     $out_thumbnailname_xdim = 0;
254     $out_thumbnailname_ydim = 0;
255    
256     //Establish target dimensions. Two cases, depending on which is the longer
257     //side.
258    
259     //Construct.
260     $imagick = new Imagick();
261    
262     //Load image.
263     $imagick->readImage($in_filename);
264    
265     //Get the dimensions of the image we just loaded.
266     $geo = $imagick->getImageGeometry();
267     $out_filename_xdim = $geo['width'];
268     $out_filename_ydim = $geo['height'];
269    
270     //Calculate target sizes. We rearrange parameters based on which is our
271     //longest side.
272     if ($out_filename_xdim >= $out_filename_ydim)
273     {
274     //Longer width (x-dimension), or square.
275     calc_thumbnail_conversion_pars
276     (
277     $out_filename_xdim,
278     $out_filename_ydim,
279     CFG_THUMBNAIL_DIMENSION_MAX,
280     $out_thumbnailname_xdim,
281     $out_thumbnailname_ydim,
282     $orig_crop_dim_x,
283     $orig_crop_dim_y,
284     $orig_crop_start_x,
285     $orig_crop_start_y
286     );
287     }
288     else
289     {
290     //Longer height (y-dimension).
291     calc_thumbnail_conversion_pars
292     (
293     $out_filename_ydim,
294     $out_filename_xdim,
295     CFG_THUMBNAIL_DIMENSION_MAX,
296     $out_thumbnailname_ydim,
297     $out_thumbnailname_xdim,
298     $orig_crop_dim_y,
299     $orig_crop_dim_x,
300     $orig_crop_start_y,
301     $orig_crop_start_x
302     );
303     }
304    
305     //For debugging only, might want to know intermediate calculation results.
306     //echo "xcropdim, ycropdim, xcropstart, ycropstart: "
307     // .
308     // $orig_crop_dim_x
309     // .
310     // " "
311     // .
312     // $orig_crop_dim_y
313     // .
314     // " "
315     // .
316     // $orig_crop_start_x
317     // .
318     // " "
319     // .
320     // $orig_crop_start_y
321     // .
322     // "\n";
323    
324     //Crop the original to try to preserve the aspect ratio of the thumbnail
325     //as precisely as possible.
326     $imagick->cropImage(
327     $orig_crop_dim_x,
328     $orig_crop_dim_y,
329     $orig_crop_start_x,
330     $orig_crop_start_y
331     );
332    
333     //For debugging only, might want to get a look at the cropped image, to
334     //be sure nothing unexpected happens on the canvas.
335     //$imagick->writeImage($in_filename . ".cropped.jpg");
336    
337     //Resize to thumbnail size.
338     if (CFG_LANCZOS_FILTER_APPLY)
339     {
340     $imagick->resizeImage($out_thumbnailname_xdim,
341     $out_thumbnailname_ydim,
342     Imagick::FILTER_LANCZOS,
343     1);
344     }
345     else
346     {
347     $imagick->resizeImage($out_thumbnailname_xdim,
348     $out_thumbnailname_ydim,
349     0,
350     1);
351     }
352    
353     //Create the border.
354     $imagick->raiseImage(CFG_THUMBNAIL_BEVELED_BORDER_WIDTH,
355     CFG_THUMBNAIL_BEVELED_BORDER_WIDTH,
356     0,
357     0,
358     1);
359    
360     //Set compression to get a smaller thumbnail written, and strip
361     //header information. stripImage() seems to have the largest effect
362     //on thumbnail file size, so leaving the thumbnail quality near 100%
363     //is feasible. The jump in file size between 90% and 95% seemed to be
364     //fairly large (40% to 50%), so I left it at 90%. My rationale is
365     //that with the proliferation of mobile devices and cellular data,
366     //getting the thumbnail as small as possible is more important than
367     //the thumbnail looking perfect. If the viewer wants a perfect image,
368     //they can view the full-sized image.
369     $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
370     $imagick->setImageCompressionQuality(90);
371     $imagick->stripImage();
372    
373     //Write the thumbnail.
374     $imagick->writeImage($in_thumbnailname);
375    
376     //Destroy to prevent memory leak.
377     $imagick->clear();
378     $imagick->destroy();
379    
380     //All of the writing is done. Try to obtain the file sizes.
381     $fsize = filesize($in_filename);
382     if ($fsize !== FALSE)
383     $out_filename_filesize = $fsize;
384     $fsize = filesize($in_thumbnailname);
385     if ($fsize !== FALSE)
386     $out_thumbnailname_filesize = $fsize;
387     }
388     //--------------------------------------------------------------------------------
389     //Write introductory message.
390     hor_line_thick();
391     echo CFG_PROGNAME . " Copyright (C) 2015 David T. Ashley\n";
392     echo "This program comes with ABSOLUTELY NO WARRANTY; and is licensed under\n";
393     echo "the GNU General Public License, Version 3. A copy of this license is\n";
394     echo "provided in the source code of this program.\n";
395     hor_line_thin();
396     //-----------------------------------------------------------------------------
397     //Get and emit the names of everything in the directory.
398     $file_list = get_file_names_in_dir();
399     if ($file_list === FALSE)
400     {
401     echo "List of files from PHP function scandir() is empty.\n";
402     echo "Serious internal error, or nothing to do. Script cannot continue.\n";
403     hor_line_thick();
404     exit(1);
405     }
406     else
407     {
408     echo "Files in working directory (unsorted, unfiltered, "
409     .
410     count($file_list)
411     .
412     " files):\n";
413     for ($i = 0; $i < count($file_list); $i++)
414     echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . "\n";
415     }
416     hor_line_thin();
417     //-----------------------------------------------------------------------------
418     //Remove the standard directory entries "." and ".." from the list, and
419     //remove any directories.
420     $temp_list = $file_list;
421     unset($file_list);
422     $n = 0;
423     for ($i = 0; $i < count($temp_list); $i++)
424     {
425     //echo "Checking " . $temp_list[$i] . "\n";
426    
427     if (strcmp($temp_list[$i], ".") == 0)
428     {
429     //. entry, not a file.
430     }
431     else if (strcmp($temp_list[$i], "..") == 0)
432     {
433     //.. entry, not a file.
434     }
435     else if (is_file($temp_list[$i]))
436     {
437     //This is a regular file.
438     $file_list[] = $temp_list[$i];
439     $n++;
440     }
441     }
442    
443     if ($n == 0)
444     $file_list = FALSE;
445    
446     unset($n);
447     unset($temp_list);
448     //-----------------------------------------------------------------------------
449     //If there is nothing to do, end the script.
450     if ($file_list === FALSE)
451     {
452     echo "No files to process.\n";
453     hor_line_thick();
454     exit(0);
455     }
456     //-----------------------------------------------------------------------------
457     //Sort the list. This is a non-event. The only rationale for sorting is that
458     //it ensures that the same set of files will be processed in the same order,
459     //regardless of the order provided by the underlying OS internals.
460     sort($file_list);
461     //-----------------------------------------------------------------------------
462     //Emit the names we now have.
463     echo "Files in working directory (directory entries removed, sorted, "
464     .
465     count($file_list)
466     .
467     " files):\n";
468     for ($i = 0; $i < count($file_list); $i++)
469     echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . "\n";
470     hor_line_thin();
471    
472     //-----------------------------------------------------------------------------
473     //For each file that is appropriate and where the thumbnail does not already
474     //exist, up to the maximum we may do in one invocation, create the thumbnail.
475     $i = 0;
476     $completed = 0;
477     while (($completed < CFG_MAX_THUMBNAILS_PER_INVOCATION) && ($i < count($file_list)))
478     {
479     if (is_full_sized_image_file_name($file_list[$i]))
480     {
481     $thumbnail_name = file_name_to_thumbnail_name($file_list[$i]);
482    
483     if (file_exists($thumbnail_name))
484     {
485     echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . " : skipping because corresponding thumbnail exists.\n";
486     hor_line_thin();
487     }
488     else
489     {
490     echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . " : creating thumbnail.\n";
491    
492     echo "Creating thumbnail \"" .
493     $thumbnail_name .
494     "\" from image \"" .
495     $file_list[$i] .
496     "\".\n";
497    
498     create_thumbnail($file_list[$i],
499     $thumbnail_name,
500     $filename_filesize,
501     $filename_xdim,
502     $filename_ydim,
503     $thumbnail_filesize,
504     $thumbnail_xdim,
505     $thumbnail_ydim);
506    
507     echo "Conversion complete.\n";
508     echo " Full-sized image file size/xdim/ydim = " .
509     $filename_filesize . "/" . $filename_xdim . "/" . $filename_ydim .
510     ",\n";
511     echo " Thumbnail image filesize/xdim/ydim = " .
512     $thumbnail_filesize . "/" . $thumbnail_xdim . "/" . $thumbnail_ydim .
513     ",\n";
514    
515     hor_line_thin();
516     $completed++;
517     }
518     }
519     else
520     {
521     //Unsuitable base name. Can't use it.
522     echo " " . sprintf("[%5d]", $i) . " " . $file_list[$i] . " : skipping due to unsuitable name.\n";
523     hor_line_thin();
524     }
525    
526     $i++;
527     }
528    
529     //Emit a message about whether the program should be run again. I am aware of
530     //the uncovered case--where the last thumbnail was made on the last iteration
531     //of this invocation--but I will leave it uncovered for now. All that happens
532     //is the user runs the program unnecessarily one more time.
533     if ($completed == 0)
534     {
535     echo "No thumbnails were created--this program is done creating thumbnails.\n";
536     echo "It is not necessary to run this program again.\n";
537     hor_line_thin();
538     }
539     else
540     {
541     echo $completed . " thumbnail(s) were created. Please run this program repeatedly again\n";
542     echo "until no more thumbnails are created.\n";
543     hor_line_thin();
544     }
545    
546     echo CFG_PROGNAME . " execution ends.\n";
547     hor_line_thick();
548     //--------------------------------------------------------------------------------
549     //End of File
550     //--------------------------------------------------------------------------------
551     ?>

Properties

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

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25