PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/3.0/modules/image_optimizer/helpers/image_optimizer.php

http://github.com/gallery/gallery3-contrib
PHP | 343 lines | 227 code | 21 blank | 95 comment | 26 complexity | 698baa3fdd04b5fa45aae183ed5c1790 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, LGPL-2.1
  1. <?php defined("SYSPATH") or die("No direct script access.");
  2. /**
  3. * Gallery - a web based photo album viewer and editor
  4. * Copyright (C) 2000-2013 Bharat Mediratta
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or (at
  9. * your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. class image_optimizer_Core {
  21. /**
  22. * These functions deal with the toolkit installations
  23. */
  24. // define the tool names
  25. static function tool_name($type) {
  26. $type = strtolower($type);
  27. switch($type) {
  28. case "jpg":
  29. $tool = "jpegtran";
  30. break;
  31. case "png":
  32. $tool = "optipng";
  33. break;
  34. case "gif":
  35. $tool = "gifsicle";
  36. break;
  37. default:
  38. $tool = "";
  39. }
  40. return $tool;
  41. }
  42. // find server-installed versions of the tools
  43. static function tool_installed_path($type) {
  44. $type = strtolower($type);
  45. $path = exec('which '.image_optimizer::tool_name($type));
  46. $path = is_file($path) ? $path : "(not found)";
  47. return $path;
  48. }
  49. // return status of tool path and attempt to fix permissions if not executable
  50. static function tool_status($type) {
  51. $type = strtolower($type);
  52. $path = module::get_var("image_optimizer","path_".$type);
  53. if (is_file($path) && !is_link($path)) {
  54. if (is_executable($path)) {
  55. $status = true;
  56. } else {
  57. // try to fix its permissions. return false if it doesn't work.
  58. $status = chmod($path,0755);
  59. }
  60. } else {
  61. $status = false;
  62. }
  63. // set module variable
  64. module::set_var("image_optimizer", "configstatus_".$type, $status);
  65. return $status;
  66. }
  67. // return tool version as string
  68. static function tool_version($type) {
  69. $type = strtolower($type);
  70. if (image_optimizer::tool_status($type)) {
  71. switch($type) {
  72. case "jpg":
  73. $path = module::get_var("image_optimizer","path_".$type);
  74. // jpegtran is weird as it doesn't have a version flag. so, we run in verbose mode with a fake file and catch stderr, which exec() can't do.
  75. // this is sort of a hack, but since there's no clean way available...
  76. $cmd = escapeshellcmd($path)." -verbose ".MODPATH."image_optimizer/this_file_does_not_exist.jpg";
  77. $output = image_optimizer::get_pipe($cmd, 2);
  78. $output = "Correctly configured! " . substr($output, 0, strpos($output, "\n"));
  79. break;
  80. case "png":
  81. case "gif":
  82. $path = module::get_var("image_optimizer","path_".$type);
  83. exec(escapeshellcmd($path)." --version", $output);
  84. $output = "Correctly configured! " . $output[0];
  85. break;
  86. default:
  87. $output = t("Only jpg/png/gif supported");
  88. }
  89. } else {
  90. $output = t("Invalid configuration");
  91. }
  92. return $output;
  93. }
  94. /**
  95. * These functions supplement the rule functions in modules/gallery/helpers/graphics.php
  96. * Note that all rule-changing functions in graphics.php trigger all images to be marked as dirty for rebuild, which we often want to avoid.
  97. */
  98. // add image_optimizer rules without marking for dirty (based on add_rule)
  99. static function add_image_optimizer_rule($target) {
  100. // to prevent duplicates, remove any existing instances first
  101. image_optimizer::remove_image_optimizer_rule($target);
  102. // then add the new one
  103. $rule = ORM::factory("graphics_rule");
  104. $rule->module_name = "image_optimizer";
  105. $rule->target = $target;
  106. $rule->operation = 'image_optimizer::optimize';
  107. $rule->priority = 999999999; // this MUST be larger than all others to be last
  108. $rule->args = serialize($target); // this isn't typical for other graphics rules
  109. $rule->active = true;
  110. $rule->save();
  111. }
  112. // remove image_optimizer rules without marking for dirty (based on remove_rule)
  113. static function remove_image_optimizer_rule($target) {
  114. db::build()
  115. ->delete("graphics_rules")
  116. ->where("target", "=", $target)
  117. ->where("module_name", "=", "image_optimizer")
  118. ->execute();
  119. }
  120. // activate update mode - disactivates all currently-active rules except those of image_optimizer without marking for dirty
  121. // sets update_mode_thumb/resize variable with serialized list of deactivated rule ids
  122. static function activate_update_mode($target) {
  123. // find all currently active non-image-optimizer rules
  124. $rules = db::build()
  125. ->from("graphics_rules")
  126. ->select("id")
  127. ->where("active", "=", true)
  128. ->where("target", "=", $target)
  129. ->where("module_name", "!=", "image_optimizer")
  130. ->execute();
  131. // use found rules to build ids array and deactivate rules
  132. $ids = array();
  133. foreach ($rules as $rule) {
  134. $ids[] = $rule->id;
  135. db::build()
  136. ->update("graphics_rules")
  137. ->where("id", "=", $rule->id)
  138. ->set("active", false) // deactivation!
  139. ->execute();
  140. }
  141. // set module variable as deactivated rule ids
  142. module::set_var("image_optimizer", "update_mode_".$target, serialize($ids));
  143. // display a warning that we're in update mode
  144. site_status::warning(
  145. t("Image optimizer is in thumb/resize update mode - remember to exit <a href=\"%url\">here</a> after rebuild!",
  146. array("url" => html::mark_clean(url::site("admin/image_optimizer")))),
  147. "image_optimizer_update_mode");
  148. }
  149. // deactivate update mode - re-activates rules marked in the update_mode_thumb/resize variable as previously deactivated
  150. static function deactivate_update_mode($target) {
  151. // get deactivated rule ids
  152. $ids = unserialize(module::get_var("image_optimizer", "update_mode_".$target));
  153. // activate them
  154. foreach ($ids as $id) {
  155. db::build()
  156. ->update("graphics_rules")
  157. ->where("id", "=", $id)
  158. ->set("active", true) // activation!
  159. ->execute();
  160. }
  161. // reset module variable
  162. module::set_var("image_optimizer", "update_mode_".$target, false);
  163. // clear update mode warning
  164. if (!module::get_var("image_optimizer", "update_mode_thumb") && !module::get_var("image_optimizer", "update_mode_resize")) {
  165. site_status::clear("image_optimizer_update_mode");
  166. }
  167. }
  168. // mark all as dirty (in similar syntax to above)
  169. static function dirty($target) {
  170. graphics::mark_dirty($target == "thumb", $target == "resize");
  171. }
  172. /**
  173. * the main optimize function
  174. *
  175. * the function arguments are the same format as other graphics rules. the only "option" is $target, hence why it's renamed in the function def.
  176. *
  177. * NOTE: unlike other graphics transformations, this only uses the output file! if it isn't already there, we don't do anything.
  178. * among other things, this means that the original, full-size images are never touched.
  179. */
  180. static function optimize($input_file, $output_file, $target, $item=null) {
  181. // see if output file exists and is writable
  182. if (is_writable($output_file)) {
  183. // see if input is a supported file type. if not, return without doing anything.
  184. $image_info = getimagesize($input_file); // [0]=w, [1]=h, [2]=type (1=GIF, 2=JPG, 3=PNG)
  185. switch ($image_info[2]) {
  186. case 1:
  187. $type_old = "gif";
  188. $convert = module::get_var("image_optimizer", "convert_".$target."_gif");
  189. break;
  190. case 2:
  191. $type_old = "jpg";
  192. $convert = 0; // no conversion possible here...
  193. break;
  194. case 3:
  195. $type_old = "png";
  196. $convert = module::get_var("image_optimizer", "convert_".$target."_png");
  197. break;
  198. default:
  199. // not a supported file type
  200. return;
  201. }
  202. } else {
  203. // file doesn't exist or isn't writable
  204. return;
  205. }
  206. // set new file type
  207. $type = $convert ? $convert : $type_old;
  208. // convert image type (if applicable). this isn't necessarily lossless.
  209. if ($convert) {
  210. $output_file_new = legal_file::change_extension($output_file, $type);
  211. // perform conversion using standard Gallery toolkit (GD/ImageMagick/GraphicsMagick)
  212. // note: if input was a GIF, this will kill animation
  213. $image = Image::factory($output_file)
  214. ->quality(module::get_var("gallery", "image_quality"))
  215. ->save($output_file_new);
  216. // if filenames are different, move the new on top of the old
  217. if ($output_file != $output_file_new) {
  218. /**
  219. * HACK ALERT! Gallery3 is still broken with regard to treating thumb/resizes with proper extensions. This doesn't try to fix that.
  220. * Normal Gallery setup:
  221. * photo thumb -> keep photo type, keep photo extension
  222. * album thumb -> keep source photo thumb type, change extension to jpg (i.e. ".album.jpg" even for png/gif)
  223. * Also, missing_photo.png is similarly altered...
  224. *
  225. * Anyway, to avoid many rewrites of core functions (and not-easily-reversible database changes), this module also forces the extension to stay the same.
  226. * With image optimizer conversion:
  227. * photo thumb -> change type, keep photo extension (i.e. "photo.png" photo becomes "photo.png" thumb even if type has changed)
  228. * album thumb -> keep source photo thumb type, change extension to jpg (i.e. ".album.jpg" even for png/gif)
  229. */
  230. rename($output_file_new, $output_file);
  231. }
  232. }
  233. // get module variables
  234. $configstatus = module::get_var("image_optimizer", "configstatus_".$type);
  235. $path = module::get_var("image_optimizer", "path_".$type);
  236. $opt = module::get_var("image_optimizer", "optlevel_".$target."_".$type);
  237. $meta = module::get_var("image_optimizer", "metastrip_".$target);
  238. $prog = module::get_var("image_optimizer", "progressive_".$target);
  239. // make sure the toolkit is configured correctly and we want to use it - if not, return without doing anything.
  240. if ($configstatus) {
  241. if (!$prog && !$meta && !$opt) {
  242. // nothing to do!
  243. return;
  244. }
  245. } else {
  246. // not configured correctly
  247. return;
  248. }
  249. /**
  250. * do the actual optimization
  251. */
  252. // set parameters
  253. switch ($type) {
  254. case "jpg":
  255. $exec_args = $opt ? " -optimize" : "";
  256. $exec_args .= $meta ? " -copy none" : " -copy all";
  257. $exec_args .= $prog ? " -progressive" : "";
  258. $exec_args .= " -outfile ";
  259. break;
  260. case "png":
  261. $exec_args = $opt ? " -o".$opt : "";
  262. $exec_args .= $meta ? " -strip all" : "";
  263. $exec_args .= $prog ? " -i 1" : "";
  264. $exec_args .= " -quiet -out ";
  265. break;
  266. case "gif":
  267. $exec_args = $opt ? " --optimize=3" : ""; // levels 1 and 2 don't really help us
  268. $exec_args .= $meta ? " --no-comments --no-extensions --no-names" : " --same-comments --same-extensions --same-names";
  269. $exec_args .= $prog ? " --interlace" : " --same-interlace";
  270. $exec_args .= " --careful --output ";
  271. break;
  272. }
  273. // run it - from output_file to tmp_file.
  274. $tmp_file = image_optimizer::make_temp_name($output_file);
  275. exec(escapeshellcmd($path) . $exec_args . escapeshellarg($tmp_file) . " " . escapeshellarg($output_file), $exec_output, $exec_status);
  276. if ($exec_status || !filesize($tmp_file)) {
  277. // either a blank/nonexistant file or an error - do nothing to the output, but log an error and delete the temp (if any)
  278. Kohana_Log::add("error", "image_optimizer optimization failed on ".$output_file);
  279. unlink($tmp_file);
  280. } else {
  281. // worked - move temp to output
  282. rename($tmp_file, $output_file);
  283. }
  284. }
  285. /**
  286. * make a unique temporary filename. this bit is inspired/copied from
  287. * the system/libraries/Image.php save function and the system/libraries/drivers/Image/ImageMagick.php process function
  288. */
  289. static function make_temp_name($file) {
  290. // Separate the directory and filename
  291. $dir = pathinfo($file, PATHINFO_DIRNAME);
  292. $file = pathinfo($file, PATHINFO_BASENAME);
  293. // Normalize the path
  294. $dir = str_replace('\\', '/', realpath($dir)).'/';
  295. // Unique temporary filename
  296. $tmp_file = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
  297. return $tmp_file;
  298. }
  299. /**
  300. * get stdin, stdout, or stderr from shell command. php commands like exec() don't do this.
  301. * this is only used to get jpegtran's version info in the admin screen.
  302. *
  303. * see http://stackoverflow.com/questions/2320608/php-stderr-after-exec
  304. */
  305. static function get_pipe($cmd, $pipe) {
  306. $descriptorspec = array(
  307. 0 => array("pipe", "r"), // stdin
  308. 1 => array("pipe", "w"), // stdout
  309. 2 => array("pipe", "w"), // stderr
  310. );
  311. $process = proc_open($cmd, $descriptorspec, $pipes);
  312. $output = stream_get_contents($pipes[$pipe]);
  313. fclose($pipes[0]);
  314. fclose($pipes[1]);
  315. fclose($pipes[2]);
  316. proc_close($process);
  317. return $output;
  318. }
  319. }