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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 110 - (show annotations) (download)
Sat Dec 31 19:42:04 2016 UTC (6 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 <?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 //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 //--------------------------------------------------------------------------------
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